diff --git a/src/main/java/entralinked/model/dlc/DlcList.java b/src/main/java/entralinked/model/dlc/DlcList.java index ff2d752..b8e468d 100644 --- a/src/main/java/entralinked/model/dlc/DlcList.java +++ b/src/main/java/entralinked/model/dlc/DlcList.java @@ -6,11 +6,10 @@ import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.nio.file.Files; +import java.util.ArrayList; 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; @@ -22,7 +21,7 @@ import entralinked.utility.Crc16; public class DlcList { private static final Logger logger = LogManager.getLogger(); - private final Map dlcMap = new ConcurrentHashMap<>(); + private final List dlcList = new ArrayList<>(); private final File dataDirectory = new File("dlc"); public DlcList() { @@ -83,26 +82,20 @@ public class DlcList { // 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); + dlcList.add(dlc); index++; } } } } - logger.info("Loaded {} DLC file(s)", dlcMap.size()); + logger.info("Loaded {} DLC file(s)", dlcList.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; @@ -123,6 +116,7 @@ public class DlcList { int checksumInFile = (bytes[bytes.length - 2] & 0xFF) | ((bytes[bytes.length - 1] & 0xFF) << 8); if(checksum != checksumInFile) { + logger.warn("Checksum mismatch in DLC '{}'", name); projectedSize += 2; checksum = Crc16.calc(bytes, 0, bytes.length); checksumEmbedded = false; @@ -167,19 +161,18 @@ public class DlcList { return builder.toString(); } - public Dlc getDlc(String name) { - return dlcMap.get(name); + public Dlc getDlc(String gameCode, String type, String name) { + List dlcList = getDlcList(gameCode, type).stream() + .filter(dlc -> dlc.name().equals(name)).collect(Collectors.toList()); + return dlcList.isEmpty() ? null : dlcList.get(0); } - public int getDlcIndex(String name) { - return dlcExists(name) ? getDlc(name).index() : 0; - } - - public boolean dlcExists(String name) { - return name != null && dlcMap.containsKey(name); + public int getDlcIndex(String gameCode, String type, String name) { + Dlc dlc = getDlc(gameCode, type, name); + return dlc == null ? 0 : dlc.index(); } public Collection getDlc() { - return Collections.unmodifiableCollection(dlcMap.values()); + return Collections.unmodifiableCollection(dlcList); } } diff --git a/src/main/java/entralinked/network/http/dashboard/DashboardHandler.java b/src/main/java/entralinked/network/http/dashboard/DashboardHandler.java index 8ba4e2b..bc17b72 100644 --- a/src/main/java/entralinked/network/http/dashboard/DashboardHandler.java +++ b/src/main/java/entralinked/network/http/dashboard/DashboardHandler.java @@ -49,7 +49,7 @@ public class DashboardHandler implements HttpHandler { 505, 507, 510, 511, 513, 515, 519, 523, 525, 527, 529, 531, 533, 535, 538, 539, 542, 545, 546, 548, 550, 553, 556, 558, 559, 561, 564, 569, 572, 575, 578, 580, 583, 587, 588, 594, 596, 600, 605, 607, 610, 613, 616, 618, 619, 621, 622, 624, 626, 628, 630, 631, 632); - private final Map skinPreviewCache = new HashMap<>(); + private final Map skinPreviewCache = new HashMap<>(); private final DlcList dlcList; private final PlayerManager playerManager; @@ -67,7 +67,7 @@ public class DashboardHandler implements HttpHandler { skin.type().equals("ZUKAN") ? TiledImageReader.readDexSkin(inputStream) : skin.type().equals("CGEAR") ? TiledImageReader.readCGearSkin(inputStream, true) : TiledImageReader.readCGearSkin(inputStream, false); // CGEAR2 - skinPreviewCache.put(skin.name(), image); + skinPreviewCache.put(skin, image); } catch(IOException | IndexOutOfBoundsException e) { logger.error("Could not load image for skin {} of type {}", skin.name(), skin.type(), e); } @@ -110,16 +110,25 @@ public class DashboardHandler implements HttpHandler { * GET request handler for {@code /dashboard/previewskin} */ private void handlePreviewSkin(Context ctx) throws IOException { - // Make sure that the name is present and exists + String type = ctx.queryParam("type"); String name = ctx.queryParam("name"); - if(name == null || !skinPreviewCache.containsKey(name)) { + // Make sure query parameters are present + if(type == null || name == null) { + ctx.status(404); + return; + } + + Dlc dlc = dlcList.getDlc("IRAO", type, name); + + // Check if DLC exists + if(dlc == null) { ctx.status(404); return; } // Write cached image data - ImageIO.write(skinPreviewCache.get(name), "png", ctx.outputStream()); + ImageIO.write(skinPreviewCache.get(dlc), "png", ctx.outputStream()); } /** diff --git a/src/main/java/entralinked/network/http/dls/DlsHandler.java b/src/main/java/entralinked/network/http/dls/DlsHandler.java index e1ed94e..11de5fe 100644 --- a/src/main/java/entralinked/network/http/dls/DlsHandler.java +++ b/src/main/java/entralinked/network/http/dls/DlsHandler.java @@ -67,19 +67,8 @@ public class DlsHandler implements HttpHandler { * POST handler for {@code /download action=list} */ private void handleRetrieveDlcList(DlsRequest request, Context ctx) throws IOException { - String gameCode = switch(request.dlcGameCode()) { - case "IRAJ" -> "IRAO"; - default -> request.dlcGameCode(); - }; - - // 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(); - }; + String gameCode = getDlcGameCode(request.dlcGameCode()); + String type = getRegionlessDlcType(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(gameCode, type, request.dlcIndex()))); @@ -89,9 +78,11 @@ public class DlsHandler implements HttpHandler { * 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()); + String gameCode = getDlcGameCode(request.dlcGameCode()); + String type = getRegionlessDlcType(request.dlcType()); + Dlc dlc = dlcList.getDlc(gameCode, type, request.dlcName()); + // Check if the requested DLC exists if(dlc == null) { ctx.status(HttpStatus.NOT_FOUND); return; @@ -108,4 +99,27 @@ public class DlsHandler implements HttpHandler { } } } + + /** + * @return The game serial that should be used for downloading DLC based on the provided input. + */ + private String getDlcGameCode(String gameCode) { + return switch(gameCode) { + case "IRAJ" -> "IRAO"; + default -> gameCode; + }; + } + + /** + * @return The DLC type without the region identifier, or the input if it is an unknown type. + */ + private String getRegionlessDlcType(String dlcType) { + return switch(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 -> dlcType; + }; + } } diff --git a/src/main/java/entralinked/network/http/pgl/PglHandler.java b/src/main/java/entralinked/network/http/pgl/PglHandler.java index 226b7f1..2686e89 100644 --- a/src/main/java/entralinked/network/http/pgl/PglHandler.java +++ b/src/main/java/entralinked/network/http/pgl/PglHandler.java @@ -228,9 +228,9 @@ public class PglHandler implements HttpHandler { // 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(dlcList.getDlcIndex("IRAO", "MUSICAL", player.getMusical())); + outputStream.write(dlcList.getDlcIndex("IRAO", player.getGameVersion().isVersion2() ? "CGEAR2" : "CGEAR", player.getCGearSkin())); + outputStream.write(dlcList.getDlcIndex("IRAO", "ZUKAN", player.getDexSkin())); outputStream.write(decorList.isEmpty() ? 0 : 1); // Seems to be a flag for indicating whether or not decor data is present outputStream.write(0); // Must be zero? diff --git a/src/main/resources/dashboard/profile.html b/src/main/resources/dashboard/profile.html index 6471266..facb75c 100644 --- a/src/main/resources/dashboard/profile.html +++ b/src/main/resources/dashboard/profile.html @@ -124,13 +124,13 @@
- Preview + Preview
- Preview + Preview diff --git a/src/main/resources/dashboard/scripts/profile.js b/src/main/resources/dashboard/scripts/profile.js index 8c77787..3d8fe51 100644 --- a/src/main/resources/dashboard/scripts/profile.js +++ b/src/main/resources/dashboard/scripts/profile.js @@ -325,7 +325,7 @@ function closeItemForm() { window.location.href = "#"; } -function previewSkin(inputElementId) { +function previewSkin(inputElementId, type) { let value = document.getElementById(inputElementId).value; if(value == "none") { @@ -333,10 +333,18 @@ function previewSkin(inputElementId) { return false; } - window.open("/dashboard/previewskin?name=" + value); + if(type == "CGEAR" && isVersion2()) { + type = "CGEAR2"; + } + + window.open("/dashboard/previewskin?type=" + type + "&name=" + value); return false; } +function isVersion2() { + return profile.gameVersion.includes("2"); +} + async function fetchData(path) { return fetchData(path, "GET", null); } @@ -363,24 +371,22 @@ async function fetchData(path, method, body) { } 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) => { + // Fetch C-Gear skins + fetchData("/dashboard/dlc?type=" + (isVersion2() ? "CGEAR2" : "CGEAR")).then((response) => { addValuesToComboBox(ELEMENT_CGEAR_SKIN_INPUT, response); - ELEMENT_CGEAR_SKIN_INPUT.value = profile.cgearSkin; + ELEMENT_CGEAR_SKIN_INPUT.value = response.includes(profile.cgearSkin) ? profile.cgearSkin : "none"; }); // Fetch Dex skins fetchData("/dashboard/dlc?type=ZUKAN").then((response) => { addValuesToComboBox(ELEMENT_DEX_SKIN_INPUT, response); - ELEMENT_DEX_SKIN_INPUT.value = profile.dexSkin; + ELEMENT_DEX_SKIN_INPUT.value = response.includes(profile.dexSkin) ? profile.dexSkin : "none"; }); // Fetch musicals fetchData("/dashboard/dlc?type=MUSICAL").then((response) => { addValuesToComboBox(ELEMENT_MUSICAL_INPUT, response); - ELEMENT_MUSICAL_INPUT.value = profile.musical; + ELEMENT_MUSICAL_INPUT.value = response.includes(profile.musical) ? profile.musical : "none"; }); } @@ -402,8 +408,7 @@ function fetchProfileData() { profile.gameVersion = gameVersion; ELEMENT_GAME_SUMMARY.innerHTML = "Game Card in use: " + gameVersion; - // Still don't like this! - if(gameVersion.includes("2")) { + if(isVersion2()) { ELEMENT_ENCOUNTER_SPECIES.max = 649; ELEMENT_ITEM_ID.max = 638;