From 7ce0211e25c76ddfdcad147fc2e095ce7a30a1f3 Mon Sep 17 00:00:00 2001 From: kuroppoi <68156848+kuroppoi@users.noreply.github.com> Date: Thu, 22 Jun 2023 21:55:19 +0200 Subject: [PATCH] Commit source --- .gitattributes | 6 + .github/workflows/dist-pull-request.yml | 19 + .github/workflows/dist-upload-artifact.yml | 25 ++ .gitignore | 12 + README.md | 29 +- build.gradle | 86 ++++ gradle.properties | 1 + gradle/wrapper/gradle-wrapper.jar | Bin 0 -> 60756 bytes gradle/wrapper/gradle-wrapper.properties | 5 + gradlew | 240 +++++++++++ gradlew.bat | 91 +++++ scripts/gradlew-clean.bat | 3 + scripts/gradlew-dist.bat | 3 + scripts/gradlew-test.bat | 3 + settings.gradle | 1 + .../entralinked/CommandLineArguments.java | 15 + src/main/java/entralinked/Configuration.java | 14 + src/main/java/entralinked/Entralinked.java | 156 +++++++ src/main/java/entralinked/GameVersion.java | 104 +++++ src/main/java/entralinked/LauncherAgent.java | 47 +++ src/main/java/entralinked/gui/MainView.java | 125 ++++++ src/main/java/entralinked/model/dlc/Dlc.java | 7 + .../java/entralinked/model/dlc/DlcList.java | 185 +++++++++ .../entralinked/model/pkmn/PkmnGender.java | 11 + .../java/entralinked/model/pkmn/PkmnInfo.java | 28 ++ .../model/pkmn/PkmnInfoReader.java | 123 ++++++ .../entralinked/model/pkmn/PkmnNature.java | 37 ++ .../model/player/DreamAnimation.java | 51 +++ .../model/player/DreamEncounter.java | 9 + .../entralinked/model/player/DreamItem.java | 7 + .../java/entralinked/model/player/Player.java | 117 ++++++ .../entralinked/model/player/PlayerDto.java | 47 +++ .../model/player/PlayerManager.java | 160 ++++++++ .../model/player/PlayerStatus.java | 27 ++ .../entralinked/model/user/GameProfile.java | 58 +++ .../model/user/GameProfileDto.java | 19 + .../model/user/ServiceCredentials.java | 3 + .../model/user/ServiceSession.java | 15 + .../java/entralinked/model/user/User.java | 50 +++ .../java/entralinked/model/user/UserDto.java | 30 ++ .../entralinked/model/user/UserManager.java | 284 +++++++++++++ .../entralinked/network/NettyServerBase.java | 76 ++++ .../network/dns/DnsQueryHandler.java | 45 +++ .../entralinked/network/dns/DnsServer.java | 41 ++ .../network/gamespy/GameSpyHandler.java | 208 ++++++++++ .../gamespy/GameSpyMessageEncoder.java | 45 +++ .../gamespy/GameSpyRequestDecoder.java | 80 ++++ .../network/gamespy/GameSpyServer.java | 73 ++++ .../message/GameSpyChallengeMessage.java | 8 + .../gamespy/message/GameSpyErrorMessage.java | 10 + .../gamespy/message/GameSpyLoginResponse.java | 11 + .../gamespy/message/GameSpyMessage.java | 14 + .../message/GameSpyProfileResponse.java | 24 ++ .../request/GameSpyKeepAliveRequest.java | 13 + .../gamespy/request/GameSpyLoginRequest.java | 31 ++ .../gamespy/request/GameSpyLogoutRequest.java | 15 + .../request/GameSpyProfileRequest.java | 19 + .../request/GameSpyProfileUpdateRequest.java | 24 ++ .../gamespy/request/GameSpyRequest.java | 19 + .../gamespy/request/GameSpyStatusRequest.java | 17 + .../entralinked/network/http/HttpHandler.java | 16 + .../network/http/HttpRequestHandler.java | 11 + .../entralinked/network/http/HttpServer.java | 170 ++++++++ .../http/dashboard/DashboardHandler.java | 246 +++++++++++ .../dashboard/DashboardProfileMessage.java | 25 ++ .../DashboardProfileUpdateRequest.java | 17 + .../dashboard/DashboardStatusMessage.java | 8 + .../network/http/dls/DlsHandler.java | 106 +++++ .../network/http/dls/DlsRequest.java | 25 ++ .../network/http/nas/NasHandler.java | 146 +++++++ .../network/http/nas/NasLoginResponse.java | 9 + .../network/http/nas/NasRequest.java | 33 ++ .../network/http/nas/NasResponse.java | 21 + .../network/http/nas/NasReturnCode.java | 61 +++ .../http/nas/NasServiceLocationResponse.java | 14 + .../network/http/nas/NasStatusResponse.java | 3 + .../network/http/pgl/PglHandler.java | 382 ++++++++++++++++++ .../network/http/pgl/PglRequest.java | 20 + .../serialization/GameSpyMessageFactory.java | 51 +++ .../GameSpyMessageGenerator.java | 47 +++ .../serialization/GameSpyMessageParser.java | 105 +++++ .../serialization/GsidDeserializer.java | 36 ++ .../serialization/SimpleGeneratorBase.java | 131 ++++++ .../serialization/SimpleParserBase.java | 158 ++++++++ .../serialization/UrlEncodedFormFactory.java | 101 +++++ .../UrlEncodedFormGenerator.java | 130 ++++++ .../serialization/UrlEncodedFormParser.java | 164 ++++++++ .../utility/CertificateGenerator.java | 157 +++++++ .../entralinked/utility/ConsumerAppender.java | 61 +++ src/main/java/entralinked/utility/Crc16.java | 38 ++ .../utility/CredentialGenerator.java | 35 ++ .../java/entralinked/utility/GsidUtility.java | 42 ++ .../entralinked/utility/LEOutputStream.java | 58 +++ src/main/java/entralinked/utility/MD5.java | 34 ++ src/main/resources/dashboard/login.html | 14 + src/main/resources/dashboard/profile.html | 173 ++++++++ src/main/resources/dashboard/scripts/login.js | 22 + .../resources/dashboard/scripts/profile.js | 365 +++++++++++++++++ src/main/resources/dashboard/styles/login.css | 38 ++ .../resources/dashboard/styles/profile.css | 164 ++++++++ src/main/resources/dlc.paths | 44 ++ .../dlc/IRAO/CGEAR/C003_munna_1_en.bin | Bin 0 -> 9730 bytes .../dlc/IRAO/CGEAR/C004_pikachu_1_e.bin | Bin 0 -> 9730 bytes .../dlc/IRAO/CGEAR/C005_chirami_1_e.bin | Bin 0 -> 9730 bytes .../dlc/IRAO/CGEAR/C007_tabunne_1_e.bin | Bin 0 -> 9730 bytes .../dlc/IRAO/CGEAR/C008_pocchama_1_e.bin | Bin 0 -> 9730 bytes .../dlc/IRAO/CGEAR/C009_guregguru_1_e.bin | Bin 0 -> 9730 bytes .../dlc/IRAO/CGEAR/C010_fushigibana_1_e.bin | Bin 0 -> 9730 bytes .../dlc/IRAO/CGEAR/C011_rizadon_1_e.bin | Bin 0 -> 9730 bytes .../dlc/IRAO/CGEAR/C012_kamekkusu_1_e.bin | Bin 0 -> 9730 bytes .../dlc/IRAO/CGEAR/C013_zekurom_1_e_v02.bin | Bin 0 -> 9730 bytes .../dlc/IRAO/CGEAR/C014_reshiram_1_e.bin | Bin 0 -> 9730 bytes .../dlc/IRAO/CGEAR/C015_vikutini_1_e.bin | Bin 0 -> 9730 bytes .../dlc/IRAO/CGEAR/C017_porigonz_0_e.bin | Bin 0 -> 9730 bytes .../resources/dlc/IRAO/CGEAR/C018_WCS_1_e.bin | Bin 0 -> 9730 bytes .../dlc/IRAO/CGEAR/C019_shikijikaharu_1_e.bin | Bin 0 -> 9730 bytes .../dlc/IRAO/CGEAR/C020_shikijikanatu_1_e.bin | Bin 0 -> 9730 bytes .../dlc/IRAO/CGEAR/C021_shikijikaaki_1_e.bin | Bin 0 -> 9730 bytes .../dlc/IRAO/CGEAR/C022_shikijikafuyu_1_e.bin | Bin 0 -> 9730 bytes .../dlc/IRAO/CGEAR/C023_iwaparesu_1_e.bin | Bin 0 -> 9730 bytes .../dlc/IRAO/CGEAR/C024_zoroark_1_e.bin | Bin 0 -> 9730 bytes .../dlc/IRAO/CGEAR/C025_giaru_1_e.bin | Bin 0 -> 9730 bytes .../dlc/IRAO/CGEAR/C030_kerudhio_1_en.bin | Bin 0 -> 9730 bytes .../dlc/IRAO/CGEAR/C031-1_meroetta_1_e.bin | Bin 0 -> 9730 bytes .../dlc/IRAO/CGEAR/C032-1_wcs2012_1_e.bin | Bin 0 -> 9730 bytes .../dlc/IRAO/CGEAR/C100_defo_1_en.bin | Bin 0 -> 9730 bytes .../dlc/IRAO/CGEAR2/C003-2_munna_1_en.bin | Bin 0 -> 9730 bytes .../dlc/IRAO/CGEAR2/C013-2_zekrom_1_e.bin | Bin 0 -> 9730 bytes .../dlc/IRAO/CGEAR2/C014-2_reshiram_1_e.bin | Bin 0 -> 9730 bytes .../dlc/IRAO/CGEAR2/C015-2_victini_1_e.bin | Bin 0 -> 9730 bytes .../dlc/IRAO/CGEAR2/C030-2_kerudhio_1_en.bin | Bin 0 -> 9730 bytes .../dlc/IRAO/CGEAR2/C031-2_meroetta_1_e.bin | Bin 0 -> 9730 bytes .../dlc/IRAO/CGEAR2/C033_halloween_1_en.bin | 31 ++ .../resources/dlc/IRAO/CGEAR2/C035_BK_1_e.bin | Bin 0 -> 9730 bytes .../resources/dlc/IRAO/CGEAR2/C036_WK_1_e.bin | Bin 0 -> 9730 bytes .../dlc/IRAO/CGEAR2/C100-2_default_1_e.bin | Bin 0 -> 9730 bytes .../dlc/IRAO/MUSICAL/M010_munna_1_e_02.bin | Bin 0 -> 75242 bytes .../dlc/IRAO/MUSICAL/M013_meloetta_1_e.bin | Bin 0 -> 69262 bytes .../dlc/IRAO/ZUKAN/Z003_BWsyokigirl_1_en.bin | Bin 0 -> 25090 bytes .../dlc/IRAO/ZUKAN/Z004_BWsyokiboy_1_en.bin | Bin 0 -> 25090 bytes .../dlc/IRAO/ZUKAN/Z007_hyuu_1_en.bin | Bin 0 -> 25090 bytes .../dlc/IRAO/ZUKAN/Z008_bell_1_e.bin | 1 + .../dlc/IRAO/ZUKAN/Z009_tyeren_1_e.bin | 1 + .../dlc/IRAO/ZUKAN/Z100_defogirl_1_en.bin | Bin 0 -> 25090 bytes .../dlc/IRAO/ZUKAN/Z101_defoboy_1_en.bin | Bin 0 -> 25090 bytes src/main/resources/log4j2.xml | 25 ++ .../java/entralinked/utility/Crc16Test.java | 32 ++ .../entralinked/utility/GsidUtilityTest.java | 45 +++ .../java/entralinked/utility/MD5Test.java | 17 + 149 files changed, 6658 insertions(+), 1 deletion(-) create mode 100644 .gitattributes create mode 100644 .github/workflows/dist-pull-request.yml create mode 100644 .github/workflows/dist-upload-artifact.yml create mode 100644 .gitignore create mode 100644 build.gradle create mode 100644 gradle.properties create mode 100644 gradle/wrapper/gradle-wrapper.jar create mode 100644 gradle/wrapper/gradle-wrapper.properties create mode 100644 gradlew create mode 100644 gradlew.bat create mode 100644 scripts/gradlew-clean.bat create mode 100644 scripts/gradlew-dist.bat create mode 100644 scripts/gradlew-test.bat create mode 100644 settings.gradle create mode 100644 src/main/java/entralinked/CommandLineArguments.java create mode 100644 src/main/java/entralinked/Configuration.java create mode 100644 src/main/java/entralinked/Entralinked.java create mode 100644 src/main/java/entralinked/GameVersion.java create mode 100644 src/main/java/entralinked/LauncherAgent.java create mode 100644 src/main/java/entralinked/gui/MainView.java create mode 100644 src/main/java/entralinked/model/dlc/Dlc.java create mode 100644 src/main/java/entralinked/model/dlc/DlcList.java create mode 100644 src/main/java/entralinked/model/pkmn/PkmnGender.java create mode 100644 src/main/java/entralinked/model/pkmn/PkmnInfo.java create mode 100644 src/main/java/entralinked/model/pkmn/PkmnInfoReader.java create mode 100644 src/main/java/entralinked/model/pkmn/PkmnNature.java create mode 100644 src/main/java/entralinked/model/player/DreamAnimation.java create mode 100644 src/main/java/entralinked/model/player/DreamEncounter.java create mode 100644 src/main/java/entralinked/model/player/DreamItem.java create mode 100644 src/main/java/entralinked/model/player/Player.java create mode 100644 src/main/java/entralinked/model/player/PlayerDto.java create mode 100644 src/main/java/entralinked/model/player/PlayerManager.java create mode 100644 src/main/java/entralinked/model/player/PlayerStatus.java create mode 100644 src/main/java/entralinked/model/user/GameProfile.java create mode 100644 src/main/java/entralinked/model/user/GameProfileDto.java create mode 100644 src/main/java/entralinked/model/user/ServiceCredentials.java create mode 100644 src/main/java/entralinked/model/user/ServiceSession.java create mode 100644 src/main/java/entralinked/model/user/User.java create mode 100644 src/main/java/entralinked/model/user/UserDto.java create mode 100644 src/main/java/entralinked/model/user/UserManager.java create mode 100644 src/main/java/entralinked/network/NettyServerBase.java create mode 100644 src/main/java/entralinked/network/dns/DnsQueryHandler.java create mode 100644 src/main/java/entralinked/network/dns/DnsServer.java create mode 100644 src/main/java/entralinked/network/gamespy/GameSpyHandler.java create mode 100644 src/main/java/entralinked/network/gamespy/GameSpyMessageEncoder.java create mode 100644 src/main/java/entralinked/network/gamespy/GameSpyRequestDecoder.java create mode 100644 src/main/java/entralinked/network/gamespy/GameSpyServer.java create mode 100644 src/main/java/entralinked/network/gamespy/message/GameSpyChallengeMessage.java create mode 100644 src/main/java/entralinked/network/gamespy/message/GameSpyErrorMessage.java create mode 100644 src/main/java/entralinked/network/gamespy/message/GameSpyLoginResponse.java create mode 100644 src/main/java/entralinked/network/gamespy/message/GameSpyMessage.java create mode 100644 src/main/java/entralinked/network/gamespy/message/GameSpyProfileResponse.java create mode 100644 src/main/java/entralinked/network/gamespy/request/GameSpyKeepAliveRequest.java create mode 100644 src/main/java/entralinked/network/gamespy/request/GameSpyLoginRequest.java create mode 100644 src/main/java/entralinked/network/gamespy/request/GameSpyLogoutRequest.java create mode 100644 src/main/java/entralinked/network/gamespy/request/GameSpyProfileRequest.java create mode 100644 src/main/java/entralinked/network/gamespy/request/GameSpyProfileUpdateRequest.java create mode 100644 src/main/java/entralinked/network/gamespy/request/GameSpyRequest.java create mode 100644 src/main/java/entralinked/network/gamespy/request/GameSpyStatusRequest.java create mode 100644 src/main/java/entralinked/network/http/HttpHandler.java create mode 100644 src/main/java/entralinked/network/http/HttpRequestHandler.java create mode 100644 src/main/java/entralinked/network/http/HttpServer.java create mode 100644 src/main/java/entralinked/network/http/dashboard/DashboardHandler.java create mode 100644 src/main/java/entralinked/network/http/dashboard/DashboardProfileMessage.java create mode 100644 src/main/java/entralinked/network/http/dashboard/DashboardProfileUpdateRequest.java create mode 100644 src/main/java/entralinked/network/http/dashboard/DashboardStatusMessage.java create mode 100644 src/main/java/entralinked/network/http/dls/DlsHandler.java create mode 100644 src/main/java/entralinked/network/http/dls/DlsRequest.java create mode 100644 src/main/java/entralinked/network/http/nas/NasHandler.java create mode 100644 src/main/java/entralinked/network/http/nas/NasLoginResponse.java create mode 100644 src/main/java/entralinked/network/http/nas/NasRequest.java create mode 100644 src/main/java/entralinked/network/http/nas/NasResponse.java create mode 100644 src/main/java/entralinked/network/http/nas/NasReturnCode.java create mode 100644 src/main/java/entralinked/network/http/nas/NasServiceLocationResponse.java create mode 100644 src/main/java/entralinked/network/http/nas/NasStatusResponse.java create mode 100644 src/main/java/entralinked/network/http/pgl/PglHandler.java create mode 100644 src/main/java/entralinked/network/http/pgl/PglRequest.java create mode 100644 src/main/java/entralinked/serialization/GameSpyMessageFactory.java create mode 100644 src/main/java/entralinked/serialization/GameSpyMessageGenerator.java create mode 100644 src/main/java/entralinked/serialization/GameSpyMessageParser.java create mode 100644 src/main/java/entralinked/serialization/GsidDeserializer.java create mode 100644 src/main/java/entralinked/serialization/SimpleGeneratorBase.java create mode 100644 src/main/java/entralinked/serialization/SimpleParserBase.java create mode 100644 src/main/java/entralinked/serialization/UrlEncodedFormFactory.java create mode 100644 src/main/java/entralinked/serialization/UrlEncodedFormGenerator.java create mode 100644 src/main/java/entralinked/serialization/UrlEncodedFormParser.java create mode 100644 src/main/java/entralinked/utility/CertificateGenerator.java create mode 100644 src/main/java/entralinked/utility/ConsumerAppender.java create mode 100644 src/main/java/entralinked/utility/Crc16.java create mode 100644 src/main/java/entralinked/utility/CredentialGenerator.java create mode 100644 src/main/java/entralinked/utility/GsidUtility.java create mode 100644 src/main/java/entralinked/utility/LEOutputStream.java create mode 100644 src/main/java/entralinked/utility/MD5.java create mode 100644 src/main/resources/dashboard/login.html create mode 100644 src/main/resources/dashboard/profile.html create mode 100644 src/main/resources/dashboard/scripts/login.js create mode 100644 src/main/resources/dashboard/scripts/profile.js create mode 100644 src/main/resources/dashboard/styles/login.css create mode 100644 src/main/resources/dashboard/styles/profile.css create mode 100644 src/main/resources/dlc.paths create mode 100644 src/main/resources/dlc/IRAO/CGEAR/C003_munna_1_en.bin create mode 100644 src/main/resources/dlc/IRAO/CGEAR/C004_pikachu_1_e.bin create mode 100644 src/main/resources/dlc/IRAO/CGEAR/C005_chirami_1_e.bin create mode 100644 src/main/resources/dlc/IRAO/CGEAR/C007_tabunne_1_e.bin create mode 100644 src/main/resources/dlc/IRAO/CGEAR/C008_pocchama_1_e.bin create mode 100644 src/main/resources/dlc/IRAO/CGEAR/C009_guregguru_1_e.bin create mode 100644 src/main/resources/dlc/IRAO/CGEAR/C010_fushigibana_1_e.bin create mode 100644 src/main/resources/dlc/IRAO/CGEAR/C011_rizadon_1_e.bin create mode 100644 src/main/resources/dlc/IRAO/CGEAR/C012_kamekkusu_1_e.bin create mode 100644 src/main/resources/dlc/IRAO/CGEAR/C013_zekurom_1_e_v02.bin create mode 100644 src/main/resources/dlc/IRAO/CGEAR/C014_reshiram_1_e.bin create mode 100644 src/main/resources/dlc/IRAO/CGEAR/C015_vikutini_1_e.bin create mode 100644 src/main/resources/dlc/IRAO/CGEAR/C017_porigonz_0_e.bin create mode 100644 src/main/resources/dlc/IRAO/CGEAR/C018_WCS_1_e.bin create mode 100644 src/main/resources/dlc/IRAO/CGEAR/C019_shikijikaharu_1_e.bin create mode 100644 src/main/resources/dlc/IRAO/CGEAR/C020_shikijikanatu_1_e.bin create mode 100644 src/main/resources/dlc/IRAO/CGEAR/C021_shikijikaaki_1_e.bin create mode 100644 src/main/resources/dlc/IRAO/CGEAR/C022_shikijikafuyu_1_e.bin create mode 100644 src/main/resources/dlc/IRAO/CGEAR/C023_iwaparesu_1_e.bin create mode 100644 src/main/resources/dlc/IRAO/CGEAR/C024_zoroark_1_e.bin create mode 100644 src/main/resources/dlc/IRAO/CGEAR/C025_giaru_1_e.bin create mode 100644 src/main/resources/dlc/IRAO/CGEAR/C030_kerudhio_1_en.bin create mode 100644 src/main/resources/dlc/IRAO/CGEAR/C031-1_meroetta_1_e.bin create mode 100644 src/main/resources/dlc/IRAO/CGEAR/C032-1_wcs2012_1_e.bin create mode 100644 src/main/resources/dlc/IRAO/CGEAR/C100_defo_1_en.bin create mode 100644 src/main/resources/dlc/IRAO/CGEAR2/C003-2_munna_1_en.bin create mode 100644 src/main/resources/dlc/IRAO/CGEAR2/C013-2_zekrom_1_e.bin create mode 100644 src/main/resources/dlc/IRAO/CGEAR2/C014-2_reshiram_1_e.bin create mode 100644 src/main/resources/dlc/IRAO/CGEAR2/C015-2_victini_1_e.bin create mode 100644 src/main/resources/dlc/IRAO/CGEAR2/C030-2_kerudhio_1_en.bin create mode 100644 src/main/resources/dlc/IRAO/CGEAR2/C031-2_meroetta_1_e.bin create mode 100644 src/main/resources/dlc/IRAO/CGEAR2/C033_halloween_1_en.bin create mode 100644 src/main/resources/dlc/IRAO/CGEAR2/C035_BK_1_e.bin create mode 100644 src/main/resources/dlc/IRAO/CGEAR2/C036_WK_1_e.bin create mode 100644 src/main/resources/dlc/IRAO/CGEAR2/C100-2_default_1_e.bin create mode 100644 src/main/resources/dlc/IRAO/MUSICAL/M010_munna_1_e_02.bin create mode 100644 src/main/resources/dlc/IRAO/MUSICAL/M013_meloetta_1_e.bin create mode 100644 src/main/resources/dlc/IRAO/ZUKAN/Z003_BWsyokigirl_1_en.bin create mode 100644 src/main/resources/dlc/IRAO/ZUKAN/Z004_BWsyokiboy_1_en.bin create mode 100644 src/main/resources/dlc/IRAO/ZUKAN/Z007_hyuu_1_en.bin create mode 100644 src/main/resources/dlc/IRAO/ZUKAN/Z008_bell_1_e.bin create mode 100644 src/main/resources/dlc/IRAO/ZUKAN/Z009_tyeren_1_e.bin create mode 100644 src/main/resources/dlc/IRAO/ZUKAN/Z100_defogirl_1_en.bin create mode 100644 src/main/resources/dlc/IRAO/ZUKAN/Z101_defoboy_1_en.bin create mode 100644 src/main/resources/log4j2.xml create mode 100644 src/test/java/entralinked/utility/Crc16Test.java create mode 100644 src/test/java/entralinked/utility/GsidUtilityTest.java create mode 100644 src/test/java/entralinked/utility/MD5Test.java diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..00a51af --- /dev/null +++ b/.gitattributes @@ -0,0 +1,6 @@ +# +# https://help.github.com/articles/dealing-with-line-endings/ +# +# These are explicitly windows files and should use crlf +*.bat text eol=crlf + diff --git a/.github/workflows/dist-pull-request.yml b/.github/workflows/dist-pull-request.yml new file mode 100644 index 0000000..c87d41f --- /dev/null +++ b/.github/workflows/dist-pull-request.yml @@ -0,0 +1,19 @@ +name: Build on Pull Request +on: pull_request +jobs: + dist: + runs-on: ubuntu-lastest + steps: + - name: Checkout code + uses: actions/checkout@v3 + - name: Checkout submodules + run: git submodule update --init --recursive + - name: Setup Java 17 + uses: actions/setup-java@v3 + with: + java-version: 17 + distribution: temurin + - name: Setup Gradle + uses: gradle/gradle-build-action@v2 + - name: Run Gradle dist + run: ./gradlew dist diff --git a/.github/workflows/dist-upload-artifact.yml b/.github/workflows/dist-upload-artifact.yml new file mode 100644 index 0000000..1663acb --- /dev/null +++ b/.github/workflows/dist-upload-artifact.yml @@ -0,0 +1,25 @@ +name: Build and Upload Artifact +on: workflow_dispatch +jobs: + dist: + runs-on: ubuntu-lastest + steps: + - name: Checkout code + uses: actions/checkout@v3 + - name: Checkout submodules + run: git submodule update --init --recursive + - name: Setup Java 17 + uses: actions/setup-java@v3 + with: + java-version: 17 + distribution: temurin + - name: Setup Gradle + uses: gradle/gradle-build-action@v2 + - name: Run Gradle dist + run: ./gradlew dist + - name: Upload artifact + uses: actions/upload-artifact@v3 + with: + name: entralinked + path: build/libs/entralinked.jar + retention-days: 7 diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..07da21d --- /dev/null +++ b/.gitignore @@ -0,0 +1,12 @@ +# Gradle +.gradle +build +run +testRun + +# Eclipse +.metadata +.settings +.project +.classpath +bin diff --git a/README.md b/README.md index 48c0c09..7898881 100644 --- a/README.md +++ b/README.md @@ -1 +1,28 @@ -# entralinked +# Entralinked +[![build](https://github.com/kuroppoi/entralinked/actions/workflows/dist-upload-artifact.yml/badge.svg)](https://github.com/kuroppoi/entralinked/actions) + +Entralinked is a standalone Game Sync emulator developed for use with Pokémon Black & White and Pokémon Black 2 & White 2.\ +Its purpose is to serve as a simple utility for downloading Pokémon, Items, C-Gear skins, Pokédex skins and Musicals\ +without needing to edit your save file. + +## Building + +#### Prerequisites + +- Java 17 Development Kit + +``` +git clone --recurse-submodules https://github.com/kuroppoi/entralinked.git +cd entralinked +./gradlew dist +``` + +## Usage + +Execute `entralinked.jar`, or without the user interface: +``` +java -jar entralinked.jar disablegui +``` +Entralinked has a built-in DNS server. In order for your game to connect, you must configure the DNS settings of your DS.\ +By default, Entralinked is configured to use the local host of the system.\ +After tucking in a Pokémon, navigate to `http://localhost/dashboard/profile.html` in a web browser to configure Game Sync settings. diff --git a/build.gradle b/build.gradle new file mode 100644 index 0000000..532f975 --- /dev/null +++ b/build.gradle @@ -0,0 +1,86 @@ +plugins { + id 'java' +} + +project.ext { + mainClass = 'entralinked.Entralinked' + agentClass = 'entralinked.LauncherAgent' + workingDirectory = 'run' +} + +repositories { + mavenCentral() +} + +configurations { + signedImplementation + implementation.extendsFrom signedImplementation +} + +dependencies { + testImplementation 'org.junit.jupiter:junit-jupiter:5.7.1' + signedImplementation 'org.bouncycastle:bcpkix-jdk15on:1.70' + implementation 'org.apache.logging.log4j:log4j-core:2.20.0' + implementation 'org.apache.logging.log4j:log4j-api:2.20.0' + implementation 'com.fasterxml.jackson.core:jackson-databind:2.15.2' + implementation 'com.fasterxml.jackson.datatype:jackson-datatype-jsr310:2.15.2' + implementation 'io.netty:netty-all:4.1.79.Final' + implementation 'io.javalin:javalin:5.5.0' + implementation 'org.apache.logging.log4j:log4j-slf4j2-impl:2.20.0' +} + +sourceSets { + main { + resources { + srcDir 'poke-sprites-v' + } + } +} + +test { + workingDir = "testRun"; + useJUnitPlatform() + + doFirst { + mkdir workingDir + } +} + +compileJava { + options.encoding = "UTF-8" +} + +task dist(type: Jar) { + manifest { + attributes 'Main-Class': project.ext.mainClass, + 'Launcher-Agent-Class': project.ext.agentClass, + 'Multi-Release': 'true' + } + + from { + (configurations.runtimeClasspath - configurations.signedImplementation).collect { + it.isDirectory() ? it : zipTree(it) + } + } + + from { + configurations.signedImplementation + } + + duplicatesStrategy = DuplicatesStrategy.EXCLUDE + dependsOn configurations.runtimeClasspath, test + with jar +} + +task run(type: JavaExec) { + mainClass = project.ext.mainClass + workingDir = project.ext.workingDirectory + classpath = sourceSets.main.runtimeClasspath + + doFirst { + mkdir workingDir + } +} + +sourceCompatibility = JavaVersion.VERSION_17 +targetCompatibility = JavaVersion.VERSION_17 diff --git a/gradle.properties b/gradle.properties new file mode 100644 index 0000000..6c7b331 --- /dev/null +++ b/gradle.properties @@ -0,0 +1 @@ +org.gradle.logging.level=info diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar new file mode 100644 index 0000000000000000000000000000000000000000..249e5832f090a2944b7473328c07c9755baa3196 GIT binary patch literal 60756 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 '} + case $link in #( + /*) app_path=$link ;; #( + *) app_path=$APP_HOME$link ;; + esac +done + +APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit + +APP_NAME="Gradle" +APP_BASE_NAME=${0##*/} + +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' + +# Use the maximum available, or set MAX_FD != -1 to use that value. +MAX_FD=maximum + +warn () { + echo "$*" +} >&2 + +die () { + echo + echo "$*" + echo + exit 1 +} >&2 + +# OS specific support (must be 'true' or 'false'). +cygwin=false +msys=false +darwin=false +nonstop=false +case "$( uname )" in #( + CYGWIN* ) cygwin=true ;; #( + Darwin* ) darwin=true ;; #( + MSYS* | MINGW* ) msys=true ;; #( + NONSTOP* ) nonstop=true ;; +esac + +CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar + + +# Determine the Java command to use to start the JVM. +if [ -n "$JAVA_HOME" ] ; then + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD=$JAVA_HOME/jre/sh/java + else + JAVACMD=$JAVA_HOME/bin/java + fi + if [ ! -x "$JAVACMD" ] ; then + die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +else + JAVACMD=java + which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." +fi + +# Increase the maximum file descriptors if we can. +if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then + case $MAX_FD in #( + max*) + MAX_FD=$( ulimit -H -n ) || + warn "Could not query maximum file descriptor limit" + esac + case $MAX_FD in #( + '' | soft) :;; #( + *) + ulimit -n "$MAX_FD" || + warn "Could not set maximum file descriptor limit to $MAX_FD" + esac +fi + +# Collect all arguments for the java command, stacking in reverse order: +# * args from the command line +# * the main class name +# * -classpath +# * -D...appname settings +# * --module-path (only if needed) +# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables. + +# For Cygwin or MSYS, switch paths to Windows format before running java +if "$cygwin" || "$msys" ; then + APP_HOME=$( cygpath --path --mixed "$APP_HOME" ) + CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" ) + + JAVACMD=$( cygpath --unix "$JAVACMD" ) + + # Now convert the arguments - kludge to limit ourselves to /bin/sh + for arg do + if + case $arg in #( + -*) false ;; # don't mess with options #( + /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath + [ -e "$t" ] ;; #( + *) false ;; + esac + then + arg=$( cygpath --path --ignore --mixed "$arg" ) + fi + # Roll the args list around exactly as many times as the number of + # args, so each arg winds up back in the position where it started, but + # possibly modified. + # + # NB: a `for` loop captures its iteration list before it begins, so + # changing the positional parameters here affects neither the number of + # iterations, nor the values presented in `arg`. + shift # remove old arg + set -- "$@" "$arg" # push replacement arg + done +fi + +# Collect all arguments for the java command; +# * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of +# shell script including quotes and variable substitutions, so put them in +# double quotes to make sure that they get re-expanded; and +# * put everything else in single quotes, so that it's not re-expanded. + +set -- \ + "-Dorg.gradle.appname=$APP_BASE_NAME" \ + -classpath "$CLASSPATH" \ + org.gradle.wrapper.GradleWrapperMain \ + "$@" + +# Stop when "xargs" is not available. +if ! command -v xargs >/dev/null 2>&1 +then + die "xargs is not available" +fi + +# Use "xargs" to parse quoted args. +# +# With -n1 it outputs one arg per line, with the quotes and backslashes removed. +# +# In Bash we could simply go: +# +# readarray ARGS < <( xargs -n1 <<<"$var" ) && +# set -- "${ARGS[@]}" "$@" +# +# but POSIX shell has neither arrays nor command substitution, so instead we +# post-process each arg (as a line of input to sed) to backslash-escape any +# character that might be a shell metacharacter, then use eval to reverse +# that process (while maintaining the separation between arguments), and wrap +# the whole thing up as a single "set" statement. +# +# This will of course break if any of these variables contains a newline or +# an unmatched quote. +# + +eval "set -- $( + printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" | + xargs -n1 | + sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' | + tr '\n' ' ' + )" '"$@"' + +exec "$JAVACMD" "$@" diff --git a/gradlew.bat b/gradlew.bat new file mode 100644 index 0000000..f127cfd --- /dev/null +++ b/gradlew.bat @@ -0,0 +1,91 @@ +@rem +@rem Copyright 2015 the original author or authors. +@rem +@rem Licensed under the Apache License, Version 2.0 (the "License"); +@rem you may not use this file except in compliance with the License. +@rem You may obtain a copy of the License at +@rem +@rem https://www.apache.org/licenses/LICENSE-2.0 +@rem +@rem Unless required by applicable law or agreed to in writing, software +@rem distributed under the License is distributed on an "AS IS" BASIS, +@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +@rem See the License for the specific language governing permissions and +@rem limitations under the License. +@rem + +@if "%DEBUG%"=="" @echo off +@rem ########################################################################## +@rem +@rem Gradle startup script for Windows +@rem +@rem ########################################################################## + +@rem Set local scope for the variables with windows NT shell +if "%OS%"=="Windows_NT" setlocal + +set DIRNAME=%~dp0 +if "%DIRNAME%"=="" set DIRNAME=. +set APP_BASE_NAME=%~n0 +set APP_HOME=%DIRNAME% + +@rem Resolve any "." and ".." in APP_HOME to make it shorter. +for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi + +@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" + +@rem Find java.exe +if defined JAVA_HOME goto findJavaFromJavaHome + +set JAVA_EXE=java.exe +%JAVA_EXE% -version >NUL 2>&1 +if %ERRORLEVEL% equ 0 goto execute + +echo. +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:findJavaFromJavaHome +set JAVA_HOME=%JAVA_HOME:"=% +set JAVA_EXE=%JAVA_HOME%/bin/java.exe + +if exist "%JAVA_EXE%" goto execute + +echo. +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:execute +@rem Setup the command line + +set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar + + +@rem Execute Gradle +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* + +:end +@rem End local scope for the variables with windows NT shell +if %ERRORLEVEL% equ 0 goto mainEnd + +:fail +rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of +rem the _cmd.exe /c_ return code! +set EXIT_CODE=%ERRORLEVEL% +if %EXIT_CODE% equ 0 set EXIT_CODE=1 +if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE% +exit /b %EXIT_CODE% + +:mainEnd +if "%OS%"=="Windows_NT" endlocal + +:omega diff --git a/scripts/gradlew-clean.bat b/scripts/gradlew-clean.bat new file mode 100644 index 0000000..edb42d5 --- /dev/null +++ b/scripts/gradlew-clean.bat @@ -0,0 +1,3 @@ +@echo off +call ../gradlew clean -p .. --stacktrace +pause diff --git a/scripts/gradlew-dist.bat b/scripts/gradlew-dist.bat new file mode 100644 index 0000000..cd58368 --- /dev/null +++ b/scripts/gradlew-dist.bat @@ -0,0 +1,3 @@ +@echo off +call ../gradlew dist -p .. --stacktrace +pause diff --git a/scripts/gradlew-test.bat b/scripts/gradlew-test.bat new file mode 100644 index 0000000..ffa9fef --- /dev/null +++ b/scripts/gradlew-test.bat @@ -0,0 +1,3 @@ +@echo off +call ../gradlew test -p .. --stacktrace +pause diff --git a/settings.gradle b/settings.gradle new file mode 100644 index 0000000..9ccf8f9 --- /dev/null +++ b/settings.gradle @@ -0,0 +1 @@ +rootProject.name = 'entralinked' diff --git a/src/main/java/entralinked/CommandLineArguments.java b/src/main/java/entralinked/CommandLineArguments.java new file mode 100644 index 0000000..764ec9a --- /dev/null +++ b/src/main/java/entralinked/CommandLineArguments.java @@ -0,0 +1,15 @@ +package entralinked; + +import java.util.Collection; +import java.util.List; + +public record CommandLineArguments(boolean disableGui) { + + public CommandLineArguments(Collection args) { + this(args.contains("disablegui")); + } + + public CommandLineArguments(String... args) { + this(List.of(args)); + } +} diff --git a/src/main/java/entralinked/Configuration.java b/src/main/java/entralinked/Configuration.java new file mode 100644 index 0000000..c22b607 --- /dev/null +++ b/src/main/java/entralinked/Configuration.java @@ -0,0 +1,14 @@ +package entralinked; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonProperty; + +@JsonIgnoreProperties(ignoreUnknown = true) +public record Configuration( + @JsonProperty(required = true) String hostName, + @JsonProperty(required = true) boolean clearPlayerDreamInfoOnWake, + @JsonProperty(required = true) boolean allowOverwritingPlayerDreamInfo, + @JsonProperty(required = true) boolean allowWfcRegistrationThroughLogin) { + + public static final Configuration DEFAULT = new Configuration("local", true, false, true); +} diff --git a/src/main/java/entralinked/Entralinked.java b/src/main/java/entralinked/Entralinked.java new file mode 100644 index 0000000..6e130a5 --- /dev/null +++ b/src/main/java/entralinked/Entralinked.java @@ -0,0 +1,156 @@ +package entralinked; + +import java.io.File; +import java.io.IOException; +import java.lang.reflect.InvocationTargetException; +import java.net.InetAddress; +import java.net.UnknownHostException; + +import javax.swing.SwingUtilities; + +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; + +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.SerializationFeature; + +import entralinked.gui.MainView; +import entralinked.model.dlc.DlcList; +import entralinked.model.player.PlayerManager; +import entralinked.model.user.UserManager; +import entralinked.network.dns.DnsServer; +import entralinked.network.gamespy.GameSpyServer; +import entralinked.network.http.HttpServer; +import entralinked.network.http.dashboard.DashboardHandler; +import entralinked.network.http.dls.DlsHandler; +import entralinked.network.http.nas.NasHandler; +import entralinked.network.http.pgl.PglHandler; + +public class Entralinked { + + public static void main(String[] args) { + new Entralinked(args); + } + + private static final Logger logger = LogManager.getLogger(); + private final ObjectMapper mapper = new ObjectMapper().enable(SerializationFeature.INDENT_OUTPUT); + private final Configuration configuration; + private final DlcList dlcList; + private final UserManager userManager; + private final PlayerManager playerManager; + private final DnsServer dnsServer; + private final GameSpyServer gameSpyServer; + private final HttpServer httpServer; + + public Entralinked(String[] args) { + // Read command line arguments + CommandLineArguments arguments = new CommandLineArguments(args); + + // Create GUI if enabled + if(!arguments.disableGui()) { + try { + SwingUtilities.invokeAndWait(() -> new MainView(this)); + } catch (InvocationTargetException | InterruptedException e) { + logger.error("An error occured whilst creating main view", e); + } + } + + // Load config + configuration = loadConfigFile(); + logger.info("Using configuration {}", configuration); + + // Get host address + InetAddress hostAddress = null; + String hostName = configuration.hostName(); + + if(hostName.equals("local") || hostName.equals("localhost")) { + hostAddress = getLocalHost(); + } else { + try { + hostAddress = InetAddress.getByName(hostName); + } catch(UnknownHostException e) { + hostAddress = getLocalHost(); + logger.error("Could not resolve host name - falling back to {} ", hostAddress, e); + } + } + + // Load persistent data + dlcList = new DlcList(); + userManager = new UserManager(); + playerManager = new PlayerManager(); + + // Create DNS server + dnsServer = new DnsServer(hostAddress); + dnsServer.start(); + + // Create GameSpy server + gameSpyServer = new GameSpyServer(this); + gameSpyServer.start(); + + // Create HTTP server + httpServer = new HttpServer(this); + httpServer.addHandler(new NasHandler(this)); + httpServer.addHandler(new PglHandler(this)); + httpServer.addHandler(new DlsHandler(this)); + httpServer.addHandler(new DashboardHandler(this)); + httpServer.start(); + } + + public void stopServers() { + if(httpServer != null) { + httpServer.stop(); + } + + if(gameSpyServer != null) { + gameSpyServer.stop(); + } + + if(dnsServer != null) { + dnsServer.stop(); + } + } + + private Configuration loadConfigFile() { + logger.info("Loading configuration ..."); + + try { + File configFile = new File("config.json"); + + if(!configFile.exists()) { + logger.info("No configuration file exists - default configuration will be used"); + mapper.writeValue(configFile, Configuration.DEFAULT); + return Configuration.DEFAULT; + } else { + return mapper.readValue(configFile, Configuration.class); + } + } catch(IOException e) { + logger.error("Could not load configuration - default configuration will be used", e); + return Configuration.DEFAULT; + } + } + + private InetAddress getLocalHost() { + try { + return InetAddress.getLocalHost(); + } catch(UnknownHostException e) { + logger.error("Could not resolve local host", e); + return null; + } + } + + public Configuration getConfiguration() { + return configuration; + } + + public DlcList getDlcList() { + return dlcList; + } + + public UserManager getUserManager() { + return userManager; + } + + public PlayerManager getPlayerManager() { + return playerManager; + } +} diff --git a/src/main/java/entralinked/GameVersion.java b/src/main/java/entralinked/GameVersion.java new file mode 100644 index 0000000..bd4a1e3 --- /dev/null +++ b/src/main/java/entralinked/GameVersion.java @@ -0,0 +1,104 @@ +package entralinked; + +import java.util.HashMap; +import java.util.Map; + +public enum GameVersion { + + // ================================== + // Black Version & White Version + // ================================== + + BLACK_JAPANESE(21, 1, "IRBJ", "ブラック"), + BLACK_ENGLISH(21, 2, "IRBO", "Black Version"), + BLACK_FRENCH(21, 3, "IRBF", "Version Noire"), + BLACK_ITALIAN(21, 4, "IRBI", "Versione Nera"), + BLACK_GERMAN(21, 5, "IRBD", "Schwarze Edition"), + BLACK_SPANISH(21, 7, "IRBS", "Edicion Negra"), + BLACK_KOREAN(21, 8, "IRBK", "블랙"), + + WHITE_JAPANESE(20, 1, "IRAJ", "ホワイト"), + WHITE_ENGLISH(20, 2, "IRAO", "White Version"), + WHITE_FRENCH(20, 3, "IRAF", "Version Blanche"), + WHITE_ITALIAN(20, 4, "IRAI", "Versione Bianca"), + WHITE_GERMAN(20, 5, "IRAD", "Weisse Edition"), + WHITE_SPANISH(20, 7, "IRAS", "Edicion Blanca"), + WHITE_KOREAN(20, 8, "IRAK", "화이트"), + + // ================================== + // Black Version 2 & White Version 2 + // ================================== + + BLACK_2_JAPANESE(23, 1, "IREJ", "ブラック2", true), + BLACK_2_ENGLISH(23, 2, "IREO", "Black Version 2", true), + BLACK_2_FRENCH(23, 3, "IREF", "Version Noire 2", true), + BLACK_2_ITALIAN(23, 4, "IREI", "Versione Nera 2", true), + BLACK_2_GERMAN(23, 5, "IRED", "Schwarze Edition 2", true), + BLACK_2_SPANISH(23, 7, "IRES", "Edicion Negra 2", true), + BLACK_2_KOREAN(23, 8, "IREK", "블랙2", true), + + WHITE_2_JAPANESE(22, 1, "IRDJ", "ホワイト2", true), + WHITE_2_ENGLISH(22, 2, "IRDO", "White Version 2", true), + WHITE_2_FRENCH(22, 3, "IRDF", "Version Blanche 2", true), + WHITE_2_ITALIAN(22, 4, "IRDI", "Versione Bianca 2", true), + WHITE_2_GERMAN(22, 5, "IRDD", "Weisse Edition 2", true), + WHITE_2_SPANISH(22, 7, "IRDS", "Edicion Blanca 2", true), + WHITE_2_KOREAN(22, 8, "IRDK", "화이트2", true); + + // Lookup maps + private static final Map mapBySerial = new HashMap<>(); + private static final Map mapByCodes = new HashMap<>(); + + static { + for(GameVersion version : values()) { + mapBySerial.put(version.getSerial(), version); + mapByCodes.put(version.getRomCode() << version.getLanguageCode(), version); + } + } + + private final int romCode; + private final int languageCode; // Values are not tested + private final String serial; + private final String displayName; + private final boolean isVersion2; + + private GameVersion(int romCode, int languageCode, String serial, String displayName, boolean isVersion2) { + this.romCode = romCode; + this.languageCode = languageCode; + this.serial = serial; + this.displayName = displayName; + this.isVersion2 = isVersion2; + } + + private GameVersion(int romCode, int languageCode, String serial, String displayName) { + this(romCode, languageCode, serial, displayName, false); + } + + public static GameVersion lookup(String serial) { + return mapBySerial.get(serial); + } + + public static GameVersion lookup(int romCode, int languageCode) { + return mapByCodes.get(romCode << languageCode); + } + + public int getRomCode() { + return romCode; + } + + public int getLanguageCode() { + return languageCode; + } + + public String getSerial() { + return serial; + } + + public String getDisplayName() { + return displayName; + } + + public boolean isVersion2() { + return isVersion2; + } +} diff --git a/src/main/java/entralinked/LauncherAgent.java b/src/main/java/entralinked/LauncherAgent.java new file mode 100644 index 0000000..bfbab49 --- /dev/null +++ b/src/main/java/entralinked/LauncherAgent.java @@ -0,0 +1,47 @@ +package entralinked; + +import java.io.File; +import java.lang.instrument.Instrumentation; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.StandardCopyOption; +import java.util.jar.JarFile; + +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; + +/** + * Stupid solution to a stupid problem. + * If this just randomly breaks in the future because of some nonsense security reason I will completely lose it. + */ +public class LauncherAgent { + + private static final Logger logger = LogManager.getLogger(); + private static boolean bouncyCastlePresent = true; + + public static void agentmain(String args, Instrumentation instrumentation) { + try { + String[] jarNames = { + "bcutil-jdk15on-1.70.jar", + "bcprov-jdk15on-1.70.jar", + "bcpkix-jdk15on-1.70.jar" + }; + + for(int i = 0; i < jarNames.length; i++) { + String jarName = jarNames[i]; + Path jarPath = Files.createTempFile(jarNames[i], null); + File jarFile = jarPath.toFile(); + jarFile.deleteOnExit(); // Doesn't actually do anything on terminal exit because Java. + Files.copy(LauncherAgent.class.getResourceAsStream("/%s".formatted(jarName)), jarPath, StandardCopyOption.REPLACE_EXISTING); + instrumentation.appendToSystemClassLoaderSearch(new JarFile(jarFile)); + } + } catch(Exception e) { + logger.error("Could not add BouncyCastle to SystemClassLoader search", e); + bouncyCastlePresent = false; + } + } + + public static boolean isBouncyCastlePresent() { + return bouncyCastlePresent; + } +} diff --git a/src/main/java/entralinked/gui/MainView.java b/src/main/java/entralinked/gui/MainView.java new file mode 100644 index 0000000..5d8a6a2 --- /dev/null +++ b/src/main/java/entralinked/gui/MainView.java @@ -0,0 +1,125 @@ +package entralinked.gui; + +import java.awt.BorderLayout; +import java.awt.Desktop; +import java.awt.Dimension; +import java.awt.Font; +import java.awt.event.WindowAdapter; +import java.awt.event.WindowEvent; +import java.io.IOException; +import java.net.URISyntaxException; +import java.net.URL; +import java.util.concurrent.CompletableFuture; + +import javax.swing.JButton; +import javax.swing.JFrame; +import javax.swing.JPanel; +import javax.swing.JScrollPane; +import javax.swing.JTextPane; +import javax.swing.UIManager; +import javax.swing.UnsupportedLookAndFeelException; +import javax.swing.text.AttributeSet; +import javax.swing.text.BadLocationException; +import javax.swing.text.DefaultCaret; +import javax.swing.text.Document; +import javax.swing.text.SimpleAttributeSet; +import javax.swing.text.StyleConstants; +import javax.swing.text.StyleContext; + +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; + +import entralinked.Entralinked; +import entralinked.utility.ConsumerAppender; + +/** + * Simple Swing user interface. + */ +public class MainView { + + private static Logger logger = LogManager.getLogger(); + private final StyleContext styleContext = StyleContext.getDefaultStyleContext(); + private final AttributeSet fontAttribute = styleContext.addAttribute(SimpleAttributeSet.EMPTY, StyleConstants.FontFamily, "Consolas"); + + public MainView(Entralinked entralinked) { + // Try set Look and Feel + try { + UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName()); + } catch (ReflectiveOperationException | UnsupportedLookAndFeelException e) { + logger.error("Could not set Look and Feel", e); + } + + // Create dashboard button + JButton dashboardButton = new JButton("Open User Dashboard"); + dashboardButton.setFocusable(false); + dashboardButton.addActionListener(event -> { + openUrl("http://127.0.0.1/dashboard/profile.html"); + }); + + // Create console output + JTextPane consoleOutputPane = new JTextPane() { + @Override + public boolean getScrollableTracksViewportWidth() { + return getPreferredSize().width <= getParent().getSize().width; + } + + @Override + public Dimension getPreferredSize() { + return getUI().getPreferredSize(this); + }; + }; + consoleOutputPane.setFont(new Font("Consola", Font.PLAIN, 12)); + consoleOutputPane.setEditable(false); + ((DefaultCaret)consoleOutputPane.getCaret()).setUpdatePolicy(DefaultCaret.ALWAYS_UPDATE); + + // Create console output appender + ConsumerAppender.addConsumer("GuiOutput", message -> { + Document document = consoleOutputPane.getDocument(); + try { + consoleOutputPane.getDocument().insertString(document.getLength(), message, fontAttribute); + } catch(BadLocationException e) {} + }); + + // Create console output scroll pane + JScrollPane scrollPane = new JScrollPane(); + scrollPane.setVerticalScrollBarPolicy(JScrollPane.VERTICAL_SCROLLBAR_ALWAYS); + scrollPane.setViewportView(consoleOutputPane); + + // Create main panel + JPanel panel = new JPanel(new BorderLayout()); + panel.add(scrollPane, BorderLayout.CENTER); + panel.add(dashboardButton, BorderLayout.PAGE_END); + + // Create window + JFrame frame = new JFrame("Entralinked"); + frame.addWindowListener(new WindowAdapter() { + @Override + public void windowClosing(WindowEvent event) { + // Run asynchronously so it doesn't just awkwardly freeze + // Still scuffed but better than nothing I guess + CompletableFuture.runAsync(() -> { + entralinked.stopServers(); + System.exit(0); + }); + } + }); + frame.setDefaultCloseOperation(JFrame.DO_NOTHING_ON_CLOSE); + frame.setMinimumSize(new Dimension(512, 288)); + frame.add(panel); + frame.pack(); + frame.setLocationRelativeTo(null); + frame.setVisible(true); + } + + private void openUrl(String url) { + Desktop desktop = Desktop.getDesktop(); + + if(desktop != null && desktop.isSupported(Desktop.Action.BROWSE)) { + try { + desktop.browse(new URL(url).toURI()); + } catch(IOException | URISyntaxException e) { + e.printStackTrace(); + } + } + } +} diff --git a/src/main/java/entralinked/model/dlc/Dlc.java b/src/main/java/entralinked/model/dlc/Dlc.java new file mode 100644 index 0000000..8b576de --- /dev/null +++ b/src/main/java/entralinked/model/dlc/Dlc.java @@ -0,0 +1,7 @@ +package entralinked.model.dlc; + +/** + * Simple record for DLC data. + */ +public record Dlc(String path, String name, String gameCode, String type, + int index, int projectedSize, int checksum, boolean checksumEmbedded) {} diff --git a/src/main/java/entralinked/model/dlc/DlcList.java b/src/main/java/entralinked/model/dlc/DlcList.java new file mode 100644 index 0000000..baa59c4 --- /dev/null +++ b/src/main/java/entralinked/model/dlc/DlcList.java @@ -0,0 +1,185 @@ +package entralinked.model.dlc; + +import java.io.BufferedReader; +import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.nio.file.Files; +import java.util.Collection; +import java.util.Collections; +import java.util.List; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; +import java.util.function.Predicate; +import java.util.stream.Collectors; + +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; + +import entralinked.utility.Crc16; + +public class DlcList { + + private static final Logger logger = LogManager.getLogger(); + private final Map dlcMap = new ConcurrentHashMap<>(); + private final File dataDirectory = new File("dlc"); + + public DlcList() { + logger.info("Loading DLC ..."); + + // Extract defaults if external DLC directory is not present + if(!dataDirectory.exists()) { + logger.info("Extracting default DLC files ..."); + BufferedReader reader = new BufferedReader(new InputStreamReader(getClass().getResourceAsStream("/dlc.paths"))); + String line = null; + + try { + while((line = reader.readLine()) != null) { + InputStream resource = getClass().getResourceAsStream(line); + File outputFile = new File("./%s".formatted(line)); + + // Create parent directories + if(outputFile.getParentFile() != null) { + outputFile.getParentFile().mkdirs(); + } + System.out.println(outputFile); + // Copy resource to destination + if(resource != null) { + Files.copy(resource, outputFile.toPath()); + } + } + } catch (IOException e) { + logger.error("Could not extract default DLC files", e); + return; + } + } + + // Just to be sure... + if(!dataDirectory.isDirectory()) { + return; + } + + // Game Serial level + for(File file : dataDirectory.listFiles()) { + // Make sure that file is a directory, log warning and skip otherwise + if(!file.isDirectory()) { + logger.warn("Non-directory '{}' in DLC root folder", file.getName()); + continue; + } + + // DLC Type level + for(File subFile : file.listFiles()) { + // Check if file is directory + if(!subFile.isDirectory()) { + logger.warn("Non-directory '{}' in DLC subfolder '{}'", file.getName(), subFile.getName()); + continue; + } + + int index = 1; + + // DLC Content level + for(File dlcFile : subFile.listFiles()) { + // Load DLC data + Dlc dlc = loadDlcFile(file.getName(), subFile.getName(), index, dlcFile); + + // Index DLC object if loading succeeded + if(dlc != null) { + dlcMap.put(dlc.name(), dlc); + index++; + } + } + } + } + + logger.info("Loaded {} DLC file(s)", dlcMap.size()); + } + + private Dlc loadDlcFile(String gameCode, String type, int index, File dlcFile) { + String name = dlcFile.getName(); + + if(dlcMap.containsKey(name)) { + logger.warn("Duplicate DLC name {}", name); + return null; + } + + if(dlcFile.isDirectory()) { + logger.warn("Directory '{}' in {} DLC folder", name, gameCode); + return null; + } + + // Check if there is a valid CRC-16 checksum appended at the end of the file. + // If not, it will be marked in the DLC record object and the server will automatically append the checksum + // when the DLC content is requested. + // Makes it easier to just throw stuff into the DLC folder. + int projectedSize = 0; + int checksum = 0; + boolean checksumEmbedded = true; + + try { + byte[] bytes = Files.readAllBytes(dlcFile.toPath()); + projectedSize = bytes.length; + checksum = Crc16.calc(bytes, 0, bytes.length - 2); + int checksumInFile = (bytes[bytes.length - 2] & 0xFF) | ((bytes[bytes.length - 1] & 0xFF) << 8); + + if(checksum != checksumInFile) { + projectedSize += 2; + checksum = Crc16.calc(bytes, 0, bytes.length); + checksumEmbedded = false; + } + } catch(IOException e) { + logger.error("Could not read checksum data for {}", dlcFile.getAbsolutePath(), e); + return null; + } + + return new Dlc(dlcFile.getAbsolutePath(), name, gameCode, type, index, projectedSize, checksum, checksumEmbedded); + } + + public List getDlcList(Predicate filter) { + return getDlc().stream().filter(filter).collect(Collectors.toList()); + } + + public List getDlcList(String gameCode, String type, int index) { + return getDlcList(dlc -> + dlc.gameCode().equals(gameCode) && + dlc.type().equals(type) && + dlc.index() == index + ); + } + + public List getDlcList(String gameCode, String type) { + return getDlcList(dlc -> + dlc.gameCode().equals(gameCode) && + dlc.type().equals(type) + ); + } + + public List getDlcList(String gameCode) { + return getDlcList(dlc -> dlc.gameCode().equals(gameCode)); + } + + public String getDlcListString(Collection dlcList) { + StringBuilder builder = new StringBuilder(); + dlcList.forEach(dlc -> { + builder.append("%s\t\t%s\t%s\t\t%s\r\n".formatted(dlc.name(), dlc.type(), dlc.index(), dlc.projectedSize())); + }); + + return builder.toString(); + } + + public Dlc getDlc(String name) { + return dlcMap.get(name); + } + + public int getDlcIndex(String name) { + return dlcExists(name) ? getDlc(name).index() : 0; + } + + public boolean dlcExists(String name) { + return name != null && dlcMap.containsKey(name); + } + + public Collection getDlc() { + return Collections.unmodifiableCollection(dlcMap.values()); + } +} diff --git a/src/main/java/entralinked/model/pkmn/PkmnGender.java b/src/main/java/entralinked/model/pkmn/PkmnGender.java new file mode 100644 index 0000000..455756d --- /dev/null +++ b/src/main/java/entralinked/model/pkmn/PkmnGender.java @@ -0,0 +1,11 @@ +package entralinked.model.pkmn; + +import com.fasterxml.jackson.annotation.JsonEnumDefaultValue; + +public enum PkmnGender { + + @JsonEnumDefaultValue + MALE, + FEMALE, + GENDERLESS; +} diff --git a/src/main/java/entralinked/model/pkmn/PkmnInfo.java b/src/main/java/entralinked/model/pkmn/PkmnInfo.java new file mode 100644 index 0000000..bcfc595 --- /dev/null +++ b/src/main/java/entralinked/model/pkmn/PkmnInfo.java @@ -0,0 +1,28 @@ +package entralinked.model.pkmn; + +import com.fasterxml.jackson.annotation.JsonIgnore; +import com.fasterxml.jackson.annotation.JsonProperty; + +/** + * Record containing information about a Pokémon + */ +public record PkmnInfo( + @JsonProperty(required = true) int personality, + @JsonProperty(required = true) int species, + @JsonProperty(required = true) int heldItem, + @JsonProperty(required = true) int trainerId, + @JsonProperty(required = true) int trainerSecretId, + @JsonProperty(required = true) int level, + @JsonProperty(required = true) int form, + @JsonProperty(required = true) PkmnNature nature, + @JsonProperty(required = true) PkmnGender gender, + @JsonProperty(required = true) String nickname, + @JsonProperty(required = true) String trainerName) { + + @JsonIgnore + public boolean isShiny() { + int p1 = (personality >> 16) & 0xFFFF; + int p2 = personality & 0xFFFF; + return (trainerId ^ trainerSecretId ^ p1 ^ p2) < 8; + } +} diff --git a/src/main/java/entralinked/model/pkmn/PkmnInfoReader.java b/src/main/java/entralinked/model/pkmn/PkmnInfoReader.java new file mode 100644 index 0000000..e2a07f5 --- /dev/null +++ b/src/main/java/entralinked/model/pkmn/PkmnInfoReader.java @@ -0,0 +1,123 @@ +package entralinked.model.pkmn; + +import java.io.IOException; +import java.io.InputStream; + +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; + +import io.netty.buffer.ByteBuf; +import io.netty.buffer.ByteBufAllocator; +import io.netty.buffer.PooledByteBufAllocator; + +/** + * Utility class for reading binary Pokémon data. + */ +public class PkmnInfoReader { + + private static final Logger logger = LogManager.getLogger(); + private static final ByteBufAllocator bufferAllocator = PooledByteBufAllocator.DEFAULT; + private static final byte[] blockShuffleTable = { + 0, 1, 2, 3, 0, 1, 3, 2, 0, 2, 1, 3, 0, 3, 1, 2, + 0, 2, 3, 1, 0, 3, 2, 1, 1, 0, 2, 3, 1, 0, 3, 2, + 2, 0, 1, 3, 3, 0, 1, 2, 2, 0, 3, 1, 3, 0, 2, 1, + 1, 2, 0, 3, 1, 3, 0, 2, 2, 1, 0, 3, 3, 1, 0, 2, + 2, 3, 0, 1, 3, 2, 0, 1, 1, 2, 3, 0, 1, 3, 2, 0, + 2, 1, 3, 0, 3, 1, 2, 0, 2, 3, 1, 0, 3, 2, 1, 0 + }; + + public static PkmnInfo readPokeInfo(InputStream inputStream) throws IOException { + ByteBuf buffer = bufferAllocator.buffer(236); // Allocate buffer + buffer.writeBytes(inputStream, 236); // Read data from input stream into buffer + + // Read header info + int personality = buffer.readIntLE(); + buffer.skipBytes(2); + int checksum = buffer.readShortLE() & 0x0000FFFF; + + // Decrypt data + decryptData(buffer, 8, 128, checksum); + decryptData(buffer, 136, 100, personality); + + // Unshuffle blocks + ByteBuf shuffleBuffer = bufferAllocator.buffer(128); // Allocate shuffle buffer + int shift = ((personality & 0x3E000) >> 0xD) % 24; + + for(int i = 0; i < 4; i++) { + int fromIndex = blockShuffleTable[i + shift * 4] * 32; + int toIndex = i * 32; + shuffleBuffer.setBytes(toIndex, buffer, 8 + fromIndex, 32); + } + + buffer.setBytes(8, shuffleBuffer, 0, 128); + + // Try release shuffle buffer + if(!shuffleBuffer.release()) { + logger.warn("Buffer was not deallocated!"); + } + + // Read Pokémon data + int species = buffer.getShortLE(8); + int item = buffer.getShortLE(10) & 0xFFFF; + int trainerId = buffer.getShortLE(12) & 0xFFFF; + int trainerSecretId = buffer.getShortLE(14) & 0xFFFF; + int level = buffer.getByte(140); + int ability = buffer.getByte(21) & 0xFF; + int form = (buffer.getByte(64) >> 3) & 0xFF; + boolean genderless = ((buffer.getByte(64) >> 2) & 1) == 1; + boolean female = ((buffer.getByte(64) >> 1) & 1) == 1; + PkmnGender gender = genderless ? PkmnGender.GENDERLESS : female ? PkmnGender.FEMALE : PkmnGender.MALE; + PkmnNature nature = PkmnNature.valueOf(buffer.getByte(65)); + String nickname = getString(buffer, 72, 20); + String trainerName = getString(buffer, 104, 14); + + // Try release buffer + if(!buffer.release()) { + logger.warn("Buffer was not deallocated!"); + } + + // Loosely verify data + if(species < 1 || species > 649) throw new IOException("Invalid species"); + if(item < 0 || item > 638) throw new IOException("Invalid held item"); + if(ability < 1 || ability > 164) throw new IOException("Invalid ability"); + if(level < 1 || level > 100) throw new IOException("Level is out of range"); + if(nature == null) throw new IOException("Invalid nature"); + + // Create record + PkmnInfo info = new PkmnInfo(personality, species, item, trainerId, trainerSecretId, level, form, nature, gender, nickname, trainerName); + return info; + } + + private static void decryptData(ByteBuf buffer, int offset, int length, int seed) throws IOException { + if(length % 2 != 0) { + throw new IOException("Length must be multiple of 2"); + } + + int tempSeed = seed; + + for(int i = 0; i < length / 2; i++) { + int index = offset + i * 2; + short word = buffer.getShortLE(index); + tempSeed = 0x41C64E6D * tempSeed + 0x6073; + buffer.setShortLE(index, (short)(word ^ (tempSeed >> 16))); + } + } + + private static String getString(ByteBuf buffer, int offset, int length) { + char[] charBuffer = new char[length]; + int read = 0; + + for(int i = 0; i < charBuffer.length; i++) { + int c = buffer.getShortLE(offset + i * 2) & 0xFFFF; + + if(c == 0 || c == 65535) { + break; // Doubt 65535 is a legitimate character.. + } + + charBuffer[i] = (char)c; + read++; + } + + return new String(charBuffer, 0, read); + } +} diff --git a/src/main/java/entralinked/model/pkmn/PkmnNature.java b/src/main/java/entralinked/model/pkmn/PkmnNature.java new file mode 100644 index 0000000..8bf4389 --- /dev/null +++ b/src/main/java/entralinked/model/pkmn/PkmnNature.java @@ -0,0 +1,37 @@ +package entralinked.model.pkmn; + +import com.fasterxml.jackson.annotation.JsonEnumDefaultValue; + +public enum PkmnNature { + + @JsonEnumDefaultValue + HARDY, + LONELY, + BRAVE, + ADAMANT, + NAUGHTY, + BOLD, + DOCILE, + RELAXED, + IMPISH, + LAX, + TIMID, + HASTY, + SERIOUS, + JOLLY, + NAIVE, + MODEST, + MILD, + QUIET, + BASHFUL, + RASH, + CALM, + GENTLE, + SASSY, + CAREFUL, + QUIRKY; + + public static PkmnNature valueOf(int index) { + return index >= 0 && index < values().length ? values()[index] : null; + } +} diff --git a/src/main/java/entralinked/model/player/DreamAnimation.java b/src/main/java/entralinked/model/player/DreamAnimation.java new file mode 100644 index 0000000..1abc427 --- /dev/null +++ b/src/main/java/entralinked/model/player/DreamAnimation.java @@ -0,0 +1,51 @@ +package entralinked.model.player; + +import com.fasterxml.jackson.annotation.JsonEnumDefaultValue; + +public enum DreamAnimation { + + /** + * Look around, but stay in the same position. + */ + @JsonEnumDefaultValue + LOOK_AROUND, + + /** + * Walk around, but never change direction without moving a step in that direction. + */ + WALK_AROUND, + + /** + * Walk around and occasionally change direction without moving. + */ + WALK_LOOK_AROUND, + + /** + * Only walk up and down. + */ + WALK_VERTICALLY, + + /** + * Only walk left and right. + */ + WALK_HORIZONTALLY, + + /** + * Only walk left and right, and occasionally change direction without moving. + */ + WALK_LOOK_HORIZONTALLY, + + /** + * Continuously spin right. + */ + SPIN_RIGHT, + + /** + * Continuously spin left. + */ + SPIN_LEFT; + + public static DreamAnimation valueOf(int index) { + return index >= 0 && index < values().length ? values()[index] : null; + } +} diff --git a/src/main/java/entralinked/model/player/DreamEncounter.java b/src/main/java/entralinked/model/player/DreamEncounter.java new file mode 100644 index 0000000..44db730 --- /dev/null +++ b/src/main/java/entralinked/model/player/DreamEncounter.java @@ -0,0 +1,9 @@ +package entralinked.model.player; + +import com.fasterxml.jackson.annotation.JsonProperty; + +public record DreamEncounter( + @JsonProperty(required = true) int species, + @JsonProperty(required = true) int move, + @JsonProperty(required = true) int form, + @JsonProperty(required = true) DreamAnimation animation) {} diff --git a/src/main/java/entralinked/model/player/DreamItem.java b/src/main/java/entralinked/model/player/DreamItem.java new file mode 100644 index 0000000..95e7613 --- /dev/null +++ b/src/main/java/entralinked/model/player/DreamItem.java @@ -0,0 +1,7 @@ +package entralinked.model.player; + +import com.fasterxml.jackson.annotation.JsonProperty; + +public record DreamItem( + @JsonProperty(required = true) int id, + @JsonProperty(required = true) int quantity) {} diff --git a/src/main/java/entralinked/model/player/Player.java b/src/main/java/entralinked/model/player/Player.java new file mode 100644 index 0000000..ba2e417 --- /dev/null +++ b/src/main/java/entralinked/model/player/Player.java @@ -0,0 +1,117 @@ +package entralinked.model.player; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.List; + +import entralinked.GameVersion; +import entralinked.model.pkmn.PkmnInfo; + +public class Player { + + private final String gameSyncId; + private final GameVersion gameVersion; + private final List encounters = new ArrayList<>(); + private final List items = new ArrayList<>(); + private PlayerStatus status; + private PkmnInfo dreamerInfo; + private int levelsGained; + private String cgearSkin; + private String dexSkin; + private String musical; + + public Player(String gameSyncId, GameVersion gameVersion) { + this.gameSyncId = gameSyncId; + this.gameVersion = gameVersion; + } + + public void resetDreamInfo() { + status = PlayerStatus.AWAKE; + dreamerInfo = null; + encounters.clear(); + items.clear(); + levelsGained = 0; + cgearSkin = null; + dexSkin = null; + musical = null; + } + + public String getGameSyncId() { + return gameSyncId; + } + + public GameVersion getGameVersion() { + return gameVersion; + } + + public void setEncounters(Collection encounters) { + if(encounters.size() <= 10) { + this.encounters.clear(); + this.encounters.addAll(encounters); + } + } + + public List getEncounters() { + return Collections.unmodifiableList(encounters); + } + + public void setItems(Collection items) { + if(encounters.size() <= 20) { + this.items.clear(); + this.items.addAll(items); + } + } + + public List getItems() { + return Collections.unmodifiableList(items); + } + + public void setStatus(PlayerStatus status) { + this.status = status; + } + + public PlayerStatus getStatus() { + return status; + } + + public void setDreamerInfo(PkmnInfo dreamerInfo) { + this.dreamerInfo = dreamerInfo; + } + + public PkmnInfo getDreamerInfo() { + return dreamerInfo; + } + + public void setLevelsGained(int levelsGained) { + this.levelsGained = levelsGained; + } + + public int getLevelsGained() { + return levelsGained; + } + + public void setCGearSkin(String cgearSkin) { + this.cgearSkin = cgearSkin; + } + + public String getCGearSkin() { + return cgearSkin; + } + + public void setDexSkin(String dexSkin) { + this.dexSkin = dexSkin; + } + + public String getDexSkin() { + return dexSkin; + } + + public void setMusical(String musical) { + this.musical = musical; + } + + public String getMusical() { + return musical; + } +} diff --git a/src/main/java/entralinked/model/player/PlayerDto.java b/src/main/java/entralinked/model/player/PlayerDto.java new file mode 100644 index 0000000..c5f55c0 --- /dev/null +++ b/src/main/java/entralinked/model/player/PlayerDto.java @@ -0,0 +1,47 @@ +package entralinked.model.player; + +import java.util.Collection; +import java.util.Collections; + +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; + +import entralinked.GameVersion; +import entralinked.model.pkmn.PkmnInfo; + +/** + * Serialization DTO for Global Link user information. + */ +public record PlayerDto( + @JsonProperty(required = true) String gameSyncId, + @JsonProperty(required = true) GameVersion gameVersion, + PlayerStatus status, + PkmnInfo dreamerInfo, + String cgearSkin, + String dexSkin, + String musical, + int levelsGained, + @JsonDeserialize(contentAs = DreamEncounter.class) Collection encounters, + @JsonDeserialize(contentAs = DreamItem.class) Collection items) { + + public PlayerDto(Player player) { + this(player.getGameSyncId(), player.getGameVersion(), player.getStatus(), player.getDreamerInfo(), player.getCGearSkin(), + player.getDexSkin(), player.getMusical(), player.getLevelsGained(), player.getEncounters(), player.getItems()); + } + + /** + * Constructs a new {@link Player} object using the data in this DTO. + */ + public Player toPlayer() { + Player player = new Player(gameSyncId, gameVersion); + player.setStatus(status); + player.setDreamerInfo(dreamerInfo); + player.setCGearSkin(cgearSkin); + player.setDexSkin(dexSkin); + player.setMusical(musical); + player.setLevelsGained(levelsGained); + player.setEncounters(encounters == null ? Collections.emptyList() : encounters); + player.setItems(items == null ? Collections.emptyList() : items); + return player; + } +} diff --git a/src/main/java/entralinked/model/player/PlayerManager.java b/src/main/java/entralinked/model/player/PlayerManager.java new file mode 100644 index 0000000..2d0d299 --- /dev/null +++ b/src/main/java/entralinked/model/player/PlayerManager.java @@ -0,0 +1,160 @@ +package entralinked.model.player; + +import java.io.File; +import java.io.IOException; +import java.nio.file.Files; +import java.util.Collection; +import java.util.Collections; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; + +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.SerializationFeature; + +import entralinked.GameVersion; + +/** + * Manager class for managing {@link Player} information (Global Link users) + */ +public class PlayerManager { + + private static final Logger logger = LogManager.getLogger(); + private final ObjectMapper mapper = new ObjectMapper().enable(SerializationFeature.INDENT_OUTPUT); + private final Map playerMap = new ConcurrentHashMap<>(); + private final File dataDirectory = new File("players"); + + public PlayerManager() { + logger.info("Loading player data ..."); + + // Check if player directory exists + if(!dataDirectory.exists()) { + return; + } + + // Load player data + for(File file : dataDirectory.listFiles()) { + if(!file.isDirectory()) { + loadPlayer(file); + } + } + + logger.info("Loaded {} player(s)", playerMap.size()); + } + + /** + * Loads a {@link Player} from the specified input file. + * The loaded player instance is automatically mapped, unless it has an already-mapped Game Sync ID. + */ + private void loadPlayer(File inputFile) { + try { + // Deserialize player data + Player player = mapper.readValue(inputFile, PlayerDto.class).toPlayer(); + String gameSyncId = player.getGameSyncId(); + + // Check for duplicate Game Sync ID + if(doesPlayerExist(gameSyncId)) { + throw new IOException("Duplicate Game Sync ID %s".formatted(gameSyncId)); + } + + playerMap.put(gameSyncId, player); + } catch(IOException e) { + logger.error("Could not load player data at {}", inputFile.getAbsolutePath(), e); + } + } + + /** + * Saves all player data. + */ + public void savePlayers() { + playerMap.values().forEach(this::savePlayer); + } + + /** + * Saves the data of the specified player to disk, and returns {@code true} if it succeeds. + * The output file is generated as follows: + * + * {@code new File(dataDirectory, "PGL-%s".formatted(gameSyncId))} + */ + public boolean savePlayer(Player player) { + return savePlayer(player, new File(dataDirectory, "PGL-%s.json".formatted(player.getGameSyncId()))); + } + + /** + * Saves the data of the specified player to the specified output file. + * + * @return {@code true} if the data was saved successfully, {@code false} otherwise. + */ + private boolean savePlayer(Player player, File outputFile) { + try { + // Create parent directories + File parentFile = outputFile.getParentFile(); + + if(parentFile != null) { + parentFile.mkdirs(); + } + + // Serialize the entire player object first to minimize risk of corrupted files + byte[] bytes = mapper.writeValueAsBytes(new PlayerDto(player)); + + // Write serialized data to output file + Files.write(outputFile.toPath(), bytes); + } catch(IOException e) { + logger.error("Could not save player data for {}", player.getGameSyncId(), e); + return false; + } + + return true; + } + + /** + * Attempts to register a new {@link Player} with the given data. + * + * @return The newly created {@link Player} object if the registration succeeded. + * That is, the specified Game Sync ID wasn't already registered and the player data + * was saved without any errors. + */ + public Player registerPlayer(String gameSyncId, GameVersion gameVersion) { + // Check for duplicate Game Sync ID + if(playerMap.containsKey(gameSyncId)) { + logger.warn("Attempted to register duplicate player {}", gameSyncId); + return null; + } + + // Construct player object + Player player = new Player(gameSyncId, gameVersion); + player.setStatus(PlayerStatus.AWAKE); + + // Try to save player data + if(!savePlayer(player)) { + return null; + } + + // Map player object & return it + playerMap.put(gameSyncId, player); + return player; + } + + /** + * @return {@code true} if a player with the specified Game Sync ID exists, {@code false} otherwise. + */ + public boolean doesPlayerExist(String gameSyncId) { + return playerMap.containsKey(gameSyncId); + } + + /** + * @return The {@link Player} object to which this Game Sync ID belongs, or {@code null} if no such player exists. + */ + public Player getPlayer(String gameSyncId) { + return playerMap.get(gameSyncId); + } + + /** + * @return An immutable {@link Collection} containing all players. + */ + public Collection getPlayers() { + return Collections.unmodifiableCollection(playerMap.values()); + } +} diff --git a/src/main/java/entralinked/model/player/PlayerStatus.java b/src/main/java/entralinked/model/player/PlayerStatus.java new file mode 100644 index 0000000..1855174 --- /dev/null +++ b/src/main/java/entralinked/model/player/PlayerStatus.java @@ -0,0 +1,27 @@ +package entralinked.model.player; + +import com.fasterxml.jackson.annotation.JsonEnumDefaultValue; + +public enum PlayerStatus { + + /** + * No Pokémon is currently tucked in. + */ + @JsonEnumDefaultValue + AWAKE, + + /** + * The tucked in Pokémon is asleep, but is not dreaming yet. + */ + SLEEPING, + + /** + * The tucked in Pokémon is dreaming - waking it up is not allowed. + */ + DREAMING, + + /** + * The dreamer is ready to be woken up. + */ + WAKE_READY, +} diff --git a/src/main/java/entralinked/model/user/GameProfile.java b/src/main/java/entralinked/model/user/GameProfile.java new file mode 100644 index 0000000..32e40fc --- /dev/null +++ b/src/main/java/entralinked/model/user/GameProfile.java @@ -0,0 +1,58 @@ +package entralinked.model.user; + +public class GameProfile { + + private final int id; + private String firstName; + private String lastName; + private String aimName; + private String zipCode; + + public GameProfile(int id) { + this.id = id; + } + + public GameProfile(int id, String firstName, String lastName, String aimName, String zipCode) { + this(id); + this.firstName = firstName; + this.lastName = lastName; + this.aimName = aimName; + this.zipCode = zipCode; + } + + public int getId() { + return id; + } + + public void setFirstName(String firstName) { + this.firstName = firstName; + } + + public String getFirstName() { + return firstName; + } + + public void setLastName(String lastName) { + this.lastName = lastName; + } + + public String getLastName() { + return lastName; + } + + public void setAimName(String aimName) { + this.aimName = aimName; + } + + public String getAimName() { + return aimName; + } + + public void setZipCode(String zipCode) { + this.zipCode = zipCode; + } + + public String getZipCode() { + return zipCode; + } +} diff --git a/src/main/java/entralinked/model/user/GameProfileDto.java b/src/main/java/entralinked/model/user/GameProfileDto.java new file mode 100644 index 0000000..fd3ed5c --- /dev/null +++ b/src/main/java/entralinked/model/user/GameProfileDto.java @@ -0,0 +1,19 @@ +package entralinked.model.user; + +import com.fasterxml.jackson.annotation.JsonProperty; + +public record GameProfileDto( + @JsonProperty(required = true) int id, + String firstName, + String lastName, + String aimName, + String zipCode) { + + public GameProfileDto(GameProfile profile) { + this(profile.getId(), profile.getFirstName(), profile.getLastName(), profile.getAimName(), profile.getZipCode()); + } + + public GameProfile toProfile() { + return new GameProfile(id, firstName, lastName, aimName, zipCode); + } +} diff --git a/src/main/java/entralinked/model/user/ServiceCredentials.java b/src/main/java/entralinked/model/user/ServiceCredentials.java new file mode 100644 index 0000000..85ad0e1 --- /dev/null +++ b/src/main/java/entralinked/model/user/ServiceCredentials.java @@ -0,0 +1,3 @@ +package entralinked.model.user; + +public record ServiceCredentials(String authToken, String challenge) {} diff --git a/src/main/java/entralinked/model/user/ServiceSession.java b/src/main/java/entralinked/model/user/ServiceSession.java new file mode 100644 index 0000000..2c919c2 --- /dev/null +++ b/src/main/java/entralinked/model/user/ServiceSession.java @@ -0,0 +1,15 @@ +package entralinked.model.user; + +import java.time.LocalDateTime; +import java.time.temporal.TemporalUnit; + +public record ServiceSession(User user, String service, String branchCode, String challengeHash, LocalDateTime expiry) { + + public ServiceSession(User user, String service, String branchCode, String challengeHash, long expiry, TemporalUnit expiryUnit) { + this(user, service, branchCode, challengeHash, LocalDateTime.now().plus(expiry, expiryUnit)); + } + + public boolean hasExpired() { + return LocalDateTime.now().isAfter(expiry); + } +} diff --git a/src/main/java/entralinked/model/user/User.java b/src/main/java/entralinked/model/user/User.java new file mode 100644 index 0000000..bd0a7a8 --- /dev/null +++ b/src/main/java/entralinked/model/user/User.java @@ -0,0 +1,50 @@ +package entralinked.model.user; + +import java.util.Collection; +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; + +public class User { + + private final String id; + private final String password; // I debated hashing it, but.. it's a 3-digit password... + private final Map profiles = new HashMap<>(); + + public User(String id, String password) { + this.id = id; + this.password = password; + } + + public String getFormattedId() { + return "%s000".formatted(id).replaceAll("(.{4})(?!$)", "$1-"); + } + + public String getId() { + return id; + } + + public String getPassword() { + return password; + } + + protected void addProfile(String branchCode, GameProfile profile) { + profiles.put(branchCode, profile); + } + + protected void removeProfile(String branchCode) { + profiles.remove(branchCode); + } + + public GameProfile getProfile(String branchCode) { + return profiles.get(branchCode); + } + + public Collection getProfiles() { + return Collections.unmodifiableCollection(profiles.values()); + } + + protected Map getProfileMap() { + return Collections.unmodifiableMap(profiles); + } +} diff --git a/src/main/java/entralinked/model/user/UserDto.java b/src/main/java/entralinked/model/user/UserDto.java new file mode 100644 index 0000000..f78c5d8 --- /dev/null +++ b/src/main/java/entralinked/model/user/UserDto.java @@ -0,0 +1,30 @@ +package entralinked.model.user; + +import java.util.Map; +import java.util.Map.Entry; +import java.util.stream.Collectors; + +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; + +public record UserDto( + @JsonProperty(required = true) String id, + @JsonProperty(required = true) String password, + @JsonDeserialize(contentAs = GameProfileDto.class) Map profiles) { + + public UserDto(User user) { + this(user.getId(), user.getPassword(), + user.getProfileMap().entrySet() + .stream() + .collect(Collectors.toMap(Entry::getKey, entry -> new GameProfileDto(entry.getValue())))); + } + + public User toUser() { + User user = new User(id, password); + profiles.entrySet() + .stream() + .collect(Collectors.toMap(Entry::getKey, e -> e.getValue().toProfile())) // Map GameProfileDto to GameProfile + .forEach(user::addProfile); // Add each GameProfile to the User object + return user; + } +} diff --git a/src/main/java/entralinked/model/user/UserManager.java b/src/main/java/entralinked/model/user/UserManager.java new file mode 100644 index 0000000..39639d2 --- /dev/null +++ b/src/main/java/entralinked/model/user/UserManager.java @@ -0,0 +1,284 @@ +package entralinked.model.user; + +import java.io.File; +import java.io.IOException; +import java.nio.file.Files; +import java.time.temporal.ChronoUnit; +import java.util.Collection; +import java.util.Collections; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; +import java.util.regex.Pattern; + +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; + +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.SerializationFeature; + +import entralinked.utility.CredentialGenerator; +import entralinked.utility.MD5; + +/** + * Manager class for managing {@link User} information (Wi-Fi Connection users) + * + * TODO session management is a bit scuffed + */ +public class UserManager { + + public static final Pattern USER_ID_PATTERN = Pattern.compile("[0-9]{13}"); + private static final Logger logger = LogManager.getLogger(); + private final ObjectMapper mapper = new ObjectMapper().enable(SerializationFeature.INDENT_OUTPUT); + private final Map users = new ConcurrentHashMap<>(); + private final Map profiles = new ConcurrentHashMap<>(); + private final Map serviceSessions = new ConcurrentHashMap<>(); + private final File dataDirectory = new File("users"); + + public UserManager() { + logger.info("Loading user and profile data ..."); + + // Check if directory exists + if(!dataDirectory.exists()) { + return; + } + + // Load user data + for(File file : dataDirectory.listFiles()) { + if(!file.isDirectory()) { + loadUser(file); + } + } + + logger.info("Loaded {} user(s) with a total of {} profile(s)", users.size(), profiles.size()); + } + + /** + * @return {@code true} if this is a valid user ID. + * That is, it has a length of 13 and only contains digits. + */ + public static boolean isValidUserId(String id) { + return USER_ID_PATTERN.matcher(id).matches(); + } + + /** + * Loads a {@link User} from the specified input file. + * The user is indexed automatically, unless it has a duplicate ID or any duplicate profile ID. + */ + private void loadUser(File inputFile) { + try { + // Deserialize user data + User user = mapper.readValue(inputFile, UserDto.class).toUser(); + String id = user.getId(); + + // Check for duplicate user ID + if(users.containsKey(id)) { + throw new IOException("Duplicate user ID %s".formatted(id)); + } + + // Check for duplicate profile IDs before indexing anything + Collection userProfiles = user.getProfiles(); + + if(userProfiles.stream().map(GameProfile::getId).anyMatch(profiles::containsKey)) { + throw new IOException("Duplicate profile ID in user %s".formatted(id)); + } + + // Index user + users.put(id, user); + + // Index profiles + for(GameProfile profile : userProfiles) { + profiles.put(profile.getId(), profile); + } + } catch(IOException e) { + logger.error("Could not load user data at {}", inputFile.getAbsolutePath(), e); + } + } + + /** + * Saves the data of all users. + */ + public void saveUsers() { + users.values().forEach(this::saveUser); + } + + /** + * Saves the data of the specified user to disk, and returns {@code true} if it succeeds. + * The output file is generated as follows: + * + * {@code new File(dataDirectory, "WFC-%s".formatted(formattedId))} + * where {@code formattedId} is the user ID formatted to {@code ####-####-####-#000} + */ + public boolean saveUser(User user) { + return saveUser(user, new File(dataDirectory, "WFC-%s.json".formatted(user.getFormattedId()))); + } + + /** + * Saves the data of the specified user to the specified output file. + * + * @return {@code true} if the data was saved successfully, {@code false} otherwise. + */ + private boolean saveUser(User user, File outputFile) { + try { + // Create parent directories + File parentFile = outputFile.getParentFile(); + + if(parentFile != null) { + parentFile.mkdirs(); + } + + // Serialize the entire user object first to minimize risk of corrupted files + byte[] bytes = mapper.writeValueAsBytes(new UserDto(user)); + + // Finally, write the data. + Files.write(outputFile.toPath(), bytes); + } catch(IOException e) { + logger.error("Could not save user data for user {}", user.getId(), e); + return false; + } + + return true; + } + + /** + * Generates credentials for the client to use when logging into a separate service. + * The information to be used by the service to verify the client will be cached and may be retrieved using {@link #getServiceSession(token, service)}. + * + * @return A {@link ServiceCredentials} record containing the auth token and (optional) challenge to send to the client. + */ + public ServiceCredentials createServiceSession(User user, String service, String branchCode) { + if(service == null) { + throw new IllegalArgumentException("service cannot be null"); + } + + // Create token + String authToken = "NDS%s".formatted(CredentialGenerator.generateAuthToken(96)); + + if(serviceSessions.containsKey(authToken)) { + return createServiceSession(user, service, branchCode); // Top 5 things that never happen + } + + // Create challenge + String challenge = CredentialGenerator.generateChallenge(8); + + // Create session object + ServiceSession session = new ServiceSession(user, service, branchCode, MD5.digest(challenge), 30, ChronoUnit.MINUTES); + serviceSessions.put(authToken, session); + return new ServiceCredentials(authToken, challenge); + } + + /** + * @return A {@link ServiceSession} matching the specified auth token and service, + * or {@code null} if there is none or if the existing session expired. + */ + public ServiceSession getServiceSession(String authToken, String service) { + ServiceSession session = serviceSessions.get(authToken); + + // Check if session exists + if(session == null) { + return null; + } + + // Check if session has expired + if(session.hasExpired()) { + serviceSessions.remove(authToken); + return null; + } + + return session.service().equals(service) ? session : null; + } + + /** + * Registers a user with the given ID and password. + + * @return {@code true} if the registration was successful. + * Otherwise, if this user ID has already been registered, or if the user data could not be saved, {@code false} is returned instead. + */ + public boolean registerUser(String userId, String plainPassword) { + // Check if user id already exists + if(users.containsKey(userId)) { + logger.warn("Attempted to register user with duplicate ID: {}", userId); + return false; + } + + // Create user + User user = new User(userId, plainPassword); // TODO hash + + // Save user data and return null if it fails + if(!saveUser(user)) { + return false; + } + + users.put(userId, user); + return true; + } + + /** + * Simple method that returns a {@link User} object whose credentials match the given ones. + * If no user exists with matching credentials, {@code null} is returned instead. + */ + public User authenticateUser(String userId, String password) { + User user = users.get(userId); + return user == null || !user.getPassword().equals(password) ? null : user; + } + + /** + * Creates a new profile for the specified user. + * + * @return The newly created profile, or {@code null} if profile creation failed. + */ + public GameProfile createProfileForUser(User user, String branchCode) { + // Check for duplicate profile + if(user.getProfile(branchCode) != null) { + logger.warn("Attempted to create duplicate profile {} in user {}", branchCode, user.getId()); + return null; + } + + int profileId = nextProfileId(); + GameProfile profile = new GameProfile(profileId); + user.addProfile(branchCode, profile); + + // Try to save user data and return null if it fails + if(!saveUser(user)) { + user.removeProfile(branchCode); + return null; + } + + profiles.put(profileId, profile); + return profile; + } + + /** + * @return A unique random 32-bit profile ID. + */ + private int nextProfileId() { + int profileId = (int)(Math.random() * Integer.MAX_VALUE); + + // I live for that microscopic chance of StackOverflowError + if(profiles.containsKey(profileId)) { + return nextProfileId(); + } + + return profileId; + } + + /** + * @return {@code true} if a user with the specified ID exists, otherwise {@code false}. + */ + public boolean doesUserExist(String id) { + return users.containsKey(id); + } + + /** + * @return The {@link User} object to which this ID belongs, or {@code null} if it doesn't exist. + */ + public User getUser(String id) { + return users.get(id); + } + + /** + * @return An immutable {@link Collection} containing all users. + */ + public Collection getUsers() { + return Collections.unmodifiableCollection(users.values()); + } +} diff --git a/src/main/java/entralinked/network/NettyServerBase.java b/src/main/java/entralinked/network/NettyServerBase.java new file mode 100644 index 0000000..5f45bd6 --- /dev/null +++ b/src/main/java/entralinked/network/NettyServerBase.java @@ -0,0 +1,76 @@ +package entralinked.network; + +import java.util.concurrent.ThreadFactory; + +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; + +import io.netty.channel.ChannelFuture; +import io.netty.channel.EventLoopGroup; +import io.netty.channel.epoll.Epoll; +import io.netty.channel.epoll.EpollEventLoopGroup; +import io.netty.channel.nio.NioEventLoopGroup; +import io.netty.util.concurrent.DefaultThreadFactory; +import io.netty.util.concurrent.Future; + +public abstract class NettyServerBase { + + private static final Logger logger = LogManager.getLogger(); + protected final ThreadFactory threadFactory; + protected final EventLoopGroup eventLoopGroup; + protected final String name; + protected final int port; + protected boolean started; + + public NettyServerBase(String name, int port) { + this.threadFactory = new DefaultThreadFactory(name); + this.name = name; + this.port = port; + + if(Epoll.isAvailable()) { + eventLoopGroup = new EpollEventLoopGroup(threadFactory); + } else { + eventLoopGroup = new NioEventLoopGroup(threadFactory); + } + } + + protected abstract ChannelFuture bootstrap(int port); + + public boolean start() { + if(started) { + logger.warn("start() was called while {} server was already running!", name); + return true; + } + + logger.info("Staring {} server ...", name); + ChannelFuture future = bootstrap(port); + + if(!future.isSuccess()) { + logger.error("Could not start {} server", name, future.cause()); + return false; + } + + logger.info("{} server listening @ port {}", name, port); + started = true; + return true; + } + + public boolean stop() { + if(!started) { + logger.warn("stop() was called while {} server wasn't running!", name); + return true; + } + + logger.info("Stopping {} server ...", name); + Future future = eventLoopGroup.shutdownGracefully().awaitUninterruptibly(); + + if(!future.isSuccess()) { + logger.info("Could not stop {} server", name, future.cause()); + return false; + } + + logger.info("{} server stopped", name); + started = false; + return true; + } +} diff --git a/src/main/java/entralinked/network/dns/DnsQueryHandler.java b/src/main/java/entralinked/network/dns/DnsQueryHandler.java new file mode 100644 index 0000000..1f4f95b --- /dev/null +++ b/src/main/java/entralinked/network/dns/DnsQueryHandler.java @@ -0,0 +1,45 @@ +package entralinked.network.dns; + +import java.net.InetAddress; + +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; + +import io.netty.buffer.ByteBuf; +import io.netty.buffer.Unpooled; +import io.netty.channel.ChannelHandlerContext; +import io.netty.channel.SimpleChannelInboundHandler; +import io.netty.handler.codec.dns.DatagramDnsQuery; +import io.netty.handler.codec.dns.DatagramDnsResponse; +import io.netty.handler.codec.dns.DefaultDnsQuestion; +import io.netty.handler.codec.dns.DefaultDnsRawRecord; +import io.netty.handler.codec.dns.DnsRecordType; +import io.netty.handler.codec.dns.DnsSection; + +public class DnsQueryHandler extends SimpleChannelInboundHandler { + + private static final Logger logger = LogManager.getLogger(); + private final InetAddress hostAddress; + + public DnsQueryHandler(InetAddress hostAddress) { + this.hostAddress = hostAddress; + } + + @Override + protected void channelRead0(ChannelHandlerContext ctx, DatagramDnsQuery query) throws Exception { + DefaultDnsQuestion question = query.recordAt(DnsSection.QUESTION); + DnsRecordType type = question.type(); + + // We only need type A (32 bit IPv4) for the DS + if(type != DnsRecordType.A) { + logger.warn("Unsupported record type in DNS question: {}", type); + return; + } + + ByteBuf addressBuffer = Unpooled.wrappedBuffer(hostAddress.getAddress()); + DefaultDnsRawRecord answer = new DefaultDnsRawRecord(question.name(), DnsRecordType.A, 0, addressBuffer); + DatagramDnsResponse response = new DatagramDnsResponse(query.recipient(), query.sender(), query.id()); + response.addRecord(DnsSection.ANSWER, answer); + ctx.writeAndFlush(response); + } +} diff --git a/src/main/java/entralinked/network/dns/DnsServer.java b/src/main/java/entralinked/network/dns/DnsServer.java new file mode 100644 index 0000000..6619638 --- /dev/null +++ b/src/main/java/entralinked/network/dns/DnsServer.java @@ -0,0 +1,41 @@ +package entralinked.network.dns; + +import java.net.InetAddress; + +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; + +import entralinked.network.NettyServerBase; +import io.netty.bootstrap.Bootstrap; +import io.netty.channel.ChannelFuture; +import io.netty.channel.ChannelInitializer; +import io.netty.channel.socket.nio.NioDatagramChannel; +import io.netty.handler.codec.dns.DatagramDnsQueryDecoder; +import io.netty.handler.codec.dns.DatagramDnsResponseEncoder; + +public class DnsServer extends NettyServerBase { + + private static final Logger logger = LogManager.getLogger(); + private InetAddress hostAddress; + + public DnsServer(InetAddress hostAddress) { + super("DNS", 53); + this.hostAddress = hostAddress; + logger.info("DNS queries will be resolved to {}", hostAddress); + } + + @Override + public ChannelFuture bootstrap(int port) { + return new Bootstrap() + .group(eventLoopGroup) + .channel(NioDatagramChannel.class) + .handler(new ChannelInitializer() { + @Override + protected void initChannel(NioDatagramChannel channel) throws Exception { + channel.pipeline().addLast(new DatagramDnsQueryDecoder()); + channel.pipeline().addLast(new DatagramDnsResponseEncoder()); + channel.pipeline().addLast(new DnsQueryHandler(hostAddress)); + } + }).bind(port).awaitUninterruptibly(); + } +} diff --git a/src/main/java/entralinked/network/gamespy/GameSpyHandler.java b/src/main/java/entralinked/network/gamespy/GameSpyHandler.java new file mode 100644 index 0000000..f992897 --- /dev/null +++ b/src/main/java/entralinked/network/gamespy/GameSpyHandler.java @@ -0,0 +1,208 @@ +package entralinked.network.gamespy; + +import java.nio.channels.ClosedChannelException; +import java.security.SecureRandom; +import java.util.function.Consumer; +import java.util.function.Supplier; + +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; + +import entralinked.Entralinked; +import entralinked.model.user.GameProfile; +import entralinked.model.user.ServiceSession; +import entralinked.model.user.User; +import entralinked.model.user.UserManager; +import entralinked.network.gamespy.message.GameSpyChallengeMessage; +import entralinked.network.gamespy.message.GameSpyErrorMessage; +import entralinked.network.gamespy.message.GameSpyLoginResponse; +import entralinked.network.gamespy.message.GameSpyProfileResponse; +import entralinked.network.gamespy.request.GameSpyLoginRequest; +import entralinked.network.gamespy.request.GameSpyProfileRequest; +import entralinked.network.gamespy.request.GameSpyProfileUpdateRequest; +import entralinked.network.gamespy.request.GameSpyRequest; +import entralinked.utility.CredentialGenerator; +import entralinked.utility.MD5; +import io.netty.channel.Channel; +import io.netty.channel.ChannelFuture; +import io.netty.channel.ChannelFutureListener; +import io.netty.channel.ChannelHandlerContext; +import io.netty.channel.SimpleChannelInboundHandler; + +/** + * GameSpy request handler. + */ +public class GameSpyHandler extends SimpleChannelInboundHandler { + + private static final Logger logger = LogManager.getLogger(); + private final SecureRandom secureRandom = new SecureRandom(); + private final UserManager userManager; + private Channel channel; + private String serverChallenge; + private int sessionKey = -1; // It's pointless + private User user; + private GameProfile profile; + + public GameSpyHandler(Entralinked entralinked) { + this.userManager = entralinked.getUserManager(); + } + + @Override + public void channelActive(ChannelHandlerContext ctx) { + channel = ctx.channel(); + + // Generate random server challenge + serverChallenge = CredentialGenerator.generateChallenge(10); + + // Send challenge message + sendMessage(new GameSpyChallengeMessage(serverChallenge, 1)); + } + + @Override + public void channelInactive(ChannelHandlerContext ctx) { + // Clear data + serverChallenge = null; + sessionKey = -1; + user = null; + profile = null; + } + + @Override + protected void channelRead0(ChannelHandlerContext ctx, GameSpyRequest request) throws Exception { + request.process(this); + } + + @Override + public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception { + if(cause instanceof ClosedChannelException) { + return; // Ignore this stupid exception. + } + + logger.error("Exception caught in GameSpy handler", cause); + + // Send error message and close channel afterwards. + sendErrorMessage(0x100, "An internal error occured on the server.", true, 0).addListener(future -> close()); + } + + public void handleLoginRequest(GameSpyLoginRequest request) { + String authToken = request.partnerToken(); + String clientChallenge = request.challenge(); + + // Check if session exists + ServiceSession session = userManager.getServiceSession(authToken, "gamespy"); + + if(session == null) { + sendErrorMessage(0x200, "Invalid partner token.", request.sequenceId()); + return; + } + + // Verify client credential hash + String partnerChallengeHash = session.challengeHash(); + String expectedResponse = createCredentialHash(partnerChallengeHash, authToken, clientChallenge, serverChallenge); + + if(!expectedResponse.equals(request.response())) { + sendErrorMessage(0x202, "Invalid response.", request.sequenceId()); + return; + } + + // Fetch profile or create one if it doesn't exist + user = session.user(); + profile = user.getProfile(session.branchCode()); + + if(profile == null) { + profile = userManager.createProfileForUser(user, session.branchCode()); + + // Check if creation succeeded + if(profile == null) { + sendErrorMessage(0x203, "Profile creation failed due to an error.", request.sequenceId()); + return; + } + } + + // Prepare and send response + sessionKey = secureRandom.nextInt(Integer.MAX_VALUE); + String proof = createCredentialHash(partnerChallengeHash, authToken, serverChallenge, clientChallenge); + sendMessage(new GameSpyLoginResponse(user.getId(), profile.getId(), proof, sessionKey, request.sequenceId())); + } + + public void handleProfileRequest(GameSpyProfileRequest request) { + sendMessage(new GameSpyProfileResponse(profile, request.sequenceId())); + } + + public void handleUpdateProfileRequest(GameSpyProfileUpdateRequest request) { + // Update profile info + boolean profileChanged = setValue(request::firstName, profile::setFirstName, profile::getFirstName); + profileChanged |= setValue(request::lastName, profile::setLastName, profile::getLastName); + profileChanged |= setValue(request::aimName, profile::setAimName, profile::getAimName); + profileChanged |= setValue(request::zipCode, profile::setZipCode, profile::getZipCode); + + // Save user data if the profile was changed + if(profileChanged) { + userManager.saveUser(user); + } + } + + /** + * Sets a value if it isn't {@code null} and returns {@code true} if the value is different from the current value. + * + * @param valueSupplier Supplies the value that needs to be set + * @param valueConsumer Consumes the value from {@code valueSupplier} (a setter) + * @param currentValueSupplier Supplies the current value to test against the new value + */ + private boolean setValue(Supplier valueSupplier, Consumer valueConsumer, Supplier currentValueSupplier) { + T value = valueSupplier.get(); + + // Return false if value is null or is equal to the existing value + if(value == null || value.equals(currentValueSupplier.get())) { + return false; + } + + valueConsumer.accept(value); + return true; + } + + protected String createCredentialHash(String passwordHash, String user, String inChallenge, String outChallenge) { + return MD5.digest("%s%s%s%s%s%s".formatted( + passwordHash, + " ", + user, + inChallenge, + outChallenge, + passwordHash)); + } + + public void destroySessionKey(int sessionKey) { + if(validateSessionKey(sessionKey)) { + sessionKey = -1; + } + } + + public boolean validateSessionKey(int sessionKey) { + return validateSessionKey(sessionKey, 0); + } + + public boolean validateSessionKey(int sessionKey, int sequenceId) { + if(sessionKey < 0 || this.sessionKey != sessionKey) { + sendErrorMessage(0x201, "Invalid session key.", sequenceId); + return false; + } + + return true; + } + + public ChannelFuture sendMessage(Object message) { + return channel.writeAndFlush(message).addListener(ChannelFutureListener.FIRE_EXCEPTION_ON_FAILURE); + } + + public ChannelFuture sendErrorMessage(int errorCode, String errorMessage, boolean fatal, int sequenceId) { + return sendMessage(new GameSpyErrorMessage(errorCode, errorMessage, fatal ? 1 : 0, sequenceId)); + } + + public ChannelFuture sendErrorMessage(int errorCode, String errorMessage, int sequenceId) { + return sendErrorMessage(errorCode, errorMessage, false, sequenceId); + } + + public ChannelFuture close() { + return channel.close(); + } +} diff --git a/src/main/java/entralinked/network/gamespy/GameSpyMessageEncoder.java b/src/main/java/entralinked/network/gamespy/GameSpyMessageEncoder.java new file mode 100644 index 0000000..5a80ff3 --- /dev/null +++ b/src/main/java/entralinked/network/gamespy/GameSpyMessageEncoder.java @@ -0,0 +1,45 @@ +package entralinked.network.gamespy; + +import java.io.IOException; +import java.nio.charset.StandardCharsets; + +import com.fasterxml.jackson.databind.ObjectMapper; + +import entralinked.network.gamespy.message.GameSpyMessage; +import entralinked.serialization.GameSpyMessageFactory; +import io.netty.buffer.ByteBuf; +import io.netty.channel.ChannelHandlerContext; +import io.netty.handler.codec.MessageToByteEncoder; + +public class GameSpyMessageEncoder extends MessageToByteEncoder { + + protected final ObjectMapper mapper; + + /** + * Supplied {@link ObjectMapper} should be configured to use the {@link GameSpyMessageFactory} + */ + public GameSpyMessageEncoder(ObjectMapper mapper) { + this.mapper = mapper; + } + + @Override + protected void encode(ChannelHandlerContext ctx, Object in, ByteBuf out) throws Exception { + Class type = in.getClass(); + GameSpyMessage messageInfo = type.getAnnotation(GameSpyMessage.class); + + if(messageInfo == null) { + throw new IOException("Outbound message type '%s' must have the GameSpyMessage annotation.".formatted(type.getName())); + } + + out.writeByte('\\'); + writeString(out, messageInfo.name()); + out.writeByte('\\'); + writeString(out, messageInfo.value()); + out.writeBytes(mapper.writeValueAsBytes(in)); + writeString(out, "\\final\\"); + } + + private void writeString(ByteBuf out, String string) { + out.writeCharSequence(string, StandardCharsets.UTF_8); + } +} diff --git a/src/main/java/entralinked/network/gamespy/GameSpyRequestDecoder.java b/src/main/java/entralinked/network/gamespy/GameSpyRequestDecoder.java new file mode 100644 index 0000000..53b228d --- /dev/null +++ b/src/main/java/entralinked/network/gamespy/GameSpyRequestDecoder.java @@ -0,0 +1,80 @@ +package entralinked.network.gamespy; + +import java.io.IOException; +import java.util.List; +import java.util.Map; + +import com.fasterxml.jackson.databind.ObjectMapper; + +import entralinked.network.gamespy.request.GameSpyRequest; +import entralinked.serialization.GameSpyMessageFactory; +import io.netty.buffer.ByteBuf; +import io.netty.channel.ChannelHandlerContext; +import io.netty.handler.codec.MessageToMessageDecoder; + +public class GameSpyRequestDecoder extends MessageToMessageDecoder { + + protected final ObjectMapper mapper; + protected final Map> requestTypes; + + /** + * Supplied {@link ObjectMapper} should be configured to use the {@link GameSpyMessageFactory} + */ + public GameSpyRequestDecoder(ObjectMapper mapper, Map> requestTypes) { + this.mapper = mapper; + this.requestTypes = requestTypes; + } + + @Override + protected void decode(ChannelHandlerContext ctx, ByteBuf in, List out) throws Exception { + // Sanity check + byte b = in.readByte(); + + if(b != '\\') { + throw new IOException("Was expecting '\\', got '%s'.".formatted((char)b)); + } + + // Get request type + String typeName = parseString(in, false); + Class requestType = requestTypes.get(typeName); + + if(requestType == null) { + throw new IOException("Invalid or unimplemented request type '%s'".formatted(typeName)); + } + + // Parse request value (?) if any bytes are remaining + if(in.readableBytes() > 0) { + parseString(in, true); + } + + // If there are still bytes left, use ObjectMapper to parse and map them. + // Otherwise, create empty instance using reflection. + GameSpyRequest request = null; + + if(in.readableBytes() > 0) { + byte[] bytes = new byte[in.readableBytes() + 1]; + bytes[0] = '\\'; // Cuz it was read as a terminator earlier.. + in.readBytes(bytes, 1, bytes.length - 1); + request = mapper.readValue(bytes, requestType); + } else { + request = requestType.getConstructor().newInstance(); + } + + out.add(request); + } + + private String parseString(ByteBuf in, boolean allowEOI) { + StringBuilder builder = new StringBuilder(); + byte b = 0; + + while((b = in.readByte()) != '\\') { + builder.append((char)b); + + if(allowEOI && in.readableBytes() == 0) { + break; + } + } + + return builder.toString(); + } +} \ No newline at end of file diff --git a/src/main/java/entralinked/network/gamespy/GameSpyServer.java b/src/main/java/entralinked/network/gamespy/GameSpyServer.java new file mode 100644 index 0000000..fb8eaea --- /dev/null +++ b/src/main/java/entralinked/network/gamespy/GameSpyServer.java @@ -0,0 +1,73 @@ +package entralinked.network.gamespy; + +import java.util.Collection; +import java.util.HashMap; +import java.util.Map; + +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; + +import com.fasterxml.jackson.databind.DeserializationConfig; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.introspect.AnnotatedClass; +import com.fasterxml.jackson.databind.introspect.AnnotatedClassResolver; +import com.fasterxml.jackson.databind.jsontype.NamedType; + +import entralinked.Entralinked; +import entralinked.network.NettyServerBase; +import entralinked.network.gamespy.request.GameSpyRequest; +import entralinked.serialization.GameSpyMessageFactory; +import io.netty.bootstrap.ServerBootstrap; +import io.netty.buffer.Unpooled; +import io.netty.channel.Channel; +import io.netty.channel.ChannelFuture; +import io.netty.channel.ChannelInitializer; +import io.netty.channel.ChannelPipeline; +import io.netty.channel.socket.nio.NioServerSocketChannel; +import io.netty.handler.codec.DelimiterBasedFrameDecoder; +import io.netty.util.concurrent.DefaultEventExecutor; +import io.netty.util.concurrent.EventExecutorGroup; + +public class GameSpyServer extends NettyServerBase { + + private static final Logger logger = LogManager.getLogger(); + private static final ObjectMapper mapper = new ObjectMapper(new GameSpyMessageFactory()); + private static final Map> requestTypes = new HashMap<>(); + private final EventExecutorGroup handlerGroup = new DefaultEventExecutor(threadFactory); + private final Entralinked entralinked; + + static { + logger.info("Mapping GameSpy request types ..."); + DeserializationConfig config = mapper.getDeserializationConfig(); + AnnotatedClass annotated = AnnotatedClassResolver.resolveWithoutSuperTypes(config, GameSpyRequest.class); + Collection types = mapper.getSubtypeResolver().collectAndResolveSubtypesByClass(config, annotated); + + for(NamedType type : types) { + if(type.hasName()) { + requestTypes.put(type.getName(), (Class)type.getType()); + } + } + } + + public GameSpyServer(Entralinked entralinked) { + super("GameSpy", 29900); + this.entralinked = entralinked; + } + + @Override + public ChannelFuture bootstrap(int port) { + return new ServerBootstrap() + .group(eventLoopGroup) + .channel(NioServerSocketChannel.class) + .childHandler(new ChannelInitializer() { + @Override + protected void initChannel(Channel channel) throws Exception { + ChannelPipeline pipeline = channel.pipeline(); + pipeline.addLast(new DelimiterBasedFrameDecoder(512, Unpooled.wrappedBuffer("\\final\\".getBytes()))); + pipeline.addLast(new GameSpyRequestDecoder(mapper, requestTypes)); + pipeline.addLast(new GameSpyMessageEncoder(mapper)); + pipeline.addLast(handlerGroup, new GameSpyHandler(entralinked)); + } + }).bind(port).awaitUninterruptibly(); + } +} diff --git a/src/main/java/entralinked/network/gamespy/message/GameSpyChallengeMessage.java b/src/main/java/entralinked/network/gamespy/message/GameSpyChallengeMessage.java new file mode 100644 index 0000000..d15fee4 --- /dev/null +++ b/src/main/java/entralinked/network/gamespy/message/GameSpyChallengeMessage.java @@ -0,0 +1,8 @@ +package entralinked.network.gamespy.message; + +import com.fasterxml.jackson.annotation.JsonProperty; + +@GameSpyMessage(name = "lc", value = "1") +public record GameSpyChallengeMessage( + @JsonProperty("challenge") String challenge, + @JsonProperty("id") int sequenceId) {} diff --git a/src/main/java/entralinked/network/gamespy/message/GameSpyErrorMessage.java b/src/main/java/entralinked/network/gamespy/message/GameSpyErrorMessage.java new file mode 100644 index 0000000..a2c722e --- /dev/null +++ b/src/main/java/entralinked/network/gamespy/message/GameSpyErrorMessage.java @@ -0,0 +1,10 @@ +package entralinked.network.gamespy.message; + +import com.fasterxml.jackson.annotation.JsonProperty; + +@GameSpyMessage(name = "error") +public record GameSpyErrorMessage( + @JsonProperty("err") int errorCode, + @JsonProperty("errmsg") String errorMessage, + @JsonProperty("fatal") int fatal, + @JsonProperty("id") int sequenceId) {} diff --git a/src/main/java/entralinked/network/gamespy/message/GameSpyLoginResponse.java b/src/main/java/entralinked/network/gamespy/message/GameSpyLoginResponse.java new file mode 100644 index 0000000..168f270 --- /dev/null +++ b/src/main/java/entralinked/network/gamespy/message/GameSpyLoginResponse.java @@ -0,0 +1,11 @@ +package entralinked.network.gamespy.message; + +import com.fasterxml.jackson.annotation.JsonProperty; + +@GameSpyMessage(name = "lc", value = "2") +public record GameSpyLoginResponse( + @JsonProperty("userid") String userId, + @JsonProperty("profileid") int profileId, + @JsonProperty("proof") String proof, + @JsonProperty("sesskey") int sessionKey, + @JsonProperty("id") int sequenceId) {} diff --git a/src/main/java/entralinked/network/gamespy/message/GameSpyMessage.java b/src/main/java/entralinked/network/gamespy/message/GameSpyMessage.java new file mode 100644 index 0000000..3a74952 --- /dev/null +++ b/src/main/java/entralinked/network/gamespy/message/GameSpyMessage.java @@ -0,0 +1,14 @@ +package entralinked.network.gamespy.message; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +@Target(ElementType.TYPE) +@Retention(RetentionPolicy.RUNTIME) +public @interface GameSpyMessage { + + public String name(); + public String value() default ""; // Still not sure what purpose this serves.. +} diff --git a/src/main/java/entralinked/network/gamespy/message/GameSpyProfileResponse.java b/src/main/java/entralinked/network/gamespy/message/GameSpyProfileResponse.java new file mode 100644 index 0000000..0409b7d --- /dev/null +++ b/src/main/java/entralinked/network/gamespy/message/GameSpyProfileResponse.java @@ -0,0 +1,24 @@ +package entralinked.network.gamespy.message; + +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonInclude.Include; + +import entralinked.model.user.GameProfile; + +import com.fasterxml.jackson.annotation.JsonProperty; + +@JsonInclude(Include.NON_NULL) +@GameSpyMessage(name = "pi") +public record GameSpyProfileResponse( + @JsonProperty("profileid") int profileId, + @JsonProperty("firstname") String firstName, + @JsonProperty("lastname") String lastName, + @JsonProperty("aim") String aimName, + @JsonProperty("zipcode") String zipCode, + @JsonProperty("sig") String signature, + @JsonProperty("id") int sequenceId) { + + public GameSpyProfileResponse(GameProfile profile, int sequenceId) { + this(profile.getId(), profile.getFirstName(), profile.getLastName(), profile.getAimName(), profile.getZipCode(), "signature", sequenceId); + } +} diff --git a/src/main/java/entralinked/network/gamespy/request/GameSpyKeepAliveRequest.java b/src/main/java/entralinked/network/gamespy/request/GameSpyKeepAliveRequest.java new file mode 100644 index 0000000..51e1a34 --- /dev/null +++ b/src/main/java/entralinked/network/gamespy/request/GameSpyKeepAliveRequest.java @@ -0,0 +1,13 @@ +package entralinked.network.gamespy.request; + +import entralinked.network.gamespy.GameSpyHandler; +import entralinked.network.gamespy.message.GameSpyMessage; + +@GameSpyMessage(name = "ka") +public record GameSpyKeepAliveRequest() implements GameSpyRequest { + + @Override + public void process(GameSpyHandler handler) { + handler.sendMessage(this); // Pong it back + } +} diff --git a/src/main/java/entralinked/network/gamespy/request/GameSpyLoginRequest.java b/src/main/java/entralinked/network/gamespy/request/GameSpyLoginRequest.java new file mode 100644 index 0000000..50501e7 --- /dev/null +++ b/src/main/java/entralinked/network/gamespy/request/GameSpyLoginRequest.java @@ -0,0 +1,31 @@ +package entralinked.network.gamespy.request; + +import com.fasterxml.jackson.annotation.JsonProperty; + +import entralinked.network.gamespy.GameSpyHandler; + +public record GameSpyLoginRequest( + // Important stuff + @JsonProperty(value = "response", required = true) String response, + @JsonProperty(value = "challenge", required = true) String challenge, + @JsonProperty(value = "authtoken", required = true) String partnerToken, + @JsonProperty(value = "id", required = true) int sequenceId, + + // Not-so-important stuff + @JsonProperty("userid") String userId, + @JsonProperty("gamename") String gameName, + @JsonProperty("profileid") int profileId, + @JsonProperty("namespaceid") int namespaceId, + @JsonProperty("partnerid") int partnerId, + @JsonProperty("productid") int productId, + @JsonProperty("sdkrevision") int sdkRevision, + @JsonProperty("firewall") int firewall, + @JsonProperty("port") int port, + @JsonProperty("quiet") int quiet +) implements GameSpyRequest { + + @Override + public void process(GameSpyHandler handler) { + handler.handleLoginRequest(this); + } +} diff --git a/src/main/java/entralinked/network/gamespy/request/GameSpyLogoutRequest.java b/src/main/java/entralinked/network/gamespy/request/GameSpyLogoutRequest.java new file mode 100644 index 0000000..6b1fbcc --- /dev/null +++ b/src/main/java/entralinked/network/gamespy/request/GameSpyLogoutRequest.java @@ -0,0 +1,15 @@ +package entralinked.network.gamespy.request; + +import com.fasterxml.jackson.annotation.JsonProperty; + +import entralinked.network.gamespy.GameSpyHandler; + +public record GameSpyLogoutRequest( + @JsonProperty("sesskey") int sessionKey +) implements GameSpyRequest { + + @Override + public void process(GameSpyHandler handler) { + handler.destroySessionKey(sessionKey); + } +} diff --git a/src/main/java/entralinked/network/gamespy/request/GameSpyProfileRequest.java b/src/main/java/entralinked/network/gamespy/request/GameSpyProfileRequest.java new file mode 100644 index 0000000..6a818f8 --- /dev/null +++ b/src/main/java/entralinked/network/gamespy/request/GameSpyProfileRequest.java @@ -0,0 +1,19 @@ +package entralinked.network.gamespy.request; + +import com.fasterxml.jackson.annotation.JsonProperty; + +import entralinked.network.gamespy.GameSpyHandler; + +public record GameSpyProfileRequest( + @JsonProperty(value = "sesskey", required = true) int sessionKey, + @JsonProperty(value = "id", required = true) int sequenceId, + @JsonProperty("profileid") int profileId +) implements GameSpyRequest { + + @Override + public void process(GameSpyHandler handler) { + if(handler.validateSessionKey(sessionKey, sequenceId)) { + handler.handleProfileRequest(this); + } + } +} diff --git a/src/main/java/entralinked/network/gamespy/request/GameSpyProfileUpdateRequest.java b/src/main/java/entralinked/network/gamespy/request/GameSpyProfileUpdateRequest.java new file mode 100644 index 0000000..74ee0a8 --- /dev/null +++ b/src/main/java/entralinked/network/gamespy/request/GameSpyProfileUpdateRequest.java @@ -0,0 +1,24 @@ +package entralinked.network.gamespy.request; + +import com.fasterxml.jackson.annotation.JsonProperty; + +import entralinked.network.gamespy.GameSpyHandler; + +public record GameSpyProfileUpdateRequest( + @JsonProperty(value = "sesskey", required = true) int sessionKey, + @JsonProperty("partnerid") int partnerId, + + // Lots of possible values here, but these seem to be the only ones sent by generation 5 games. + @JsonProperty("firstname") String firstName, + @JsonProperty("lastname") String lastName, + @JsonProperty("aim") String aimName, + @JsonProperty("zipcode") String zipCode +) implements GameSpyRequest { + + @Override + public void process(GameSpyHandler handler) { + if(handler.validateSessionKey(sessionKey)) { + handler.handleUpdateProfileRequest(this); + } + } +} diff --git a/src/main/java/entralinked/network/gamespy/request/GameSpyRequest.java b/src/main/java/entralinked/network/gamespy/request/GameSpyRequest.java new file mode 100644 index 0000000..054e7fd --- /dev/null +++ b/src/main/java/entralinked/network/gamespy/request/GameSpyRequest.java @@ -0,0 +1,19 @@ +package entralinked.network.gamespy.request; + +import com.fasterxml.jackson.annotation.JsonSubTypes; +import com.fasterxml.jackson.annotation.JsonSubTypes.Type; + +import entralinked.network.gamespy.GameSpyHandler; + +@JsonSubTypes({ + @Type(name = "ka", value = GameSpyKeepAliveRequest.class), + @Type(name = "login", value = GameSpyLoginRequest.class), + @Type(name = "logout", value = GameSpyLogoutRequest.class), + @Type(name = "status", value = GameSpyStatusRequest.class), + @Type(name = "getprofile", value = GameSpyProfileRequest.class), + @Type(name = "updatepro", value = GameSpyProfileUpdateRequest.class), +}) +public interface GameSpyRequest { + + public void process(GameSpyHandler handler); +} diff --git a/src/main/java/entralinked/network/gamespy/request/GameSpyStatusRequest.java b/src/main/java/entralinked/network/gamespy/request/GameSpyStatusRequest.java new file mode 100644 index 0000000..a879edf --- /dev/null +++ b/src/main/java/entralinked/network/gamespy/request/GameSpyStatusRequest.java @@ -0,0 +1,17 @@ +package entralinked.network.gamespy.request; + +import com.fasterxml.jackson.annotation.JsonProperty; + +import entralinked.network.gamespy.GameSpyHandler; + +public record GameSpyStatusRequest( + @JsonProperty(value = "sesskey", required = true) int sessionKey, + @JsonProperty("statstring") String statusString, + @JsonProperty("locstring") String locationString +) implements GameSpyRequest { + + @Override + public void process(GameSpyHandler handler) { + handler.validateSessionKey(sessionKey); + } +} diff --git a/src/main/java/entralinked/network/http/HttpHandler.java b/src/main/java/entralinked/network/http/HttpHandler.java new file mode 100644 index 0000000..a5a7a11 --- /dev/null +++ b/src/main/java/entralinked/network/http/HttpHandler.java @@ -0,0 +1,16 @@ +package entralinked.network.http; + +import io.javalin.Javalin; +import io.javalin.config.JavalinConfig; +import io.javalin.http.Context; +import io.javalin.http.servlet.JavalinServletContext; + +public interface HttpHandler { + + public void addHandlers(Javalin javalin); + + public default void configureJavalin(JavalinConfig config) {} + public default void clearTasks(Context ctx) { + ((JavalinServletContext)ctx).getTasks().clear(); + } +} diff --git a/src/main/java/entralinked/network/http/HttpRequestHandler.java b/src/main/java/entralinked/network/http/HttpRequestHandler.java new file mode 100644 index 0000000..38949f3 --- /dev/null +++ b/src/main/java/entralinked/network/http/HttpRequestHandler.java @@ -0,0 +1,11 @@ +package entralinked.network.http; + +import java.io.IOException; + +import io.javalin.http.Context; + +@FunctionalInterface +public interface HttpRequestHandler { + + public void process(T request, Context ctx) throws IOException; +} diff --git a/src/main/java/entralinked/network/http/HttpServer.java b/src/main/java/entralinked/network/http/HttpServer.java new file mode 100644 index 0000000..c583e32 --- /dev/null +++ b/src/main/java/entralinked/network/http/HttpServer.java @@ -0,0 +1,170 @@ +package entralinked.network.http; + +import java.io.File; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.IOException; +import java.security.GeneralSecurityException; +import java.security.KeyStore; + +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.eclipse.jetty.http.HttpVersion; +import org.eclipse.jetty.server.HttpConfiguration; +import org.eclipse.jetty.server.HttpConnectionFactory; +import org.eclipse.jetty.server.SecureRequestCustomizer; +import org.eclipse.jetty.server.Server; +import org.eclipse.jetty.server.ServerConnector; +import org.eclipse.jetty.server.SslConnectionFactory; +import org.eclipse.jetty.util.ssl.SslContextFactory; + +import entralinked.Entralinked; +import entralinked.LauncherAgent; +import entralinked.utility.CertificateGenerator; +import io.javalin.Javalin; +import io.javalin.http.HttpStatus; +import io.javalin.util.JavalinException; + +public class HttpServer { + + private static final Logger logger = LogManager.getLogger(); + private static final String keyStorePassword = "password"; // Very secure! + private final Javalin javalin; + private boolean started; + + public HttpServer(Entralinked entralinked) { + // Create certificate keystore + KeyStore keyStore = null; + + if(LauncherAgent.isBouncyCastlePresent()) { + CertificateGenerator.initialize(); + logger.info("Creating certificate keystore ..."); + keyStore = createKeyStore(); + + if(keyStore == null) { + logger.warn("SSL will be disabled because keystore creation failed. You may have to manually sign a certificate."); + } + } + + KeyStore _keyStore = keyStore; // Java moment + + // Create Javalin instance + javalin = Javalin.create(config -> { + config.jetty.server(() -> createJettyServer(80, 443, _keyStore)); + }); + + // Create exception handler + javalin.exception(Exception.class, (exception, ctx) -> { + logger.error("Caught exception", exception); + ctx.status(HttpStatus.INTERNAL_SERVER_ERROR); + }); + + // Conntest + javalin.get("/", ctx -> { + ctx.header("X-Organization", "Nintendo"); // Conntest fails with 052210-1 if this is not present + ctx.result("Test"); + }); + } + + public void addHandler(HttpHandler handler) { + javalin.updateConfig(handler::configureJavalin); // Dirty + handler.addHandlers(javalin); + } + + public boolean start() { + if(started) { + logger.warn("start() was called while HTTP server was already running!"); + return true; + } + + logger.info("Starting HTTP server ..."); + + try { + javalin.start(); + } catch(JavalinException e) { + logger.error("Could not start HTTP server", e); + return false; + } + + started = true; + return true; + } + + public boolean stop() { + if(!started) { + logger.warn("stop() was called while HTTP server wasn't running!"); + return true; + } + + logger.info("Stopping HTTP server ..."); + + try { + javalin.stop(); + } catch(JavalinException e) { + logger.error("Could not stop HTTP server", e); + return false; + } + + started = false; + return true; + } + + private KeyStore createKeyStore() { + try { + KeyStore keyStore = KeyStore.getInstance("PKCS12"); + File keyStoreFile = new File("server.p12"); + + if(keyStoreFile.exists()) { + // Load keystore from file if it exists + logger.info("Cached keystore found - loading it!"); + keyStore.load(new FileInputStream(keyStoreFile), keyStorePassword.toCharArray()); + } else { + // Otherwise, generate a new one and store it in a file + logger.info("No keystore found - generating one!"); + keyStore = CertificateGenerator.generateCertificateKeyStore("PKCS12", null); + keyStore.store(new FileOutputStream(keyStoreFile), keyStorePassword.toCharArray()); + } + + return keyStore; + } catch (GeneralSecurityException | IOException e) { + logger.error("Could not create keystore", e); + } + + return null; + } + + private Server createJettyServer(int port, int sslPort, KeyStore keyStore) { + Server server = new Server(); + + // Regular HTTP connector + ServerConnector httpConnector = new ServerConnector(server); + httpConnector.setPort(port); + server.addConnector(httpConnector); + + if(keyStore != null) { + // Create SSL/HTTPS connector if a keystore is present + SslContextFactory.Server sslContextFactory = new SslContextFactory.Server(); + sslContextFactory.setIncludeProtocols("SSLv3"); + sslContextFactory.setExcludeProtocols(""); + sslContextFactory.setIncludeCipherSuites("SSL_RSA_WITH_RC4_128_SHA", "SSL_RSA_WITH_RC4_128_MD5"); + sslContextFactory.setExcludeCipherSuites(""); + sslContextFactory.setKeyStore(keyStore); + sslContextFactory.setKeyStorePassword(keyStorePassword); + sslContextFactory.setSslSessionCacheSize(0); + sslContextFactory.setSslSessionTimeout(0); + + HttpConfiguration httpsConfiguration = new HttpConfiguration(); + httpsConfiguration.addCustomizer(new SecureRequestCustomizer(false)); + httpsConfiguration.setSendServerVersion(true); + + ServerConnector httpsConnector = new ServerConnector(server, + new SslConnectionFactory(sslContextFactory, HttpVersion.HTTP_1_1.asString()), + new HttpConnectionFactory(httpsConfiguration)); + + httpsConnector.setPort(sslPort); + server.addConnector(httpsConnector); + } + + return server; + } +} diff --git a/src/main/java/entralinked/network/http/dashboard/DashboardHandler.java b/src/main/java/entralinked/network/http/dashboard/DashboardHandler.java new file mode 100644 index 0000000..da1fc25 --- /dev/null +++ b/src/main/java/entralinked/network/http/dashboard/DashboardHandler.java @@ -0,0 +1,246 @@ +package entralinked.network.http.dashboard; + +import java.util.Collections; +import java.util.stream.Collectors; + +import com.fasterxml.jackson.databind.ObjectMapper; + +import entralinked.Entralinked; +import entralinked.model.dlc.Dlc; +import entralinked.model.dlc.DlcList; +import entralinked.model.pkmn.PkmnGender; +import entralinked.model.pkmn.PkmnInfo; +import entralinked.model.player.DreamEncounter; +import entralinked.model.player.DreamItem; +import entralinked.model.player.Player; +import entralinked.model.player.PlayerManager; +import entralinked.model.player.PlayerStatus; +import entralinked.network.http.HttpHandler; +import entralinked.utility.GsidUtility; +import io.javalin.Javalin; +import io.javalin.config.JavalinConfig; +import io.javalin.http.Context; +import io.javalin.http.HttpStatus; +import io.javalin.http.staticfiles.Location; +import io.javalin.json.JavalinJackson; + +/** + * HTTP handler for requests made to the user dashboard. + */ +public class DashboardHandler implements HttpHandler { + + private final DlcList dlcList; + private final PlayerManager playerManager; + + public DashboardHandler(Entralinked entralinked) { + this.dlcList = entralinked.getDlcList(); + this.playerManager = entralinked.getPlayerManager(); + } + + @Override + public void addHandlers(Javalin javalin) { + javalin.get("/dashboard/dlc", this::handleRetrieveDlcList); + javalin.get("/dashboard/profile", this::handleRetrieveProfile); + javalin.post("/dashboard/profile", this::handleUpdateProfile); + javalin.post("/dashboard/login", this::handleLogin); + javalin.post("/dashboard/logout", this::handleLogout); + } + + @Override + public void configureJavalin(JavalinConfig config) { + // Configure JSON mapper + config.jsonMapper(new JavalinJackson(new ObjectMapper())); + + // Add dashboard pages + config.staticFiles.add(staticFileConfig -> { + staticFileConfig.location = Location.CLASSPATH; + staticFileConfig.directory = "/dashboard"; + staticFileConfig.hostedPath = "/dashboard"; + }); + + // Add sprites + config.staticFiles.add(staticFileConfig -> { + staticFileConfig.location = Location.CLASSPATH; + staticFileConfig.directory = "/sprites"; + staticFileConfig.hostedPath = "/sprites"; + }); + } + + /** + * GET request handler for {@code /dashboard/dlc} + */ + private void handleRetrieveDlcList(Context ctx) { + // Make sure that the DLC type is present + String type = ctx.queryParam("type"); + + if(type == null) { + ctx.json(Collections.EMPTY_LIST); + return; + } + + // Send result + ctx.json(dlcList.getDlcList("IRAO", type).stream().map(Dlc::name).collect(Collectors.toList())); + } + + /** + * GET request handler for {@code /dashboard/profile} + */ + private void handleRetrieveProfile(Context ctx) { + // Validate session + Player player = ctx.sessionAttribute("player"); + + if(player == null || player.getStatus() == PlayerStatus.AWAKE) { + ctx.json(new DashboardStatusMessage("Unauthorized", true)); + ctx.status(HttpStatus.UNAUTHORIZED); + return; + } + + // Send profile data + ctx.json(new DashboardProfileMessage(getSpritePath(player.getDreamerInfo()), player)); + } + + /** + * GET request handler for {@code /dashboard/login} + */ + private void handleLogin(Context ctx) { + String gsid = ctx.formParam("gsid"); + + // Check if the Game Sync ID is valid + if(gsid == null || !GsidUtility.isValidGameSyncId(gsid)) { + ctx.json(new DashboardStatusMessage("Please enter a valid Game Sync ID.", true)); + return; + } + + Player player = playerManager.getPlayer(gsid); + + // Check if the Game Sync ID exists + if(player == null) { + ctx.json(new DashboardStatusMessage("This Game Sync ID does not exist.", true)); + return; + } + + // Check if there is stuff to play around with + if(player.getStatus() == PlayerStatus.AWAKE) { + ctx.json(new DashboardStatusMessage("Please use Game Sync to tuck in a Pokémon before proceeding.", true)); + return; + } + + // Store session attribute and send response + ctx.sessionAttribute("player", player); + ctx.json(new DashboardStatusMessage("ok")); // heh + } + + /** + * POST request handler for {@code /dashboard/logout} + */ + private void handleLogout(Context ctx) { + // Who cares if the session actually exists? I sure don't. + ctx.consumeSessionAttribute("player"); + ctx.json(Collections.EMPTY_MAP); + } + + /** + * POST request handler for {@code /dashboard/profile} + */ + private void handleUpdateProfile(Context ctx) { + // Check if session exists + Player player = ctx.sessionAttribute("player"); + + if(player == null || player.getStatus() == PlayerStatus.AWAKE) { + ctx.json(new DashboardStatusMessage("Unauthorized", true)); + ctx.status(HttpStatus.UNAUTHORIZED); + return; + } + + // Validate request data + DashboardProfileUpdateRequest request = ctx.bodyAsClass(DashboardProfileUpdateRequest.class); + String error = validateProfileUpdateRequest(player, request); + + if(error != null) { + ctx.json(new DashboardStatusMessage("Profile data was NOT saved: " + error, true)); + return; + } + + // Update profile + player.setStatus(PlayerStatus.WAKE_READY); + player.setEncounters(request.encounters()); + player.setItems(request.items()); + player.setCGearSkin(request.cgearSkin().equals("none") ? null : request.cgearSkin()); + player.setDexSkin(request.dexSkin().equals("none") ? null : request.dexSkin()); + player.setMusical(request.musical().equals("none") ? null : request.musical()); + player.setLevelsGained(request.gainedLevels()); + + // Try to save profile data + if(!playerManager.savePlayer(player)) { + ctx.json(new DashboardStatusMessage("Profile data could not be saved because of an error.", true)); + return; + } + + // Send response if all succeeded + ctx.json(new DashboardStatusMessage("Your changes have been saved. Use Game Sync to wake up your Pokémon and download your selected content.")); + } + + /** + * Validates a {@link DashboardProfileUpdateRequest} and returns an error string if the data is invalid. + * If the data is valid, {@code null} is returned instead. + */ + private String validateProfileUpdateRequest(Player player, DashboardProfileUpdateRequest request) { + // Validate encounters + if(request.encounters().size() > 10) { + return "Encounter list size exceeds the limit."; + } + + for(DreamEncounter encounter : request.encounters()) { + if(encounter.species() < 1 || encounter.species() > 493) { + return "Species is out of range."; + } else if(encounter.move() < 1 || encounter.move() > 559) { + return "Move ID is out of range."; + } else if(encounter.animation() == null) { + return "Animation is undefined."; + } + + // TODO validate form maybe idk + } + + // Validate items + if(request.items().size() > 20) { + return "Item list size exceeds the limit."; + } + + for(DreamItem item : request.items()) { + if(item.id() < 0 || item.id() > 638) { + return "Item ID is out of range"; + } else if(item.id() > 626 && !player.getGameVersion().isVersion2()) { + return "You have selected one or more items that are exclusive to Black Version 2 and White Version 2."; + } + + if(item.quantity() < 0 || item.quantity() > 20) { + return "Item quantity is out of range."; + } + } + + // Validate gained levels + if(request.gainedLevels() < 0 || request.gainedLevels() > 99) { + return "Gained levels is out of range."; + } + + return null; + } + + private String getSpritePath(PkmnInfo info) { + String basePath = "/sprites/pokemon/%s".formatted(info.isShiny() ? "shiny" : "normal"); + String path = null; + + if(info.form() > 0) { + path = "%s/%s-%s.png".formatted(basePath, info.species(), info.form()); + } else if(info.gender() == PkmnGender.FEMALE) { + path = "%s/female/%s.png".formatted(basePath, info.species()); + } + + if(path == null || getClass().getResource(path) == null) { + return "%s/%s.png".formatted(basePath, info.species()); + } + + return path; + } +} diff --git a/src/main/java/entralinked/network/http/dashboard/DashboardProfileMessage.java b/src/main/java/entralinked/network/http/dashboard/DashboardProfileMessage.java new file mode 100644 index 0000000..4b97788 --- /dev/null +++ b/src/main/java/entralinked/network/http/dashboard/DashboardProfileMessage.java @@ -0,0 +1,25 @@ +package entralinked.network.http.dashboard; + +import java.util.Collection; + +import entralinked.model.pkmn.PkmnInfo; +import entralinked.model.player.DreamEncounter; +import entralinked.model.player.DreamItem; +import entralinked.model.player.Player; + +public record DashboardProfileMessage( + String gameVersion, + String dreamerSprite, + PkmnInfo dreamerInfo, + String cgearSkin, + String dexSkin, + String musical, + int levelsGained, + Collection encounters, + Collection items) { + + public DashboardProfileMessage(String dreamerSprite, Player player) { + this(player.getGameVersion().getDisplayName(), dreamerSprite, player.getDreamerInfo(), player.getCGearSkin(), + player.getDexSkin(), player.getMusical(), player.getLevelsGained(), player.getEncounters(), player.getItems()); + } +} diff --git a/src/main/java/entralinked/network/http/dashboard/DashboardProfileUpdateRequest.java b/src/main/java/entralinked/network/http/dashboard/DashboardProfileUpdateRequest.java new file mode 100644 index 0000000..90fc930 --- /dev/null +++ b/src/main/java/entralinked/network/http/dashboard/DashboardProfileUpdateRequest.java @@ -0,0 +1,17 @@ +package entralinked.network.http.dashboard; + +import java.util.List; + +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; + +import entralinked.model.player.DreamEncounter; +import entralinked.model.player.DreamItem; + +public record DashboardProfileUpdateRequest( + @JsonProperty(required = true) @JsonDeserialize(contentAs = DreamEncounter.class) List encounters, + @JsonProperty(required = true) @JsonDeserialize(contentAs = DreamItem.class) List items, + @JsonProperty(required = true) String cgearSkin, + @JsonProperty(required = true) String dexSkin, + @JsonProperty(required = true) String musical, + @JsonProperty(required = true) int gainedLevels) {} diff --git a/src/main/java/entralinked/network/http/dashboard/DashboardStatusMessage.java b/src/main/java/entralinked/network/http/dashboard/DashboardStatusMessage.java new file mode 100644 index 0000000..7f0321e --- /dev/null +++ b/src/main/java/entralinked/network/http/dashboard/DashboardStatusMessage.java @@ -0,0 +1,8 @@ +package entralinked.network.http.dashboard; + +public record DashboardStatusMessage(String message, boolean error) { + + public DashboardStatusMessage(String message) { + this(message, false); + } +} diff --git a/src/main/java/entralinked/network/http/dls/DlsHandler.java b/src/main/java/entralinked/network/http/dls/DlsHandler.java new file mode 100644 index 0000000..57ad52c --- /dev/null +++ b/src/main/java/entralinked/network/http/dls/DlsHandler.java @@ -0,0 +1,106 @@ +package entralinked.network.http.dls; + +import java.io.FileInputStream; +import java.io.IOException; + +import com.fasterxml.jackson.databind.ObjectMapper; + +import entralinked.Entralinked; +import entralinked.model.dlc.Dlc; +import entralinked.model.dlc.DlcList; +import entralinked.model.user.ServiceSession; +import entralinked.model.user.UserManager; +import entralinked.network.http.HttpHandler; +import entralinked.network.http.HttpRequestHandler; +import entralinked.serialization.UrlEncodedFormFactory; +import entralinked.utility.LEOutputStream; +import io.javalin.Javalin; +import io.javalin.http.Context; +import io.javalin.http.HttpStatus; + +/** + * HTTP handler for requests made to {@code dls1.nintendowifi.net} + */ +public class DlsHandler implements HttpHandler { + + private final ObjectMapper mapper = new ObjectMapper(new UrlEncodedFormFactory()); + private final DlcList dlcList; + private final UserManager userManager; + + public DlsHandler(Entralinked entralinked) { + this.dlcList = entralinked.getDlcList(); + this.userManager = entralinked.getUserManager(); + } + + @Override + public void addHandlers(Javalin javalin) { + javalin.post("/download", this::handleDownloadRequest); + } + + /** + * POST base handler for {@code /download} + */ + private void handleDownloadRequest(Context ctx) throws IOException { + // Deserialize request body + DlsRequest request = mapper.readValue(ctx.body().replace("%2A", "*"), DlsRequest.class); + + // Check if service session is valid + ServiceSession session = userManager.getServiceSession(request.serviceToken(), "dls1.nintendowifi.net"); + + if(session == null) { + ctx.status(HttpStatus.UNAUTHORIZED); + return; + } + + // Determine handler function based on request action + HttpRequestHandler handler = switch(request.action()) { + case "list" -> this::handleRetrieveDlcList; + case "contents" -> this::handleRetrieveDlcContent; + default -> throw new IllegalArgumentException("Invalid POST request action: " + request.action()); + }; + + // Handle the request + handler.process(request, ctx); + } + + /** + * POST handler for {@code /download action=list} + */ + private void handleRetrieveDlcList(DlsRequest request, Context ctx) throws IOException { + // Map to generic type, I doubt there is a real difference between the language codes anyway. + String type = switch(request.dlcType()) { + case "CGEAR_E", "CGEAR_F", "CGEAR_I", "CGEAR_G", "CGEAR_S", "CGEAR_J", "CGEAR_K" -> "CGEAR"; + case "CGEAR2_E", "CGEAR2_F", "CGEAR2_I", "CGEAR2_G", "CGEAR2_S", "CGEAR2_J", "CGEAR2_K" -> "CGEAR2"; + case "ZUKAN_E", "ZUKAN_F", "ZUKAN_I", "ZUKAN_G", "ZUKAN_S", "ZUKAN_J", "ZUKAN_K" -> "ZUKAN"; + case "MUSICAL_E", "MUSICAL_F", "MUSICAL_I", "MUSICAL_G", "MUSICAL_S", "MUSICAL_J", "MUSICAL_K" -> "MUSICAL"; + default -> request.dlcType(); + }; + + // TODO NOTE: I assume that in a conventional implementation, certain DLC attributes may be omitted from the request. + ctx.result(dlcList.getDlcListString(dlcList.getDlcList(request.dlcGameCode(), type, request.dlcIndex()))); + } + + /** + * POST handler for {@code /download action=contents} + */ + private void handleRetrieveDlcContent(DlsRequest request, Context ctx) throws IOException { + // Check if the requested DLC exists + Dlc dlc = dlcList.getDlc(request.dlcName()); + + if(dlc == null) { + ctx.status(HttpStatus.NOT_FOUND); + return; + } + + // Write DLC data + try(FileInputStream inputStream = new FileInputStream(dlc.path())) { + LEOutputStream outputStream = new LEOutputStream(ctx.outputStream()); + inputStream.transferTo(outputStream); + + // If checksum is not part of the file, manually append it + if(!dlc.checksumEmbedded()) { + outputStream.writeShort(dlc.checksum()); + } + } + } +} diff --git a/src/main/java/entralinked/network/http/dls/DlsRequest.java b/src/main/java/entralinked/network/http/dls/DlsRequest.java new file mode 100644 index 0000000..9d58016 --- /dev/null +++ b/src/main/java/entralinked/network/http/dls/DlsRequest.java @@ -0,0 +1,25 @@ +package entralinked.network.http.dls; + +import com.fasterxml.jackson.annotation.JsonProperty; + +public record DlsRequest( + // Credentials + @JsonProperty(value = "userid", required = true) String userId, + @JsonProperty(value = "passwd", required = true) String password, + @JsonProperty(value = "macadr", required = true) String macAddress, + @JsonProperty(value = "token", required = true) String serviceToken, + + // Game info + @JsonProperty("rhgamecd") String gameCode, + + // Device info + @JsonProperty("apinfo") String accessPointInfo, + + // Request-specific info + @JsonProperty(value = "action", required = true) String action, + @JsonProperty("gamecd") String dlcGameCode, + @JsonProperty("contents") String dlcName, // action=contents + @JsonProperty("attr1") String dlcType, // action=list + @JsonProperty("attr2") int dlcIndex, // action=list + @JsonProperty("offset") int offset, // ? + @JsonProperty("num") int num) {} // ? diff --git a/src/main/java/entralinked/network/http/nas/NasHandler.java b/src/main/java/entralinked/network/http/nas/NasHandler.java new file mode 100644 index 0000000..d557536 --- /dev/null +++ b/src/main/java/entralinked/network/http/nas/NasHandler.java @@ -0,0 +1,146 @@ +package entralinked.network.http.nas; + +import java.io.IOException; + +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule; + +import entralinked.Configuration; +import entralinked.Entralinked; +import entralinked.model.user.ServiceCredentials; +import entralinked.model.user.User; +import entralinked.model.user.UserManager; +import entralinked.network.http.HttpHandler; +import entralinked.network.http.HttpRequestHandler; +import entralinked.serialization.UrlEncodedFormFactory; +import io.javalin.Javalin; +import io.javalin.http.Context; + +/** + * HTTP handler for requests made to {@code nas.nintendowifi.net} + */ +public class NasHandler implements HttpHandler { + + private final ObjectMapper mapper = new ObjectMapper(new UrlEncodedFormFactory()).registerModule(new JavaTimeModule()); + private final Configuration configuration; + private final UserManager userManager; + + public NasHandler(Entralinked entralinked) { + this.configuration = entralinked.getConfiguration(); + this.userManager = entralinked.getUserManager(); + } + + @Override + public void addHandlers(Javalin javalin) { + javalin.post("/ac", this::handleNasRequest); + } + + /** + * POST base handler for {@code /ac} + * Deserializes requests and processes them accordingly. + */ + private void handleNasRequest(Context ctx) throws IOException { + // Deserialize body into a request object + NasRequest request = mapper.readValue(ctx.body(), NasRequest.class); + + // Determine handler function based on request action + HttpRequestHandler handler = switch(request.action()) { + case "login" -> this::handleLogin; + case "acctcreate" -> this::handleCreateAccount; + case "SVCLOC" -> this::handleRetrieveServiceLocation; + default -> throw new IllegalArgumentException("Invalid POST request action: " + request.action()); + }; + + // Process the request + handler.process(request, ctx); + } + + /** + * POST handler for {@code /ac action=login} + */ + private void handleLogin(NasRequest request, Context ctx) throws IOException { + // Make sure branch code is present + if(request.branchCode() == null) { + result(ctx, NasReturnCode.BAD_REQUEST); + return; + } + + String userId = request.userId(); + User user = userManager.authenticateUser(userId, request.password()); + + // Check if user exists + if(user == null) { + if(!configuration.allowWfcRegistrationThroughLogin()) { + result(ctx, NasReturnCode.USER_NOT_FOUND); + return; + } + + // Try to register, if the configuration allows it + if(!UserManager.isValidUserId(userId) || userManager.doesUserExist(userId) + || !userManager.registerUser(userId, request.password())) { + // Oh well, try again! + result(ctx, NasReturnCode.USER_NOT_FOUND); + return; + } + + // Should *never* return null in this location + user = userManager.authenticateUser(userId, request.password()); + } + + // Prepare GameSpy server credentials + ServiceCredentials credentials = userManager.createServiceSession(user, "gamespy", request.branchCode()); + result(ctx, new NasLoginResponse("gamespy.com", credentials.authToken(), credentials.challenge())); + } + + /** + * POST handler for {@code /ac action=acctcreate} + */ + private void handleCreateAccount(NasRequest request, Context ctx) throws IOException { + String userId = request.userId(); + + // Check if user ID is invalid or duplicate + if(!UserManager.isValidUserId(userId) || userManager.doesUserExist(userId)) { + result(ctx, NasReturnCode.USER_ALREADY_EXISTS); + return; + } + + // Try to register user + if(!userManager.registerUser(userId, request.password())) { + result(ctx, NasReturnCode.INTERNAL_SERVER_ERROR); + return; + } + + result(ctx, NasReturnCode.REGISTRATION_SUCCESS); + } + + /** + * POST handler for {@code /ac action=SVCLOC} + */ + private void handleRetrieveServiceLocation(NasRequest request, Context ctx) throws IOException { + // Determine service location from type + String service = switch(request.serviceType()) { + case "0000" -> "external"; // External game-specific service + case "9000" -> "dls1.nintendowifi.net"; // Download server + default -> throw new IllegalArgumentException("Invalid service type: " + request.serviceType()); + }; + + // Prepare user credentials + ServiceCredentials credentials = userManager.createServiceSession(null, service, null); + result(ctx, new NasServiceLocationResponse(true, service, credentials.authToken())); + } + + /** + * Sets context result to the specified response serialized as a URL encoded form. + */ + private void result(Context ctx, NasResponse response) throws IOException { + ctx.result(mapper.writeValueAsString(response)); + } + + /** + * Calls {@link #result(Context, NasResponse)} where {@code NasResponse} is a {@link NasStatusResponse} + * with the specified return code as its parameter. + */ + private void result(Context ctx, NasReturnCode returnCode) throws IOException { + result(ctx, new NasStatusResponse(returnCode)); + } +} diff --git a/src/main/java/entralinked/network/http/nas/NasLoginResponse.java b/src/main/java/entralinked/network/http/nas/NasLoginResponse.java new file mode 100644 index 0000000..42ff380 --- /dev/null +++ b/src/main/java/entralinked/network/http/nas/NasLoginResponse.java @@ -0,0 +1,9 @@ +package entralinked.network.http.nas; + +import com.fasterxml.jackson.annotation.JsonProperty; + +public record NasLoginResponse( + @JsonProperty("locator") String partner, + @JsonProperty("token") String partnerToken, + @JsonProperty("challenge") String partnerChallenge +) implements NasResponse {} diff --git a/src/main/java/entralinked/network/http/nas/NasRequest.java b/src/main/java/entralinked/network/http/nas/NasRequest.java new file mode 100644 index 0000000..40a9e50 --- /dev/null +++ b/src/main/java/entralinked/network/http/nas/NasRequest.java @@ -0,0 +1,33 @@ +package entralinked.network.http.nas; + +import java.time.LocalDateTime; +import java.time.MonthDay; + +import com.fasterxml.jackson.annotation.JsonFormat; +import com.fasterxml.jackson.annotation.JsonFormat.Shape; +import com.fasterxml.jackson.annotation.JsonProperty; + +public record NasRequest( + // Credentials + @JsonProperty(value = "userid", required = true) String userId, + @JsonProperty(value = "passwd", required = true) String password, + @JsonProperty(value = "macadr", required = true) String macAddress, + + // Game info + @JsonProperty("gamecd") String gameCode, + @JsonProperty("makercd") String makerCode, + @JsonProperty("unitcd") String unitCode, + @JsonProperty("sdkver") String sdkVersion, + @JsonProperty("lang") String language, + + // Device info + @JsonProperty("bssid") String bssid, + @JsonProperty("apinfo") String accessPointInfo, + @JsonProperty("devname") String deviceName, + @JsonProperty("devtime") @JsonFormat(shape = Shape.STRING, pattern = "yyMMddHHmmss") LocalDateTime deviceTime, + @JsonProperty("birth") @JsonFormat(shape = Shape.STRING, pattern = "MMdd") MonthDay birthDate, + + // Request-specific info + @JsonProperty(value = "action", required = true) String action, + @JsonProperty("gsbrcd") String branchCode, // action=login + @JsonProperty("svc") String serviceType) {} // action=SVCLOC diff --git a/src/main/java/entralinked/network/http/nas/NasResponse.java b/src/main/java/entralinked/network/http/nas/NasResponse.java new file mode 100644 index 0000000..7a57681 --- /dev/null +++ b/src/main/java/entralinked/network/http/nas/NasResponse.java @@ -0,0 +1,21 @@ +package entralinked.network.http.nas; + +import java.time.LocalDateTime; + +import com.fasterxml.jackson.annotation.JsonFormat; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.annotation.JsonFormat.Shape; + +public interface NasResponse { + + @JsonProperty("returncd") + default NasReturnCode returnCode() { + return NasReturnCode.SUCCESS; + } + + @JsonProperty("datetime") + @JsonFormat(shape = Shape.STRING, pattern = "yyMMddHHmmss") + default LocalDateTime dateTime() { + return LocalDateTime.now(); + } +} diff --git a/src/main/java/entralinked/network/http/nas/NasReturnCode.java b/src/main/java/entralinked/network/http/nas/NasReturnCode.java new file mode 100644 index 0000000..02c79da --- /dev/null +++ b/src/main/java/entralinked/network/http/nas/NasReturnCode.java @@ -0,0 +1,61 @@ +package entralinked.network.http.nas; + +import com.fasterxml.jackson.annotation.JsonValue; + +/** + * List of NAS return codes ({@code returncd}) + */ +public enum NasReturnCode { + + /** + * (Generic) Action was successful. + */ + SUCCESS(1), + + /** + * (Generic) An error occured on the server whilst processing a request. + */ + INTERNAL_SERVER_ERROR(100), + + /** + * (Registration) User account creation was successful. + * When this is sent, the user ID (WFC ID) will be stored on the client device. + */ + REGISTRATION_SUCCESS(2), + + /** + * (Generic) Request is missing data. + */ + BAD_REQUEST(102), + + /** + * (Registration) The client tried to register a user ID that already exists on the server. + * When this is sent, the client will generate a new user ID and try again. + */ + USER_ALREADY_EXISTS(104), + + /** + * (Login) The user data has been deleted from the server. + */ + USER_EXPIRED(108), + + /** + * (Login) The user with the client-specified user ID does not exist + */ + USER_NOT_FOUND(204); + + private final int clientId; + + private NasReturnCode(int clientId) { + this.clientId = clientId; + } + + @JsonValue + public String getFormattedClientId() { + return "%03d".formatted(clientId); + } + + public int getClientId() { + return clientId; + } +} diff --git a/src/main/java/entralinked/network/http/nas/NasServiceLocationResponse.java b/src/main/java/entralinked/network/http/nas/NasServiceLocationResponse.java new file mode 100644 index 0000000..a6e22f7 --- /dev/null +++ b/src/main/java/entralinked/network/http/nas/NasServiceLocationResponse.java @@ -0,0 +1,14 @@ +package entralinked.network.http.nas; + +import com.fasterxml.jackson.annotation.JsonProperty; + +public record NasServiceLocationResponse( + @JsonProperty("statusdata") char serviceStatus, // If 0x59 ('Y') sets flag to true. Game doesn't care when connecting to GL server, though. + @JsonProperty("svchost") String serviceHost, + @JsonProperty("servicetoken") String serviceToken +) implements NasResponse { + + public NasServiceLocationResponse(boolean serviceAvailable, String serviceHost, String serviceToken) { + this(serviceAvailable ? 'Y' : 'N', serviceHost, serviceToken); + } +} diff --git a/src/main/java/entralinked/network/http/nas/NasStatusResponse.java b/src/main/java/entralinked/network/http/nas/NasStatusResponse.java new file mode 100644 index 0000000..a0c67d8 --- /dev/null +++ b/src/main/java/entralinked/network/http/nas/NasStatusResponse.java @@ -0,0 +1,3 @@ +package entralinked.network.http.nas; + +public record NasStatusResponse(NasReturnCode returnCode) implements NasResponse {} diff --git a/src/main/java/entralinked/network/http/pgl/PglHandler.java b/src/main/java/entralinked/network/http/pgl/PglHandler.java new file mode 100644 index 0000000..d302fdf --- /dev/null +++ b/src/main/java/entralinked/network/http/pgl/PglHandler.java @@ -0,0 +1,382 @@ +package entralinked.network.http.pgl; + +import java.io.IOException; +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; + +import com.fasterxml.jackson.databind.ObjectMapper; + +import entralinked.Configuration; +import entralinked.Entralinked; +import entralinked.model.dlc.DlcList; +import entralinked.model.pkmn.PkmnInfo; +import entralinked.model.pkmn.PkmnInfoReader; +import entralinked.model.player.DreamEncounter; +import entralinked.model.player.DreamItem; +import entralinked.model.player.Player; +import entralinked.model.player.PlayerManager; +import entralinked.model.player.PlayerStatus; +import entralinked.model.user.ServiceSession; +import entralinked.model.user.UserManager; +import entralinked.network.http.HttpHandler; +import entralinked.network.http.HttpRequestHandler; +import entralinked.serialization.UrlEncodedFormFactory; +import entralinked.serialization.UrlEncodedFormParser; +import entralinked.utility.LEOutputStream; +import io.javalin.Javalin; +import io.javalin.http.Context; +import io.javalin.http.HttpStatus; +import io.javalin.security.BasicAuthCredentials; +import jakarta.servlet.ServletInputStream; + +/** + * HTTP handler for requests made to {@code en.pokemon-gl.com} + */ +public class PglHandler implements HttpHandler { + + private static final Logger logger = LogManager.getLogger(); + private static final String username = "pokemon"; + private static final String password = "2Phfv9MY"; // Best security in the world + private final ObjectMapper mapper = new ObjectMapper(new UrlEncodedFormFactory() + .disable(UrlEncodedFormParser.Feature.BASE64_DECODE_VALUES)); + private final Set sleepyList = new HashSet<>(); + private final Configuration configuration; + private final DlcList dlcList; + private final UserManager userManager; + private final PlayerManager playerManager; + + public PglHandler(Entralinked entralinked) { + this.configuration = entralinked.getConfiguration(); + this.dlcList = entralinked.getDlcList(); + this.userManager = entralinked.getUserManager(); + this.playerManager = entralinked.getPlayerManager(); + + // Add all species to the sleepy list + for(int i = 1; i <= 649; i++) { + sleepyList.add(i); + } + } + + @Override + public void addHandlers(Javalin javalin) { + javalin.before("/dsio/gw", this::authorizePglRequest); + javalin.get("/dsio/gw", this::handlePglGetRequest); + javalin.post("/dsio/gw", this::handlePglPostRequest); + } + + /** + * BEFORE handler for {@code /dsio/gw} that serves to deserialize and authenticate the request. + * The deserialized request will be stored in a context attribute named {@code request} and may be retrieved + * by subsequent handlers. + */ + private void authorizePglRequest(Context ctx) throws IOException { + // Verify the authorization header credentials + BasicAuthCredentials credentials = ctx.basicAuthCredentials(); + + if(credentials == null || + !username.equals(credentials.getUsername()) || !password.equals(credentials.getPassword())) { + ctx.status(HttpStatus.UNAUTHORIZED); + clearTasks(ctx); + return; + } + + // Deserialize the request + PglRequest request = mapper.readValue(ctx.queryString(), PglRequest.class); + + // Check game version + if(request.gameVersion() == null) { + ctx.status(HttpStatus.BAD_REQUEST); + clearTasks(ctx); + return; + } + + // Verify the service session token + ServiceSession session = userManager.getServiceSession(request.token(), "external"); + + if(session == null) { + ctx.status(HttpStatus.UNAUTHORIZED); + clearTasks(ctx); + return; + } + + // Store request object for subsequent handlers + ctx.attribute("request", request); + } + + /** + * GET base handler for {@code /dsio/gw} + */ + private void handlePglGetRequest(Context ctx) throws IOException { + PglRequest request = ctx.attribute("request"); + + // Determine request handler function based on type + HttpRequestHandler handler = switch(request.type()) { + case "sleepily.bitlist" -> this::handleGetSleepyList; + case "account.playstatus" -> this::handleGetAccountStatus; + case "savedata.download" -> this::handleDownloadSaveData; + default -> throw new IllegalArgumentException("Invalid GET request type: " + request.type()); + }; + + // Handle the request + handler.process(request, ctx); + } + + /** + * GET handler for {@code /dsio/gw?p=sleepily.bitlist} + */ + private void handleGetSleepyList(PglRequest request, Context ctx) throws IOException { + LEOutputStream outputStream = new LEOutputStream(ctx.outputStream()); + + // Check if player exists + if(!playerManager.doesPlayerExist(request.gameSyncId())) { + writeStatusCode(outputStream, 1); // Unauthorized + return; + } + + // Create bitlist + byte[] bitlist = new byte[128]; // TODO pool? maybe just cache + + for(int sleepy : sleepyList) { + // 8 Pokémon (bits) in 1 byte + int byteOffset = sleepy / 8; + int bitOffset = sleepy % 8; + + // Set the bit to 1! + bitlist[byteOffset] |= 1 << bitOffset; + } + + // Send bitlist + writeStatusCode(outputStream, 0); + outputStream.write(bitlist); + } + + /** + * GET handler for {@code /dsio/gw?p=account.playstatus} + * + * Black 2 - {@code sub_21B74B4} (overlay #199) + */ + private void handleGetAccountStatus(PglRequest request, Context ctx) throws IOException { + LEOutputStream outputStream = new LEOutputStream(ctx.outputStream()); + Player player = playerManager.getPlayer(request.gameSyncId()); + + // Request account creation if one doesn't exist yet + if(player == null) { + writeStatusCode(outputStream, 8); // 5 is also handled separately, but doesn't seem to do anything unique + return; + } + + writeStatusCode(outputStream, 0); + outputStream.writeShort(player.getStatus().ordinal()); + } + + /** + * GET handler for {@code /dsio/gw?p=savedata.download} + * + * Black 2 - {@code sub_21B6C9C} (overlay #199) + */ + private void handleDownloadSaveData(PglRequest request, Context ctx) throws IOException { + LEOutputStream outputStream = new LEOutputStream(ctx.outputStream()); + Player player = playerManager.getPlayer(request.gameSyncId()); + + // Check if player exists + if(player == null) { + writeStatusCode(outputStream, 1); // Unauthorized + return; + } + + // Write status code + writeStatusCode(outputStream, 0); + + // Allow it to wake up anyway, maybe the poor sap is stuck.. + // Just don't send any other data. + if(player.getStatus() == PlayerStatus.AWAKE) { + logger.info("Player {} is downloading save data, but is already awake!", player.getGameSyncId()); + return; + } + + List encounters = player.getEncounters(); + List items = player.getItems(); + + // When waking up a Pokémon, these 4 bytes are written to 0x1D304 in the save file. + // If the bytes in the game's save file match the new bytes, they will be set to 0x00000000 + // and no content will be downloaded. + outputStream.writeInt((int)(Math.random() * Integer.MAX_VALUE)); + + // Write encounter data (max 10) + for(DreamEncounter encounter : encounters) { + outputStream.writeShort(encounter.species()); + outputStream.writeShort(encounter.move()); + outputStream.write(encounter.form()); + outputStream.write(0); // unknown + outputStream.write(encounter.animation().ordinal()); + outputStream.write(0); // unknown + } + + // Write encounter padding + outputStream.writeBytes(0, (10 - encounters.size()) * 8); + + // Write misc stuff and DLC information + outputStream.writeShort(player.getLevelsGained()); + outputStream.write(0); // Unknown + outputStream.write(dlcList.getDlcIndex(player.getMusical())); + outputStream.write(dlcList.getDlcIndex(player.getCGearSkin())); + outputStream.write(dlcList.getDlcIndex(player.getDexSkin())); + outputStream.write(0); // Unknown + outputStream.write(0); // Must be zero? + + // Write item IDs + for(DreamItem item : items) { + outputStream.writeShort(item.id()); + } + + // Write item ID padding + outputStream.writeBytes(0, (20 - items.size()) * 2); + + // Write item quantities + for(DreamItem item : items) { + outputStream.write(item.quantity()); // Hard caps at 20? + } + + // Write quantity padding + outputStream.writeBytes(0, (20 - items.size())); + } + + /** + * POST base handler for {@code /dsio/gw} + */ + private void handlePglPostRequest(Context ctx) throws IOException { + // Retrieve context attributes + PglRequest request = ctx.attribute("request"); + + // Determine handler function based on request type + HttpRequestHandler handler = switch(request.type()) { + case "savedata.upload" -> this::handleUploadSaveData; + case "savedata.download.finish" -> this::handleDownloadSaveDataFinish; + case "account.create.upload" -> this::handleCreateAccount; + default -> throw new IllegalArgumentException("Invalid POST request type: " + request.type()); + }; + + handler.process(request, ctx); + } + + /** + * POST handler for {@code /dsio/gw?p=savedata.download.finish} + */ + private void handleDownloadSaveDataFinish(PglRequest request, Context ctx) throws IOException { + LEOutputStream outputStream = new LEOutputStream(ctx.outputStream()); + Player player = playerManager.getPlayer(request.gameSyncId()); + + // Check if player exists + if(player == null) { + writeStatusCode(outputStream, 1); // Unauthorized + return; + } + + // Reset player dream information if configured to do so + if(configuration.clearPlayerDreamInfoOnWake()) { + player.resetDreamInfo(); + + // Try to save player data + if(!playerManager.savePlayer(player)) { + logger.warn("Save data failure for player {}", player.getGameSyncId()); + ctx.status(HttpStatus.INTERNAL_SERVER_ERROR); + } + } + + // Write status code + writeStatusCode(outputStream, 0); + } + + /** + * POST handler for {@code /dsio/gw?p=savedata.upload} + */ + private void handleUploadSaveData(PglRequest request, Context ctx) throws IOException { + // Read save data + ServletInputStream inputStream = ctx.req().getInputStream(); + inputStream.skip(0x1D300); // Skip to dream world data + inputStream.skip(8); // Skip to Pokémon data + PkmnInfo info = PkmnInfoReader.readPokeInfo(inputStream); + + // Don't care about anything else -- continue reading bytes until we're done. + while(!inputStream.isFinished()) { + inputStream.read(); + } + + // Prepare response + LEOutputStream outputStream = new LEOutputStream(ctx.outputStream()); + Player player = playerManager.getPlayer(request.gameSyncId()); + + // Check if player exists + if(player == null) { + writeStatusCode(outputStream, 1); // Unauthorized + return; + } + + // Check if player doesn't already have a Pokémon tucked in + if(player.getStatus() != PlayerStatus.AWAKE) { + logger.warn("Player {} tried to upload save data while already asleep", player.getGameSyncId()); + + // Return error if not allowed + if(!configuration.allowOverwritingPlayerDreamInfo()) { + writeStatusCode(outputStream, 4); // Already dreaming + return; + } + } + + // Update and save player information + player.setStatus(PlayerStatus.SLEEPING); + player.setDreamerInfo(info); + + if(!playerManager.savePlayer(player)) { + logger.warn("Save data failure for player {}", player.getGameSyncId()); + ctx.status(HttpStatus.INTERNAL_SERVER_ERROR); + return; + } + + // Send status code + writeStatusCode(outputStream, 0); + } + + /** + * POST handler for {@code /dsio/gw?p=account.create.upload} + */ + private void handleCreateAccount(PglRequest request, Context ctx) throws IOException { + // It sends the entire save file, but we just skip through it because we don't need anything from it here + ServletInputStream inputStream = ctx.req().getInputStream(); + + while(!inputStream.isFinished()) { + inputStream.read(); + } + + // Prepare response + LEOutputStream outputStream = new LEOutputStream(ctx.outputStream()); + String gameSyncId = request.gameSyncId(); + + // Check if player doesn't exist already + if(playerManager.doesPlayerExist(gameSyncId)) { + writeStatusCode(outputStream, 2); // Duplicate Game Sync ID + return; + } + + // Try to register player + if(playerManager.registerPlayer(gameSyncId, request.gameVersion()) == null) { + writeStatusCode(outputStream, 3); // Registration error + return; + } + + // Write status code + writeStatusCode(outputStream, 0); + } + + /** + * Writes the 4-byte status code and 124 empty bytes to the output stream. + */ + private void writeStatusCode(LEOutputStream outputStream, int status) throws IOException { + outputStream.writeInt(status); + outputStream.writeBytes(0, 124); + } +} diff --git a/src/main/java/entralinked/network/http/pgl/PglRequest.java b/src/main/java/entralinked/network/http/pgl/PglRequest.java new file mode 100644 index 0000000..c7ff9ee --- /dev/null +++ b/src/main/java/entralinked/network/http/pgl/PglRequest.java @@ -0,0 +1,20 @@ +package entralinked.network.http.pgl; + +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; + +import entralinked.GameVersion; +import entralinked.serialization.GsidDeserializer; + +public record PglRequest( + @JsonProperty(value = "gsid", required = true) @JsonDeserialize(using = GsidDeserializer.class) String gameSyncId, + @JsonProperty(value = "p", required = true) String type, + @JsonProperty(value = "rom", required = true) int romCode, + @JsonProperty(value = "langcode", required = true) int languageCode, + @JsonProperty(value = "dreamw", required = true) int dreamWorld, // Always 1, but what is it for? + @JsonProperty(value = "tok", required = true) String token) { + + public GameVersion gameVersion() { + return GameVersion.lookup(romCode(), languageCode()); + } +} diff --git a/src/main/java/entralinked/serialization/GameSpyMessageFactory.java b/src/main/java/entralinked/serialization/GameSpyMessageFactory.java new file mode 100644 index 0000000..7ef1965 --- /dev/null +++ b/src/main/java/entralinked/serialization/GameSpyMessageFactory.java @@ -0,0 +1,51 @@ +package entralinked.serialization; + +import java.io.ByteArrayInputStream; +import java.io.CharArrayReader; +import java.io.DataInput; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.io.OutputStream; +import java.io.OutputStreamWriter; +import java.io.Writer; +import java.nio.charset.StandardCharsets; + +import com.fasterxml.jackson.core.JsonFactory; +import com.fasterxml.jackson.core.JsonParseException; +import com.fasterxml.jackson.core.io.IOContext; + +public class GameSpyMessageFactory extends JsonFactory { + + private static final long serialVersionUID = -8313019676052328025L; + + @Override + protected GameSpyMessageGenerator _createGenerator(Writer writer, IOContext context) throws IOException { + return new GameSpyMessageGenerator(_generatorFeatures, _objectCodec, writer); + } + + @Override + protected GameSpyMessageGenerator _createUTF8Generator(OutputStream outputStream, IOContext context) throws IOException { + return _createGenerator(new OutputStreamWriter(outputStream, StandardCharsets.UTF_8), context); + } + + @Override + protected GameSpyMessageParser _createParser(InputStream inputStream, IOContext context) throws IOException { + return new GameSpyMessageParser(_parserFeatures, new InputStreamReader(inputStream)); + } + + @Override + protected GameSpyMessageParser _createParser(byte[] data, int offset, int length, IOContext context) throws IOException, JsonParseException { + return new GameSpyMessageParser(_parserFeatures, new InputStreamReader(new ByteArrayInputStream(data, offset, length))); + } + + @Override + protected GameSpyMessageParser _createParser(char[] data, int offset, int length, IOContext context, boolean recyclable) throws IOException { + return new GameSpyMessageParser(_parserFeatures, new CharArrayReader(data, offset, length)); + } + + @Override + protected GameSpyMessageParser _createParser(DataInput input, IOContext context) throws IOException { + throw new UnsupportedOperationException(); + } +} \ No newline at end of file diff --git a/src/main/java/entralinked/serialization/GameSpyMessageGenerator.java b/src/main/java/entralinked/serialization/GameSpyMessageGenerator.java new file mode 100644 index 0000000..b37218c --- /dev/null +++ b/src/main/java/entralinked/serialization/GameSpyMessageGenerator.java @@ -0,0 +1,47 @@ +package entralinked.serialization; + +import java.io.IOException; +import java.io.Writer; + +import com.fasterxml.jackson.core.ObjectCodec; + +public class GameSpyMessageGenerator extends SimpleGeneratorBase { + + protected final Writer writer; + + public GameSpyMessageGenerator(int features, ObjectCodec codec, Writer writer) { + super(features, codec); + this.writer = writer; + } + + @Override + public void writeEndObject() throws IOException { + _writeContext = _writeContext.getParent(); + } + + @Override + public void writeFieldName(String name) throws IOException { + writer.write('\\'); + writer.write(name); + writer.write('\\'); + } + + @Override + public void writeString(String text) throws IOException { + writer.write(text); + } + + @Override + public void flush() throws IOException { + writer.flush(); + } + + @Override + public void close() throws IOException { + try { + writer.close(); + } finally { + super.close(); + } + } +} \ No newline at end of file diff --git a/src/main/java/entralinked/serialization/GameSpyMessageParser.java b/src/main/java/entralinked/serialization/GameSpyMessageParser.java new file mode 100644 index 0000000..fbc744c --- /dev/null +++ b/src/main/java/entralinked/serialization/GameSpyMessageParser.java @@ -0,0 +1,105 @@ +package entralinked.serialization; + +import java.io.IOException; +import java.io.Reader; + +import com.fasterxml.jackson.core.JsonToken; + +public class GameSpyMessageParser extends SimpleParserBase { + + private final Reader reader; + private String parsedString; + private boolean endOfInput; + private boolean closed; + + public GameSpyMessageParser(int features, Reader reader) { + super(features); + this.reader = reader; + } + + @Override + public JsonToken nextToken() throws IOException { + if(endOfInput) { + return _currToken = null; // Return null if there is no content left to read + } + + if(_currToken == null) { + // TODO + skipBackSlash(); + } + + int i = parseString(); + + if(_currToken == JsonToken.VALUE_STRING || _currToken == null) { + // Parse field name + _currToken = JsonToken.FIELD_NAME; + + if(parsedString.isEmpty()) { + _reportUnexpectedChar(i, "expected field name"); + } + + context.setCurrentName(parsedString); + + if(i != '\\') { + _reportUnexpectedChar(i, "expected '\\' to close field name"); + } + + } else if(_currToken == JsonToken.FIELD_NAME) { + // Parse value string + _currToken = JsonToken.VALUE_STRING; + context.setCurrentValue(parsedString); + + if(i != '\\') { + if(i != -1) { + _reportUnexpectedChar(i, "expected '\\' to open field name"); + } + + endOfInput = true; // We have reached the end, so let it be known! + } + } + + return _currToken; + } + + private int skipBackSlash() throws IOException { + int i = reader.read(); + + if(i != '\\') { + _reportUnexpectedChar(i, "expected '\\' to open field name"); + } + + return i; + } + + private int parseString() throws IOException { + StringBuilder builder = new StringBuilder(); + int i = -1; + + while((i = reader.read()) != -1) { + if(i == '\\') { + break; + } + + builder.append((char)i); + } + + parsedString = builder.toString(); + return i; + } + + @Override + public void close() throws IOException { + if(!closed) { + try { + reader.close(); + } finally { + closed = true; + } + } + } + + @Override + public boolean isClosed() { + return closed; + } +} diff --git a/src/main/java/entralinked/serialization/GsidDeserializer.java b/src/main/java/entralinked/serialization/GsidDeserializer.java new file mode 100644 index 0000000..8a1c296 --- /dev/null +++ b/src/main/java/entralinked/serialization/GsidDeserializer.java @@ -0,0 +1,36 @@ +package entralinked.serialization; + +import java.io.IOException; + +import com.fasterxml.jackson.core.JsonParser; +import com.fasterxml.jackson.databind.DeserializationContext; +import com.fasterxml.jackson.databind.deser.std.StdDeserializer; + +import entralinked.utility.GsidUtility; + +/** + * Deserializer that stringifies integers using {@link GsidUtility} + */ +public class GsidDeserializer extends StdDeserializer { + + private static final long serialVersionUID = -2973925169701434892L; + + public GsidDeserializer() { + this(String.class); + } + + protected GsidDeserializer(Class type) { + super(type); + } + + @Override + public String deserialize(JsonParser parser, DeserializationContext context) throws IOException { + int gsid = parser.getIntValue(); + + if(gsid < 0) { + throw new IOException("Game Sync ID cannot be a negative number."); + } + + return GsidUtility.stringifyGameSyncId(gsid); + } +} diff --git a/src/main/java/entralinked/serialization/SimpleGeneratorBase.java b/src/main/java/entralinked/serialization/SimpleGeneratorBase.java new file mode 100644 index 0000000..b27a2b9 --- /dev/null +++ b/src/main/java/entralinked/serialization/SimpleGeneratorBase.java @@ -0,0 +1,131 @@ +package entralinked.serialization; + +import java.io.IOException; +import java.math.BigDecimal; +import java.math.BigInteger; + +import com.fasterxml.jackson.core.Base64Variant; +import com.fasterxml.jackson.core.ObjectCodec; +import com.fasterxml.jackson.core.base.GeneratorBase; + +/** + * Generator base for lazy implementations that simply write everything as strings. + * Keeps subclasses clean. + */ +public abstract class SimpleGeneratorBase extends GeneratorBase { + + protected SimpleGeneratorBase(int features, ObjectCodec codec) { + super(features, codec); + } + + @Override + protected void _releaseBuffers() {} + + @Override + protected void _verifyValueWrite(String typeMsg) throws IOException {} + + @Override + public void writeStartArray() throws IOException { + throw new UnsupportedOperationException("this format does not support arrays"); + } + + @Override + public void writeEndArray() throws IOException { + throw new UnsupportedOperationException("this format does not support arrays"); + } + + @Override + public void writeStartObject() throws IOException { + if(!_writeContext.inRoot()) { + throw new UnsupportedOperationException("this format does not support nested objects"); + } + + // Quirk + _writeContext = _writeContext.createChildObjectContext(); + } + + @Override + public void writeString(char[] buffer, int offset, int length) throws IOException { + writeString(new String(buffer, offset, length)); + } + + @Override + public void writeRawUTF8String(byte[] buffer, int offset, int length) throws IOException { + throw new UnsupportedOperationException(); + } + + @Override + public void writeUTF8String(byte[] buffer, int offset, int length) throws IOException { + throw new UnsupportedOperationException(); + } + + @Override + public void writeRaw(String text) throws IOException { + throw new UnsupportedOperationException(); + } + + @Override + public void writeRaw(String text, int offset, int len) throws IOException { + throw new UnsupportedOperationException(); + } + + @Override + public void writeRaw(char[] text, int offset, int length) throws IOException { + throw new UnsupportedOperationException(); + } + + @Override + public void writeRaw(char c) throws IOException { + writeString(String.valueOf(c)); + } + + @Override + public void writeBinary(Base64Variant variant, byte[] data, int offset, int length) throws IOException { + throw new UnsupportedOperationException(); + } + + @Override + public void writeNumber(int value) throws IOException { + writeString(String.valueOf(value)); + } + + @Override + public void writeNumber(long value) throws IOException { + writeString(String.valueOf(value)); + } + + @Override + public void writeNumber(BigInteger value) throws IOException { + writeString(value.toString()); + } + + @Override + public void writeNumber(double value) throws IOException { + writeString(String.valueOf(value)); + } + + @Override + public void writeNumber(float value) throws IOException { + writeString(String.valueOf(value)); + } + + @Override + public void writeNumber(BigDecimal value) throws IOException { + writeString(value.toPlainString()); + } + + @Override + public void writeNumber(String encodedValue) throws IOException { + writeString(encodedValue); + } + + @Override + public void writeBoolean(boolean state) throws IOException { + writeString(String.valueOf(state)); + } + + @Override + public void writeNull() throws IOException { + writeString("null"); + } +} diff --git a/src/main/java/entralinked/serialization/SimpleParserBase.java b/src/main/java/entralinked/serialization/SimpleParserBase.java new file mode 100644 index 0000000..6e6709e --- /dev/null +++ b/src/main/java/entralinked/serialization/SimpleParserBase.java @@ -0,0 +1,158 @@ +package entralinked.serialization; + +import java.io.IOException; +import java.math.BigDecimal; +import java.math.BigInteger; + +import com.fasterxml.jackson.core.Base64Variant; +import com.fasterxml.jackson.core.JsonLocation; +import com.fasterxml.jackson.core.JsonParseException; +import com.fasterxml.jackson.core.JsonParser; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.core.JsonStreamContext; +import com.fasterxml.jackson.core.ObjectCodec; +import com.fasterxml.jackson.core.Version; +import com.fasterxml.jackson.core.base.ParserMinimalBase; +import com.fasterxml.jackson.core.json.DupDetector; +import com.fasterxml.jackson.core.json.JsonReadContext; + +/** + * Parser base for lazy implementations that simply parse everything as {@code VALUE_STRING}. + * Keeps subclasses clean. + */ +public abstract class SimpleParserBase extends ParserMinimalBase { + + protected JsonReadContext context; + protected ObjectCodec codec; + + public SimpleParserBase(int features) { + super(features); + DupDetector detector = isEnabled(JsonParser.Feature.STRICT_DUPLICATE_DETECTION) ? DupDetector.rootDetector(this) : null; + this.context = JsonReadContext.createRootContext(detector); + } + + @Override + protected void _handleEOF() throws JsonParseException {} + + @Override + public Number getNumberValue() throws IOException { + throw new UnsupportedOperationException(); + } + + @Override + public NumberType getNumberType() throws IOException { + throw new UnsupportedOperationException(); + } + + @Override + public int getIntValue() throws IOException { + return Integer.parseInt(getStringValue()); + } + + @Override + public long getLongValue() throws IOException { + return Long.parseLong(getStringValue()); + } + + @Override + public BigInteger getBigIntegerValue() throws IOException { + return new BigInteger(getStringValue()); + } + + @Override + public float getFloatValue() throws IOException { + return Float.parseFloat(getStringValue()); + } + + @Override + public double getDoubleValue() throws IOException { + return Double.parseDouble(getStringValue()); + } + + @Override + public BigDecimal getDecimalValue() throws IOException { + return new BigDecimal(getStringValue()); + } + + @Override + public byte[] getBinaryValue(Base64Variant variant) throws IOException { + throw new UnsupportedOperationException(); + } + + @Override + public JsonStreamContext getParsingContext() { + return context; + } + + @Override + public void overrideCurrentName(String name) { + try { + context.setCurrentName(name); + } catch(JsonProcessingException e) { + throw new IllegalStateException(e); + } + } + + @Override + public String getCurrentName() throws IOException { + return context.getCurrentName(); + } + + @Override + public String getText() throws IOException { + switch(_currToken) { + case FIELD_NAME: return context.getCurrentName(); + case VALUE_STRING: return context.getCurrentValue().toString(); + default: throw new IllegalStateException(); // Should not happen + } + } + + @Override + public char[] getTextCharacters() throws IOException { + return getText().toCharArray(); + } + + @Override + public boolean hasTextCharacters() { + return true; + } + + @Override + public int getTextLength() throws IOException { + return getText().length(); + } + + @Override + public int getTextOffset() throws IOException { + return 0; + } + + @Override + public void setCodec(ObjectCodec codec) { + this.codec = codec; + } + + @Override + public ObjectCodec getCodec() { + return codec; + } + + @Override + public JsonLocation getCurrentLocation() { + return JsonLocation.NA; + } + + @Override + public JsonLocation getTokenLocation() { + return JsonLocation.NA; + } + + @Override + public Version version() { + return null; + } + + public String getStringValue() { + return getCurrentValue() == null ? "null" : getCurrentValue().toString(); + } +} diff --git a/src/main/java/entralinked/serialization/UrlEncodedFormFactory.java b/src/main/java/entralinked/serialization/UrlEncodedFormFactory.java new file mode 100644 index 0000000..9ad1d78 --- /dev/null +++ b/src/main/java/entralinked/serialization/UrlEncodedFormFactory.java @@ -0,0 +1,101 @@ +package entralinked.serialization; + +import java.io.ByteArrayInputStream; +import java.io.CharArrayReader; +import java.io.DataInput; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.io.OutputStream; +import java.io.OutputStreamWriter; +import java.io.Writer; +import java.nio.charset.StandardCharsets; + +import com.fasterxml.jackson.core.JsonFactory; +import com.fasterxml.jackson.core.JsonParseException; +import com.fasterxml.jackson.core.io.IOContext; + +public class UrlEncodedFormFactory extends JsonFactory { + + private static final long serialVersionUID = -8313019676052328025L; + public static final int DEFAULT_FORMAT_GENERATOR_FEATURES = UrlEncodedFormGenerator.Feature.getDefaults(); + public static final int DEFAULT_FORMAT_PARSER_FEATURES = UrlEncodedFormParser.Feature.getDefaults(); + protected int formatGeneratorFeatures = DEFAULT_FORMAT_GENERATOR_FEATURES; + protected int formatParserFeatures = DEFAULT_FORMAT_PARSER_FEATURES; + + @Override + protected UrlEncodedFormGenerator _createGenerator(Writer writer, IOContext context) throws IOException { + return new UrlEncodedFormGenerator(_generatorFeatures, formatGeneratorFeatures, _objectCodec, writer); + } + + @Override + protected UrlEncodedFormGenerator _createUTF8Generator(OutputStream outputStream, IOContext context) throws IOException { + return _createGenerator(new OutputStreamWriter(outputStream, StandardCharsets.UTF_8), context); + } + + @Override + protected UrlEncodedFormParser _createParser(InputStream inputStream, IOContext context) throws IOException { + return new UrlEncodedFormParser(_parserFeatures, formatParserFeatures, new InputStreamReader(inputStream)); + } + + @Override + protected UrlEncodedFormParser _createParser(byte[] data, int offset, int length, IOContext context) throws IOException, JsonParseException { + return new UrlEncodedFormParser(_parserFeatures, formatParserFeatures, new InputStreamReader(new ByteArrayInputStream(data, offset, length))); + } + + @Override + protected UrlEncodedFormParser _createParser(char[] data, int offset, int length, IOContext context, boolean recyclable) throws IOException { + return new UrlEncodedFormParser(_parserFeatures, formatParserFeatures, new CharArrayReader(data, offset, length)); + } + + @Override + protected UrlEncodedFormParser _createParser(DataInput input, IOContext context) throws IOException { + throw new UnsupportedOperationException(); + } + + @Override + public int getFormatGeneratorFeatures() { + return formatGeneratorFeatures; + } + + @Override + public int getFormatParserFeatures() { + return formatParserFeatures; + } + + public UrlEncodedFormFactory configure(UrlEncodedFormGenerator.Feature feature, boolean state) { + return state ? enable(feature) : disable(feature); + } + + public UrlEncodedFormFactory enable(UrlEncodedFormGenerator.Feature feature) { + formatGeneratorFeatures |= feature.getMask(); + return this; + } + + public UrlEncodedFormFactory disable(UrlEncodedFormGenerator.Feature feature) { + formatGeneratorFeatures &= ~feature.getMask(); + return this; + } + + public final boolean isEnabled(UrlEncodedFormGenerator.Feature feature) { + return feature.enabledIn(formatGeneratorFeatures); + } + + public UrlEncodedFormFactory configure(UrlEncodedFormParser.Feature feature, boolean state) { + return state ? enable(feature) : disable(feature); + } + + public UrlEncodedFormFactory enable(UrlEncodedFormParser.Feature feature) { + formatParserFeatures |= feature.getMask(); + return this; + } + + public UrlEncodedFormFactory disable(UrlEncodedFormParser.Feature feature) { + formatParserFeatures &= ~feature.getMask(); + return this; + } + + public final boolean isEnabled(UrlEncodedFormParser.Feature feature) { + return feature.enabledIn(formatParserFeatures); + } +} diff --git a/src/main/java/entralinked/serialization/UrlEncodedFormGenerator.java b/src/main/java/entralinked/serialization/UrlEncodedFormGenerator.java new file mode 100644 index 0000000..65041cb --- /dev/null +++ b/src/main/java/entralinked/serialization/UrlEncodedFormGenerator.java @@ -0,0 +1,130 @@ +package entralinked.serialization; + +import java.io.IOException; +import java.io.Writer; +import java.net.URLEncoder; +import java.nio.charset.StandardCharsets; +import java.util.Base64; + +import com.fasterxml.jackson.core.FormatFeature; +import com.fasterxml.jackson.core.ObjectCodec; +import com.fasterxml.jackson.core.json.JsonWriteContext; + +/** + * Generator for URL encoded forms. + * I don't doubt its imperfection, but it does what it needs to do. + */ +public class UrlEncodedFormGenerator extends SimpleGeneratorBase { + + /** + * Generator format features for form-data + */ + public enum Feature implements FormatFeature { + + /** + * Whether values should be encoded as base64 + */ + BASE64_ENCODE_VALUES(true); + + private final boolean defaultState; + private final int mask; + + private Feature(boolean defaultState) { + this.defaultState = defaultState; + this.mask = 1 << ordinal(); + } + + public static int getDefaults() { + int flags = 0; + + for(Feature feature : values()) { + if(feature.enabledByDefault()) { + flags |= feature.getMask(); + } + } + + return flags; + } + + @Override + public boolean enabledByDefault() { + return defaultState; + } + + @Override + public int getMask() { + return mask; + } + + @Override + public boolean enabledIn(int flags) { + return (flags & mask) != 0; + } + + } + + protected final int formatFeatures; + protected final Writer writer; + + protected UrlEncodedFormGenerator(int features, int formatFeatures, ObjectCodec codec, Writer writer) { + super(features, codec); + this.formatFeatures = formatFeatures; + this.writer = writer; + } + + @Override + public void writeEndObject() throws IOException { + _writeContext = _writeContext.getParent(); + } + + @Override + public void writeFieldName(String name) throws IOException { + int status = _writeContext.writeFieldName(name); + + // Check if an entry separator should be appended before the field name + if(status == JsonWriteContext.STATUS_OK_AFTER_COMMA) { + writer.write('&'); + } + + writer.write(URLEncoder.encode(name, StandardCharsets.UTF_8)); + } + + @Override + public void writeString(String text) throws IOException { + int status = _writeContext.writeValue(); + + // Check if a key/value separator should be appended before the string value (should always be the case) + if(status == JsonWriteContext.STATUS_OK_AFTER_COLON) { + writer.write('='); + } + + String value = text; + + // Encode value as base64 if feature is enabled + if(Feature.BASE64_ENCODE_VALUES.enabledIn(formatFeatures)) { + value = Base64.getEncoder().encodeToString(text.getBytes(StandardCharsets.ISO_8859_1)) + .replace('=', '*').replace('+', '.').replace('/', '-'); + } + + writer.write(URLEncoder.encode(value, StandardCharsets.UTF_8)); + } + + @Override + public int getFormatFeatures() { + return formatFeatures; + } + + @Override + public void flush() throws IOException { + writer.flush(); + } + + @Override + public void close() throws IOException { + try { + writer.close(); + } finally { + super.close(); + } + } +} diff --git a/src/main/java/entralinked/serialization/UrlEncodedFormParser.java b/src/main/java/entralinked/serialization/UrlEncodedFormParser.java new file mode 100644 index 0000000..ae13258 --- /dev/null +++ b/src/main/java/entralinked/serialization/UrlEncodedFormParser.java @@ -0,0 +1,164 @@ +package entralinked.serialization; + +import java.io.IOException; +import java.io.Reader; +import java.net.URLDecoder; +import java.nio.charset.StandardCharsets; +import java.util.Base64; + +import com.fasterxml.jackson.core.FormatFeature; +import com.fasterxml.jackson.core.JsonToken; + +/** + * Parser for URL encoded forms. + * It's not perfect, but it works well enough for the intended purpose. + */ +public class UrlEncodedFormParser extends SimpleParserBase { + + /** + * Parser format features for form-data + */ + public enum Feature implements FormatFeature { + + /** + * Whether values are encoded in base64 and should be decoded as such + */ + BASE64_DECODE_VALUES(true); + + private final boolean defaultState; + private final int mask; + + private Feature(boolean defaultState) { + this.defaultState = defaultState; + this.mask = 1 << ordinal(); + } + + public static int getDefaults() { + int flags = 0; + + for(Feature feature : values()) { + if(feature.enabledByDefault()) { + flags |= feature.getMask(); + } + } + + return flags; + } + + @Override + public boolean enabledByDefault() { + return defaultState; + } + + @Override + public int getMask() { + return mask; + } + + @Override + public boolean enabledIn(int flags) { + return (flags & mask) != 0; + } + + } + + private final Reader reader; + private final int formatFeatures; + private String parsedString; + private boolean closed; + private boolean endOfInput; + + public UrlEncodedFormParser(int features, int formatFeatures, Reader reader) { + super(features); + this.formatFeatures = formatFeatures; + this.reader = reader; + } + + @Override + public JsonToken nextToken() throws IOException { + if(endOfInput) { + return _currToken = null; // Return null if there is no content left to read + } + + int i = parseStringAndSkipSeparator(); + + if(_currToken == JsonToken.VALUE_STRING || _currToken == null) { + // Parse field name + _currToken = JsonToken.FIELD_NAME; + + if(parsedString.isEmpty()) { + _reportUnexpectedChar(i, "expected field name"); + } + + context.setCurrentName(URLDecoder.decode(parsedString, StandardCharsets.UTF_8)); + + if(i != '=') { + _reportUnexpectedChar(i, "expected '=' to mark end of key and start of value"); + } + + } else if(_currToken == JsonToken.FIELD_NAME) { + // Parse value string + _currToken = JsonToken.VALUE_STRING; + + // Decode base64 if feature is enabled + if(Feature.BASE64_DECODE_VALUES.enabledIn(formatFeatures)) { + parsedString = new String(Base64.getDecoder().decode( + parsedString.replace('*', '=').replace('.', '+').replace('-', '/')), StandardCharsets.ISO_8859_1); + } + + context.setCurrentValue(URLDecoder.decode(parsedString, StandardCharsets.UTF_8)); + + if(i != '&') { + if(i != -1) { + _reportUnexpectedChar(i, "expected '&' to mark end of value and start of new key"); + } + + endOfInput = true; // We have reached the end, so let it be known! + } + } + + return _currToken; + } + + /** + * Parses the next string and stores the output in {@link #parsedString}. + * The returned value is the first character read after the parsed string, or -1 if end-of-input. + */ + private int parseStringAndSkipSeparator() throws IOException { + StringBuilder builder = new StringBuilder(); + int i = -1; + + while((i = reader.read()) != -1) { + if(i == '&' || i == '=') { + break; + } + + builder.append((char)i); + } + + parsedString = builder.toString(); + return i; + + } + + @Override + public void close() throws IOException { + if(!closed) { + try { + reader.close(); + } finally { + closed = true; + } + } + } + + @Override + public boolean isClosed() { + return closed; + } + + @Override + public int getFormatFeatures() { + return formatFeatures; + } +} diff --git a/src/main/java/entralinked/utility/CertificateGenerator.java b/src/main/java/entralinked/utility/CertificateGenerator.java new file mode 100644 index 0000000..2f13857 --- /dev/null +++ b/src/main/java/entralinked/utility/CertificateGenerator.java @@ -0,0 +1,157 @@ +package entralinked.utility; + +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.math.BigInteger; +import java.security.GeneralSecurityException; +import java.security.KeyFactory; +import java.security.KeyPair; +import java.security.KeyPairGenerator; +import java.security.KeyStore; +import java.security.NoSuchAlgorithmException; +import java.security.PrivateKey; +import java.security.Security; +import java.security.cert.CertificateException; +import java.security.cert.CertificateFactory; +import java.security.cert.X509Certificate; +import java.security.spec.InvalidKeySpecException; +import java.security.spec.PKCS8EncodedKeySpec; +import java.util.Base64; +import java.util.Calendar; +import java.util.zip.DataFormatException; +import java.util.zip.Inflater; + +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.bouncycastle.asn1.x500.X500Name; +import org.bouncycastle.asn1.x509.AuthorityKeyIdentifier; +import org.bouncycastle.asn1.x509.Extension; +import org.bouncycastle.asn1.x509.SubjectPublicKeyInfo; +import org.bouncycastle.cert.X509v3CertificateBuilder; +import org.bouncycastle.cert.jcajce.JcaX509CertificateConverter; +import org.bouncycastle.cert.jcajce.JcaX509CertificateHolder; +import org.bouncycastle.cert.jcajce.JcaX509ExtensionUtils; +import org.bouncycastle.jce.provider.BouncyCastleProvider; +import org.bouncycastle.operator.ContentSigner; +import org.bouncycastle.operator.OperatorCreationException; +import org.bouncycastle.operator.jcajce.JcaContentSignerBuilder; + +/** + * Utility class for generating SSL certificates that are trusted by the DS. + */ +public class CertificateGenerator { + + private static final Logger logger = LogManager.getLogger(); + private static final String issuerCertificateString = + "eNp9lNuyqjgQhu95irm3dokoHi4TEk4aNBBAuAPRIOBZCPL0G5e1Zq+ZqZpcdqr/Tvf/dX796g/EhuX8pWGXWbqlAYbfwV8SsSw0ZZoGRksOhAUBt6BRAAfy8paXR2" + + "MhZAiorwOkwQLhFQGlAUY+hjnRgoC0Eu6AC7kT9JlMq7J8p+TX9JTJqTJpLQT2n7sL02X1mp7dKj25jWUsThbWn/HWvkqRgrkXqgWhQmg8QgGlKyycNZNxqyPgfQ" + + "QI034KYAcSzxIWiGxpeYmtvNk5gJZQz50i8mGdKrhOT1WdGUEdG/M6UhZPAidbxIBC0K4lBVZIR7t1cNlKiJF3UDjo76DgMW21Dtif6hEDVcCISwSmXy+0sLhuo6" + + "1zSUL1LGXGoiYuEOjzfBOLzItD95kqakk8LEzxFV/iNl/3CXIcqmX6gk18hDANg5eUhE5u4aqOXpNW70Dw3TOqsmviwXUgE+6bdhNroP3u+2fb0v/2bRkHAmRD82" + + "6GZ6VjRPHbUgAmhvO29UiXUOIUxaq8cRfEcrHijeaFOyPquizn6UQE7fi1TXYvmBeHyPM2bmovzVXhogl7ni+zk51OJPNg7LEiKy5bdA8gBlNTni6aod9MydZdDT" + + "S+3AB9tqvjKfNJ4pbyKps14fO4qZMiDqJaKumJE81SqqOjWc0Qbs8g2bvuphuMcwzY4Kjfh+tNFWwYXy9ojxYF8DKxIOt6ev2HkLS3Na7MILAEQOABecXzksO8yT" + + "VAZWy2UcbGsEorhzENmr0L1e6k925QTpXehcyoTm8nMsMX5kdsDWGE9dWRdObZEVSFNwXe6krfkNnwah6TrB+u+nbL9AiWeIoFT6ZAN59bv5u3dzNr54fRUuUL9H" + + "CCgMZD7xIZ3tUyNn0iIfQhtA9QBhZ2IPkdZqTf1PeGaZAsA0XvEqOq47HbpAXmPcKfu5xs2R+beapEnIajStqdqiLyoJ2eSIsQWH5AejAgqxvKsE+g9SXQY7T6Kd" + + "CPp7UKwKWfe69RHGLFyamhNpGn1n/QVYtUUfqhAuF85nSA2kSsELhJF+3I7UtUJqYr79ClWSnOK9X+nSw3/6B0bD++CZW+EYUfRCEwwmJiGDf3tDvYLpqfj+6Tne" + + "Fw2ewOxmIyw3d4HFGrLx9yvk6Q5HpTlSgvbgO22k/vo0N43dO7utgfn/e4Xo9alIAktnewGqj5nRm75H7iaLEJBo983jJdugeXdp6y5X6sDmaDmyw/p91TKPrKmD" + + "4ebbTY38D5WT67fONjW/X1OLNHg46GYYZa6etbxQ7671f7GypgwnI="; + + + private static final String issuerPrivateKeyString = + "eNod0Em2a0AAANAFGRBNYVgK0QalfWaIPproZfX/nH+XcG1dR4mnSxCaUu3JKUe5WLR1rND+Q+gwb3NO3ws5e0YXcydZcUtNV/35votzw9SsDstssI0TPxg5q1XPUq" + + "EpGgfib4UnATQKiAcZHsBOsEWg2nShyhd7CoLQznBPWW/+iLfW3bMujf723htqG+n0p30h/SClZIRZibH7I5hGgQHRqgvpuJ/IDWpH9HQZelCC0xPK8uXZ87jcwv" + + "Al64ufS7EqDw4HZfb6fi81sZvq02Xx84aBxLRvYg+ASqvaLsqPb9CrjrxW0koV02lUS9bWCSuQctd+t4w/3BwIjy0ZUdeOqEKzl95s4Sgcw66TQ55RWT5f78KalH" + + "ncOiUv2Gk9VoV8tHw7sYC9RsH3m2xH6QeAFirI8+Qff3Lmmgrp3rqZbymH8skaH7Cku2uPPCe0xn2oP3uM7pOqtbvUKFyUyZ9tKqj7StW5zJ2UgvpYMeuc7INHAo" + + "hV2CuQM0H0TU3f/wzd42bWlkx50Fc5eWTmBGCsMe95I05TreXUNFNLiisySELsXkZC8JNhR3KaLsmrWI4fa6iNpzrP1TOg/6StsTjERretEDEB7gZ28+mhQAs7T6" + + "MzgI1W5a+hUvdFVGB43qZr5eOJ07g/FVIZ7xCYsGlcooISBawqUerOMyT4ivKwCC8XMn0wDNXSVb/+FtVXuzmmoQKx6C2OiW921ctlpI7+L7QPxl9RWjM8QsdL54" + + "4sSHNmIW+3mVEWW8H9+3/s59kaKT97p4oFm+9Jc1hKrMu5/lhDrSe0QqBnwRnZx0jKRqdxHG7Z6lflnu0SfxCFKIwe2laBvNJzVHPlTayy/g/JGQy0"; + + private static X509Certificate issuerCertificate; + private static PrivateKey issuerPrivateKey; + private static boolean initialized; + + public static void initialize() { + if(initialized) { + logger.warn("CertificateGenerator is already initialized!"); + return; + } + + System.setProperty("jdk.tls.useExtendedMasterSecret", "false"); // Will straight up not reuse SSL sessions if this is enabled + Security.setProperty("jdk.tls.disabledAlgorithms", ""); + Security.setProperty("jdk.tls.legacyAlgorithms", ""); + Security.insertProviderAt(new BouncyCastleProvider(), 1); + + try { + issuerCertificate = (X509Certificate)CertificateFactory.getInstance("X.509").generateCertificate( + new ByteArrayInputStream(decodeAndDeflate(issuerCertificateString))); + } catch (CertificateException | DataFormatException e) { + logger.error("Could not create issuer certificate", e); + } + + try { + byte[] keyBytes = Base64.getDecoder().decode(decodeAndDeflate(issuerPrivateKeyString)); + KeyFactory keyFactory = KeyFactory.getInstance("RSA"); + issuerPrivateKey = keyFactory.generatePrivate(new PKCS8EncodedKeySpec(keyBytes)); + } catch (NoSuchAlgorithmException | InvalidKeySpecException | DataFormatException e) { + logger.error("Could not create issuer private key", e); + } + + initialized = true; + } + + public static KeyStore generateCertificateKeyStore(String type, String password) throws GeneralSecurityException, IOException { + // Generate subject keys + KeyPairGenerator keyGenerator = KeyPairGenerator.getInstance("RSA"); + keyGenerator.initialize(1024); + KeyPair keyPair = keyGenerator.generateKeyPair(); + SubjectPublicKeyInfo publicKeyInfo = SubjectPublicKeyInfo.getInstance(keyPair.getPublic().getEncoded()); + + // Generate start/end dates + Calendar notBefore = Calendar.getInstance(); + Calendar notAfter = Calendar.getInstance(); + notAfter.add(Calendar.YEAR, 50); + + // Voodoo because PKCS12 will cry that the DN doesn't match if the attributes aren't in the same order + X500Name issuerName = new JcaX509CertificateHolder(issuerCertificate).getSubject(); + + // Configure certificate builder + X509v3CertificateBuilder builder = new X509v3CertificateBuilder( + issuerName, + BigInteger.ONE, + notBefore.getTime(), + notAfter.getTime(), + new X500Name("CN=*.*.*"), + publicKeyInfo) + .addExtension(Extension.authorityKeyIdentifier, false, AuthorityKeyIdentifier.getInstance( + JcaX509ExtensionUtils.parseExtensionValue(issuerCertificate.getExtensionValue(Extension.authorityKeyIdentifier.getId())))); + + // Sign certificate and create chain + ContentSigner signer = null; + + try { + signer = new JcaContentSignerBuilder("SHA1withRSA").build(issuerPrivateKey); + } catch(OperatorCreationException e) { + // Delegate exception + throw new GeneralSecurityException(e); + } + + X509Certificate subjectCertificate = new JcaX509CertificateConverter().getCertificate(builder.build(signer)); + X509Certificate[] certificateChain = { subjectCertificate, issuerCertificate }; + + // And finally, create the keystore + KeyStore keyStore = KeyStore.getInstance(type); + keyStore.load(null, null); + keyStore.setCertificateEntry("server", subjectCertificate); + keyStore.setKeyEntry("server", keyPair.getPrivate(), password == null ? null : password.toCharArray(), certificateChain); + return keyStore; + } + + private static byte[] decodeAndDeflate(String input) throws DataFormatException { + byte[] bytes = Base64.getDecoder().decode(input.getBytes()); + Inflater inflater = new Inflater(); + inflater.setInput(bytes); + byte[] buffer = new byte[2048]; + byte[] output = new byte[inflater.inflate(buffer)]; + System.arraycopy(buffer, 0, output, 0, output.length); + return output; + } +} diff --git a/src/main/java/entralinked/utility/ConsumerAppender.java b/src/main/java/entralinked/utility/ConsumerAppender.java new file mode 100644 index 0000000..baaeb0b --- /dev/null +++ b/src/main/java/entralinked/utility/ConsumerAppender.java @@ -0,0 +1,61 @@ +package entralinked.utility; + +import java.io.Serializable; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; +import java.util.function.Consumer; + +import org.apache.logging.log4j.core.Appender; +import org.apache.logging.log4j.core.Core; +import org.apache.logging.log4j.core.Filter; +import org.apache.logging.log4j.core.Layout; +import org.apache.logging.log4j.core.LogEvent; +import org.apache.logging.log4j.core.appender.AbstractAppender; +import org.apache.logging.log4j.core.config.Property; +import org.apache.logging.log4j.core.config.plugins.Plugin; +import org.apache.logging.log4j.core.config.plugins.PluginAttribute; +import org.apache.logging.log4j.core.config.plugins.PluginElement; +import org.apache.logging.log4j.core.config.plugins.PluginFactory; + +@Plugin(name = "ConsumerAppender", + category = Core.CATEGORY_NAME, + elementType = Appender.ELEMENT_TYPE, + printObject = true) +public class ConsumerAppender extends AbstractAppender { + + protected static final Map>> consumerMap = new ConcurrentHashMap<>(); + + protected ConsumerAppender(String name, Filter filter, Layout layout, + boolean ignoreExceptions, Property[] properties) { + super(name, filter, layout, ignoreExceptions, properties); + } + + @PluginFactory + public static ConsumerAppender createAppender( + @PluginAttribute("name") String name, + @PluginElement("filter") Filter filter, + @PluginElement("Layout") Layout layout, + @PluginAttribute("ignoreExceptions") boolean ignoreExceptions) { + return new ConsumerAppender(name, filter, layout, ignoreExceptions, null); + } + + public static void addConsumer(String appenderName, Consumer consumer) { + List> consumers = consumerMap.getOrDefault(appenderName, new ArrayList<>()); + consumers.add(consumer); + consumerMap.putIfAbsent(appenderName, consumers); + } + + @Override + public void append(LogEvent event) { + String formattedMessage = getLayout().toSerializable(event).toString(); + List> consumers = consumerMap.get(getName()); + + if(consumers != null) { + for(Consumer consumer : consumers) { + consumer.accept(formattedMessage); + } + } + } +} \ No newline at end of file diff --git a/src/main/java/entralinked/utility/Crc16.java b/src/main/java/entralinked/utility/Crc16.java new file mode 100644 index 0000000..d44de02 --- /dev/null +++ b/src/main/java/entralinked/utility/Crc16.java @@ -0,0 +1,38 @@ +package entralinked.utility; + +/** + * Utility class for calculating CRC-16 checksums. + */ +public class Crc16 { + + public static int calc(byte[] input, int offset, int length) { + int crc = 0xFFFF; + + for(int i = offset; i < offset + length; i++) { + crc ^= (input[i] << 8); + + for(int j = 0; j < 8; j++) { + if((crc & 0x8000) != 0) { + crc = crc << 1 ^ 0x1021; + } else { + crc <<= 1; + } + } + } + + return crc & 0xFFFF; + } + + public static int calc(byte[] input) { + return calc(input, 0, input.length); + } + + public static int calc(int input) { + return calc(new byte[] { + (byte)(input & 0xFF), + (byte)((input >> 8) & 0xFF), + (byte)((input >> 16) & 0xFF), + (byte)((input >> 24) & 0xFF) + }); + } +} diff --git a/src/main/java/entralinked/utility/CredentialGenerator.java b/src/main/java/entralinked/utility/CredentialGenerator.java new file mode 100644 index 0000000..c4d5511 --- /dev/null +++ b/src/main/java/entralinked/utility/CredentialGenerator.java @@ -0,0 +1,35 @@ +package entralinked.utility; + +import java.security.SecureRandom; +import java.util.Base64; + +/** + * Simple utility class for generating client credentials. + */ +public class CredentialGenerator { + + public static final String CHALLENGE_CHARTABLE = "ABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890"; + private static final SecureRandom secureRandom = new SecureRandom(); + + /** + * @return A securely-generated server challenge of the specified length. + */ + public static String generateChallenge(int length) { + char[] challenge = new char[length]; + + for(int i = 0; i < challenge.length; i++) { + challenge[i] = CHALLENGE_CHARTABLE.charAt(secureRandom.nextInt(CHALLENGE_CHARTABLE.length())); + } + + return new String(challenge); + } + + /** + * @return A base64-encoded, securely-generated auth token of the specified length. + */ + public static String generateAuthToken(int length) { + byte[] bytes = new byte[length]; + secureRandom.nextBytes(bytes); + return Base64.getUrlEncoder().encodeToString(bytes); + } +} diff --git a/src/main/java/entralinked/utility/GsidUtility.java b/src/main/java/entralinked/utility/GsidUtility.java new file mode 100644 index 0000000..c949d3b --- /dev/null +++ b/src/main/java/entralinked/utility/GsidUtility.java @@ -0,0 +1,42 @@ +package entralinked.utility; + +import java.util.regex.Pattern; + +public class GsidUtility { + + public static final String GSID_CHARTABLE = "ABCDEFGHJKLMNPQRSTUVWXYZ23456789"; + public static final Pattern GSID_PATTERN = Pattern.compile("[A-HJ-NP-Z2-9]{10}"); + + /** + * Stringifies the specified numerical Game Sync ID + * + * Black 2 - {@code sub_21B480C} (overlay #199) + */ + public static String stringifyGameSyncId(int gsid) { + char[] output = new char[10]; + int index = 0; + + // v12 = gsid + // v5 = sub_204405C(gsid, 4u) + // v8 = v5 + __CFSHR__(v12, 31) + (v12 >> 31) + + // uses unsigned ints for bitshift operations + long ugsid = gsid; + long checksum = Crc16.calc(gsid); // + __CFSHR__(v12, 31) + (v12 >> 31); ?? + + // do while v4 < 10 + for(int i = 0; i < output.length; i++) { + index = (int)((ugsid & 0x1F) & 0x1FFFF); // chartable string is unicode, so normally multiplies by 2 + ugsid = (ugsid >> 5) | (checksum << 27); + checksum >>= 5; + output[i] = GSID_CHARTABLE.charAt(index); // sub_2048734(v4, chartable + index) + + } + + return new String(output); + } + + public static boolean isValidGameSyncId(String gsid) { + return GSID_PATTERN.matcher(gsid).matches(); + } +} diff --git a/src/main/java/entralinked/utility/LEOutputStream.java b/src/main/java/entralinked/utility/LEOutputStream.java new file mode 100644 index 0000000..d5283b0 --- /dev/null +++ b/src/main/java/entralinked/utility/LEOutputStream.java @@ -0,0 +1,58 @@ +package entralinked.utility; + +import java.io.FilterOutputStream; +import java.io.IOException; +import java.io.OutputStream; + +/** + * Like {@link DataOutputStream}, but for little endian. + * It's comical how this is not in base Java yet. + */ +public class LEOutputStream extends FilterOutputStream { + + protected final byte[] buffer = new byte[8]; + + public LEOutputStream(OutputStream outputStream) { + super(outputStream); + } + + public void writeBytes(int value, int amount) throws IOException { + for(int i = 0; i < amount; i++) { + out.write(value); + } + } + + public void writeShort(int value) throws IOException { + buffer[0] = (byte)(value & 0xFF); + buffer[1] = (byte)((value >> 8) & 0xFF); + out.write(buffer, 0, 2); + } + + public void writeInt(int value) throws IOException { + buffer[0] = (byte)(value & 0xFF); + buffer[1] = (byte)((value >> 8) & 0xFF); + buffer[2] = (byte)((value >> 16) & 0xFF); + buffer[3] = (byte)((value >> 24) & 0xFF); + out.write(buffer, 0, 4); + } + + public void writeFloat(float value) throws IOException { + writeInt(Float.floatToIntBits(value)); + } + + public void writeLong(long value) throws IOException { + buffer[0] = (byte)(value & 0xFF); + buffer[1] = (byte)((value >> 8) & 0xFF); + buffer[2] = (byte)((value >> 16) & 0xFF); + buffer[3] = (byte)((value >> 24) & 0xFF); + buffer[4] = (byte)((value >> 32) & 0xFF); + buffer[5] = (byte)((value >> 40) & 0xFF); + buffer[6] = (byte)((value >> 48) & 0xFF); + buffer[7] = (byte)((value >> 56) & 0xFF); + out.write(buffer, 0, 8); + } + + public void writeDouble(double value) throws IOException { + writeLong(Double.doubleToLongBits(value)); + } +} diff --git a/src/main/java/entralinked/utility/MD5.java b/src/main/java/entralinked/utility/MD5.java new file mode 100644 index 0000000..845a02c --- /dev/null +++ b/src/main/java/entralinked/utility/MD5.java @@ -0,0 +1,34 @@ +package entralinked.utility; + +import java.nio.charset.StandardCharsets; +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; + +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; + +import io.netty.util.internal.StringUtil; + +/** + * Utility class for generating hex-formatted MD5 hashes. + */ +public class MD5 { + + private static final Logger logger = LogManager.getLogger(); + private static MessageDigest digest; + + /** + * @return A hex-formatted MD5 hash of the specified input. + */ + public static String digest(String string) { + if(digest == null) { + try { + digest = MessageDigest.getInstance("MD5"); + } catch(NoSuchAlgorithmException e) { + logger.error("Could not get MD5 MessageDigest instance", e); + } + } + + return StringUtil.toHexStringPadded(digest.digest(string.getBytes(StandardCharsets.ISO_8859_1))); + } +} diff --git a/src/main/resources/dashboard/login.html b/src/main/resources/dashboard/login.html new file mode 100644 index 0000000..681b2fb --- /dev/null +++ b/src/main/resources/dashboard/login.html @@ -0,0 +1,14 @@ + + + + + + +
+
+ + +
+ + + diff --git a/src/main/resources/dashboard/profile.html b/src/main/resources/dashboard/profile.html new file mode 100644 index 0000000..d6e6dde --- /dev/null +++ b/src/main/resources/dashboard/profile.html @@ -0,0 +1,173 @@ + + + + + + + + + + + + + + diff --git a/src/main/resources/dashboard/scripts/login.js b/src/main/resources/dashboard/scripts/login.js new file mode 100644 index 0000000..ba6f3d9 --- /dev/null +++ b/src/main/resources/dashboard/scripts/login.js @@ -0,0 +1,22 @@ +const ELEMENT_GSID_INPUT = document.getElementById("gsid"); + +function postLogin() { + let loginData = { + gsid: ELEMENT_GSID_INPUT.value + } + + fetch("/dashboard/login", { + method: "POST", + body: new URLSearchParams(loginData) + }).then((response) => { + return response.json(); + }).then((response) => { + console.log(response); + + if(response.error) { + alert(response.message); + } else { + window.location.href = "/dashboard/profile.html"; + } + }); +} diff --git a/src/main/resources/dashboard/scripts/profile.js b/src/main/resources/dashboard/scripts/profile.js new file mode 100644 index 0000000..28e3c0e --- /dev/null +++ b/src/main/resources/dashboard/scripts/profile.js @@ -0,0 +1,365 @@ +const ELEMENT_GAME_SUMMARY = document.getElementById("game-summary"); + +// Dreamer elements +const ELEMENT_DREAMER_SPRITE = document.getElementById("dreamer-sprite"); +const ELEMENT_DREAMER_SPECIES = document.getElementById("dreamer-species"); +const ELEMENT_DREAMER_NATURE = document.getElementById("dreamer-nature"); +const ELEMENT_DREAMER_NAME = document.getElementById("dreamer-name"); +const ELEMENT_DREAMER_GENDER = document.getElementById("dreamer-gender"); +const ELEMENT_DREAMER_TRAINER = document.getElementById("dreamer-trainer"); +const ELEMENT_DREAMER_TRAINER_ID = document.getElementById("dreamer-trainer-id"); +const ELEMENT_DREAMER_LEVEL = document.getElementById("dreamer-level"); + +// Encounter form elements +const ELEMENT_ENCOUNTER_SPECIES = document.getElementById("encounter-form-species"); +const ELEMENT_ENCOUNTER_MOVE = document.getElementById("encounter-form-move"); +const ELEMENT_ENCOUNTER_FORM = document.getElementById("encounter-form-form"); +const ELEMENT_ENCOUNTER_ANIMATION = document.getElementById("encounter-form-animation"); + +// Item form elements +const ELEMENT_ITEM_ID = document.getElementById("item-form-id"); +const ELEMENT_ITEM_QUANTITY = document.getElementById("item-form-quantity"); + +// Misc input elements +const ELEMENT_CGEAR_SKIN_INPUT = document.getElementById("cgear-skin"); +const ELEMENT_DEX_SKIN_INPUT = document.getElementById("dex-skin"); +const ELEMENT_MUSICAL_INPUT = document.getElementById("musical"); +const ELEMENT_LEVEL_GAIN_INPUT = document.getElementById("level-gain-input"); + +// Create event listeners +ELEMENT_ENCOUNTER_SPECIES.addEventListener("change", clampValue); +ELEMENT_ENCOUNTER_MOVE.addEventListener("change", clampValue); +ELEMENT_ITEM_ID.addEventListener("change", clampValue); +ELEMENT_ITEM_QUANTITY.addEventListener("change", clampValue); +ELEMENT_LEVEL_GAIN_INPUT.addEventListener("change", clampValue); + +function clampValue() { + let value = parseInt(this.value); + + if(value < this.min) { + this.value = this.min; + } else if(value > this.max) { + console.log(value); + this.value = this.max; + } +} + +// Local variables +var encounterTableIndex = -1; +var itemTableIndex = -1; +var profile = { + encounters: [], + items: [] +}; + +function configureEncounter(index) { + encounterTableIndex = Math.min(10, Math.min(index, profile.encounters.length)); + + // Load existing settings + let encounter = profile.encounters[encounterTableIndex]; + ELEMENT_ENCOUNTER_SPECIES.value = encounter ? encounter.species : 1; + ELEMENT_ENCOUNTER_MOVE.value = encounter ? encounter.move : 1; + ELEMENT_ENCOUNTER_FORM.value = encounter ? encounter.form : 0; + ELEMENT_ENCOUNTER_ANIMATION.value = encounter ? encounter.animation : "WALK_AROUND"; +} + +function saveEncounter() { + if(encounterTableIndex < 0) { + return; + } + + // Create encounter data + let encounterData = { + species: ELEMENT_ENCOUNTER_SPECIES.value, + move: ELEMENT_ENCOUNTER_MOVE.value, + form: ELEMENT_ENCOUNTER_FORM.value, + animation: ELEMENT_ENCOUNTER_ANIMATION.value + } + + // Set form to highest form available if it too great + let maxForm = 0; + + switch(encounterData.species) { + case "201": maxForm = 27; break; // Unown + case "386": maxForm = 3; break; // Deoxys + case "412": + case "413": maxForm = 2; break; // Burmy & Wormadam + case "422": + case "423": + case "487": maxForm = 1; break; // Shellos, Gastrodon & Giratina + case "479": maxForm = 5; break; // Rotom + case "493": maxForm = 16; break; // Arceus + } + + if(encounterData.form > maxForm) { + encounterData.form = maxForm; + } + + profile.encounters[encounterTableIndex] = encounterData; + updateEncounterCell(encounterTableIndex); + closeEncounterForm(); +} + +function removeEncounter() { + if(encounterTableIndex < 0) { + return; + } + + let oldLength = profile.encounters.length; + profile.encounters.splice(encounterTableIndex, 1); + + for(let i = encounterTableIndex; i < oldLength; i++) { + updateEncounterCell(i); + } + + closeEncounterForm(); +} + +function updateEncounterCell(index) { + let cell = document.getElementById("encounter" + index); + let encounterData = profile.encounters[index]; + let spriteBase = "/sprites/pokemon/normal/"; + let spriteImage = spriteBase + "0.png"; + + if(encounterData) { + spriteImage = spriteBase + encounterData.species + ".png"; + + if(encounterData.form > 0) { + let formSpriteImage = spriteBase + encounterData.species + "-" + encounterData.form + ".png"; + + if(checkURL(formSpriteImage)){ + spriteImage = formSpriteImage; + } + } + } + + cell.innerHTML = ""; +} + +function closeEncounterForm() { + encounterTableIndex = -1; + window.location.href = "#"; +} + +function configureItem(index) { + itemTableIndex = Math.min(20, Math.min(index, profile.items.length)); + + // Loadg existing settings + let item = profile.items[itemTableIndex]; + ELEMENT_ITEM_ID.value = item ? item.id : 1; + ELEMENT_ITEM_QUANTITY.value = item ? item.quantity : 1; +} + +function saveItem() { + if(itemTableIndex < 0) { + return; + } + + let itemData = { + id: ELEMENT_ITEM_ID.value, + quantity: ELEMENT_ITEM_QUANTITY.value + } + + profile.items[itemTableIndex] = itemData; + updateItemCell(itemTableIndex); + closeItemForm(); +} + +function removeItem() { + if(itemTableIndex < 0) { + return; + } + + let oldLength = profile.items.length; + profile.items.splice(itemTableIndex, 1); + + for(let i = itemTableIndex; i < oldLength; i++) { + updateItemCell(i); + } + + closeItemForm(); +} + +function updateItemCell(index) { + let cell = document.getElementById("item" + index); + let item = profile.items[index]; + let spriteBase = "/sprites/items/"; + let spriteImage = spriteBase + "0.png"; + let quantityStr = ""; + + if(item) { + let newSpriteImage = spriteBase + item.id + ".png"; + quantityStr = "x" + item.quantity; + + if(checkURL(newSpriteImage)){ + spriteImage = newSpriteImage; + } + } + + cell.innerHTML = "
" + quantityStr; +} + +function closeItemForm() { + itemTableIndex = -1; + window.location.href = "#"; +} + +async function fetchData(path) { + return fetchData(path, "GET", null); +} + +async function fetchData(path, method, body) { + let response = await fetch(path, { + method: method, + body: body + }); + + // Return to login page if unauthorized + if(response.status == 401) { + window.location.href = "/dashboard/login.html"; + return; + } + + try { + return await response.json(); + } catch(error) { + window.alert(error); + } + + return null; +} + +function fetchDlcData() { + let cgearType = profile.gameVersion.includes("2") ? "CGEAR2" : "CGEAR"; // Not a good way to do this! + + // Fetch CGear skins + fetchData("/dashboard/dlc?type=" + cgearType).then((response) => { + addValuesToComboBox(ELEMENT_CGEAR_SKIN_INPUT, response); + ELEMENT_CGEAR_SKIN_INPUT.value = profile.cgearSkin; + }); + + // Fetch Dex skins + fetchData("/dashboard/dlc?type=ZUKAN").then((response) => { + addValuesToComboBox(ELEMENT_DEX_SKIN_INPUT, response); + ELEMENT_DEX_SKIN_INPUT.value = profile.dexSkin; + }); + + // Fetch musicals + fetchData("/dashboard/dlc?type=MUSICAL").then((response) => { + addValuesToComboBox(ELEMENT_MUSICAL_INPUT, response); + ELEMENT_MUSICAL_INPUT.value = profile.musical; + }); +} + +// TODO +function fetchProfileData() { + fetchData("/dashboard/profile").then((response) => { + let gameVersion = response["gameVersion"]; + let dreamerSprite = response["dreamerSprite"]; + let dreamerInfo = response["dreamerInfo"]; + let encounters = response["encounters"]; + let items = response["items"]; + let cgearSkin = response["cgearSkin"]; + let dexSkin = response["dexSkin"]; + let musical = response["musical"]; + let levelsGained = response["levelsGained"]; + + // Update game summary + profile.gameVersion = gameVersion; + ELEMENT_GAME_SUMMARY.innerHTML = "Game Card in use: " + gameVersion; + + // Update dreamer summary + if(dreamerInfo) { + let species = dreamerInfo["species"]; + let nature = dreamerInfo["nature"]; + let nickname = dreamerInfo["nickname"]; + let gender = dreamerInfo["gender"]; + let trainerName = dreamerInfo["trainerName"]; + let trainerId = dreamerInfo["trainerId"]; + let level = dreamerInfo["level"]; + + // Set element values + ELEMENT_DREAMER_SPRITE.innerHTML = ""; + ELEMENT_DREAMER_SPECIES.innerHTML = "#" + species; + ELEMENT_DREAMER_NATURE.innerHTML = stringToWord(nature); + ELEMENT_DREAMER_NAME.innerHTML = nickname; + ELEMENT_DREAMER_GENDER.innerHTML = stringToWord(gender); + ELEMENT_DREAMER_TRAINER.innerHTML = trainerName; + ELEMENT_DREAMER_TRAINER_ID.innerHTML = trainerId; + ELEMENT_DREAMER_LEVEL.innerHTML = level; + } + + // Update encounter table + if(encounters){ + profile.encounters = encounters; + + for(let i = 0; i < 10; i++) { + updateEncounterCell(i); + } + } + + // Update item table + if(items){ + profile.items = items; + + for(let i = 0; i < 20; i++) { + updateItemCell(i); + } + } + + // Update selected DLC + profile.cgearSkin = cgearSkin ? cgearSkin : "none"; + profile.dexSkin = dexSkin ? dexSkin : "none"; + profile.musical = musical ? musical : "none"; + fetchDlcData(); + + // Update level gain + ELEMENT_LEVEL_GAIN_INPUT.value = levelsGained; + + // Show div + document.getElementById("main-container").style.display = "grid"; + }); +} + +function postProfileData() { + // Construct body + let profileData = { + encounters: profile.encounters, + items: profile.items, + cgearSkin: ELEMENT_CGEAR_SKIN_INPUT.value, + dexSkin: ELEMENT_DEX_SKIN_INPUT.value, + musical: ELEMENT_MUSICAL_INPUT.value, + gainedLevels: ELEMENT_LEVEL_GAIN_INPUT.value + } + + // Send data + fetchData("/dashboard/profile", "POST", JSON.stringify(profileData)).then((response) => { + alert(response.message); + }); +} + +function postLogout() { + fetchData("/dashboard/logout", "POST", null).then((response) => { + // Assume it succeeded + window.location.href = "/dashboard/login.html"; + }); +} + +// TODO bad +function checkURL(url) { + var request = new XMLHttpRequest(); + request.open('HEAD', url, false); + request.send(); + return request.status == 200; +} + +function stringToWord(string) { + return string.charAt(0).toUpperCase() + string.slice(1).toLowerCase(); +} + +function addValuesToComboBox(selectorElement, values) { + for(i in values) { + let value = values[i]; + selectorElement.options[selectorElement.options.length] = new Option(value, value); + } +} diff --git a/src/main/resources/dashboard/styles/login.css b/src/main/resources/dashboard/styles/login.css new file mode 100644 index 0000000..dbfcb57 --- /dev/null +++ b/src/main/resources/dashboard/styles/login.css @@ -0,0 +1,38 @@ +body { + background-color: #191919; + color: white; + font-size: 20px; +} + +input { + color: white; + background-color: #262626; + border: 0px; + border-radius: 10px; + width: 100%; + margin-top: 4px; + margin-bottom: 16px; + padding: 4px 4px; + font-size: 16px; +} + +button { + color: white; + background-color: #3D3D3D; + border: 0px; + border-radius: 10px; + font-size: 16px; + padding: 16px 32px; +} + +button:active { + background-color: #333333; +} + +.root-container { + position: absolute; + left: 50%; + top: 25%; + transform: translate(-50%, 0%); + padding: 10px; +} diff --git a/src/main/resources/dashboard/styles/profile.css b/src/main/resources/dashboard/styles/profile.css new file mode 100644 index 0000000..7e95003 --- /dev/null +++ b/src/main/resources/dashboard/styles/profile.css @@ -0,0 +1,164 @@ +body { + background-color: #191919; + color: white; + font-size: 20px; +} + +form { + margin-bottom: 0px; +} + +img { + image-rendering: pixelated; +} + +input, select { + color: white; + background-color: #262626; + border: 0px; + border-radius: 10px; + width: 100%; + margin-top: 4px; + margin-bottom: 16px; + padding: 4px 4px; + font-size: 16px; +} + +button { + color: white; + background-color: #3D3D3D; + border: 0px; + border-radius: 10px; +} + +button:active { + background-color: #333333; +} + +.root-container { + position: absolute; + left: 50%; + top: 50%; + transform: translate(-50%, -50%); + padding: 10px; + max-width: 600px; +} + +.grid-container { + display: grid; + grid-auto-columns: 1fr; + grid-auto-flow: column; + gap: 5px 10px; +} + +.grid-container input { + width: 100%; + margin-top: 4px; + margin-bottom: 16px; + padding: 4px 4px; + font-size: 16px; + color: white; +} + +.dreamer-summary { + width: 100%; + margin-top: 3px; + margin-bottom: 20px; +} + +.dreamer-summary th { + text-align: left; + padding-left: 10px; + width: 64px; +} + +.dreamer-summary td { + width: 96px; + background-color: #262626; + border-radius: 10px; + padding-left: 5px; +} + +.dreamer-sprite { + text-align: center; +} + +.header-text { + text-align: center; + font-weight: bold; + font-size: 24px; + margin-top: 0px; +} + +.popup { + position: fixed; + top: 0; + bottom: 0; + left: 0; + right: 0; + background: rgba(0, 0, 0, 0.6); + opacity: 0; + transition: opacity 200ms; + visibility: hidden; +} + +.popup:target { + opacity: 1; + visibility: visible; +} + +.popup .close-button { + position: absolute; + background-color: #3D3D3D; + top: 10px; + right: 10px; +} + +.popup .content { + background-color: #191919; + position: absolute; + left: 50%; + top: 25%; + transform: translate(-50%, 0%); + padding: 20px; + width: 300px; +} + +.encounter-image-table { + width: 100%; + border-spacing: 8px; + margin-bottom: 20px; +} + +.encounter-image-table td { + background-color: #262626; + border-radius: 10px; + width: 96px; + height: 96px; + text-align: center; +} + +.item-table { + width: 100%; + border-spacing: 8px; + margin-bottom: 20px; +} + +.item-table td { + background-color: #262626; + border-radius: 10px; + width: 48px; + height: 48px; + text-align: center; +} + +.item-table td a { + display: block; + color: inherit; + text-decoration: none; +} + +.big-button { + font-size: 16px; + padding: 16px 32px; +} diff --git a/src/main/resources/dlc.paths b/src/main/resources/dlc.paths new file mode 100644 index 0000000..d123a61 --- /dev/null +++ b/src/main/resources/dlc.paths @@ -0,0 +1,44 @@ +/dlc/IRAO/CGEAR/C003_munna_1_en.bin +/dlc/IRAO/CGEAR/C004_pikachu_1_e.bin +/dlc/IRAO/CGEAR/C005_chirami_1_e.bin +/dlc/IRAO/CGEAR/C007_tabunne_1_e.bin +/dlc/IRAO/CGEAR/C008_pocchama_1_e.bin +/dlc/IRAO/CGEAR/C009_guregguru_1_e.bin +/dlc/IRAO/CGEAR/C010_fushigibana_1_e.bin +/dlc/IRAO/CGEAR/C011_rizadon_1_e.bin +/dlc/IRAO/CGEAR/C012_kamekkusu_1_e.bin +/dlc/IRAO/CGEAR/C013_zekurom_1_e_v02.bin +/dlc/IRAO/CGEAR/C014_reshiram_1_e.bin +/dlc/IRAO/CGEAR/C015_vikutini_1_e.bin +/dlc/IRAO/CGEAR/C017_porigonz_0_e.bin +/dlc/IRAO/CGEAR/C018_WCS_1_e.bin +/dlc/IRAO/CGEAR/C019_shikijikaharu_1_e.bin +/dlc/IRAO/CGEAR/C020_shikijikanatu_1_e.bin +/dlc/IRAO/CGEAR/C021_shikijikaaki_1_e.bin +/dlc/IRAO/CGEAR/C022_shikijikafuyu_1_e.bin +/dlc/IRAO/CGEAR/C023_iwaparesu_1_e.bin +/dlc/IRAO/CGEAR/C024_zoroark_1_e.bin +/dlc/IRAO/CGEAR/C025_giaru_1_e.bin +/dlc/IRAO/CGEAR/C030_kerudhio_1_en.bin +/dlc/IRAO/CGEAR/C031-1_meroetta_1_e.bin +/dlc/IRAO/CGEAR/C032-1_wcs2012_1_e.bin +/dlc/IRAO/CGEAR/C100_defo_1_en.bin +/dlc/IRAO/CGEAR2/C003-2_munna_1_en.bin +/dlc/IRAO/CGEAR2/C013-2_zekrom_1_e.bin +/dlc/IRAO/CGEAR2/C014-2_reshiram_1_e.bin +/dlc/IRAO/CGEAR2/C015-2_victini_1_e.bin +/dlc/IRAO/CGEAR2/C030-2_kerudhio_1_en.bin +/dlc/IRAO/CGEAR2/C031-2_meroetta_1_e.bin +/dlc/IRAO/CGEAR2/C033_halloween_1_en.bin +/dlc/IRAO/CGEAR2/C035_BK_1_e.bin +/dlc/IRAO/CGEAR2/C036_WK_1_e.bin +/dlc/IRAO/CGEAR2/C100-2_default_1_e.bin +/dlc/IRAO/MUSICAL/M010_munna_1_e_02.bin +/dlc/IRAO/MUSICAL/M013_meloetta_1_e.bin +/dlc/IRAO/ZUKAN/Z003_BWsyokigirl_1_en.bin +/dlc/IRAO/ZUKAN/Z004_BWsyokiboy_1_en.bin +/dlc/IRAO/ZUKAN/Z007_hyuu_1_en.bin +/dlc/IRAO/ZUKAN/Z008_bell_1_e.bin +/dlc/IRAO/ZUKAN/Z009_tyeren_1_e.bin +/dlc/IRAO/ZUKAN/Z100_defogirl_1_en.bin +/dlc/IRAO/ZUKAN/Z101_defoboy_1_en.bin diff --git a/src/main/resources/dlc/IRAO/CGEAR/C003_munna_1_en.bin b/src/main/resources/dlc/IRAO/CGEAR/C003_munna_1_en.bin new file mode 100644 index 0000000000000000000000000000000000000000..75d1933176ce3b6bf816f93953ccfa8884d2bf13 GIT binary patch literal 9730 zcmeHL&2t<_6(5jf$2I|^DiuX>vBoH@*MX?qy9c8K3%hn?hd3+C?X6fxg^e);{?bbKqx160&)1N!Ux~r_j+dbL%S=P zRZK+TxB96+-s{&r{eIIQ<2V~t^1^Y_KrCEN2`4XvlM$kONC=TeK9U}!V*pn$vQar0 zFC7WSr3@aHa$ZU~2C$$gQ7z?^=gA2`N=6Bo0IC8v4RQn}Zxc`eiVdpUQOHM5ryS>) z=Qt%`%A!>CtgxPkQ8%VZ&%*;n61A@;>mGjwGn*3Kju2fot$MCzFlLc+5Fd-EI@66> zH06j{WCc?ASSd~Z;}(cL0uOTuWPlU^7l+d#E2ThS#bGH^fXxfJ7fO*8Jun)>PzBh; zz_8%~)eiR_^C1fOEYf8lh*B})UJv>yw(hG%_qR|Lk7W~_Hw*fW7@Y`->E&p%|yP?RsDG>rh?8?BqwTs`UsvUCx z-C8Bb>BE+Io~kKkt~iM(1JNw@K(`6l3`B2r)%td4n7D1Fwu-DUvE!e_GgN7?!k|{H z&gYwVJI-5gh2Z=hu`^Jcsp8ZGUTlfCkoTiuL?#%!rT5%2idltafl(871mUXsYFim{ zt>AeD^mGJ}OP-ewit+;1KE^DN4(xcK9mYl(JY15&FxJ0$Adt&JuoPGhFqh#Ey%pPy zzs(qU&uuGrD6;+;DBwJEq&ma1*u9n!S458yEC+~N0ilHnEM;iPd*aj9VqD6l`5+wx z!xw^J4dv?yo!$c%KEeqVv~;2ZyDV z`)eL!aTv&8>j(0HJp-(Na@u>CcATFX{Fz3R%$dv?V-`(K>h-nSTCQF{gMG%BnQmz3 zVvBlxoF75P><8vJcY6gf4H#)aB8GVF1HAsj0KyZD1xSKmEIjuNSD7Q1pbZ7~mQ%cD zND}`_1O~nLsP&2Y}fOAY;rH;DD1dx#sgVH@GA9Gr&BV#kzv{KY{oCyg_0) zfb2&d`^Ip$Dt$R8%z>0JSs_dgz(uDXxwELq0fG@r7M3|*h-sfEg89P-az04&SvZA4 z4uz2vYyPiH)fZe`E~E0!*1zq*S}$+C7`UVc`1(Zn*fI;qi5L zmBt%HmB*TJ0{97V&h$8B3eJKlVuSExyXkkeN%{#>2HFU7i8%*Og zcOPa2mLcp@sVv;GUla2nPIJtc3Gc|*geQ*z%z@}m%Q<|n@_H%1qLbH*$D6ibAk051~YM;SahZOVy7ljMknvfh0s>H2OG>rjk}>zC)4771oT z$P4Didi$}?KHPtN=B*qp73I-VL4I~rrW2!b$sLt}i)#q5+=okO%mZ9eqVE#8tMGj# z7{`q!2sQ`}+783}%#+}RYYNg`lyYD}%0Ug3$7R2Z3zge$N_qdNv82h0=WYh@8#E}* zAkbWiYwSAqp9ZgWg$l;&3X|L|OakxuZlF$>aXT<*0V}a%VNM90s8cs&nV!4Z*qCI5 zok-apt+}71MIR!4=mLOm6r&Mvcb&&o6w{XuhM$VRODy5rL=Agz*Zp7M2Xi2>TE*Oz4;~$?1=1`q*@z;ku!d(G&!YZMko^elOTd*?V_VuDc zO%`AY_yA5Y)^AN|D#*_O@MV31LJ6i(EC~!?H|m6$L;Nkk1?B+Y0)i2WE5wd6YH`*q zqjpDJFEB8H<%+k;gzw*EI}_qQq-F3%n}6cz_$e77510@9%*>5Ogx@mlw+T$8Ovmd* zoCd>S!T1t?#NZ{QN!q@+8$`kD1O`3OH1Mdxb^9d#Iu_>EAm(`xGusc2Nq3?0y}c_3f|&ZOXX)+j9bKuvX*GSauSpYH z4qz+m2N5tHycYvab5Y|1AC{ASaCZp2U)fFD@&*ZW$HTa5Aie+SgWsS0&FIkHl6-FB zb4UO9-LD+~=J9_Wzi0a&zW1EOZMU;u-H5iG*xJEw+jQ(=*NJ2Yzg@Pyf;iMUI&T^F zx}LtdZP@==bxu8N^BPYRmTI1#QSJ4i)l2GS^@@64y`V0t7gbz}J=ALI^x9I6&04QL}PR4YE;vx4|@w+bTW&fw@zBtw~3$e*3vBHXfN%f`zf}s_qA63 z5qgv!qY0X%B0U~?i2bacK1pTzAx+as`Vsw@e44QyeyM(?7H@^YM5O5%I!ov1SvpS( zv`B`2PQO*ZQ?IGZ@bC-8zJ6_^)|`KyUZ9KgBE3W}(<}5UU82|MGQCcJRew|OtLy4@ z^@d_!zgKUz%wM6m=^eUC*XUh(kN!k|ruXSOeL#29Cus|9r4Q6!6#M$S`iE+jcl5{f z$90$P);)TY-mLG^Tl7}FO@EQTMCa9l+D4zSzCJ~trdIjwdO*8+hyJ`C)c5Ew=!D*> zlX{nakPgzT>XO<;_Y(WMkA|pKKBK>?zoxS~r}yf8`hGp4_v-`t0ey@f(foUWzE14x zA<9#${4xD4SUsvA(-V497xm-%+xk2D*g2(Q522s`^YQeg6hv4x^C( literal 0 HcmV?d00001 diff --git a/src/main/resources/dlc/IRAO/CGEAR/C004_pikachu_1_e.bin b/src/main/resources/dlc/IRAO/CGEAR/C004_pikachu_1_e.bin new file mode 100644 index 0000000000000000000000000000000000000000..7a1dac04bf4648ab9457745fd27ebd4de0dba6f9 GIT binary patch literal 9730 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 G9{M+nK7G&t literal 0 HcmV?d00001 diff --git a/src/main/resources/dlc/IRAO/CGEAR/C005_chirami_1_e.bin b/src/main/resources/dlc/IRAO/CGEAR/C005_chirami_1_e.bin new file mode 100644 index 0000000000000000000000000000000000000000..d9bfc9b4d4ac10c7c8688f9ce7d03f3994398452 GIT binary patch literal 9730 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 literal 0 HcmV?d00001 diff --git a/src/main/resources/dlc/IRAO/CGEAR/C008_pocchama_1_e.bin b/src/main/resources/dlc/IRAO/CGEAR/C008_pocchama_1_e.bin new file mode 100644 index 0000000000000000000000000000000000000000..6c24bf6588782ef173ad41b37b32eb0c64bef8ba GIT binary patch literal 9730 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 AZU6uP literal 0 HcmV?d00001 diff --git a/src/main/resources/dlc/IRAO/CGEAR/C009_guregguru_1_e.bin b/src/main/resources/dlc/IRAO/CGEAR/C009_guregguru_1_e.bin new file mode 100644 index 0000000000000000000000000000000000000000..b9a1bac343700154107dfd82cc0f6d60217c0283 GIT binary patch literal 9730 zcmeHKU2GIp6rS1^Ypdw4xCy2b_s&8B_#m^`$OFlCJ61t$nwT+)331zctNa+B=mRmX zg1Cy3__*+LZ?!&53@Koho(n|hxU!_21fhZ41@In ztPRMvp}EzA2ghTYGaA22(#SEZ6>TkmwZaBxDNtv)(||h-awiNDaL1tU4hQBsFjtR( zq)uQagPG$+a#M>+UV#Q3hPCc)5$nB{=W* zU}n45F;-5ZPk?S3fyIFw5}sl?zB$hMNRIPWSRfM4j~9#lXt9_m7AJF`7I*Q)YOf~2 zF)U9`@=fVoJgGgx6O1KTJeG{N7_7xG;s||{nOQ_-im^Tx@3n0d3wk4H-$`fzf#7wq zz0$=e;tYOf1S_hZmZ$|juIr3pLe;UwQ5Utq`AFe^4eSBEU$$vvvUTx~I$sKgkyjl- zu0GA9ZsACMd8I@TP|4LO2$2qi%2NG}B_=n}{~7d8X&c~ugq0Uha3s`_P|imdYJi}a zl_=hHtRbzFm`y>4*esRn>ab<38+JVcYyy(4aI9q*CZq4ifdcAPjoAWud{^n4h%aZ} z`wmj9&^IPZVB^vPC($l7g5Dz9rUuAk^rcX}CZ*Yc z3&AkKy7H(bEC3W1i_q=;ysGr8^rskI@PNt=lvlww9Q&})0NwzLD+)&T(>0u{H9VnI zELKyavpH08KC5(#*{Bw30HrCV2AGEwQo<9!>2_dKmk67Pj7z`FE?Yeiecue z$?Z91^L~|x@j3NUd&9ja-@|OR6v8_eq0iGEI!f&%(B9YXKh1&diriZ?*K_BQ60npy z!T1Og4^iXKpbnd_3@8*m1{7FD&{8*GsDK(23Za4;)f=Jt{69Ug4-vc{Y-DsDq8hBD zLQEC|{{^TPYSTpCn6+;-gfZe)#3B)!oiE}+cm+5Gbm5~JUc2UBAYZ7O0JJpXZb7}{ zOf9Onp@rx7bsNWQ$;e!Cjca9BbDa@pWw>dL@Sbd$^9S9%oV!p!Yv|lR3XB6J#LH5k zFTijrIn4V(OThm&*i%!wzV<=fMvJ{F)(d(gL2r)6L27b*x(F3qQXhUMxCOghcI`<} zr{ZPcVYhrfQ7*yPV_7zR&eD9(O55mA!rACP2QJmevRycwR^Y5^#hKNHLkkeVumO&d`&_S@xHQ?q6A6RoT8sn^{0lhnT@bx8fSdl48VZFJ zo$va**W9qWX>G{S&y&XwymqzqL7w8;Yg;{DkOM!`U%nt;o6^y6q_!}e&*RNoir(c5 z1%0lB`-Jp@e=dZIZcFv}0&#$7XpTxpcq0S^qd`hOAn0?9)?5R!F^_Yj5pa(8s5LpR z@G}9bFaoN8lz=k)6(NpN>u{*!+c(E~&n!Z0wqGNGE(x6Vj12(FCQhL2nj=crquOR! zTjAKR6MA1kO*EINt3&~+N#gi@NFoa!qWQ=`kg}}Fdg&!>C>F*bu(@i)Q5L78zt(?) z@F95LAb=R9e|^L%{R4+Y(Ye1={46(ELSt4`Fi(d7=Hkr2Py|mDf0zl)QGmV>g(%K~ z;lk2^Of6QT>Ub+2YBFm_u-efZ@vE5 z*0K^=6B^gZ{zAZ7|gygzu`8njk>>R${p$f3A@K{ z+4j#WJwAsvQOPYwIWA6!lVVa#>BgtTX>n#b-TXGk#aZ!%_)=tKuguCmY0IZ%KN+zs zd0IXrx5@4DS^1oNUJl5w#07Cte7&r`LR=Exh;PMr;(PIfxGa7YF0PXJN&LL5{#V4S zGAD=RupE)ENiIj_>+%h(ovvN_+Aa6MlD%4gQ_hH4@r&3ce-(2mn-{-{9Qt1u3*vY2 z2g>KfpW-j!^}iwLQsO37^0(IW@?H6!RHv!@fIKMQmxtsBvLHW{AIZb=V|hb-B9BM| zbGHCq|GQ)>=J)!y$#%Iy-mPmC$~xpdeydK|CGUlPpInLO{qg}Rr1a}no*9>)`TFN_ z!q<+=6Tarnb5c$suLSj<0#0F8Z-mqGjBa^WKfTtCo{!2TS}`g8@H1r!*INA~ZF!e@ MYr)%Kr{C%M7av-R-T(jq literal 0 HcmV?d00001 diff --git a/src/main/resources/dlc/IRAO/CGEAR/C010_fushigibana_1_e.bin b/src/main/resources/dlc/IRAO/CGEAR/C010_fushigibana_1_e.bin new file mode 100644 index 0000000000000000000000000000000000000000..5a19e59f39e447161a87f69079667ca3e2aea8fa GIT binary patch literal 9730 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 J-T(2Ue*;^Sk9`0D literal 0 HcmV?d00001 diff --git a/src/main/resources/dlc/IRAO/CGEAR/C012_kamekkusu_1_e.bin b/src/main/resources/dlc/IRAO/CGEAR/C012_kamekkusu_1_e.bin new file mode 100644 index 0000000000000000000000000000000000000000..6ef61179c464a84acd0568f1aa985f28a545f166 GIT binary patch literal 9730 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 literal 0 HcmV?d00001 diff --git a/src/main/resources/dlc/IRAO/CGEAR/C013_zekurom_1_e_v02.bin b/src/main/resources/dlc/IRAO/CGEAR/C013_zekurom_1_e_v02.bin new file mode 100644 index 0000000000000000000000000000000000000000..7957f3ed954656841cd30c16d0f6e18fd58238d2 GIT binary patch literal 9730 zcmcIp>vJ4em2Z>{cyy#xjJLHS62svi(64Gj~9C z$k>&cRvA|zV@!G-_VJC{Z+$wMk(rSLLr6%?h$;GPosg@ypgwgb)r@!@`>!Y8dpjxDP^LN zv$`C`%M{jgdt2Jer7Iaj)eV#npd<-H%1Pk-n&FM@YYXo1K5iJ}3V+f7gHb)d+bGgJ zugs2mrEIYF|3#LR=O71Qoj2s*J%(%y81jVy!`e4sWSl#29~Q9;wt5qV!}4&L_z#7~^Ka7|NJNDr1hPGn4nV zGU~;B*wTA2CUeECAejsk%@^~T)wxX{n+Q>N>JF4_9hFs*64j1ORz!JeiSx4N3^>p; zr3OOOp{NYVzws6%*Wgb1P^Z_`jL&ve_x6%(1alVg3ayZX`GUo^Gk;;gh?hQVVsFiY ztO5pJj9H+XDHzp#K)N86!-T`;I0;$bQZ^-oxI@E1l#yvtPmI+Z3%flCW*ATr;{s3v zSP5btk-Zbm7*(edR=C>?*L7ziAOXu2l{HOho-q@_*k*7GUCFy+qgv{M#6t^<%9!tP z{ODOVs>V@cygE8isg63Tf?rkDWknSwk`P5%Q^i`PZdO#OmR}wx9WfzYBK3nUc+UuAy1^;uv{TgcCgwH@(iFXx zkXW~5amJaarV;c^oA84h`bjCFC zm!v`~V-|QHO$dravb;zVWL01pl(ev?lK_q{vWibJgKaOiF9fWCQc!t&xS&)m!=_me zgaIv4kb`0j$RIz0l}yXY!L*b$w9v?6W6`{V0V-;^Tv0a0hgNJxHLO5Y0k8%dydo8V z(;`rs-Pn-nhQ1@Iue}7u#^QAhjy zNMqu6K0KJKh@+)&w7?#tP=<^wr#$DQ(JU~`47XZB_;W%$(8bFOD4jslbLb1bEY#4# zY~c~K1qJi1RwI}%r!x6G#>r#;jDrVNRO$k@bQl7oKWbO#aDFe=eYHQ<7!jh`N{Lcy zNQ^dzgjG(9awRQfv)wKCF_Y$V3}Z3R;5{Q16|CZjb>4;+NT`lRth2n{F|lew&&L7S z0QijX4qy(H0kuTJzU*5dyNyQ8jT*wt8ts9q)A)Q*C{f1wufq=T_X;+lQZFAZtK!j8 zh++vx;yA3#k-C&e0azRmvmPF&Fpb$?pRz&a)~m?E9y116rN$J1ogTwi&qVRyAU1)q zRyR<@-=dB<>ADOWbKTAr*IfYSfz$YABXQTr&F9!sDm$R;^9KHA>Z%T*Z8pb|ILdJctc6!zVNxyuK0Ig!JFxelG& zDNZUuWrecb{D*{C=%rhot0&8?vYLX7j5D^C+RY4zR;zVM#w#(^ggW$qIjc~f7p+__ z{rpa$x|dqb-h^zwh|XxCnv-g3D6Mk2%kBZpfmw5u76;T|OQ=bk)FjTqgi_#I(@v3d zcka$T5BH#*?zVH(ZtqlT;p#4k*aDtLE0z|+xjZI@yPU?umDkd)d!mD?HNMsf_wotF z;%Z_+8aTTrIzqTt+QM}k0z);}9^qI6Y&+Kz_Gv2sKV+*R3Wz+ngwZDwO=r|ldZeJ5 zBW6LtCKA3|Bk=r2ps4$>3cJ3+6CRKrnZgVZhThTu6pwbHair8q6Ti z?*J+y;(9%vn<$Jck#c?LF((QWS=~SJzBU`xV=rCae@<5s*W*OYhL|34UB;~Gy2Ax` z`lODX+4TZaPy;sh|w^GC$CSI4>tcVim>wTk=;oqM|v_y^E~HN716#@I~81o6(vvg6bcAfhZr zT+JckI^wbz>ng*7aU}4~SSB0}2ISJRg;s41Tk&fd9|flZM+|%}6WpkZs^gm!drO>_ z(TH0tGrA_j0oaE1=o_=KrW;=C4xHR&VmaX)FT;VSiL06rxLddbjiW4!?2Z(GLm_jIb@Rcye=q7uHCA7Zi{8X z2Xd8*SC7@G@lpF-+`*Q;~v;LCHBNYfP{E9WIfh^is9f!K@RI!vxbwU<3j8cTH~`esUOIML2yE5lKGjQ%+rZKy<2D|*mv~~%Mz35HV`{M#+LtQi#FY>ZebFk`AT#e7 z%6Kn0wseMhheE=6i9UCzjL7fS4bz;eV{6N(Tt|`c<8`B&E!VRJy|)Q(y%LSRPBEg9 zw92d-b;E*qWQb5hBYlU3on4{u&XhG&i-`S4JdJ=_jYcsw!Yv!XYyK!uYPMzGo|pMj z04$XA`4>@DLQP0D80}FQ?V}h4Q7BMSd0Z8w>b9I1)+Yb zh2O{LWSMvnyzD8dp}CUk1f`*VfFu}#-UG<_pp79pV%!zs8q#ZS zOBziW!3MrN2e%z-jGaIb(m+Jl#iiy63(k5(@WTb-BZ2Tph(|alpGT7o2|m1drrSfi z48}mTgv+y_Kc5ML-6EJ5oy8#C=LPPmGl8L6h;fvyJ6(b& zehKmERd-kJLzj2u{yvxMK@i#K?RHPd2Df1+es`22Y;o}|)@u;8Ye((azt*&NfueS4 zw1W(;{(q9*Y<$oKt_9GZw^i`^U2F3_%RDYwwQ?Z|i9e0F0t?$hCs_M7B%JR^2cc%` zDbn|Gq*!mhi>*WmZ2{IMNpSJbt8A)L7r4sA$C_Qf&FK6_mAWGyw}OeGW{_^Gr9Dmc zv+Eu8>QYBFm#`_B zxB%%K+=)9AF_n6z{jlg<8tUMRMouY9d{_I=~N&um!rtk>|G-mG^m(c-U-%k^Ia zT3*|m^RDCfocHxa|2WI-Hj?ujo<~I7*QaObS!!%DX;>AH#4|T(R`N>+2PX8?7)Ca~+o4 z^a5|u`;qtKsQ*vA@6ru=nQqc6y21BWI<6Xz$Gt`0rytM{X@PEfeF`A|BJd;nx%Uh2 zHSd?+ue{g2U&jorN_oZJ@P6a{*883Jdyi-OZ|@J@AH6rdKY2f=U(jpxOZpYPPS4F< zU)68r<=6BE{f2%^zoXxCZ~6oMkqq#$h2BAT(cQF_?x6vC?%8b#{$t5`ly}m0+Clfy zyXf8Y9=eYVe~bSP|1SS-f2)6wKj3fk-|27nclh@vJL&0}_nUK`_b&h4{)3bulOCeI zWKoue>0x>=jnF>YPw%7m(+B8-^x$&;1S1$BW3v#NX%d_uuEg z-~TWjqQmqk<>?5GuI}cAyMB~Dg5O8!G5Q#NoIXLHq)*W?`mle7VH;JxyPs vf1!V+f1`h=uhTc^$Mh3=lm0~S@$d6L;D6Bnlz+^B;tss8Nby#4m+SuviEWfi literal 0 HcmV?d00001 diff --git a/src/main/resources/dlc/IRAO/CGEAR/C014_reshiram_1_e.bin b/src/main/resources/dlc/IRAO/CGEAR/C014_reshiram_1_e.bin new file mode 100644 index 0000000000000000000000000000000000000000..59b6bfca6cfa5061c87a6aace0c1767ac483a4fc GIT binary patch literal 9730 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 HcKQDS4Hje* literal 0 HcmV?d00001 diff --git a/src/main/resources/dlc/IRAO/CGEAR/C015_vikutini_1_e.bin b/src/main/resources/dlc/IRAO/CGEAR/C015_vikutini_1_e.bin new file mode 100644 index 0000000000000000000000000000000000000000..73cdfb331dd6d33f0436250f263643749126d7c8 GIT binary patch literal 9730 zcmbuETZ|l6na2z0i6JqUv9ymXG2;-x4i_mREA7iRa2gX&kEb6R@rED)C)|-7rJY4u z0?RsWoV`4(lo>4&bqbp}-GhVBNNc7>iW3@QrppNrqkZckkl_}XZXWt+BTe)BpHtOc zJ?^%X@J)UFo%{K2=klEif}6n%4TOa$IM%;N&ICcx1QoLtn5_=Q+E%sf~-RTK4XuK$IlDRYmtEv30) zfFOuw5Uqn~9fK5nos#HQr^9!RYytt31d>3KYl#VxauqBD5|?a1HXs{#=??^z2TxIQ zfT<{Sn90wq$P*yfs4i=h(Uij+$$G1W15%WC++uc5-%ewsRR1;F1MOO=nlWxeeVt0qsJ&**K!&JJX9HaUyfUe4|ks zUP@9CR6NqSWco$Z9~d(jKn8r!?zZXmx`Vy$c5g_#yFTCD-QMnWc5y0bvWrt~Zzstv zz6HUz_QRDVIoL{)BBTHz3zR|Qv0NodjhV^pDw$-HsZcRB>ZI4qPE(s+Z)uHuoqdM3 z(LkL}S9;#tf^I<#)a>l;l}xu+G2Oi$I@$xL(ZOH|Z4Q>eiJ&zmGvWFHiZ9Td_O6fm z4JHUvsp3x=P)~2{$Ocw4rqAbW=Q`8h*%B&Sz7BYSKEeb7CJ7|r6C4DwWP-iQ&_V%H zJXk0ciU1^|Ewa=Wk|G8#6k1SPw=(-1Z*vxJf+GAnX8(~+WS-%kqf8F9mzrbM!0hyb z(#{?#>CvO_eOh~=^NFHlmVE>nl`fobLvchgR@{U(8|7jgw+nF`Z-NeTa%A#2O7jBE zuh2V_ha9^o&I>UR1F33Ha`uIFF?9of#uN4*WR*`MSKhyAG7hIuf%Y8jS2^tr_q6fS ztVK4}_*lbSR;}ivwWYMyMhQd|>A2LApJ}fskuYojKQM7~uolMyl=q4hNOcBJks$vx zNcq#gks6ZB;2eoMHUQ6okAcc%aPnmRfxGS+V*T#BRKEe%FM{>U@tJx(zK;Z07dAHw zNxZ(9#LXsjy#Z~;Bt$o$*r*`CgJ36rejG=#Cg}!cO%B5b1;X& zgIovFRWT@W$1vNR$=yz|-|d$AgD$Gl0$)OHON#~X=kj&87ob#k68a^+l^?a_e5o36 zMCwccgJ&u=UylK#$pTBc{eje-B zX!0;9-x$K(ho6n_Jv{&H3NzLCsnu3iPS)$Tt- zlZ&<5)wq7u4YmF#j&LzvZ>&Psp^ygt(ZD?#xC?OHjHB%Ce4yIcGzMRH{^v%U^n;O` zU>jR6VDbePfsni_?{0Vc9HD*gXnnu!yR*W($dakDs&poBHWUJgKi^Z6IRlxEI4G^f zL8TQ_3zpXWndY_|_Or<#zKyH8^H3Zg1!Lh6SCspi-3Ld=$46ur53{rL>$9_S@$Br6 z zt8ZYq*9uEp(8Zv#wHR>W4T?LRYH??qGrYTN`n}z~{-D4BbaKX|W5pC)q|(ydOGs7H zA*{0$G(st+EUm<4Nhw(*m%HOr%UP`^ErZ_(96v?V;-h~fX<;CB<1_JsnQ4_7w7k|# z${TUwOJk;XIxjH<(*wpd!MJULxCzE)hW>>(UMRTP*jOdQB#^#t0D1kB=FDMVM1>oC z@Ue>D8^gCDc_fmT%8BNF_UA`6W2sEumV`6Os199WOZ3n-$b(#xk>H4IYTu%)EG$lv zW0yQVQH{H=@1vRx3CiWtg8M}Z2C)W_UVNTb$v;k;x^*;(%N;n>2xF& zI5kK$!Xe}{WlDvV8Hk4oVrxyH7I#h9a(lgd%P^gy;(lUA#f}Oc1sNqdivNeP|1d@? zGn*y_WrI;1me>E}r7Ft^r(7VM0F?(Fned}mh3T&gR3eOfr_nvi?UC%8JPc5Q>?N*3 zg@Djl_o1qbk=^@^Q^BI-&rc~XC6C+NNhn6nLDAo7-TtyuG^T4zuS=uh8;bOr#=AqR zIe?LF$KF=F4R7!}^oj{>%H9^PCbbLuH%u#5giCi{%-{dBbt0*9){wvC_tBilcn=QZ z-dUt)r{i(`X73EsXb*n&I7E9yn%d%e+-so2cvzE(7k4hEI@g&?^D6fmN~VM4xC!Lt z%C8HZ2`(09Z%~@)d|b>Hd?OJ1f+5}O>fNJz4Vv_N&nJ7`BD!M_RQ-UbjVeH%c&R38~7Ue4(y|2{AlSEundadrA$fUeM+j!CaCy$`v! z0{`ac{0cc=g|A2*d5}&^D#+G~#>i07lH%(8TdjL<*!R|{GhMN71$5^+8TO~X+n-5H zQ=ZDF_~O%45hJhr7&(1&8E_HS$^TkFve8~=Ss9e8Xb7*2_@O-WDmt3D5UcgT&G8Y~-*aJQ;q zhmr)`4=UUbirm!|Cvit$(VU~`I~2JX5xwZS;o><3u`ZTXOzZI#Z;V0w%HZkC@nBtH z;~62wP;8rfyU-{TfCmYpP>;({hiBvXyDM@0KaXdzvzxlU3^`56TBH0l(erYh_l!~1 z!vwo(GAdcQOu%}TcZ@oe!Pn118QjrFp`(`uyv@Bh5MSz#oU}^pl%NvbVNz@o1GJMh z;-$RSNte*0yL(sYfGV9cxpIc#<(~c4^tnb3Vy;N>ah$m9d|Yc_h*~d$6a7K2yXcTU z#5=sH_Iq8by3`8AAAOcg$DQvpyk#>9w%)(qc?7KC4JE27lkO6{Q9N$qf39Kjn%QXA z;QKW9!*kd^#s{!Wy}rWt_{s{;0}nk8{^WFRC4G5{V|01=f8)Ck|Ag0uxIKSIe3aN% z$ooVb^FT2UbMuD|9h#q;n z-Wal2ci1X_hACaq7_bqSTpVv&EoLF8aC?2a+3TJ@1e(G#ab5-tFs$^6rrs4=2(BiI zlqvRlqbvuT*-W`I-W&Y;f*0BU@&sX0;al9k)+t@h)f4jg^Eb~&gE%VH7;#k|n&qY+ ztSUbi3~SR0-R)99NAl3Ni{~YeAid9^B_HomR+Pru!my|-*03N$u1cUD4h`M3reyB zqy7iB|ca6H0UT!BO~sVdDJB z$?Ig#?ho*RHPAaQ*fKLk2>KJl7* zUkslN8)4J+S>^YKE_G?&7*B;i+E=WanebP4em4#u` zuG$~kOZJNExf(tbJ{$ftY^6iz$UxrugGuHEw zYwD8E+Mn8%{h9qaTs&v5+F#gB+qQ|lW}kOGUL1Dtd}Q)u<-ZPp6TTGggue}c7ydr{ zL-@z=zl0PYzI0R`hQ07F;fwY~`)lww_9eSxe`|kde{cU_|7b5< zeAzy6F~oi^KbhH2Hpt2)({oS$$^MzXFWOz(wSj$ueWQJoE!Y`*3$q;nrN1tt2r?TS zaMFpxH`}+^Tj_n9J!s!*-)7%#-$9=sdPDTaNIl*ZiEr623=5QqFQwHt3}>QSq65*J zqqju2Mz=)=qqjzHi{457oSnCa?d|qm_6~C5KyV%|JYU&3tKqxtd+dAd5qs3$Y2Rnx zZ$Dr^Xx|ylMf1_&==MmnoOhbb=Jj@Loqty(`NHs0ct`Z^=snSUqa)GL=+5YU(fgyK z{jmLr{iv%y?>NocUvfF=@RFgTg zOVO93uSAd7Z`yC!f3@GX|7OqFOBWusb$j+z%tPPLJwpTH&hXHz(V{+>(b%tR~>D(3lL@W$3i9) z9&vO?K}bOurVs2Nai}bkZ6gC2?V!0!d6)N`_PjqpYK};+hk2z6+L)DgR+Ut2VYO8= zt=4>mk~gLV#ukxnW?VNWRA$*U%FC23NvC}fpEpKBtYZsCXI?DeiRP}15Qm-bJH$73Vx=B^`Cd#T#T}`%!my}xhU|1=r*S%`F zRQFO!%|-+aU#HxifPli?d5~4|P4^WctWQoaxZZ++Y;XRqF%=eR!OSRAEthAYO9~J0 zqDYypD#7$L^Qb03>VXbTG%bwk?^ns zX~xWKq%+ZZQ0$uESO-g+c62tQbxgy67CsnXY@k3d?S38fjuan#IvCh8*w3Vz_PnZM zBS6)!i+svL*3mT&y_-b;5_6>qHAh8FDn*}CAgdoC8W8Ep&gv_*K*>BfM~R?|PLxm@ zWoRNw6UnbeDt!u`2A=_+1y`EQ<`Ta~bA@!4&jqv6G%0rpHCvKiDog+n9^|{Zl%&y8 zvc^lfE6u3#fS0s_R7iH{+!c!Y*D}Yq1BtVgbaeA~bbh6MB35W0E$MTyX-xbSrhbZJ zFqV(0tr!Map>t~}(RV7$K)aG=$Xs(GVkjp1kLUgcw@1%;82?^}WG zZW?TMQ`?U-oNXy4GBa?lB(58=Z^leYUk6Ppn)g;1PO7zo=Dkgu5q|D^+BG2?Y&)m* zRd6ITdd$U~&cjOs6eGl((fLtyzY9u@j(Z#(GPZ!!3*_+!pcs>VqehSEYFTA4i=v(b z(4}J^2_149sOd*^*%iT^%niW-+=k76^N5SdzQJ34vH`UA7wqNv$}*b!JHiP@Oz$l;a8n$m8V-R;Rn~oWZgy6hC$Td zV;i`aXtB&(sNM&1_|$ooIjmaGnVEeAeFYhPu9bA!XQy)QvnJU-VUmG1DV)N|f^Jj` z8W&P6ueN4}UuYewEVP!DqCl;JFSN?#c~Y@oyS^{7RynYM-E~MECtq4Ucf8ipL-Q>i zTm1LPS7TE7Ip&0Zw{YqrgeURuZUf?bF{1`L&pF$uW*S~Hl4&)QUrYJzp+qsV(c00Y zsP6qK{oi`|Hc}&TBpvmT1gU0l8QvN zkQs?=|J^;r=PvYQD)QJZ+{?JsuP(AUza6yO%DvP^U1XePl3^0YIpwulDSUJQhQ-kL zL$CifLhmo{<#+3cTn|;15NVW9i9J-rpYN`>@P?X8lkwf41%uaLpMgI_iOgb3WF*jE z-@0_P#sTl*m$+3@ra{3W`d$`YLabvV;3_XOlgOaw30RBb4V?b#<2rjCk~MxEIp^ezOK>JM zXnN4@PqYrjwI$YA# zXCTw45F#-rD3rtw7`fE_za`J9?<}4jq3AXPnr0 z55$%c9?5ZN@Mk?$@5VuU6j2Vn_NttVgocXtlOeHCXuP$sfwNOZxfMPS?n@Vr6JJK~S@1Mi1C`(|h>qL`E`d>{6bMu*>;z(S8SDU%_(H;OmteOPeWYAI z>U^M|#2YS!gycJkgz!;ZEE6P4L*fOABSl%%BBM&o5CxkJsRX5;NfK@Yb`!;yVpws5 zBO4)I9Rw2B?u93b!SKXP(WE!7j6Ekvi<+rRWCHB((_P%Dl4^JyfQl4$p;6rlU3Li; zSC&`>;v7O8=Cwiv_C;>|qP-#eqabpTxTP28#PBP!1j?EWe#e$Zr*Nesd(N?_dyYjf z6<{E1;yY;(LWy&Q}?7RVdC5jZZ_*xiq^dUB5+WqSZsrHP*QV%GHMS()FY163uK+mznIGDuYsH3(*u`#`(Wyx49wFP>!t0nf}8MghiA153~T+JszL>8fl>}|mm6CGy46U?;? zibI;FY=g;nN_;dbQhZyO=6u+Jo{wI|zE5%pB;^R0lo%GFCQssx4b7WOzGS#=Uck+m z7Qe99_@ERv3|GJVe4;+u74ci7AJ#LQ;MGXv@coo+El<7!->#=dBBRw$;)=DS1 zR*7_09sNl;ZX>`&Q@!X07aITh5V%~Xl8~6Q65woEKGKA{W|d9F{#*ABaz+gr0a{F z4VO42UGH=@O4x~!ep?}Z?VP!Y+w&waNOOWiSa7@sBHV_x_~!u~BVz$<$};#T~iF8lbMRmCq?MOhOP zOC|j_R|DAuuDDzah(}JM^hdfe<%PEE5gK`fMqtdt=Mt=Y#rUjpvb7YUP8DS%9L{@_ z@?Q)03=$iA)eJ)>ggNFB0p5;F*Jrr$XzA>kR*~qwS9kBJlWnyw9S4!4oRfl)Eafi4 zk&^(s+WkL=RPHk5KbtK3fxl~jl!Lj=kP%+@#d^{ie>1fLP|ho{mvVibV40KhrwIOa z59Cfl3TPtOTXXht7nXM_*xstQ-zh49IdE5Z$?rncQG89|SYTK1C0oye zO5oE+7ViJ=A^&%Ck3N0!@sGaz_YeKUYoAEx9(edaAN#vM{5=UF-ZrF9AHIht{i)u- zkDNY!56>e}d;iw}djM_VzxC&w|Ji-i=l#1spYt#H7yV0q+ker&d<*>*|L)J{{V(`y z{%QNP{YU#x_EYv5yVvGfyZ5ZvrhU#nZ$EAS*?y+)`4_wCf5pG%zwE!_f7Sn*|8;+_ z_EmrH`G)^Zf6MRq+y1xwZ~NczeQRxCrT(1#tUYf(XD`@`_L6N!W!-i!+P&wpy<$Ia zzw3X`|GxhN|A+qT{*U}0``!AV_&@c3=KtLPh5t+cS36}=)4wEd`2Xc^+OODa_GSBu z{i^*MwO_Zd+Hcrz+AZ6$+xA;l*_a&~Xw~0?1o z6dVrHP+#*$f+NBGLCK!9kJ~3~nI0$Xq@A*#xpJrHN&9~L0cfAHAG9B`AGUvFU7HKW zgJVHHcxUjg;N8KM%XfMn3LXyL6C4j7f$qJ*qrv-vn*E5qa`~h7V|Lco$(_H;7*Pygig{{_JWKqLSF literal 0 HcmV?d00001 diff --git a/src/main/resources/dlc/IRAO/CGEAR/C018_WCS_1_e.bin b/src/main/resources/dlc/IRAO/CGEAR/C018_WCS_1_e.bin new file mode 100644 index 0000000000000000000000000000000000000000..1b865f9869f1e6d634a551d05b7508e07eec754d GIT binary patch literal 9730 zcmbtZU2q%MbzX|Hso0VwE7KW2$QgN;24fjgWL}VXVLA!{0{M?8B!rpvp$I|Kiy(>kQ2~x>Jp16c#0gh>rcPXWPphNCjj`PFBqJC!jz!%Bv zckTiZ0Fi2{9`M`y|9{T8_Z({4{fQT{oO7!R1T9o(NpG2?=2_r)fgjIg@Ifm|Q+}P0 zNU@4MNEH>QB(uw1YBauiC4KOywQ9J^$B+FD78+{wmG9kv8S6&{-KtG}`kZedz6{I*OMnMp9!Q)-1TiU30=EH4I0d{+ zqE2J z{IeJ@4)g#qfbuDfvvDjki~UZs!NE8i8|wjLh-3Q`DK?016lIXK7|W+oFn|Lj!Z8_` zuFGV}?Zhd~V@8;=i!2veE*%`Ab>3~{U*h2gJ|^A-j}osgMseKT?rOB#UC?Xu$5ij3 zIC8LtIO-7S90^�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 GRrY_%^8;=G literal 0 HcmV?d00001 diff --git a/src/main/resources/dlc/IRAO/CGEAR/C020_shikijikanatu_1_e.bin b/src/main/resources/dlc/IRAO/CGEAR/C020_shikijikanatu_1_e.bin new file mode 100644 index 0000000000000000000000000000000000000000..dce28607d71f50c5db4f11ae1858d4a01b059d69 GIT binary patch literal 9730 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 q!glXb&+BAXi;^;}-f~w*LbgN_X1; literal 0 HcmV?d00001 diff --git a/src/main/resources/dlc/IRAO/CGEAR/C021_shikijikaaki_1_e.bin b/src/main/resources/dlc/IRAO/CGEAR/C021_shikijikaaki_1_e.bin new file mode 100644 index 0000000000000000000000000000000000000000..4973233aec3b879eb33eba1baea0d5a498406c16 GIT binary patch literal 9730 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 literal 0 HcmV?d00001 diff --git a/src/main/resources/dlc/IRAO/CGEAR/C022_shikijikafuyu_1_e.bin b/src/main/resources/dlc/IRAO/CGEAR/C022_shikijikafuyu_1_e.bin new file mode 100644 index 0000000000000000000000000000000000000000..8664eb59584098f53c42b220fbfa5c8eb96518da GIT binary patch literal 9730 zcmeHLTWlN072PCs9(I_7`ZCZ8wYLGp*s)_^%@2PV5MZh4D@_22A8lcyUu2a>TlhmG zeF%_+0%jko|D-MqwR_i!(>M?JvjwHnucV1h#gqw~CZx6da+CPM1n7@b)tH_;yF)D> zVrao>kiwUH_THI0Gdnx?%wuU7t;K2~Tyq=z1_OoxgL9D0P}0J`jSIABUb*~M_}<-P z82w43J7F39c#cL;AOhVB^ue%TX!!1=VLWH8F%njv0^0`iK6@DtJ)&S3g>hUaE)$m$ z3qezH;rK+1{zy+UiOcFkSzKJ-;5PdaTW+HdZR+~k zk0yG0@yuRhyR|Fua~X!u1Lg#CLvaKxhJ<;*oM3KnAO6R}F^?O@xCv!KnNS_$W=Cus z0giXXVu&jM9mE^Tk8fs3WP}jabP#WZTuWJqawpQ0$bdZ=6_egPm~gS&nTd{PV)JIl z`WR@iqO|g%5_svM?@a^_Z#?sB(;S;@;=P-Npkok5paeAASO>g};R%7OM&hx3nOG2O zI3~rUJzP`3Pm~ecPS4wDicZrMTb6-r6WK~wbuiZBwhyI+kIv0|QRAX0c_VlhKip=(n1TVf5)?5|BnnpvsEYP$9h5UrK$Yb=3h4`=*9Dyp z_6dKA_aq`l0+Us(-I%N}G4XqPBbfV9 z`vb>+Oml#@6Q;`uZX?2rjVv!avH4icoANx{o

FlATJG?19t;+hz=0 zU>*c=M9QOiU=iu7z-i!U;8W_~2>UH5m;Qv*CFshn+^3NT)EP*DGIqEf*g4=bu)t@` zg#ZYpp#f8iejk3n5%6Fbz`g=5U{U0?qfXLDGbR{mG&rGwHN>8Z8fDvu4Gj)p2T%Yt z8bx>F|5anHF7tZLCVAZuFJ+eH%uM!7 z)AwjrCtN?^!vu5+^DrGCn3L6Wo#F)M9JU`uDkahYjwwD5Fb|ix6O>2|a@5$!O*63q!j zIm`~qE2=xGvdpn+s7@FRT8 zEha0KN(g(OqCg|mX49dxjPS*6D6Jyxm zZUQ*|_yAXA2ncQ7$Bj$LD~J!nkCTc5bAWLjsS3~}M(2^eB7AP_21n{}eGeYfq%}0w zuJD06#3$}|LsjMgkbr{`mIM-sLLNL&m{~IB82M}ukSOM7n0pNa8f5Uky1<85kbrM& zHA+q*lqN!CB1BDNWRBgGxFoKSaKkeo=b{(NWD|J{o?MvA<5K&n@}oF^DdFfN^XOO@ zb@dyQ-~yk4LgkVu8iXsMP#+hB^GW~?j)tnkZsq`o zO#y+bk084Cxr$nUs(Y$lL#;XVol(r=ER|oJmEid-4oZu`ajB}LJc}`B$XhrP@;PY^ z;IooKV?g{Igi_+T2AKl~HN3n$3tTP&YH#LaSxZ6JV> z{Gs_w0&6tl3G^}+EUO>b4kSL;;M{%q-4VDu0$=bE_)^!1^B5R=Fv^7_-$78ovN&%`{}GxsHR&RuhhAZ zIIlUcJ8w8|*5%iit8*jCCEj&TJ7=7{Gv~b5Sm0B0K5#yCt~%G8>&^|Q;(X+Mykd=) zox^p$QF?$Lq=#rNt)qu2N{`T^^cd9@swUrZ-mcodF-lz&rzdC=ZKfw_3vFGoZe7J7 zIZ98{*XbMdP5Ksno1Q`at@IRaYrBl2hn-RS9lc6N=rwws-k>+>DE+?e-a8o2QF@2o zrPFkV@-#>9(OD|oM*R&Gp%3Uox=PpRI^CcOeMBF(tY6E>ZhD*^bRTlpy6fDB-KhJB z`>6Yvdr!56NY$!+cb)nH-A8fv33rpb*?rR8;%;@Ha<{qn(lO__bHX|4JWi*kPfnkh zK0bYHTIZc|maS=re-073bcm+uWjai+ke}QB{TLmm6LgYJ(OdL3{rR@*|Aqcaf1|(C zKj;emlit6L`iIbagADf`_g?otH-g{&?gMVSJwJq3=N2}&8(q`=Dr&EBHy++_xV`$Q Wjrxw`I<$&k&`BN0)o48bRqMaigRpo2 literal 0 HcmV?d00001 diff --git a/src/main/resources/dlc/IRAO/CGEAR/C023_iwaparesu_1_e.bin b/src/main/resources/dlc/IRAO/CGEAR/C023_iwaparesu_1_e.bin new file mode 100644 index 0000000000000000000000000000000000000000..0eef6da1f246d8fb21986480637b6b25b30ed334 GIT binary patch literal 9730 zcmbtYS&tk=5*}`BW1w3rq*?6>I(^O2Hi%(g7?3>U_RSa!Zk`sc$M!V7L4IjJthCqhoHG{OvT&ly7dJd_0CwI}@hueK-VV@ySW26Q0U%fBHnMleTyFPRHHnm2i_o zr!i*B)6*a2^w0HfdOENFDTjISJI%d6ZQb<8`lp=?NPmdX8REV~x<=k_#m#Hfn=@&- zQ{Xws!HQv6EQIAkA<`?G`(mMhGeL|O3MFtpMfM?7@^nh34LC(p9?gVMHw58~^*<99 zX2K9>mWn0xwoq~oxE?~*hU^#Cf^V{G<=wSC;xeJ@THm8MTg z9P^OslaIHnvn|Kg9~0l3`9!00*;_pubBv8|GvwAq1~7unm3;Z?ilaQ-VO(bM*fxtN zu>`)E@3Lh4XU&?5b3jdPphSHO7&6Ei`!$qWAMW--7|vE_;lK%^au(w1 zBc9_XK0C|m8H(_7i0Sp8#S6B>m7GaW3^(Ht0@o%#4|(_nbPzfiLq5(nJO;q%^Xdvt z4xVMMvn(PLPYZF3qc}U%l~#f0lG*q&GhuOdX8rK|#B=1VTa=i97X63ZQO4H8SSO7; zJ6ERhYW4GT4&yR`r+lYz-_F&YeZr{&QF41ZZas}=wQSZ9UO4vMg7p*=&-I_dX3k?V z1ZF0=sd!Dm@Y%EpMJ@@E$DOfJFH?*)#|$up9>)hakirPn}J#OW47O;2!DmSL&!7KxMQ(7XANBPoW~1Q4y$EW+_uY`|;>t-4UZ6V5$z| z(%|?H1#qX+GhL|HyBy9sOv31$YU;9{MY7jBSs&e7BnC0sp&VxUpbb*?dKW~00{5k% zzK0;}TLtc8UIiR+tSknf2WJSutaC6ekJcAM06vJu4`2#Lc?gLAG!&-HxN*Z47(RHJ zKfZnp#-pi8fuCD9at{zw@lS6PcY0hRjMFAQm^i+VsGsMNBgXAuMyGI2BG?b2{viAp zO&ceKI58>EbT7Dv$8Ns~(RbxxJP*UTY^&8CF4k%_#HjCH@Lk}=;Od>=C16x)wWWyQ zmTKQaRpt?Jt?wb&z<3dCQPf6iO9k|D_!h`&Vs!jqy?A(hTr>`Y8;uNONh91QqAs$2 zc^LMM@$q^Cmj-;mMS|Q~agkaSvUe8n@dNKEl;H5bQJ2~*UKx$71RGlkhgBF1YXFCXK6+p*bx#hwvM z(A?Q?M@IvVG`SP?hL~bY$7r?}IhI{7r?GB&MG>7k+5;6-8#o){XrqCmfg*=WJf)pE zfDxHBiA!tzsPXUNop2{`3Y^XkvXZ%X{;=Gw9fFx4bky&skNI-f-Vszfm{CErz(>Fx z%!Zs^UdakBj(H{H(W8!>ce2OE+PoI{)nMihp|-QZaegVu)_-2hM%K+Z=gRrS-|PJR ztZ2UPCZF`aqxoL$!OJHfu~L3F@jgW>9l+)t$M3W-yeFfFI83^|iacM8Gx0_O<4jSX z?rGJgCvX1W#4^MMKM*cojOF}ylO@x*cdj(=ROQ=b_KAQh>{11j7uD7FhTNGvUkC%xL z{%P`pgK+tN;J#cCA`wK33&H<)c@+L3o{K!Zjcjvk+$O|Xd~?N5l|6iOfs>~c8QeEW z;d4MK&HP(GeyC%;M*C9H92gwIYZ3lJ-aE}L0G|JLWdF;XQOI=Ce=1_o=J@P2|K$UB=@4Dv5F4YY9`EyCuQ#P22eQxtj{ULiAtdi~PE4 zpG#jJ*X{WHrN?zU=2kYw+N7f_Iu0*WmaFnwbX0y8t25)6-`^Up+Nm-9ART;4F56l<8Z+Y6BHtw7A0CAJN?^mj7 zzf38ix%IwT(@1rb^r=m_hN8(%f%->WW5zB$ za*mhDol$`qlCR`=d&Q@S!>1LF2Q!HG5PZHD_q@Nrd>$$BXW*6u9Yeg{d9&d&aO7-D z-ymM&OV9{-OVx6<9o+!^^oi{RYCYlDd+Y8a9lhT8-zeDoy_fyp-na*TLC z!2NfQCih&@!z{I%|91F`t6o?>RGRn4zpTBr@1MW_$A-J_J_es>f48yw-%I}cw_E?( z&i>cn3l7cy4ab5fo$ua5J-^}THTER4hmtSL38`@e@)h~2Jf6`xK5>(}*&M$0Yx1Og zUA`gTl#+;)IbNm-(iG4ubVAzW6&+};C-kdyoF<)=UZa!rI=v;|mNW8wQaFi>1~>!8GVM{p?B$H`5v96_vr)r zkUpX?p_%lh=p21aU&t@zS^7$TEx(c9%J1a&@&_5hE~FpjPx4~a{w4Xdyexm7xHO^Y z7y3p1M}9$H(pU5~Ic>K1Nq$4$(zp6M`kubi7h!!-{y;y_k7Voa`V(D*mc3*Bi_%(L zqDv<)${$buOqZ#HuAnPncNJY7Szklfnwr&O>+9%xnn&|#o?d|K4YZJMq?_nw>QGmx zE7eu%YNhBJb*;Ki&8O>vg>=2TMqMAw!*#w|pl$%_9}CruYGI)0HY$-o{}<8X2wy@= zP0ea7qb}-BSXwVV)Jr|u*2}a!(y%^#jTLlzj{p!Ko98q^(H-}@6#Lg2EATe zkL&bWy+*I5ie9Ps7*K<1m1(P@R;x8?ty-tns|{+S+8AsI)(7i?wZWQTbx=Y3=ua>h z3y?X z9j0gK8GTrf>oNT_J*A)2PiT^(+S#);AJ5Wr=6If7pchd)LT}KU^hqRrjy}NoTe W{T#ihU(nB^Swrmr8f7j@w0c{IDVOKjU+G!wCzlF?=08MfV zeMZn~`z@3QV~4|r&3Z5G1@veyq*4o9SF(@qw_2@*Ry!>*a>2*8Fpu9(wYh--46}M) z{ER>jn?^O`8B$=poZ?U-SFa#bX{F9!M`tc#M_wv*%1ftCxKpW$m`at!MWDN5I13!k zE#RyY`@~s>uyZdYT{~%#DY@X2+9|2~^KjTf(tmglLpmXCwUUkNxsfN0Tf=tJ5&!=O z5SM+wyyE+n0nQ5yE^C}Gi?}L8yj~UC_(XBM-p8jOMcXUUXPy)NrY!)DX$aFWoT_O! zz3@&Wo~irWpS5i}vh5lsLYEr4u#XUiV{&fWp7qhguB>dw<&`KFZhu>durEZnE`)ao zeRO;8PP9}FEM*$!yH%q!Td!hRy~x3Ubn2vNq>l@C>bMZ@yub>g=?GB|E5a^ztLcTy zM#GC7&+9p2WnGBNz^%A~1692cr^7vr5d13k0ddy?l>+zd&kO8VfF@;NWq_JS$fHu! ze6D$?;RtNe!CsC2W>c(gZ}xDk7=7kzHFO&Qwnp$mKpe+M%K}6pVGO5hh;UAX(>DI? zx#W1Zv(oduaIV-y-HpJ%N!yLa4C@=%o*3-U2~P{7-wVxukG9Q!>qlj$8ztKngKe*3 z*IU?j0?~FHW9J_3EbNB8=GJDj7{{AD*9bx!ra6{9$0dnrB&T%TTw* z)9ud-vFeu{F<&+fT$Rb|BI4#m1b{q7tN4i3Fmi+&LKGoq;7ZeRK>~IG^yZ>y&JO!g zZ?zj9BCQVrQEiX&7VE|#?md~0qp+|URabx_>Q&6BCk%~EQ{?jbwtG!F8HY7LYGd`V+zV{@YHBBP!B>4LjO?*F7UZX`B11(airBQ)rRIo zRkqt{s`KQ1@YA|Kl&qdK`OG7MUYjqI<^5WMWdR}z5R*Fu!8GBtJW?h}#FA}zhiqfz zv$j|{W;@+uv(DDhWwCX%?{1ynvX?ifXZ!2Z%izC$yl!vB=WU2Ju%vOEmSg$4ZJ+Do zKOU>o*;(k!{<>{r8atl6jGdilp+kpiaM*%!v55Z>pv#W(s3FPJksO3v7K+8Lg~Td8 zVOh(N2#AUW>By0{Iz;eI9?X;Lgi)^PhMkHqtHLm6`-!YU6%aP#&8Ua&A+Q9VE3QMJ z*Qf2-^Vn1gi_J}JV0^C=O|Q4OijIRbtY(T&hneEVZ1(BU%C-tTg0%>2BB=lcvcRgy zEtbXNB5s`z6K;I~KAw=jX!sRKA9Xw=PZTV%5(4w#yvTNPVb{u7d8+^#tw4`dA)_54 zdN)M_si;|adn~IHVqI&2V9 z8dj%j4Cs*~O~%1-{dvQw>1$}3j+tu;^zWKh=jpqwPH{Q09~!hF@I+ENDV7fN4X7G! zK?7+*dJvf(2ktvL*LW?CstJ?qn_u0k&p+M|#2MI>JP(hF)>cBS>*pUocp8P4p0Aa* zXT#XWV^qhp33K4}0^UeS5C9^Dw~`q3C|jElDpWUtRpKA7U0uI=^=k9#)&HuzaXKfdj#ZRDrI~_5si&;laB=WLgDeGf}fxyi}~SEgu&0 zdHnBydYI2Q$7?z$bl8i?!p&j0_zhC%k)D}g74J?(L|~HM3DdcR^Hqf7;*2$(ljj`( zNlNg~69Pm+RmHsd`Bt91Nr&O)@~uRcj!QzZT}fqguxf z=>k$(2|cg(hlVyS>nxDEKUYN;Vuy(SIc|$u*1(Ajr{SB9ht-Jf$-ARrP~bq^4oR2vn&>n^po!K|k^bM*7{dPXsi(9~(>4{9 zolN7YfK5fairy&^Y8|=}x^E2Wh12pPo*7Pr3^gRYVd{LM2F?KJ0@r}h8Lce!2IYwz z@I>Oh+!Jza$CW_en+_6pv21wY&&Fwxk?$(oN!LbsZz`~z0QHQM;2hNRyvLOBdC@` zgx=SAz7q*OTdDG@m=ar64+y`LzUOA8+>g%#*DI^rF}iBtn{ffCeNm6TXCnpEc`9ig zeI;_IfE6rN#$5<~cr1v23D@=1KGY#fVWNvjNe?Li)HziaEg@QoUyFq}GuOf~$U6gm zp^Z&jfHr|XE2bU6ATJld7&OE=Zo~(CB4uKOvzoWT&qC@X#-GGu$M>b=2`}ZJa9f~1 zOf1TZ5E662)CVtuKVg4x*brP0dE(~h(MHm1;LfP$sg>9dSGNP$4*Pc*HZk0trJQXq zTef`=X|^-KQnSAO)#c*W`rP#8ua>5ljxJ80JCrNgGno>4EZT@~c%Ye^2J1}W2B(&R zOCaQIRa~%fL0n@m069_(e0FRI1#X^-fIc3{0H^?YE+%xxwV?4u@# zFrFTTR{`JS+$Jct$zEAfVjMxs>{*So*~RSQG$?uqlr7okGH{YXv}IIgv$;cA%L;7} z%w94bxY3SnR|W3Cz|2GwZoyq(^^_n#f*C>Dl!E|_p>YZ+V{MG;IAZVynIKY`gd(+G z3wST+qxp1?zFoHCfbgU2a|2vLx;(%x(*70*yFI{|L}v7Ys0&QjGx!vk0qz3J`e4rb zbkWLudd4aoS$%Xpb1vL0Sh-loAoA8dbGd4+so$it(2_ao$}nRcvG}s(vX%+o6kaI| zUQ<+H_@2dsdazkG{$ZhOJ^)2@l;NU#v{e1IuC+vQe^c$VVU^Y+rtF1n^b0|GI?~ zSlmLf4|lZ>_U!_fiMt4j3eD^Iz^vV?nRUFO2=;Qc2AiP;yUT|$r4hCR%`n z=1@qC)hd!ozSDF&%|y4sE`_}v0t;ckmnrnZOfJL)Id0Z+MAkw`#D!VfXo9TB0u#G| zwgcZVz>WA%H&SmVzLeIOyZY+4I*A2KbcKr5{s-0MuD*Qd*#K`ac?$Vn;bGIbL$m6o zlbADTz)6!Dd|ML87XuM@OhXzGIOC`fXUE1zjY${u!6Flt*T;b05KqQGqb-Q^>iB02 zeFu4lu4_=)(N5I?8Qm{JJW^Yj+PF9A{n3~tqJ{`-ZEJmd1){ftD__B-YXn zoBj5b?RXQ882DlcyrU|ZpF%)Pt}NMga&Eqfhy(LoBrWljAUVvYuBzJz4kotS(Kk0p5w;pLJ={(Vh@i(6Tz2WSS*5{8K2Pr8M_Yku!#_lj@ux7 zgK-qM&wn#o%^Z5I}r z!p)u*UgorDAT-mk5J<5yRbm5fjhE;u=qT84P)zXoU^?Kw2FlA97cy425LyKo{wrZ2 z1HA;_RXcy+E5AHxc@Ffeg|LnVH;m0U^|5H{kg+9@wlIp2JKBJ0&Go3F9JX6$T9TNi zltQF{nm@SSIBI`puqb^M_$BVZFX^DL%YVb%G#BQ{mpnIPO}VP)gHE6iwah#pNXiua zG@$;yBJoo=5N>9{fS3UJ+X#ioY+Qc>eC`11%<{r~#=*zIPZJO`k21#JCfE+hS#*N6 zj$|lc0j3asjKO{6I<;c{lHcvr@w-d0YAz(z239gDs&^s412d_Cg*`l9?f@Hx;cELC zTLj=X=W*3EiYSD%i)e^52)+j}ZbR)vOkz0HJJ3%$~1tRTsZM!4o8?ivXM2+?h;NO5Iu&);|9%mi` zG+F|>A7g=pBnE~r4mSae#ov@*=s!McBMMKX!m>!==Q&z=a0Fgk$ zB754`xUJMP%O|B;6d+evq4f#F!efb0VL-`~5ocyu;iM+f= z|H-Ni$-kek!G`=HR{oKCPW@Q@L?vUd$jj=ds;?ro z`8viwuYM+fE`K3^DStIs``7X}@vFmf2Mw} zexZJ;ex-h`e)AeT*pM%(->ToK-;ehGl6qOaqOPixdXu_Ey;;3Qz4az@Y{<8%Th%@_ zrPAs)^>+0Rb-Q|}+OJZi|aBpy5@ZR8k!TrGl z!Gpnp;Gy9C!Nb9a)Q8n$Du)}|ka_hH^|<=z#2g<}2h|g*pq^A8SD#RyRG(5G3O*b> z7UY6_@R8v0;G@CEf`h?^d?F|WPX-^~b%sv_ht=oQ=hYGQ1vRUR>Wk`2YEC_+O6sUO zroOEHS{+x1G5&MG=Yu1`7lPTKI5Ec;gD;_dE_f;^1xJHp!Iy)R>hILwtA9{mQU9o( zR<5e4d36eBI6blVAwB;|{j>TP^{?uT`i}apdO=-Luc|+&cLpyYpZx6)9tl1Wd@}gd O#M;9d-gtWW&;A!1Q!$YM literal 0 HcmV?d00001 diff --git a/src/main/resources/dlc/IRAO/CGEAR/C025_giaru_1_e.bin b/src/main/resources/dlc/IRAO/CGEAR/C025_giaru_1_e.bin new file mode 100644 index 0000000000000000000000000000000000000000..5ab3de7baf7641540ada3c0741fbc572eb24800b GIT binary patch literal 9730 zcmeHL*>c>*6$O+5S1$kq@*z(&izRMOTzSYlCgGyRiJ^)Sg zU1$&(dHDs)sp2Nt{sr5q;wIVtg`CqE5HrIeDT`v5#A-~Vy8(3fT~43dJ&yAty|BR1 z7Vv_47<+y__F@k|d$kI-qZ%5q=ir;r^&;2tLdQYjpm0z)VNjGu&+xr?GY_l468?4M z7*_F$Tu0q;=8*!V9`-HXZVFdGrO7QZMVrmZ`~0}`4JaYDS<2d&$9eYxR>jYfd%E;kxZS0)aK31Y(`M$SIL zs1YWLAaZEeXZw_tfGWfwPWX$EJ~DM`>O__!qTo7yUI=0(={ncEIKt`*kjb^YT1DtZ zc8;>$fe-QtC&rqDEC810lV75;KZS#w2lCVClxKmm<5sNalNe^+&cS)wQiw%b4+EeO zg0?-kRcjy2<0Av24lP8OlGA$PqkDj|-QpD|T5dQ#Z~=UDW1~i_oOuEUxPHlkA$-;H zm$mq>Y3&1lb@+!b?AxG@)qbAwHK0q5#3^J1*b52aH29M-i{2|J8$=w3babgto4#Fm z?X5p%xBO-Kikvk{tP+2&7yrx!CL(Z!k35Mw+b*!;0yC4qj_;QK?T#zaynO@uu>3Jj ze4t@t3StZ5On9Wy<(SW-{3+G|LL*oKIl6`+Ny z=I9dpAAkZ{BXe(TGwZeu7!>>f!`OJiSTS1UYk~$y9kHkQM z1>%9+%smNgX=4&rfQ$aE%(`uJOxW?M?8g{N?qrN5RxD?bJ1mdE-@Ow#gj`Z!g4h5? zh!xCD?m+RBsS^(cK8O`!riK3!57ddeF@r3Ko$rt$e9LTQ>*hC%gdIl|g!Qm)_nX*j zvaGlGjr+A)H^2Gx&Do|levF_prMiZq@IPY@Srjl}Ou^4yt)gfnh8)MAXDj`47#&oP zW3^{v3S$ctwRV8`t0j9BI8;4}Pn@JDZx1J!r9;LR2I^8Be&QAV08z%id7wWgk3?dcXJU zIF#7^7nYqp4e_+VGbqC@X5f-I68|px@V}u`Bp)Xhx(p><$ITG<56W^k%{?n+rwqO1 zr-a|T`GvE?rhwoxp3g=&CM30p4KQ28896PW+;KiT zg`H05%6P%EY(#D7%8mpve4b@_rsW6SDVXnm(%FV9uziDKIMaeKuVWs1Ju1KA8J3?= zXZ&LNi)UThl(yw}Jj<5v`3>u3n`cayt16C@lUP!x@y^W%-NV>BPg~Hu;}y!1Dnz`3Xwc^-JdX?@Yj1 zc|Ics%1q9DzUQ;ks(&Iso_2y0``bM)V;QpDE0!E%SIJ>KfM1aKpF4+U%(C$wR_5M# z0ht_OU!FMk*?-Vu|2#it_Z#F<%cUJcP;N3t@s}tn=bRWLkK@O47VrOIdd5@6rKp!O zpbKE3$k9B2{UUwOEbxmT{d4g5gOU57_F&-MGXcQxouOyAhW}7HV(!lDGhB1FzdB`l zlsrtflFeizSx?pym0&pDpW@OPr+$(=PWF?%WH;GKwx6O`HL|vn56Q5+BrnS=(r*@< zOU+Bo%grmz`$;p&lPp(#wfqWt>W;@@>ck{jc3;9yMl0h?I`c0pyWPM+KpgvR|sXpnK0T~qP{wMjf{6+pM zf0KJ=Enm+!^3B}QuD+mW%(Q_{rjqrM`c!?UK35lIN7fZqAbZ5>PxHpmi#~-$*1y}ted;}y?k6=lL_c_ zTW*>gX2mp2C2QfQSEV5<;)pBMeP15S6Zu-cksZ^>SMo^e@^k1E>AHT;+%tDgC2QfQ zH{`C|llSD0R`(%2tY6YE>sNGtx|lEJZ_Bd$M()eodRb=%I+;q=!cT8WDyx!7F4XsQa7g)$~ewIlYuF zrG0sSK9CRQL-}K~Z}!Zt*)iLulC|*D-(_2NWLNgA?zhuh={xDo^hUapZf85$Znl^0 zXT#}GI+zZm{b^rX$y)g7Bk9xqdO#0q>dyYN-J3lKYcD2^^~60 zGkR9fp?`Iy*=m+$dDhG-ofgwd*1}KU%A#JDNeEmd>2*Hm&_msch0oBEgfSNg5CIhE?@b PUyjrMWm#A1AKU*1#TmR- literal 0 HcmV?d00001 diff --git a/src/main/resources/dlc/IRAO/CGEAR/C030_kerudhio_1_en.bin b/src/main/resources/dlc/IRAO/CGEAR/C030_kerudhio_1_en.bin new file mode 100644 index 0000000000000000000000000000000000000000..aedea97fc88245ece3dfcbdecfd6ad5c5830a47b GIT binary patch literal 9730 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 ebB|* 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 literal 0 HcmV?d00001 diff --git a/src/main/resources/dlc/IRAO/CGEAR/C032-1_wcs2012_1_e.bin b/src/main/resources/dlc/IRAO/CGEAR/C032-1_wcs2012_1_e.bin new file mode 100644 index 0000000000000000000000000000000000000000..f3df0585385fad4cb4261033c8259e9c4c06f04c GIT binary patch literal 9730 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 IJ{y1WzlF)HTmS$7 literal 0 HcmV?d00001 diff --git a/src/main/resources/dlc/IRAO/CGEAR/C100_defo_1_en.bin b/src/main/resources/dlc/IRAO/CGEAR/C100_defo_1_en.bin new file mode 100644 index 0000000000000000000000000000000000000000..fe503fb15f2e10dc0965df16372a51563cd5c70f GIT binary patch literal 9730 zcmeIuu?fH+6a-LfZ8L>V;C2=%ZQLNE1_CbdK}hlL>VxB@Bm@W$AV7cs0RjXF5FkK+ x009C72oNAZfB*pk1PBlyK!CvC1!h10u#6mNx%aa+YM|}gKSF>20Rka`YrO&7Yg7OL literal 0 HcmV?d00001 diff --git a/src/main/resources/dlc/IRAO/CGEAR2/C003-2_munna_1_en.bin b/src/main/resources/dlc/IRAO/CGEAR2/C003-2_munna_1_en.bin new file mode 100644 index 0000000000000000000000000000000000000000..e2a8b40a51ddedddd1b29b2ee04473b2978fb339 GIT binary patch literal 9730 zcmeHNX>6QF8Gc0Uq?IVh22)Bo>d&}rmO_nZ_eXG{hTTeTB-*t%<0Uo{<*=8tDoE>( zawV)%>GN`=O=>BzxoTQr4>#NVa6is+2vr5j{fCsR9O4I84dfp@?|jGZdUt&`)D?N- zXXkil-kF{EnR5f|R>>(KO++H;ltNA^3@g_;`|dpe%QH8D)22%0#QRvE1o9jgv$og!>p>FcOe8P&-`d3IHiO#*hC7MKlbJ>o;? z@HvJngdc{Ijz#z*JgVoDOK*@p9BmS*g|LqhZZ`O+Aay#48i8_jV2Pxfaf`WnW2|~h zfZVkB-|b{O#nx@LXnqq}QD64bc(br=E4Kf(-v##DCC37R_-j3p`D* z93VQC($scX{|wMEGTW_eWS>S^ZXhh(6evX^?ir?aTP~VUaXH}to3%>u(_<~sIK?4H zt~gCE6X7U!Qn#1bM})IFE`7cuLfp1eTSe9svHhRscZg}Qra&zjkLN>|0B6qzkoX0$ zBTyTu;>;8$w&pC9{b-1u@$;LS=ax~zDlQ9xnusBaWYuTe%z$eJ*DbK6aY9#JH|-bo z8LoYTIU?=b{!lx_jnY3{(*6+FzXihAE52X#EeC|lw8z$p?fT!Q53=XBl?xQv`3Mwv zo*9ovc#gZ*3jIp&3H%kGek;(mEW(mHC@Fj5X*|7^_WhwVzJGvFtfq9jdO#Fn zLy-mQB%uT=f&+{%sHz`G=;5m@kd(X0q~~NGrUX zF#8BbjJcRNh?L1TpRc9D1*sna;mI7=75e`c&i79n29^ViKHk1>ba$)NOS8%xOevF5 z%47*%bn214n;BU`2_ng`WY$yam?r}v{E-8>9HiweoMIxIgs|5qYhI7*NYvL0ah zD92pZfhFQ`UI$uvf8#vi#6bZ$LSk@TNU>5j2&o_k0<1C=PGkhfiDOi*dZ|6|Mlytg z=cPJ{J)Sp`Co+UkW*sTjF1U8jmNyK(U+{*@=L}b_v+GpeC8`|Pgrmgm#66}HcbNi~ zOpzOetJ@8~vuKiD!jy?NB3u$qZBI_HTA90o!;jXo$ZWyJ!u7-QBI1XHl6QmU3OPUq z@foGHWIYn%II&7(kDp6rR&PuxSzbcvzMRN?GPqMpzFaSN z2?^SE!|gGqD7dXPDrL`0a**~D4#8wS5|N+Ug&X>>;y5OGf5IdlNE+wCtLBM{^FXrV zd@qr797i}NwnN4UYZ|bjee-9o73PL^Ip(P5Z-Ae#`jve(a2pTkhYH5C5ng_!V_69T+MZ3bcwjfxP?2oM0g4E zB0+wX$&(YNoLDwV@mL@`-FK3X=OnofMW{Gld0uFd5GDk?5N_RXyuYB5o#Zios!2c@tv$q@S)N_p0nc9Is|^z_&nf^QU~ zA$WIP;8m2vm-dHFM(+|;zD?9{fOp*|0zZTUzU4xXGpYMY`~Gsskwr;!5RAhKe99ps zl4X9#nP8sT35S2(jMLo}Vn0hI7EjMQC%7$G>2p;06Wc3_Zs%FNUM7UY6(fN%&U2*gpUugqMYHOtKHi}Dparnp?mR+;d;b5hO( zxSC;^yje7l^4rO`6a+jFKFD_#ZZslz%d~G397>&z){7|ghsc7-syt%I64E4XC+;p$ z$U4DJ_cnDr%y67;&A)AnaI2r=+|QBiA;+|{RMB*j?j?GJU!3iGZAa)+PxK;PU0tJV_2;anTX#2Uf|9-5%6doy_J{MLw`nYDe2~L( zvYY0{$osX;v`z04H*dQe7xkvEpLo@+)Ax=JTvyWfOkd5!i~_+gun}+fL2;I-6^&zD8rbF|~DT zTd&dBVq=kW!65nsZWQN_JD z!~cl!3S5Ji;YE0{we=DVwv7Au3ciZ3;p_MYzKQ$sEqoi_!FMr=G3>)dcsZ`MwqA)$ z%lL!%K7N27;vxJ9KgLh+Q~V4+$1m`DyaCU|Ww;SJYb%dKE#qhLFn)z!<2U#%euqc! zd;9@^#GmlD@ZU{%owfBwOrcf$FZe4<{vH3oqgcg1@h|)v=i=Itl_y^Kj%)r4j)0qx literal 0 HcmV?d00001 diff --git a/src/main/resources/dlc/IRAO/CGEAR2/C013-2_zekrom_1_e.bin b/src/main/resources/dlc/IRAO/CGEAR2/C013-2_zekrom_1_e.bin new file mode 100644 index 0000000000000000000000000000000000000000..13c2704bf742faf49738c71b49dcee721ddae226 GIT binary patch literal 9730 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 A%>V!Z literal 0 HcmV?d00001 diff --git a/src/main/resources/dlc/IRAO/CGEAR2/C014-2_reshiram_1_e.bin b/src/main/resources/dlc/IRAO/CGEAR2/C014-2_reshiram_1_e.bin new file mode 100644 index 0000000000000000000000000000000000000000..6e11faf77d2260812a7fc438449d083a6aabaf5c GIT binary patch literal 9730 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 Qe`)_}59ey9|2zHv0dE`)+5i9m literal 0 HcmV?d00001 diff --git a/src/main/resources/dlc/IRAO/CGEAR2/C030-2_kerudhio_1_en.bin b/src/main/resources/dlc/IRAO/CGEAR2/C030-2_kerudhio_1_en.bin new file mode 100644 index 0000000000000000000000000000000000000000..e9c6e3b8c94307b8cb99e26f892abc5677e0d1a1 GIT binary patch literal 9730 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 literal 0 HcmV?d00001 diff --git a/src/main/resources/dlc/IRAO/CGEAR2/C031-2_meroetta_1_e.bin b/src/main/resources/dlc/IRAO/CGEAR2/C031-2_meroetta_1_e.bin new file mode 100644 index 0000000000000000000000000000000000000000..e176c52a5026d6acb323e7a0ce7dbd91d45b2269 GIT binary patch literal 9730 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 literal 0 HcmV?d00001 diff --git a/src/main/resources/dlc/IRAO/CGEAR2/C033_halloween_1_en.bin b/src/main/resources/dlc/IRAO/CGEAR2/C033_halloween_1_en.bin new file mode 100644 index 0000000..032233b --- /dev/null +++ b/src/main/resources/dlc/IRAO/CGEAR2/C033_halloween_1_en.bin @@ -0,0 +1,31 @@ +DDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDD4DDD4DDD4DDD4DDD4DDD4DDD4DDD4ADDDADDDADDDADDDADDDADDDADDDADDDDDDDDDDDDDDDDDDDDDDDDDDDADDDADDDADDDADDDADDDADDDADDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDD4DDD4DDD4DDD4DDD4DDD4DDD4DDDDDDDDDDDDDDDDDDDDD3333DDDDDDDDDDDDDDDDDDDDDDDDDDDD3333DDD4DDD4DDD4DDD4DDD4DDD4DDD41333ADDDADDDADDDADDDADDDADDDADDD333DDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDCDAD1DDDCAADDDDDDDDDDDDDDDDD4DDD4DDD4DDD4DDD41DD4AD4C4DDDDDDDDDDDDDDDDD̼̼܁DDDDDDDDDDDDDDDD4ADD4DD4AD4D4A44QffAffAffAffffefdfafffffffffffffFffffffffffffffffDD4AD4AD4D4D4D4A4DDDDDDD4DDDw4qwxwDDDDDDDD4DDDDDDDDD܁̌1A1DDDADDDCDDDDDDDDDDDDDDDCDD1AA3333ADDDDDDADDDDDDADDaf4QfDAfDfDeDaDQD4AfffffffVffVUffUffVeVfUfUVefUUfVeUUUfeUefVUffUeffUfffefffffffffffffVUffVeffUffVUffVeffUfffUffVeffDDDDDDDDDDDDDDDḎ́ﻈA1DDDDDDDDDDDDCDDDADDD1DDDDDDDDDDD4DD4AD4AD4AD4AD4AD4dVQU4eDAf4afefffaffefUeffUffVeffVeffUfffUffVeffVefffffffffffffffffffffffffVfffVfffVVeffVeffUfffUfffUfffefffefffefffDDADDDADDDADDDADDDADDADDADADDDDDĎ̪̌Ѽˋ̱DDDADDD4DDD4DDD4DDD4ADD4DD4DD4AD4ADAADAADQADaA4aAaAdAdfffUfffUfffUfffUfffUfffUfffUffVDffVffFffFffFffFffVfffdffQffVAefETUffefdfdfefffAffQff3CCefFAffefQfAfedafEDTfffUfffUfffUfffUfffUeffQffffffffffffffffffffffffVQffffEffFffVfffffffffffffffffVCCCCDCDCDCDD῱ADCDDD1DD1DD1D1CDCDCDCDCDCD4CDDDDDCDDADDADD1DDDDCDAD33DDDDDDDDDDDDDDADDDDDDDDDDDDDDDDDD῁DDD4DDDDDDDDDDDDDDDDDDDDDDDDDDDDAf6efaf4fDdD41DDDD4QdVAffVeffVefffUfafUefVeUfffUfffUfffUfffVeffVefffUfffUffDDDDDDDDADDCDD1DDDDDDDDDDDDADDAD4DDDDDDDDDDDDDDDDDDDD4DD4DDDD4DDD1333DDDDDDDDD4DDD4DDD4DDD4DDD4AAAD333DD4DD4DD4DD4DD4AD4AD43333DDDDDDDDDDDDDDDDD4DDDD3DDDDDDDD4DDD4DDD4DDD4DDD43UA4ADDDADDDADDDADDDUUffDDD3DDDDDDDDDDDD33CCADADCDDCDDDCDDDA4AAAAAAA43CD4DDD4DDD413ADDDADDDADDDADDDDD3DDDDDDDDDDDDDDDDDDDDDDDD4D444DD4CDCD4CDDDDDDDCDD4DDDDDDDDCDDDDDDDDDDDCDDDCDDDCDDDCDDDDDDDDDDDDDDDD1DDDCD133343C41D1DDDADDDCCDDDDDDDDDDDDDD1AA4DDD1efAffQffaffaffdffdffeff3VAfFffffFffffffffff3333DDDDDDDDADDDDDDADDDDCD3333DDDDDDDDDDDDDDD4DDDDDDDDD33333CDDDADDDADDDADDD1DDDDDDDDDeffeffeffdffdffaffaffaffffffffffffffffffffffffffffffffffADFDfAfAfFffffffFDDD4DDD4DDD4DDD4DDD4DDD4ADD4DD4DDDDD4DDDDDDDDDDDDCD1DA4ADDDDDDDDDDCDDD1DDCC1DV13fDfDffffFAfETe3333DDAUAeffeffVfffU{wwwwwwwfUefUeffUfffefff333DDDDDDDDDDс3̱̼̪̽́1̼̼3333ADDDCDDADDDDAD1DDDDD4DDD4DDDDDDDDDDDDDDDDDDCADDD4DDD4DDD4DDD4ADD4ADD4DD4DD4DDDDDDDD4DDDDDDDDDDDD4ffffdfffafffAffffffdffQffff33DDD4DDD4DDD4DDD4ADD4CD4D433ACDDADDADDADDADDADDADD333DDDDDDDDDDDDDDDDDDDDDDDDDD133ADDADDDDDDDDDDD3333DDDDDDDDDDDDDDDDDDDDADDDADD133ADDADDAD4ADADADADcffeffAfffQffVaffVdffVeffVfffVUfffUfffUfffefffefffeffVeffefffffVfffUfffUfEDTEefEQffVafffafffefffffffffffffffQfffeffQffAff߱?1331DDADDCDDDDDDDD1DDDADDD3333DDDD1DDD1DDC3333DDDDDDDDDDDDDDDD1DDDCD1ADADADCDCCCAC1CCC4CDDDDDDDDDDDDDDDDDDDDDCDDDADDD1DDDDDD4DDD4DDD4DDD4DDDDDDDDDAǍ̱D4D4A4A4444AdAdAdAaA4aADQADQfffffVfFfFfFfFfVC1DD31D4CDD4DDD4DDD4133DDDDDD4DDDDDDDDDDDDfVefdfafQffFefafQAAfffUfffUfffVfffVfffVfffVfffVfffVfcfVefeffefffefffefffeffffVTAeAefffffffffffffffffDDDDDD1333CCDC1DDADDDADDDADDDADDDADDDADDDD4DDDDDDDDDDDDDDDDDDDDDDDDٱAAADDADADDDDDDD4DDD4DDD4DDD4DDD4DDD4QfUdUT1A4AD4ADD4ffffefffefffTfffAdffefdfUeffVefffUfffUfffVefffUfffUefVffffffffffffffffffffffffefffUfffDDDD4DDDDDDDDDDDDDDDDDDDC4DDDDDD413CDDDDDDDDDDDADCDDDDDDDAD1431DDADDDADDDADDDADDDADDD1DDDCDD1DDAAD1D4D4D4D4D41D4CD4DDD4DADCDDDD4DADADDDDDDDDDDDDDDDDDDDDDDDDDD4DDD4DDD4DDD4DDD4DDD44ADDDADDDADDDADDDADDDDDDDDDDDDDDDDDDDDDDDADDDADDDADDADDADDADDADDADDDADDD4311DDDD4DDDDDDDDDDDDDDDDDDDDDDDDDDD14DDDD4DDD4DDD4ADDDADDDCDAAA1DADDDDDDDDDDDDD4DDD4DDD4DDD4!|d JA(NI1=sN        + + +  +  +  +        + + + +  +  + +       +!!"#$$#"%&'((')*+,-./00/.-1 + 23456789:;<=>>=<;:?@ABCDEFFEDCGHIJKLMNOPQRSSTUVWXYZZYXW[\]^_`abbbcdefghijklm +nopqqpo +  +r + st        bbuvwxyz{|}~bbb + +  +  +b +b    bbdb + + + +  +  +  \ No newline at end of file diff --git a/src/main/resources/dlc/IRAO/CGEAR2/C035_BK_1_e.bin b/src/main/resources/dlc/IRAO/CGEAR2/C035_BK_1_e.bin new file mode 100644 index 0000000000000000000000000000000000000000..b178759d5643b46528f70f6dfaef1c6aa312e581 GIT binary patch literal 9730 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 literal 0 HcmV?d00001 diff --git a/src/main/resources/dlc/IRAO/CGEAR2/C036_WK_1_e.bin b/src/main/resources/dlc/IRAO/CGEAR2/C036_WK_1_e.bin new file mode 100644 index 0000000000000000000000000000000000000000..805f92fe84eea578582ecb35c54a9abe6e99c052 GIT binary patch literal 9730 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%0At1GE_f(EeEI?wLgWT0bn`pDVpY6wO#zz6& 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 Zk4kHH;{W5%qngvYl0>>fg6(d^^?&P1U=aWS literal 0 HcmV?d00001 diff --git a/src/main/resources/dlc/IRAO/ZUKAN/Z003_BWsyokigirl_1_en.bin b/src/main/resources/dlc/IRAO/ZUKAN/Z003_BWsyokigirl_1_en.bin new file mode 100644 index 0000000000000000000000000000000000000000..8f59660b83199ad01fbf27a23c1259f94f4d3d42 GIT binary patch literal 25090 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 W_1C|7S9|^Oh2lhxu?GJ5hyMZaPnFF8 literal 0 HcmV?d00001 diff --git a/src/main/resources/dlc/IRAO/ZUKAN/Z008_bell_1_e.bin b/src/main/resources/dlc/IRAO/ZUKAN/Z008_bell_1_e.bin new file mode 100644 index 0000000..6de751b --- /dev/null +++ b/src/main/resources/dlc/IRAO/ZUKAN/Z008_bell_1_e.bin @@ -0,0 +1 @@ +"""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""2""""""""""2""2"B2""""CDD3wwww""2"2Oݪߪݬ̪̪̪ͪݪ着O#""O#"4""""""""""""O#""O#"O#O""""""""""""""""""""""""#"""O#""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""2""B"""""2"B2wwwywwxwwwwwwwwywwwxwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwϪϪߗwߪ̪̪̪̪̪̺̫̫̼̪̫̬̼̪̼̼ڪܪO#"O#?""""""""""""4"""4""$"#?"""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""2"""B""2"""2yxwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwߪϪw̫̫̼̬̫̪̺̪̪̪̪̪̫̬̼̼̪̪̪̪̪̪ܺ$"""#""O""?"#O""""""""""""""""""""""""?"""#"""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""B"""""B"""""B2ߪwwywywxwxwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwww̪̫̬̼̫̫̪̪̪̪̪̺̬̫̼̼̪̫̬̬̪̪̪̪̪̪̼̫̫̺̪̪?""$"#?""""""""""""""""$"""O"""$""O"""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""B"""""B"""B"B߫ﭪߪwwwwwwywwywwxwwxwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwww̗̼̫̫̺̪̪̬̬̫̪̼̫̼̺̪̫̪̼̬̼ʪ$"O"$O""""""""""""""""$"""O"""$""O"""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""2"""""""2""Bwwwwwwwwwwxwywwwwwwwwwwwwwwwwwwwwwxwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwߪ̺̪̪̪̪̫ˬ̼̬̼̫̬˪̪̪̺̼̪̼̫̼̪̺̼̪̪̪ʪ#"?""$?""""""""""""""""""""#"""?""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""B"""2""2ywwYwwwwwwWUywwxwwwwwywwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwxwwxwwwwwwwwwwwwwwwwwwwwwwwwwxwwwwwwwwwywwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwww߇ϗߪϪ̪̪̪̪̪̪̪̫̬̼̼̪̪̪̼̬̼̬̼̪̼̪̪̪̼̪̼̪̫̬̬̫̼ڪ$""?""#"/"O"#/O"""""""""""""""""""""""""""""""""""2""""""""C"CCxwwwwwwxwwwwwwwwwwwwxwwwwwwwwwwwwwwwwwwwxywwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwywwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwywwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwͪ߬ͪߪͪ着#"""?""""""$""?""O""#"$"yywywwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwywwwwxwwxwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwݬߪߪݬݬݬ̼̺̪̫̪̼̬̼着?""$?Owwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwߪߪߪ̺̪̪̪̪̫ˬ̼̬̼̫̬ʪwwwwwwwwwwwwwwwwwwwwxwwwywwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwߪߪߪ̪̪̪̪̪̪̪̫̬̼̼̪̪̪̼̬̼着着wwwwwwxwwywwwwwwywwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwywwywwxwwxwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwߪߪߪ̪̬˪̪̪̺̼̪̼̫̼̪̺̼̪̪̪̼̺̪̫̪̼̬̼ڪڪʪʪwywwwwwwwwwwwwwwwwwwxwwwwwwxwwwwxwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwywwwywwwxwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwߪʪݪ߬߬߬Ϫ̬̼̪̼̪̪̪̼̪̼̪̫̬̬̫̼̺̪̪̪̪̫ˬ̼̬̼̫̬wwwwwwwwwwwwwwwwwwwwwwwwwwwwwwywwUUUUwwwwwwwwwwwwwwwywwxwywwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwxwwwwYeUUUUwwwwwVUvWUUUVUUUUUUUUUUUUUUUUUUUwwwwwwwwwwwwvwwwuwwweUewUUUuUUUewwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwywxwxwwxwwwwwwwwwwwxwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwϪϪϪ̪̪̪̪̪̪̪̫̬̼̼̪̪̪̼̬̼wwwwwwwwwwwwwwwwwwwwwVUugUuwwWuwVUeUUUUUUVUUgUewgUUUUUUUuVUUvgUewxwwwwwwwwwwwwxwwwxwwwxwwww_UUXUUUUUhUUUwUUUwgUUwwVUwwUUUvwVuwwwvwwwwwwwwwwwwwwwvwwwuwwwUUUUUUUUVUUUWUUeWUUuWUewVUvwUUuwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwywxwwwxwwwwwxwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwϪϪߪܪͪͪͪwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwgUUwWUUwWUUwWUUwgUUwwUUwwgUwwwwUvwVUUUUUUUUUUUUUUVUUuwUewwgwwwwUUewUUUwUUUwUUUwUUewUUuwUewwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwywxwwwwwwwywwxwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwϪߪϪwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwww̪̪̪̫̫̼̪̫̬̼̪̼̼̽wwgewgUewVUewUUegUUeWUUeVUUeUUUevwwUvwUewUUwUUvUUuUUeUUUwwwwwwwwwwwwwwwwwwwwwwwwUewwUUewwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwww̫̫̼̬̫̪̺̪̪̪̪̪̫̬̼̼UUUeUUUeUUUvUUvwUewwUuwwUvwwUwwwUUUUUUUUUgUUwVUwWUwgUwwUUUUvUUUuUUUeUUUUUUUUUUUUUUUUUUUewwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwxwwywwwwwwwwxwywwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwww̪̫̬̼̫̫̪̪̪̪̪̺̬̫̼̼̪̫̬̬UwwwUvwwUuwwUewUUUUUUwwUwgUwWUwVUgUUgUUUUUUUUUUUUUUuUUUvUUewUewwUUwwUUvwUUuwUUuwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwywwwwwwwwwwwwxwwwwwwxwwwwxwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwߺߪ̡̼̫̫̺̪̪̬̬̫̪̼̫̪̪̪̪̪̺̫̫̼̬̼ͪͪͪUUUUUUUUUUUUUUUUUUeUUUvUUvwwwwwUUUUUUUUUUUUUUUUVUUUgUUUwgUUwwwwUUuwUUuwUUvwUUwwUewwUvwwvwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwgUewUUUgUUUwwwwwVUvWUUUVUUUUUUUUUUUUUUUUUUUwwwwwwwwwwwwvwwwuwwweUewUUUuUUUewwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwywwwwwwwwxwwwxwwxwwwwwwwwwwwwwwwwwwwwwwwwwxwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwߪߪߪ̫̫̼̬̫̪̺̪̪̪̪̪̬̼̼wwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwWUUUWUUUWUUUgUUUwUUUwgUUwwVUwwUUUvwVuwwwvwwwwwwwwwwwwwwwvwwwuwwwUUUUUUUUVUUUWUUeWUUuWUewVUvwUUuwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwxxwxwwwxxwwwwwwwwwwwwwߪߪߪ̺̪̪̪̪̪̪̪̫̬̼̫̫̪̪̪̪̪̺̬̫̼̼̪̫̬̬wwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwgUUwWUUwWUUwWUUwgUUwwUUwwgUwwwwUvwVUUUUUUUUUUUUUUVUUuwUewwgwwwwUUewUUUwUUUwUUUwUUewUUuwUewwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwVUwVUUhUwgUUwVUUwUUUgUUUWUUUVUUUUUUUUvwwUUvwUUewUUUwUUUvUUUuUUUeUUUUwwwwwwwwwwwwwwwwwwwwwwwwUewwUUewwwwwwwwwwwwwwwwwwwwwߪߪߪ̪̪̪̪̪̪̼̫̫̺̪̪̬̬̫̪̼̫wwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwVwwgUwwWUwfWUgUUUVUUeUUUwUUewUvwwUewwUUwwUUgvUUUegUUUwWUUwgUUwwwwwwwwwwwwwwwwwwwwvwwwuwwwuwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwgUUUWUUUVUUUUUUUUUUUUUUUUUUUVUUUUUUUUUUUUUUvUUvwUewwUuwwUvwwUwwwUUUUUUUUgUUUwgUUwwVUwwWUwwgUwwwUUUUvUUUuUUUeUUUUUUUUUUUUUUUUUUUewwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwvߪߪߪ*P "w \[goN1 Bws{oZk:g9gc^ZVRNsNRJ1FB=951k-J))))%)!!||||||c B|!J)||||||||P j-RJ9gw| w"|||p \ No newline at end of file diff --git a/src/main/resources/dlc/IRAO/ZUKAN/Z009_tyeren_1_e.bin b/src/main/resources/dlc/IRAO/ZUKAN/Z009_tyeren_1_e.bin new file mode 100644 index 0000000..a05f320 --- /dev/null +++ b/src/main/resources/dlc/IRAO/ZUKAN/Z009_tyeren_1_e.bin @@ -0,0 +1 @@ +33333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333CO333O334333333333333O333O33O3O3333333333333333333333333333O33333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333C33C3C3333ݽͻݼͼ̻ݻݻݻݻ˻̻ݻ̻O33O3?3333333333334333433433?33333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333C3333333ܽͻܽݽݼ̻̻ݻ̻̻ͻۻ4333333O33?33O333333333333333333333333?333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333C3ݻܼܽͻ̻ܽܽͻۻ̻̻̻ͻ?33433?33333333333333333333?333333?33333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333ܼܻܻ̻̻̻̽̽ͻܻ33?33?33333333333333333333?333333?333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333C߻߻߻̻̻̻ͻۻ̻33?334?333333333333333333333333?333333333333333333333333333333333333333333333333333333333333333333333333C3333333_efofef_ff_eVffffVffffffVfffVfffuW߻߻߻̻̻̻ͻۻ˻433?3333?3O33?O33333333333333333333333333333333333333333333C3333333C_efofffff_fffofffffffffffffffffffffVwffewffuwfVwwfuwwVwwwewwwuwwwwwWwwwwWwwwwwwWwwwwwwwwW߻߻߻̻̻̻̻̻ͻܻ3333?333333433?33O33334333Cofefff_ffofffff_fffoffffffVfffefffuffVwffewffuwfVwwfewwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwWwwwwWwwwwwwWwwwwwwwwW߻߻߻̻̻̻̻̻?334?O_oef_fofeffffffffffffffffffffffVfffVfffufuwwVwwwVwwwuwwwuwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwWwwwwWwwwwwwWwwwwwwwwW߻߻߻̻ͻͻۻۻefefff_ffoffefffffffffffuffVwffVwffuwffuwfVvwfVwwfVwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwWwwwwWwwwwwwWwwwwwwwwW߻߻߻̼̼̻̻̻̻̻̻̻_fff_fffofffefffeffffffffffffffffuwwfuwwVvwwVwwwVwwwewwwuwwwuwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwWwwwwWwwwwwwWwwwwwwwwW߻̼̼̼̻̻̻ͻͻͻۻۻfffVfffVfffVfffefffufffufffuffVvvwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwWwwwwWwwwwwwWwwwwwwwwW̻̼̻̼̻̼ffVwffVwffewffewffuwffUvfffUmfffwwwwwwwwwwwwwwwwwwwwwwwwvwwwUvwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwww'www"rww""w'""w"""w"""w"""'"""wwWwwrwRrwfrgfrgfwf_u_we_vw_vwwvwwwwwwwwwww_evwwwwwwwwwwwwwwwwwwwwwwwwwwgVwwwwwwwwwwwwwwwwwwwwwwwwVwgwwgwwwgwwwwwwwwﯙWw̻̼̻̱̼̼̻˻̻̻̻̻fffͻܻfUvwffUwffVwmfVwfVwfVwfVwfVwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwww'www'www'www"'""""""r"""r"""w"""w""rg""rg""rgwfgfgffffff_u_wvwowwvwwowwwvwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwgwwwwgwwwwwwgwwwwwwwwwwwwﯙWﯙw̻̼̼̻̻̻̼̼̻̼̱̻̻̻ܻͻͻͻfVwfVwfVwfVwfVwfVwfVwfVwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwww"www"www"ww'"ww'"ww'"ww'"ww'"""wf""wf""'f""gf"rgf"rgf"rgf"rbVVowuwvw_wwowwwwuwwvwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwWwgwwwwwwwwWwwg琢̻UfwwUVw̼̻̱̻̼̻fVwfVwfVwfVwfVwfVwfVwfVwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwww""ww""ww""ww""ww""ww""ww""ww"""rbV"wf"wf"wf"wf"wf"wf"wfwww_www_wwwowwwowwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwww_UVwfVwfVw_eevwwwwwwwwwwwwww_ewvwwwwwwwwwwwwwwwwwwwUfwwwwwwwwwwwwwwwwwwwwwwwww_evwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwgwwwwwgwwwwwW̼̻̼̻̱fVwfVwfVwfVwfVwfVwfVwfVwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwww""ww""ww""ww""ww""ww""ww""ww"""wf"wf"wf"wf"wf"wf"wf"rbVw_fwWfwwwwwwwwwowwwowww_www_wwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwfVwfVwfVwfVwfVwfVwfew_fewwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwWwwwwwgwgwg̼̱̼̻̻fVwfVwfVwfVwfVwfVwfVwfVwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwww'"ww'"ww'"ww'"ww'"www"www"www""rbV"rgf"rgf"rgf""gf""'f""wf""wfvvuuoVwWfwWfwWeVwwefwwefwwefwwVfwwWevwwuwwwwoww_wwvwuwVowwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwgwwWwwwwwwwg_wWoweofuwofuweVuwfVwwfVwwfVwwfewwVuwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwgwwwwwwgwwg̼̻̼̼̻ܻffffVwfVwfVwfVwfVwffVwffUwfUvwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwww"www'www'www'wwwwwwwwwwwwwwww""rg""rg""rg"""w"""w"""r"""r'"""fffffgfgfwfuwwewweowwVwwWvwwww_wwwwf_wfuf_efefVfVWefwefwwwwwwwwwwwwvwwwowwwvwwowwvwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwgwwwwwgwwwgwfW_foffV_fVefefVuofVwVwwwVwwwewwwuwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwWwwwgwg̱̱̼̻ܻ̻̻̻ͻͻͻffffffUffUvfVvwfVwwfVwwfVwwfVwwUvwwvwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwww'"""w"""w"""w"""w'""ww""ww""ww'"wfwgfrgf"wf"wff"rgf""wf""wbw_wvoVfwVffwgefwwVfwwgevwwVowwgvwwoww_wuf_fVffUffWeffwVefwwwwwwwwvwww_vww_vwefwwwwwwwwwwwwwwwwwwwwwwww_evwwwwwwwwwwwwwwwwwwwwwwwwwwgVwwwwwwwwwwwgwwgwgV__fwWo_fefoffoffUffVufVewffewfVvwfewwVvwwewwwvwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwgwwwwwgwwwwwWwg̻̼̻̼̱ݻ˻̻̻̻̻̻fVwwfVwwfVwwfVwwfVwwfVwwfVwwfVwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwww"www"www'wwwwwwwwwwwwwwwwwwww""rg"""w"""w"""r""""'"""w"""w'""fffgfwffwgfrwff"wgf"rwfvwowuVfwwUfwwgUwwwwwwww_wwwowwow_ffVefffUeffgUUfwwWUwwwwwwwwvwwwVffffffffUffffUUUwwwwwwwwefffffffffffUUUUfwwwwwwwwefffffVffVUfUUvUuwwwwwwwwwwwwwwfUwwUvwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwgwwwwwwwwgwwwgw̼̻̼̱̼̻̻̻̻̻̻̻̻fVwwfVwwfVwwfVwwfVwwfVwwfVwwfVwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwww""ww'"www"www'wwwwwwwwwwwwwwww""wg""rw"""r""""""""'"""w"""w'""ffbfVwffwgff"wbf"rwg""rw"""wfffVgfffwgffeww_efVwwwwvwwwUfwoo__wwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwgwwwwwgwwwwwW߻߻߻̻̻̻̻̻̻̻fVwwfVwwfVwwfVwwfVwwfVwwfVwwfVwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwww'"www"wwwwwwwwwwwwwwwwwwwwwwww""""""""""""'"""w'""ww""www'wwwwrwgf"rw'""rw""""""""""""""""'"""ffffffffwbffwwwf""wf""wf""wf""wfVffwwwwvwwwvwwwuwwwuwwwwwwwwwowwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwWwwwwwgwgwg߻߻߻̻̻̻̻̻̻̻̣*R|kI-9_s< BZI-Bws{oZk:g9gc^ZVRNsNRJ1FB=951k-J))))%)!!||||||cB|!J)||||||||fBZIb=-j-RJ9gw|(%I-||| \ No newline at end of file diff --git a/src/main/resources/dlc/IRAO/ZUKAN/Z100_defogirl_1_en.bin b/src/main/resources/dlc/IRAO/ZUKAN/Z100_defogirl_1_en.bin new file mode 100644 index 0000000000000000000000000000000000000000..2d1bec57dbbf19ecdd2600ea720ec465aad10851 GIT binary patch literal 25090 zcmeIuAxnc{6b8`61;c`2ATkCUyb~u|Sonk?EEv8paA9FV!@|G?!@`1tl@&n@I)h*} z3K~=dtwx&$i~9u{6)Xx1YnIjX+;ZXJhI>4{*o_b zliAE*HW!PdRC}S8%nY8=$!uJhHPh{VYoq#Iy2>4-cZ-cU>OAx_gOLBIzO<(Oy#KlX IU*LA>7bzJz=l}o! literal 0 HcmV?d00001 diff --git a/src/main/resources/dlc/IRAO/ZUKAN/Z101_defoboy_1_en.bin b/src/main/resources/dlc/IRAO/ZUKAN/Z101_defoboy_1_en.bin new file mode 100644 index 0000000000000000000000000000000000000000..e5282325e42c68099c1d3b18acfa079e3ffc0e86 GIT binary patch literal 25090 zcmeIup-Te+6bIl1!3!5IOjsG?!oq|J3x*Rc0|hTETre!CurS!dgoOnUt{81DiPc~b zEDC}~jYbWY{Rc*yI8a!#tbXs6AH4U%`${E6fB*pk1PBlyK!5-N0t5&UAV7cs0RjXF z5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk z1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72rMhGu|6w``eC)Y_7OgW_u+YPyfO%8 z#YHrl=bi9raydR3b%$I1q+jWM9lUk#_NUpjGwx(*Yd30tZoeisn`fZw_c2LV-DnXn!7L(sp#NW|-bQ@il2hA)^`j3-Z{#;p&_x?7Q{m=dX0>RM_ D8{0K~ literal 0 HcmV?d00001 diff --git a/src/main/resources/log4j2.xml b/src/main/resources/log4j2.xml new file mode 100644 index 0000000..ab8ef3a --- /dev/null +++ b/src/main/resources/log4j2.xml @@ -0,0 +1,25 @@ + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/test/java/entralinked/utility/Crc16Test.java b/src/test/java/entralinked/utility/Crc16Test.java new file mode 100644 index 0000000..c41ebfb --- /dev/null +++ b/src/test/java/entralinked/utility/Crc16Test.java @@ -0,0 +1,32 @@ +package entralinked.utility; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +public class Crc16Test { + + @Test + @DisplayName("Test if output CRC-16 checksums are valid") + void testCrc16Checksums() { + // Test checksums calculated from byte arrays with varying lengths + assertEquals(0xEF9D, Crc16.calc(new byte[] {114, 49, -30, -50, 46, -62, 47, 39, -85, 73, -91, 40, 21, -80, -95, -3})); + assertEquals(0x23DF, Crc16.calc(new byte[] {-32, -95, 74, 56, 2, -57, 90, 78, 81, 81, -126, 29, 8, 1, 65, -7})); + assertEquals(0x263D, Crc16.calc(new byte[] {-128, 47, -44, 118, 1, 91, 124, 104, 2, -4, -84, -76})); + assertEquals(0xBF19, Crc16.calc(new byte[] {-9, 108, -105, -33, -110, -8, 33, 44})); + + // Test checksums calculated from different sections of a byte array + byte[] bytes = {81, -81, -69, -18, 70, -94, -61, 73, -63, 56, 56, 113, -75, -87, -30, -31, -76, 76, -120, -14, -79, -43, -117, -22, 23, 9, -81, 77, 64, -93, 48, 1}; + assertEquals(0xC8F5, Crc16.calc(bytes, 0, 4)); // bytes 0-3 + assertEquals(0x6093, Crc16.calc(bytes, 4, 8)); // bytes 4-11 + assertEquals(0xD7C3, Crc16.calc(bytes, 8, 8)); // bytes 8-15 + assertEquals(0xFF5C, Crc16.calc(bytes, 16, 16)); // bytes 16-31 + + // Test checksums calculated from integers + assertEquals(0x9EFB, Crc16.calc(12345)); + assertEquals(0x005E, Crc16.calc(847190349)); + assertEquals(0x8C87, Crc16.calc(Integer.MAX_VALUE)); + assertEquals(0x1548, Crc16.calc(Integer.MIN_VALUE)); + } +} diff --git a/src/test/java/entralinked/utility/GsidUtilityTest.java b/src/test/java/entralinked/utility/GsidUtilityTest.java new file mode 100644 index 0000000..7aaa063 --- /dev/null +++ b/src/test/java/entralinked/utility/GsidUtilityTest.java @@ -0,0 +1,45 @@ +package entralinked.utility; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +public class GsidUtilityTest { + + @Test + @DisplayName("Test if output stringified Game Sync IDs are correct") + void testGameSyncIdStringifier() { + assertEquals("G5T5MB69TA", GsidUtility.stringifyGameSyncId(45991782)); + assertEquals("S6MJNM63AC", GsidUtility.stringifyGameSyncId(381955984)); + assertEquals("RMLLERWPSA", GsidUtility.stringifyGameSyncId(507849071)); + assertEquals("J89BGT23UD", GsidUtility.stringifyGameSyncId(576782280)); + assertEquals("K3D29LTGSB", GsidUtility.stringifyGameSyncId(1442582313)); + assertEquals("8YJN6SKKGF", GsidUtility.stringifyGameSyncId(1640375006)); + } + + @Test + @DisplayName("Test if invalid Game Sync IDs are seen as invalid") + void testInvalidGameSyncIds() { + // Illegal characters (I, O, 0, 1) + assertFalse(GsidUtility.isValidGameSyncId("0000000000")); + assertFalse(GsidUtility.isValidGameSyncId("ABCDEFGHIJ")); + assertFalse(GsidUtility.isValidGameSyncId("1OEKLRO493")); + + // Illegal length (should be 10) + assertFalse(GsidUtility.isValidGameSyncId("Y67UEN38K")); + assertFalse(GsidUtility.isValidGameSyncId("3ER5K8MBN4C")); + } + + @Test + @DisplayName("Test if valid Game Sync IDs are seen as valid") + void testValidGameSyncIds() { + assertTrue(GsidUtility.isValidGameSyncId("VFWM2QAXNF")); + assertTrue(GsidUtility.isValidGameSyncId("44DAWDJKJ8")); + assertTrue(GsidUtility.isValidGameSyncId("J6F55UB2X9")); + assertTrue(GsidUtility.isValidGameSyncId("8FAB4Z3EN9")); + assertTrue(GsidUtility.isValidGameSyncId("HWLNS7BTNB")); + } +} diff --git a/src/test/java/entralinked/utility/MD5Test.java b/src/test/java/entralinked/utility/MD5Test.java new file mode 100644 index 0000000..af32c8b --- /dev/null +++ b/src/test/java/entralinked/utility/MD5Test.java @@ -0,0 +1,17 @@ +package entralinked.utility; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +public class MD5Test { + + @Test + @DisplayName("Test if output MD5 hashes are correct") + void testMD5Hashes() { + assertEquals("ed076287532e86365e841e92bfc50d8c", MD5.digest("Hello World!")); + assertEquals("8cfd799409ac5461004bca394a92b0af", MD5.digest("Some random string.")); + assertEquals("c74efaf9dd2782003ba4b27f15ef1049", MD5.digest("What is the meaning of life?")); + } +}