diff --git a/core/src/main/java/com/agifans/agile/Commands.java b/core/src/main/java/com/agifans/agile/Commands.java index 92643a8..59b462f 100644 --- a/core/src/main/java/com/agifans/agile/Commands.java +++ b/core/src/main/java/com/agifans/agile/Commands.java @@ -1492,8 +1492,14 @@ private int executeAction(Action action) { shakeScreen(action.operands.get(0).asByte()); } else { - // AGI PAL hack. - + // AGI PAL hack (values 100-109). + int paletteNum = shakeNum - 100; + if (paletteNum < 10) { + int[] palette = state.getPalette(paletteNum); + if (palette != null) { + // TODO: Implement palette change. + } + } } } break; diff --git a/core/src/main/java/com/agifans/agile/Detection.java b/core/src/main/java/com/agifans/agile/Detection.java index 0642220..48a1367 100644 --- a/core/src/main/java/com/agifans/agile/Detection.java +++ b/core/src/main/java/com/agifans/agile/Detection.java @@ -164,7 +164,7 @@ else if (game.v3GameSig != null) { {"----", "COMBAT","AGI Combat (Beta)", "341a47d07be8490a488d0c709578dd10"}, {"----", "CNTST1","AGI Contest 1 Template", "d879aed25da6fc655564b29567358ae2"}, {"----", "CNTST2","AGI Contest 2 Template", "5a2fb2894207eff36c72f5c1b08bcc07"}, - {"M---", "MOUSED", "AGI Mouse Demo (1.10)", "f4ad396b496d6167635ad0b410312ab8"}, + {"MP--", "MOUSED", "AGI Mouse Demo (1.10)", "f4ad396b496d6167635ad0b410312ab8"}, {"----", "PIANO","AGI Piano (v1.0)", "8778b3d89eb93c1d50a70ef06ef10310"}, {"----", "AQUEST","AGI Quest (v1.46-TJ0)", "1cf1a5307c1a0a405f5039354f679814"}, {"----", "PKTETR","PK AGI Tetris (1998)", "1afcbc25bfafded2d5fb82de9da0bd9a"}, @@ -241,7 +241,7 @@ else if (game.v3GameSig != null) { {"----", "JENSQ","Jen's Quest (Demo 2)", "3c321eee33013b289ab8775449df7df2"}, {"----", "JIGGY","Jiggy Jiggy Uh! Uh!", "bc331588a71e7a1c8840f6cc9b9487e4"}, {"----", "JIMMY","Jimmy In: The Alien Attack (v0.1)", "a4e9db0564a494728de7873684a4307c"}, - {"----", "JOLIMI","Jolimie, le Village Maudit (v1.1)", "68d7aef1161bb5972fe03efdf29ccb7f"}, + {"-P--", "JOLIMI","Jolimie, le Village Maudit (v1.1)", "68d7aef1161bb5972fe03efdf29ccb7f"}, {"----", "JOEMCM","Joe McMuffin In \"What's Cooking, Doc\" (v1.0)", "8a3de7e61a99cb605fa6d233dd91c8e1"}, {"----", "JOCHEF","Journey Of Chef", "aa0a0b5a6364801ae65fdb96d6741df5"}, {"----", "JUKEBX","Jukebox (v1.0)", "c4b9c5528cc67f6ba777033830de7751"}, @@ -266,7 +266,7 @@ else if (game.v3GameSig != null) { {"----", "HANGMN","New AGI Hangman Test", "d69c0e9050ccc29fd662b74d9fc73a15"}, {"----", "NAPALM","Napalm Quest (v0.5)", "b659afb491d967bb34810d1c6ce22093"}, {"----", "NICKQ","Nick's Quest - In Pursuit of QuakeMovie (v2.1 Gold)", "e29cbf9222551aee40397fabc83eeca0"}, - {"M---", "OPENMN","Open Mic Night (v0.1)", "70000a2f67aac27d1133d019df70246d"}, + {"MP--", "OPENMN","Open Mic Night (v0.1)", "70000a2f67aac27d1133d019df70246d"}, {"----", "OPRECN","Operation: Recon", "0679ce8405411866ccffc8a6743370d0"}, {"----", "PATQST","Patrick's Quest (Demo v1.0)", "f254f5b894b98fec5f92acc07fb62841"}, {"----", "PHANTA","Phantasmagoria", "87d20c1c11aee99a4baad3797b63146b"}, @@ -310,8 +310,8 @@ else if (game.v3GameSig != null) { {"----", "SAVSAN","Save Santa (v1.3)", "f8afdb6efc5af5e7c0228b44633066af"}, {"----", "SCHILL","Schiller (preview 1)", "ade39dea968c959cfebe1cf935d653e9"}, {"----", "SCHILL","Schiller (preview 2)", "62cd1f8fc758bf6b4aa334e553624cef"}, - {"----", "SERGD1","Serguei's Destiny 1 (v1.0)", "b86725f067e456e10cdbdf5f58e01dec"}, - {"----", "SERGD1","Serguei's Destiny 1 (v1.1 2003 Apr 10)", "91975c1fb4b13b0f9a8e9ff74731030d"}, + {"-P--", "SERGD1","Serguei's Destiny 1 (v1.0)", "b86725f067e456e10cdbdf5f58e01dec"}, + {"-P--", "SERGD1","Serguei's Destiny 1 (v1.1 2003 Apr 10)", "91975c1fb4b13b0f9a8e9ff74731030d"}, {"M---", "SERGD2","Serguei's Destiny 2 (v0.1.1 Demo)", "906ccbc2ddedb29b63141acc6d10cd28"}, {"----", "SHIFTY","Shifty (v1.0)", "2a07984d27b938364bf6bd243ac75080"}, {"M---", "STILEG","Sliding Tile Game (v1.00)", "949bfff5d8a81c3139152eed4d84ca75"}, @@ -323,7 +323,7 @@ else if (game.v3GameSig != null) { {"----", "SPBIKE","Speeder Bike Challenge (v1.0)", "2deb25bab379285ca955df398d96c1e7"}, {"----", "STARCO","Star Commander 1 - The Escape (v1.0)", "a7806f01e6fa14ebc029faa58f263750"}, {"----", "STARPI","Star Pilot: Bigger Fish", "8cb26f8e1c045b75c6576c839d4a0172"}, - {"----", "STREET","Street Quest (Demo)", "cf2aa94a7eb78dce6892c37f03e310d6"}, + {"-P--", "STREET","Street Quest (Demo)", "cf2aa94a7eb78dce6892c37f03e310d6"}, {"----", "TOTIKI","Tales of the Tiki", "8103c9c87e3964690a14a3d0d83f7ddc"}, {"----", "TEXM1","Tex McPhilip 1 - Quest For The Papacy", "3c74b9a24b51aa8020ac82bee3132266"}, {"----", "TEXM2","Tex McPhilip 2 - Road To Divinity (v1.5)", "7387e8df854440bc26620ca0ea43af9a"}, diff --git a/core/src/main/java/com/agifans/agile/GameLoader.java b/core/src/main/java/com/agifans/agile/GameLoader.java index 63d32bb..6f4ce26 100644 --- a/core/src/main/java/com/agifans/agile/GameLoader.java +++ b/core/src/main/java/com/agifans/agile/GameLoader.java @@ -159,11 +159,12 @@ private Game patchGame(Game game, String gameId, String gameName) { protected boolean isGameFile(String filename) { String lowerCaseName = filename.toLowerCase(); - if (lowerCaseName.matches("^[a-z0-9]*vol.[0-9]+$") || + if (lowerCaseName.matches("^[a-z0-9]*vol[.][0-9]+$") || lowerCaseName.endsWith("dir") || lowerCaseName.equals("agidata.ovl") || lowerCaseName.equals("object") || - lowerCaseName.equals("words.tok")) { + lowerCaseName.equals("words.tok") || + lowerCaseName.matches("^pal[.]10[0-9]$")) { return true; } else { diff --git a/core/src/main/java/com/agifans/agile/GameState.java b/core/src/main/java/com/agifans/agile/GameState.java index 9e16f12..d7dbb4b 100644 --- a/core/src/main/java/com/agifans/agile/GameState.java +++ b/core/src/main/java/com/agifans/agile/GameState.java @@ -558,6 +558,17 @@ public int getMouseButton() { return variableData.getMouseButton(); } + /** + * Gets the RGBA8888 palette data (AGIPAL) for the given palette number. + * + * @param paletteNum The number of the palette to get. + * + * @return An int array containing the 16 RGBA8888 colours for the palette. + */ + public int[] getPalette(int paletteNum) { + return this.game.palettes[paletteNum]; + } + /** * Clears all of the AGI flags to be false. */ diff --git a/core/src/main/java/com/agifans/agile/agilib/Game.java b/core/src/main/java/com/agifans/agile/agilib/Game.java index c2c3a9a..80cae0c 100644 --- a/core/src/main/java/com/agifans/agile/agilib/Game.java +++ b/core/src/main/java/com/agifans/agile/agilib/Game.java @@ -42,6 +42,8 @@ public class Game { public Sound[] sounds; + public int[][] palettes; + /** * Constructor for Game. * @@ -64,6 +66,7 @@ public Game(Map gameFilesMap) { sounds = loadSounds(); objects = new Objects(resourceCache.getObjects()); words = new Words(resourceCache.getWords()); + palettes = resourceCache.getPalettes(); } catch (ResourceException | IOException e) { throw new RuntimeException("Decode of game failed.", e); diff --git a/core/src/main/java/com/agifans/agile/agilib/jagi/res/ResourceCache.java b/core/src/main/java/com/agifans/agile/agilib/jagi/res/ResourceCache.java index 1054860..eb18e1c 100644 --- a/core/src/main/java/com/agifans/agile/agilib/jagi/res/ResourceCache.java +++ b/core/src/main/java/com/agifans/agile/agilib/jagi/res/ResourceCache.java @@ -43,6 +43,8 @@ public class ResourceCache { protected View[] views; protected Words words; protected InventoryObjects objects; + + protected int[][] palettes; public ResourceCache(Map gameFilesMap) throws IOException, ResourceException { try { @@ -194,6 +196,56 @@ public InventoryObjects getObjects() throws IOException, ResourceException { } return objects; } + + public int[][] getPalettes() { + if (palettes == null) { + palettes = new int[10][]; + + for (int palNum=0; palNum < 10; palNum++) { + byte[] palBytes = resourceProvider.getPalettes()[palNum]; + if ((palBytes != null) && (palBytes.length == 192)) { + // We have a valid AGIPAL file, so decode it into RGBA8888 values, the + // same as is used in AGILE. + int[] colours = new int[16]; + // First chunk. + colours[0] = getRGBAColour(palBytes[0], palBytes[1], palBytes[2]); + colours[1] = getRGBAColour(palBytes[3], palBytes[4], palBytes[5]); + colours[2] = getRGBAColour(palBytes[6], palBytes[7], palBytes[8]); + colours[3] = getRGBAColour(palBytes[9], palBytes[10], palBytes[11]); + colours[4] = getRGBAColour(palBytes[12], palBytes[13], palBytes[14]); + colours[5] = getRGBAColour(palBytes[15], palBytes[16], palBytes[17]); + colours[6] = getRGBAColour(palBytes[18], palBytes[19], palBytes[20]); + colours[7] = getRGBAColour(palBytes[21], palBytes[22], palBytes[23]); + // Second chunk. + colours[8] = getRGBAColour(palBytes[48], palBytes[49], palBytes[50]); + colours[9] = getRGBAColour(palBytes[51], palBytes[52], palBytes[53]); + colours[10] = getRGBAColour(palBytes[54], palBytes[55], palBytes[56]); + colours[11] = getRGBAColour(palBytes[57], palBytes[58], palBytes[59]); + colours[12] = getRGBAColour(palBytes[60], palBytes[61], palBytes[62]); + colours[13] = getRGBAColour(palBytes[63], palBytes[64], palBytes[65]); + colours[14] = getRGBAColour(palBytes[66], palBytes[67], palBytes[68]); + colours[15] = getRGBAColour(palBytes[69], palBytes[70], palBytes[71]); + palettes[palNum] = colours; + } + } + } + + return palettes; + } + + private int getRGBAColour(byte r, byte g, byte b) { + // 18-bit VGA palette, so top 2 bits of each byte are discarded. + int red = (((int)r) << 2) & 0xFF; + int green = (((int)g) << 2) & 0xFF; + int blue = (((int)b) << 2) & 0xFF; + + int rgba8888Colour = 0; + rgba8888Colour |= ((red << 24) & 0xFF000000); + rgba8888Colour |= ((green << 16) & 0x00FF0000); + rgba8888Colour |= ((blue << 8) & 0x0000FF00); + rgba8888Colour |= 0x000000FF; + return rgba8888Colour; + } public String getVersion() { return resourceProvider.getVersion(); diff --git a/core/src/main/java/com/agifans/agile/agilib/jagi/res/ResourceProvider.java b/core/src/main/java/com/agifans/agile/agilib/jagi/res/ResourceProvider.java index 579f08f..a69071d 100644 --- a/core/src/main/java/com/agifans/agile/agilib/jagi/res/ResourceProvider.java +++ b/core/src/main/java/com/agifans/agile/agilib/jagi/res/ResourceProvider.java @@ -76,6 +76,8 @@ public interface ResourceProvider { */ InputStream open(byte resType, short resNumber) throws ResourceException, IOException; + byte[][] getPalettes(); + String getVersion(); String getV3GameSig(); diff --git a/core/src/main/java/com/agifans/agile/agilib/jagi/res/v2/ResourceProviderV2.java b/core/src/main/java/com/agifans/agile/agilib/jagi/res/v2/ResourceProviderV2.java index 8f94978..6bfbcd1 100644 --- a/core/src/main/java/com/agifans/agile/agilib/jagi/res/v2/ResourceProviderV2.java +++ b/core/src/main/java/com/agifans/agile/agilib/jagi/res/v2/ResourceProviderV2.java @@ -70,6 +70,11 @@ public class ResourceProviderV2 implements ResourceProvider { */ protected Map gameFilesMap; + /** + * AGIPAL palettes. Max of 10. + */ + protected byte[][] palettes; + protected String version = "unknown"; /** @@ -84,6 +89,7 @@ public ResourceProviderV2(Map gameFilesMap) throws IOException, readVolumes(); readDirectories(); readVersion(); + readPalettes(); } public static String getKey(boolean agds) { @@ -373,6 +379,22 @@ protected void readDirectories() throws NoDirectoryAvailableException, IOExcepti throw new NoDirectoryAvailableException(); } } + + private void readPalettes() { + this.palettes = new byte[10][]; + + for (String fileName : gameFilesMap.keySet()) { + if (fileName.matches("^pal[.]10[0-9]$")) { + try { + String extension = fileName.substring(fileName.lastIndexOf(".") + 1); + int palNumber = Integer.parseInt(extension) - 100; + this.palettes[palNumber] = gameFilesMap.get(fileName); + } catch(Exception e) { + // Ignore. Must not be a proper PAL file. + } + } + } + } private void readVersion() throws IOException { byte[] fileContent = getGameFile("agidata.ovl"); @@ -393,6 +415,10 @@ private void readVersion() throws IOException { } } + public byte[][] getPalettes() { + return this.palettes; + } + public String getVersion() { return this.version; }