diff --git a/.idea/modules.xml b/.idea/modules.xml deleted file mode 100644 index b2abaf15..00000000 --- a/.idea/modules.xml +++ /dev/null @@ -1,8 +0,0 @@ - - - - - - - - \ No newline at end of file diff --git a/client/src/main/java/jdash/client/GDClient.java b/client/src/main/java/jdash/client/GDClient.java index 1622f438..30432642 100644 --- a/client/src/main/java/jdash/client/GDClient.java +++ b/client/src/main/java/jdash/client/GDClient.java @@ -403,12 +403,29 @@ public Mono downloadWeeklyDemon() { return downloadLevel(-2); } - private Mono getTimelyInfo(int weekly) { - return Mono.defer(() -> GDRequest.of(GET_GJ_DAILY_LEVEL) - .addParameters(commonParams()) - .addParameter("weekly", weekly) - .execute(cache, router) - .deserialize(dailyInfoResponse())); + /** + * Downloads full data of the current Event level. It is a shorthand for: + *
+     *     downloadLevel(-3)
+     * 
+ * + * @return a Mono emitting the {@link GDLevelDownload} corresponding to the level. A {@link GDClientException} will + * be emitted if an error occurs. + */ + public Mono downloadEventLevel() { + return downloadLevel(-3); + } + + private Mono getDailyInfo(int type, @Nullable SecretRewardChkGenerator chkGenerator) { + return Mono.defer(() -> { + final var request = GDRequest.of(GET_GJ_DAILY_LEVEL) + .addParameters(commonParams()) + .addParameter("type", type); + if (chkGenerator != null) { + request.addParameter("chk", chkGenerator.get()); + } + return request.execute(cache, router).deserialize(dailyInfoResponse()); + }); } /** @@ -418,7 +435,7 @@ private Mono getTimelyInfo(int weekly) { * if an error occurs. */ public Mono getDailyLevelInfo() { - return getTimelyInfo(0); + return getDailyInfo(0, null); } /** @@ -428,7 +445,29 @@ public Mono getDailyLevelInfo() { * if an error occurs. */ public Mono getWeeklyDemonInfo() { - return getTimelyInfo(1); + return getDailyInfo(1, null); + } + + /** + * Requests information on the current Event level, such as its number or the time left before the next one. Rewards + * CHK is generated at random. + * + * @return a Mono emitting the {@link GDDailyInfo} of the Event level. A {@link GDClientException} will be emitted + * if an error occurs. + */ + public Mono getEventLevelInfo() { + return getDailyInfo(2, SecretRewardChkGenerator.random()); + } + + /** + * Requests information on the current Event level, such as its number or the time left before the next one. + * + * @param chkGenerator customize the way CHK is generated + * @return a Mono emitting the {@link GDDailyInfo} of the Event level. A {@link GDClientException} will be emitted + * if an error occurs. + */ + public Mono getEventLevelInfo(SecretRewardChkGenerator chkGenerator) { + return getDailyInfo(2, Objects.requireNonNull(chkGenerator)); } /** diff --git a/client/src/main/java/jdash/client/request/GDRequests.java b/client/src/main/java/jdash/client/request/GDRequests.java index 20e392bb..945c3462 100644 --- a/client/src/main/java/jdash/client/request/GDRequests.java +++ b/client/src/main/java/jdash/client/request/GDRequests.java @@ -31,7 +31,7 @@ public final class GDRequests { /* Params */ public static final String GAME_VERSION = "22"; - public static final String BINARY_VERSION = "42"; + public static final String BINARY_VERSION = "45"; public static final String SECRET = "Wmfd2893gb7"; private GDRequests() { diff --git a/client/src/main/java/jdash/client/request/GDRouterImpl.java b/client/src/main/java/jdash/client/request/GDRouterImpl.java index 494821f2..eb0ed2c8 100644 --- a/client/src/main/java/jdash/client/request/GDRouterImpl.java +++ b/client/src/main/java/jdash/client/request/GDRouterImpl.java @@ -37,6 +37,7 @@ public GDRouterImpl(RequestLimiter limiter, Duration timeout, String baseUrl, Sc .headers(h -> { h.add("Content-Type", "application/x-www-form-urlencoded"); h.add("User-Agent", ""); + h.add("Cookie", "gd=1;"); }); if (baseUrl.startsWith("https://")) { httpClient = httpClient.secure(); diff --git a/client/src/main/java/jdash/client/response/DailyInfoResponseDeserializer.java b/client/src/main/java/jdash/client/response/DailyInfoResponseDeserializer.java index 401a2c77..a03f533b 100644 --- a/client/src/main/java/jdash/client/response/DailyInfoResponseDeserializer.java +++ b/client/src/main/java/jdash/client/response/DailyInfoResponseDeserializer.java @@ -10,7 +10,7 @@ class DailyInfoResponseDeserializer implements Function { @Override public GDDailyInfo apply(String response) { - if (!response.matches("\\d+\\|\\d+")) { + if (!response.matches("\\d+\\|\\d+(|.*)*")) { throw new ActionFailedException(response, "Failed to load daily level info"); } final var tokens = response.split("\\|"); diff --git a/client/src/test/java/jdash/client/GDClientTest.java b/client/src/test/java/jdash/client/GDClientTest.java index 1bd537c8..fd70f75c 100644 --- a/client/src/test/java/jdash/client/GDClientTest.java +++ b/client/src/test/java/jdash/client/GDClientTest.java @@ -25,7 +25,7 @@ public final class GDClientTest { /* Not part of unit tests, this is only to test the real router implementation */ public static void main(String[] args) { final var client = GDClient.create(); - System.out.println(client.getSongInfo(10010067).block()); + System.out.println(client.getEventLevelInfo().block()); } @BeforeEach @@ -329,6 +329,13 @@ public void getWeeklyDemonInfoTest() { assertEquals(expected, actual); } + @Test + public void getEventLevelTest() { + final var expected = new GDDailyInfo(1, Duration.ofSeconds(10)); + final var actual = client.getEventLevelInfo(SecretRewardChkGenerator.fixed("ALEX1", "123456")).block(); + assertEquals(expected, actual); + } + @Test public void getCommentsForLevelTest() { final var expectedFirstComment = new GDComment( diff --git a/client/src/test/java/jdash/client/GDRouterMock.java b/client/src/test/java/jdash/client/GDRouterMock.java index 01a9ea38..3d7060c2 100644 --- a/client/src/test/java/jdash/client/GDRouterMock.java +++ b/client/src/test/java/jdash/client/GDRouterMock.java @@ -61,10 +61,14 @@ public final class GDRouterMock implements GDRouter { .addParameter("levelID", 10565740), "downloadLevel"), Map.entry(GDRequest.of(GET_GJ_DAILY_LEVEL) .addParameters(commonParams()) - .addParameter("weekly", 0), "getDailyLevelInfo"), + .addParameter("type", 0), "getDailyLevelInfo"), Map.entry(GDRequest.of(GET_GJ_DAILY_LEVEL) .addParameters(commonParams()) - .addParameter("weekly", 1), "getWeeklyDemonInfo"), + .addParameter("type", 1), "getWeeklyDemonInfo"), + Map.entry(GDRequest.of(GET_GJ_DAILY_LEVEL) + .addParameters(commonParams()) + .addParameter("type", 2) + .addParameter("chk", "ALEX1BAsCDAcD"), "getEventLevelInfo"), Map.entry(GDRequest.of(GET_GJ_COMMENTS_21) .addParameters(commonParams()) .addParameter("levelID", 10565740) diff --git a/client/src/test/resources/getEventLevelInfo.txt b/client/src/test/resources/getEventLevelInfo.txt new file mode 100644 index 00000000..b5f1d4e2 --- /dev/null +++ b/client/src/test/resources/getEventLevelInfo.txt @@ -0,0 +1 @@ +200001|10|SXDEsWE8DW2YPCAMLBgAPCwoCDwoLAB4BCR0OHgQVAAseBA==|2add3c41e4f44884d7200dd98c57feb1b07296df \ No newline at end of file diff --git a/common/src/main/java/jdash/common/RobTopsWeakEncryption.java b/common/src/main/java/jdash/common/RobTopsWeakEncryption.java index b505be8c..4fc6e0e2 100644 --- a/common/src/main/java/jdash/common/RobTopsWeakEncryption.java +++ b/common/src/main/java/jdash/common/RobTopsWeakEncryption.java @@ -4,30 +4,31 @@ import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; import java.util.Arrays; +import java.util.Random; import java.util.stream.Collectors; import java.util.stream.IntStream; -import static jdash.common.internal.InternalUtils.b64Decode; -import static jdash.common.internal.InternalUtils.b64Encode; +import static jdash.common.internal.InternalUtils.*; /** * Provides methods to encode and decode some data such as private message body, level passcodes and account passwords. */ public final class RobTopsWeakEncryption { - private static final XORCipher GD_MESSAGE_BODY_XOR_CIPHER = new XORCipher("14251"); - private static final XORCipher LEVEL_PASSCODE_XOR_CIPHER = new XORCipher("26364"); - private static final XORCipher ACCOUNT_PASSWORD_XOR_CIPHER = new XORCipher("37526"); - private static final XORCipher CHK_XOR_CIPHER = new XORCipher("58281"); + static final XORCipher GD_MESSAGE_BODY_XOR_CIPHER = new XORCipher("14251"); + static final XORCipher LEVEL_PASSCODE_XOR_CIPHER = new XORCipher("26364"); + static final XORCipher ACCOUNT_PASSWORD_XOR_CIPHER = new XORCipher("37526"); + static final XORCipher CHK_XOR_CIPHER = new XORCipher("58281"); + static final XORCipher CHK_REWARDS_XOR_CIPHER = new XORCipher("59182"); private RobTopsWeakEncryption() { } - private static String decode(String str, XORCipher algorithm) { + static String decode(String str, XORCipher algorithm) { return algorithm.cipher(b64Decode(str)); } - private static String encode(String str, XORCipher algorithm) { + static String encode(String str, XORCipher algorithm) { return b64Encode(algorithm.cipher(str)); } @@ -103,6 +104,15 @@ public static String encodeChk(Object... params) { .collect(Collectors.joining())), CHK_XOR_CIPHER); } + /** + * Generates a valid CHK to get secret rewards (chests or Event level rewards). + * + * @return the encoded string + */ + public static String generateSecretRewardChk() { + return randomString(5) + encode("" + (new Random().nextInt(900000) + 100000), CHK_REWARDS_XOR_CIPHER); + } + /** * Encodes the given password into a string that can be passed as the gjp2 parameter for authenticated requests. * diff --git a/common/src/main/java/jdash/common/SecretRewardChkGenerator.java b/common/src/main/java/jdash/common/SecretRewardChkGenerator.java new file mode 100644 index 00000000..391eab7f --- /dev/null +++ b/common/src/main/java/jdash/common/SecretRewardChkGenerator.java @@ -0,0 +1,39 @@ +package jdash.common; + +import java.util.function.Supplier; + +import static jdash.common.RobTopsWeakEncryption.CHK_REWARDS_XOR_CIPHER; +import static jdash.common.RobTopsWeakEncryption.encode; + +/** + * Function that generates a client CHK for secret rewards. Use {@link #random()} or {@link #fixed(String, String)} to + * customize + * CHK generation behavior. + */ +@FunctionalInterface +public interface SecretRewardChkGenerator extends Supplier { + + /** + * Function that generates a CHK at random, using a key representing a random number between 100_000 (inclusive) and + * 900_000 (exclusive). + * + * @return a random {@link SecretRewardChkGenerator} + */ + static SecretRewardChkGenerator random() { + return RobTopsWeakEncryption::generateSecretRewardChk; + } + + /** + * Function that generates a CHK with a fixed input. Use this when you need deterministic behavior. + * + * @param prefix a string that will be prepended to the CHK. Only the first 5 characters are taken, and will be + * padded with 'X' if provided prefix is less than 5 characters. + * @param key the key encoded in the CHK. It is recommended to be 6 characters or highers for the server to + * accept it. + * @return a {@link SecretRewardChkGenerator} with fixed input + */ + static SecretRewardChkGenerator fixed(String prefix, String key) { + return () -> String.format("%5s", prefix.substring(0, 5)).replace(' ', 'X') + + encode(key, CHK_REWARDS_XOR_CIPHER); + } +} diff --git a/common/src/test/java/jdash/common/RobTopsWeakEncryptionTest.java b/common/src/test/java/jdash/common/RobTopsWeakEncryptionTest.java index e4d71254..7fbfe2ba 100644 --- a/common/src/test/java/jdash/common/RobTopsWeakEncryptionTest.java +++ b/common/src/test/java/jdash/common/RobTopsWeakEncryptionTest.java @@ -1,5 +1,6 @@ package jdash.common; +import jdash.common.internal.InternalUtils; import org.junit.jupiter.api.Test; import static jdash.common.RobTopsWeakEncryption.*; @@ -39,8 +40,12 @@ public void encodePrivateMessageBodyTest() { @Test public void encodeChkTest() { + System.out.println(new XORCipher("59482").cipher(InternalUtils.b64Decode("BgkBCgYNCAEIAgUJ"))); + assertEquals("BFxRDAIFDlZeV1cKAAFVBwoADANRXQoKAAZbAAkAVA9UCwBTCgpeCQ==", - encodeChk(69101896, 2, "0ej5u5v6Nl", 98006, "00000000-24d6-c259-ffff-ffffddb46189", 4063664, - "ysg6pUrtjn0J")); + encodeChk(69101896, 2, "0ej5u5v6Nl", 98006, + "00000000-24d6-c259-ffff" + "-ffffddb46189", 4063664, "ysg6pUrtjn0J")); } + + } \ No newline at end of file diff --git a/common/src/test/java/jdash/common/SecretRewardChkGeneratorTest.java b/common/src/test/java/jdash/common/SecretRewardChkGeneratorTest.java new file mode 100644 index 00000000..231d43bc --- /dev/null +++ b/common/src/test/java/jdash/common/SecretRewardChkGeneratorTest.java @@ -0,0 +1,13 @@ +package jdash.common; + +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.*; + +public final class SecretRewardChkGeneratorTest { + + @Test + public void testFixedChkGenerator() { + assertEquals("ALEX1BAsCDAcD", SecretRewardChkGenerator.fixed("ALEX1", "123456").get()); + } +} \ No newline at end of file diff --git a/events/src/main/java/jdash/events/object/EventLevelChange.java b/events/src/main/java/jdash/events/object/EventLevelChange.java new file mode 100644 index 00000000..77e7a842 --- /dev/null +++ b/events/src/main/java/jdash/events/object/EventLevelChange.java @@ -0,0 +1,11 @@ +package jdash.events.object; + +import jdash.common.entity.GDDailyInfo; + +/** + * Event emitted when the Event level changes. + * + * @param before The Event level info before the change. + * @param after The Event level info after the change. + */ +public record EventLevelChange(GDDailyInfo before, GDDailyInfo after) {} diff --git a/events/src/main/java/jdash/events/producer/EventLevelProducer.java b/events/src/main/java/jdash/events/producer/EventLevelProducer.java new file mode 100644 index 00000000..8fc2c5e9 --- /dev/null +++ b/events/src/main/java/jdash/events/producer/EventLevelProducer.java @@ -0,0 +1,25 @@ +package jdash.events.producer; + +import jdash.client.GDClient; +import jdash.common.entity.GDDailyInfo; +import jdash.events.object.EventLevelChange; +import reactor.core.publisher.Flux; +import reactor.core.publisher.Mono; + +class EventLevelProducer implements GDEventProducer { + + private GDDailyInfo previousEventLevel; + + @Override + public Flux produce(GDClient client) { + return client.getEventLevelInfo() + .flatMapMany(info -> { + final var previousEventLevel = this.previousEventLevel; + this.previousEventLevel = info; + return Mono.justOrEmpty(previousEventLevel) + .filter(p -> p.number() < info.number()) + .map(p -> new EventLevelChange(p, info)); + }); + } + +} diff --git a/events/src/main/java/jdash/events/producer/GDEventProducer.java b/events/src/main/java/jdash/events/producer/GDEventProducer.java index a11c8c26..3f2563a5 100644 --- a/events/src/main/java/jdash/events/producer/GDEventProducer.java +++ b/events/src/main/java/jdash/events/producer/GDEventProducer.java @@ -66,6 +66,16 @@ static GDEventProducer dailyLevels() { return new DailyEventProducer(); } + /** + * An event producer that requests information on the current Event level on each iteration in + * order to detect when the Event level changes. + * + * @return a {@link GDEventProducer} + */ + static GDEventProducer eventLevels() { + return new EventLevelProducer(); + } + /** * Emits zero, one or more events by making requests using the given {@link GDClient}. The results of those requests * can be processed and memorized between two calls of this method in order to generate relevant events, for example diff --git a/events/src/test/java/jdash/events/producer/GDEventProducerTest.java b/events/src/test/java/jdash/events/producer/GDEventProducerTest.java index bb5c9c58..e2f7d5b5 100644 --- a/events/src/test/java/jdash/events/producer/GDEventProducerTest.java +++ b/events/src/test/java/jdash/events/producer/GDEventProducerTest.java @@ -31,6 +31,7 @@ public class GDEventProducerTest { private GDEventProducer awardedLevelProducer; private GDEventProducer awardedListProducer; private GDEventProducer dailyProducer; + private GDEventProducer eventLevelProducer; private static GDLevel createLevel(long id, int stars) { return new GDLevel( @@ -95,6 +96,7 @@ public void setUp() { awardedLevelProducer = GDEventProducer.awardedLevels(); awardedListProducer = GDEventProducer.awardedLists(); dailyProducer = GDEventProducer.dailyLevels(); + eventLevelProducer = GDEventProducer.eventLevels(); final var eventsA = awardedLevelProducer.produce(client).collectList().block(); assertNotNull(eventsA); @@ -105,6 +107,9 @@ public void setUp() { final var eventsC = awardedListProducer.produce(client).collectList().block(); assertNotNull(eventsC); assertTrue(eventsC.isEmpty()); // First iteration should yield nothing + final var eventsD = eventLevelProducer.produce(client).collectList().block(); + assertNotNull(eventsD); + assertTrue(eventsD.isEmpty()); // First iteration should yield nothing } @Test @@ -318,6 +323,19 @@ public void produceDailyChangeTest() { )); } + @Test + public void produceEventChangeTest() { + final var oldEvent = createDailyInfo(100); + final var newEvent = createDailyInfo(101); + cache.event = newEvent; + + final var events2 = eventLevelProducer.produce(client).collect(Collectors.toUnmodifiableSet()).block(); + assertNotNull(events2); + assertEquals(events2, Set.of( + new EventLevelChange(oldEvent, newEvent) + )); + } + private static class EventProducerTestCache implements GDCache { List levels0 = List.of( @@ -350,6 +368,7 @@ private static class EventProducerTestCache implements GDCache { ); GDDailyInfo daily = createDailyInfo(1); GDDailyInfo weekly = createDailyInfo(10); + GDDailyInfo event = createDailyInfo(100); @Override public Optional retrieve(GDRequest request) { @@ -366,10 +385,11 @@ public Optional retrieve(GDRequest request) { case "1" -> lists1; default -> null; }); - case GDRequests.GET_GJ_DAILY_LEVEL -> Optional.ofNullable(request.getParams().get("weekly")) + case GDRequests.GET_GJ_DAILY_LEVEL -> Optional.ofNullable(request.getParams().get("type")) .map(value -> switch (value) { case "0" -> daily; case "1" -> weekly; + case "2" -> event; default -> null; }); diff --git a/graphics/src/main/resources/icons/jetpack_06-uhd.plist b/graphics/src/main/resources/icons/jetpack_06-uhd.plist new file mode 100755 index 00000000..ccf1de3b --- /dev/null +++ b/graphics/src/main/resources/icons/jetpack_06-uhd.plist @@ -0,0 +1,86 @@ + + + + + frames + + jetpack_06_001.png + + aliases + + spriteOffset + {-2,0} + spriteSize + {174,146} + spriteSourceSize + {178,146} + textureRect + {{2,158},{174,146}} + textureRotated + + + jetpack_06_2_001.png + + aliases + + spriteOffset + {-10,-15} + spriteSize + {148,106} + spriteSourceSize + {168,136} + textureRect + {{106,306},{148,106}} + textureRotated + + + jetpack_06_extra_001.png + + aliases + + spriteOffset + {-3,5} + spriteSize + {162,102} + spriteSourceSize + {168,112} + textureRect + {{2,306},{162,102}} + textureRotated + + + jetpack_06_glow_001.png + + aliases + + spriteOffset + {-2,0} + spriteSize + {182,154} + spriteSourceSize + {186,154} + textureRect + {{2,2},{182,154}} + textureRotated + + + + metadata + + format + 3 + pixelFormat + RGBA8888 + premultiplyAlpha + + realTextureFileName + icons/jetpack_06-uhd.png + size + {214,470} + smartupdate + $TexturePacker:SmartUpdate:80540be0719b965e9596d9197b1bdaa9:f64d675956c08f78fd068ba26c39feeb:3825bc6d61db4cb5fde71c3e7894d0b6$ + textureFileName + icons/jetpack_06-uhd.png + + + diff --git a/graphics/src/main/resources/icons/jetpack_06-uhd.png b/graphics/src/main/resources/icons/jetpack_06-uhd.png new file mode 100755 index 00000000..67f73cef Binary files /dev/null and b/graphics/src/main/resources/icons/jetpack_06-uhd.png differ diff --git a/graphics/src/main/resources/icons/jetpack_07-uhd.plist b/graphics/src/main/resources/icons/jetpack_07-uhd.plist new file mode 100755 index 00000000..4d9d8515 --- /dev/null +++ b/graphics/src/main/resources/icons/jetpack_07-uhd.plist @@ -0,0 +1,86 @@ + + + + + frames + + jetpack_07_001.png + + aliases + + spriteOffset + {4,-3} + spriteSize + {160,138} + spriteSourceSize + {168,144} + textureRect + {{152,2},{160,138}} + textureRotated + + + jetpack_07_2_001.png + + aliases + + spriteOffset + {-6,-24} + spriteSize + {128,70} + spriteSourceSize + {140,118} + textureRect + {{152,164},{128,70}} + textureRotated + + + jetpack_07_extra_001.png + + aliases + + spriteOffset + {-32,19} + spriteSize + {44,78} + spriteSourceSize + {108,116} + textureRect + {{2,172},{44,78}} + textureRotated + + + jetpack_07_glow_001.png + + aliases + + spriteOffset + {4,-3} + spriteSize + {168,148} + spriteSourceSize + {176,154} + textureRect + {{2,2},{168,148}} + textureRotated + + + + metadata + + format + 3 + pixelFormat + RGBA8888 + premultiplyAlpha + + realTextureFileName + icons/jetpack_07-uhd.png + size + {292,236} + smartupdate + $TexturePacker:SmartUpdate:f0f1488d56f4990ef1cfab8e966ea2e9:512393c406ab80352bcd315b7369caf2:c8d188acbba1db402232fa4366c97876$ + textureFileName + icons/jetpack_07-uhd.png + + + diff --git a/graphics/src/main/resources/icons/jetpack_07-uhd.png b/graphics/src/main/resources/icons/jetpack_07-uhd.png new file mode 100755 index 00000000..fd18cdba Binary files /dev/null and b/graphics/src/main/resources/icons/jetpack_07-uhd.png differ diff --git a/graphics/src/main/resources/icons/jetpack_08-uhd.plist b/graphics/src/main/resources/icons/jetpack_08-uhd.plist new file mode 100755 index 00000000..20438a94 --- /dev/null +++ b/graphics/src/main/resources/icons/jetpack_08-uhd.plist @@ -0,0 +1,86 @@ + + + + + frames + + jetpack_08_001.png + + aliases + + spriteOffset + {-5,1} + spriteSize + {184,148} + spriteSourceSize + {194,150} + textureRect + {{2,162},{184,148}} + textureRotated + + + jetpack_08_2_001.png + + aliases + + spriteOffset + {-4,0} + spriteSize + {168,136} + spriteSourceSize + {176,136} + textureRect + {{2,312},{168,136}} + textureRotated + + + jetpack_08_extra_001.png + + aliases + + spriteOffset + {-28,17} + spriteSize + {100,94} + spriteSourceSize + {156,128} + textureRect + {{140,312},{100,94}} + textureRotated + + + jetpack_08_glow_001.png + + aliases + + spriteOffset + {-5,0} + spriteSize + {192,158} + spriteSourceSize + {202,158} + textureRect + {{2,2},{192,158}} + textureRotated + + + + metadata + + format + 3 + pixelFormat + RGBA8888 + premultiplyAlpha + + realTextureFileName + icons/jetpack_08-uhd.png + size + {236,482} + smartupdate + $TexturePacker:SmartUpdate:fea9ce8d29cedc7ebcea4c28a7aef5a4:0d1f53de01d049ce6b57f0f817a7ba58:bd780250daee6514ba929fc4808edd28$ + textureFileName + icons/jetpack_08-uhd.png + + + diff --git a/graphics/src/main/resources/icons/jetpack_08-uhd.png b/graphics/src/main/resources/icons/jetpack_08-uhd.png new file mode 100755 index 00000000..be4017f2 Binary files /dev/null and b/graphics/src/main/resources/icons/jetpack_08-uhd.png differ diff --git a/graphics/src/main/resources/icons/player_485-uhd.plist b/graphics/src/main/resources/icons/player_485-uhd.plist new file mode 100755 index 00000000..81898d59 --- /dev/null +++ b/graphics/src/main/resources/icons/player_485-uhd.plist @@ -0,0 +1,71 @@ + + + + + frames + + player_485_001.png + + aliases + + spriteOffset + {0,0} + spriteSize + {120,120} + spriteSourceSize + {120,120} + textureRect + {{2,132},{120,120}} + textureRotated + + + player_485_2_001.png + + aliases + + spriteOffset + {0,0} + spriteSize + {110,110} + spriteSourceSize + {110,110} + textureRect + {{124,132},{110,110}} + textureRotated + + + player_485_glow_001.png + + aliases + + spriteOffset + {0,0} + spriteSize + {128,128} + spriteSourceSize + {128,128} + textureRect + {{2,2},{128,128}} + textureRotated + + + + metadata + + format + 3 + pixelFormat + RGBA8888 + premultiplyAlpha + + realTextureFileName + icons/player_485-uhd.png + size + {236,254} + smartupdate + $TexturePacker:SmartUpdate:feca0edcc2fede95b0e0fcebc650891a:7a23c16cf205483dc3b5a94632e7c404:538ee6282c775f82c7e678d4739284df$ + textureFileName + icons/player_485-uhd.png + + + diff --git a/graphics/src/main/resources/icons/player_485-uhd.png b/graphics/src/main/resources/icons/player_485-uhd.png new file mode 100755 index 00000000..22fce0c7 Binary files /dev/null and b/graphics/src/main/resources/icons/player_485-uhd.png differ