summaryrefslogtreecommitdiff
path: root/src/com/pkrandom/romhandlers/AbstractDSRomHandler.java
diff options
context:
space:
mode:
Diffstat (limited to 'src/com/pkrandom/romhandlers/AbstractDSRomHandler.java')
-rwxr-xr-xsrc/com/pkrandom/romhandlers/AbstractDSRomHandler.java390
1 files changed, 390 insertions, 0 deletions
diff --git a/src/com/pkrandom/romhandlers/AbstractDSRomHandler.java b/src/com/pkrandom/romhandlers/AbstractDSRomHandler.java
new file mode 100755
index 0000000..3736758
--- /dev/null
+++ b/src/com/pkrandom/romhandlers/AbstractDSRomHandler.java
@@ -0,0 +1,390 @@
+package com.pkrandom.romhandlers;
+
+/*----------------------------------------------------------------------------*/
+/*-- AbstractDSRomHandler.java - a base class for DS rom handlers --*/
+/*-- which standardises common DS functions. --*/
+/*-- --*/
+/*-- Part of "Universal Pokemon Randomizer ZX" by the UPR-ZX team --*/
+/*-- Pokemon and any associated names and the like are --*/
+/*-- trademark and (C) Nintendo 1996-2020. --*/
+/*-- --*/
+/*-- The custom code written here is licensed under the terms of the GPL: --*/
+/*-- --*/
+/*-- This program is free software: you can redistribute it and/or modify --*/
+/*-- it under the terms of the GNU General Public License as published by --*/
+/*-- the Free Software Foundation, either version 3 of the License, or --*/
+/*-- (at your option) any later version. --*/
+/*-- --*/
+/*-- This program is distributed in the hope that it will be useful, --*/
+/*-- but WITHOUT ANY WARRANTY; without even the implied warranty of --*/
+/*-- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the --*/
+/*-- GNU General Public License for more details. --*/
+/*-- --*/
+/*-- You should have received a copy of the GNU General Public License --*/
+/*-- along with this program. If not, see <http://www.gnu.org/licenses/>. --*/
+/*----------------------------------------------------------------------------*/
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.io.PrintStream;
+import java.util.Arrays;
+import java.util.List;
+import java.util.Random;
+
+import com.pkrandom.FileFunctions;
+import com.pkrandom.RomFunctions;
+import com.pkrandom.exceptions.CannotWriteToLocationException;
+import com.pkrandom.exceptions.RandomizerIOException;
+import com.pkrandom.newnds.NARCArchive;
+import com.pkrandom.newnds.NDSRom;
+import com.pkrandom.pokemon.Type;
+
+public abstract class AbstractDSRomHandler extends AbstractRomHandler {
+
+ protected String dataFolder;
+ private NDSRom baseRom;
+ private String loadedFN;
+ private boolean arm9Extended = false;
+
+ public AbstractDSRomHandler(Random random, PrintStream logStream) {
+ super(random, logStream);
+ }
+
+ protected abstract boolean detectNDSRom(String ndsCode, byte version);
+
+ @Override
+ public boolean loadRom(String filename) {
+ if (!this.detectNDSRom(getROMCodeFromFile(filename), getVersionFromFile(filename))) {
+ return false;
+ }
+ // Load inner rom
+ try {
+ baseRom = new NDSRom(filename);
+ } catch (IOException e) {
+ throw new RandomizerIOException(e);
+ }
+ loadedFN = filename;
+ loadedROM(baseRom.getCode(), baseRom.getVersion());
+ return true;
+ }
+
+ @Override
+ public String loadedFilename() {
+ return loadedFN;
+ }
+
+ protected byte[] get3byte(int amount) {
+ byte[] ret = new byte[3];
+ ret[0] = (byte) (amount & 0xFF);
+ ret[1] = (byte) ((amount >> 8) & 0xFF);
+ ret[2] = (byte) ((amount >> 16) & 0xFF);
+ return ret;
+ }
+
+ protected abstract void loadedROM(String romCode, byte version);
+
+ protected abstract void savingROM();
+
+ @Override
+ public boolean saveRomFile(String filename, long seed) {
+ savingROM();
+ try {
+ baseRom.saveTo(filename);
+ } catch (IOException e) {
+ if (e.getMessage().contains("Access is denied")) {
+ throw new CannotWriteToLocationException("The randomizer cannot write to this location: " + filename);
+ } else {
+ throw new RandomizerIOException(e);
+ }
+ }
+ return true;
+ }
+
+ @Override
+ public boolean saveRomDirectory(String filename) {
+ // do nothing. DS games do have the concept of a filesystem, but it's way more
+ // convenient for users to use ROM files instead.
+ return true;
+ }
+
+ @Override
+ public boolean hasGameUpdateLoaded() {
+ return false;
+ }
+
+ @Override
+ public boolean loadGameUpdate(String filename) {
+ // do nothing, as DS games don't have external game updates
+ return true;
+ }
+
+ @Override
+ public void removeGameUpdate() {
+ // do nothing, as DS games don't have external game updates
+ }
+
+ @Override
+ public String getGameUpdateVersion() {
+ // do nothing, as DS games don't have external game updates
+ return null;
+ }
+
+ @Override
+ public void printRomDiagnostics(PrintStream logStream) {
+ baseRom.printRomDiagnostics(logStream);
+ }
+
+ public void closeInnerRom() throws IOException {
+ baseRom.closeROM();
+ }
+
+ @Override
+ public boolean canChangeStaticPokemon() {
+ return false;
+ }
+
+ @Override
+ public boolean hasPhysicalSpecialSplit() {
+ // Default value for Gen4+.
+ // Handlers can override again in case of ROM hacks etc.
+ return true;
+ }
+
+ public NARCArchive readNARC(String subpath) throws IOException {
+ return new NARCArchive(readFile(subpath));
+ }
+
+ public void writeNARC(String subpath, NARCArchive narc) throws IOException {
+ this.writeFile(subpath, narc.getBytes());
+ }
+
+ protected static String getROMCodeFromFile(String filename) {
+ try {
+ FileInputStream fis = new FileInputStream(filename);
+ fis.skip(0x0C);
+ byte[] sig = FileFunctions.readFullyIntoBuffer(fis, 4);
+ fis.close();
+ return new String(sig, "US-ASCII");
+ } catch (IOException e) {
+ throw new RandomizerIOException(e);
+ }
+ }
+
+ protected static byte getVersionFromFile(String filename) {
+ try {
+ FileInputStream fis = new FileInputStream(filename);
+ fis.skip(0x1E);
+ byte[] version = FileFunctions.readFullyIntoBuffer(fis, 1);
+ fis.close();
+ return version[0];
+ } catch (IOException e) {
+ throw new RandomizerIOException(e);
+ }
+ }
+
+ protected int readByte(byte[] data, int offset) { return data[offset] & 0xFF; }
+
+ protected int readWord(byte[] data, int offset) {
+ return (data[offset] & 0xFF) | ((data[offset + 1] & 0xFF) << 8);
+ }
+
+ protected int readLong(byte[] data, int offset) {
+ return (data[offset] & 0xFF) | ((data[offset + 1] & 0xFF) << 8) | ((data[offset + 2] & 0xFF) << 16)
+ | ((data[offset + 3] & 0xFF) << 24);
+ }
+
+ protected int readRelativePointer(byte[] data, int offset) {
+ return readLong(data, offset) + offset + 4;
+ }
+
+ protected void writeWord(byte[] data, int offset, int value) {
+ data[offset] = (byte) (value & 0xFF);
+ data[offset + 1] = (byte) ((value >> 8) & 0xFF);
+ }
+
+ protected void writeLong(byte[] data, int offset, int value) {
+ data[offset] = (byte) (value & 0xFF);
+ data[offset + 1] = (byte) ((value >> 8) & 0xFF);
+ data[offset + 2] = (byte) ((value >> 16) & 0xFF);
+ data[offset + 3] = (byte) ((value >> 24) & 0xFF);
+ }
+
+ protected void writeRelativePointer(byte[] data, int offset, int pointer) {
+ int relPointer = pointer - (offset + 4);
+ writeLong(data, offset, relPointer);
+ }
+
+ protected byte[] readFile(String location) throws IOException {
+ return baseRom.getFile(location);
+ }
+
+ protected void writeFile(String location, byte[] data) throws IOException {
+ writeFile(location, data, 0, data.length);
+ }
+
+ protected void writeFile(String location, byte[] data, int offset, int length) throws IOException {
+ if (offset != 0 || length != data.length) {
+ byte[] newData = new byte[length];
+ System.arraycopy(data, offset, newData, 0, length);
+ data = newData;
+ }
+ baseRom.writeFile(location, data);
+ }
+
+ protected byte[] readARM9() throws IOException {
+ return baseRom.getARM9();
+ }
+
+ protected void writeARM9(byte[] data) throws IOException {
+ baseRom.writeARM9(data);
+ }
+
+ protected byte[] readOverlay(int number) throws IOException {
+ return baseRom.getOverlay(number);
+ }
+
+ protected void writeOverlay(int number, byte[] data) throws IOException {
+ baseRom.writeOverlay(number, data);
+ }
+
+ protected void readByteIntoFlags(byte[] data, boolean[] flags, int offsetIntoFlags, int offsetIntoData) {
+ int thisByte = data[offsetIntoData] & 0xFF;
+ for (int i = 0; i < 8 && (i + offsetIntoFlags) < flags.length; i++) {
+ flags[offsetIntoFlags + i] = ((thisByte >> i) & 0x01) == 0x01;
+ }
+ }
+
+ protected byte getByteFromFlags(boolean[] flags, int offsetIntoFlags) {
+ int thisByte = 0;
+ for (int i = 0; i < 8 && (i + offsetIntoFlags) < flags.length; i++) {
+ thisByte |= (flags[offsetIntoFlags + i] ? 1 : 0) << i;
+ }
+ return (byte) thisByte;
+ }
+
+ protected int typeTMPaletteNumber(Type t) {
+ if (t == null) {
+ return 411; // CURSE
+ }
+ switch (t) {
+ case FIGHTING:
+ return 398;
+ case DRAGON:
+ return 399;
+ case WATER:
+ return 400;
+ case PSYCHIC:
+ return 401;
+ case NORMAL:
+ return 402;
+ case POISON:
+ return 403;
+ case ICE:
+ return 404;
+ case GRASS:
+ return 405;
+ case FIRE:
+ return 406;
+ case DARK:
+ return 407;
+ case STEEL:
+ return 408;
+ case ELECTRIC:
+ return 409;
+ case GROUND:
+ return 410;
+ case GHOST:
+ default:
+ return 411; // for CURSE
+ case ROCK:
+ return 412;
+ case FLYING:
+ return 413;
+ case BUG:
+ return 610;
+ }
+ }
+
+ private int find(byte[] data, String hexString) {
+ if (hexString.length() % 2 != 0) {
+ return -3; // error
+ }
+ byte[] searchFor = new byte[hexString.length() / 2];
+ for (int i = 0; i < searchFor.length; i++) {
+ searchFor[i] = (byte) Integer.parseInt(hexString.substring(i * 2, i * 2 + 2), 16);
+ }
+ List<Integer> found = RomFunctions.search(data, searchFor);
+ if (found.size() == 0) {
+ return -1; // not found
+ } else if (found.size() > 1) {
+ return -2; // not unique
+ } else {
+ return found.get(0);
+ }
+ }
+
+ protected byte[] extendARM9(byte[] arm9, int extendBy, String prefix, int arm9Offset) {
+ /*
+ Simply extending the ARM9 at the end doesn't work. Towards the end of the ARM9, the following sections exist:
+ 1. A section that is copied to ITCM (Instruction Tightly Coupled Memory)
+ 2. A section that is copied to DTCM (Data Tightly Coupled Memory)
+ 3. Pointers specifying to where these sections should be copied as well as their sizes
+
+ All of these sections are later overwritten(!) and the area is used more or less like a regular RAM area.
+ This means that if any new code is put after these sections, it will also be overwritten.
+ Changing which area is overwritten is not viable. There are very many pointers to this area that would need to
+ be re-indexed.
+
+ Our solution is to extend the section that is to be copied to ITCM, so that any new code gets copied to
+ ITCM and can be executed from there. This means we have to shift all the data that is after this in order to
+ make space. Additionally, elsewhere in the ARM9, pointers are stored specifying from where the ITCM
+ section should be copied, as well as some other data. They are supposedly part of some sort of NDS library
+ functions and should work the same across games; look for "[SDK+NINTENDO:" in the ARM9 and these pointers should
+ be slightly before that. They are as follows (each pointer = 4 bytes):
+ 1. Pointer specifying from where the destination pointers/sizes should be read (see point 3 above)
+ 2. Pointer specifying the end address of the ARM9.
+ 3. Pointer specifying from where data copying should start (since ITCM is first, this corresponds to the start
+ of the section that should be copied to ITCM).
+ 4. Pointer specifying where data should start being overwritten. (should be identical to #3)
+ 5. Pointer specifying where data should stop being overwritten (should correspond to start of ovl table).
+ 6. ???
+
+ Out of these, we want to change #1 (it will be moved because we have to shift the end of the ARM9 to make space
+ for enlarging the "copy to ITCM" area) and #2 (since the ARM9 will be made larger). We also want to change the
+ specified size for the ITCM area since we're enlarging it.
+ */
+
+ if (arm9Extended) return arm9; // Don't try to extend the ARM9 more than once
+
+ int tcmCopyingPointersOffset = find(arm9, prefix);
+ tcmCopyingPointersOffset += prefix.length() / 2; // because it was a prefix
+
+ int oldDestPointersOffset = FileFunctions.readFullInt(arm9, tcmCopyingPointersOffset) - arm9Offset;
+ int itcmSrcOffset =
+ FileFunctions.readFullInt(arm9, tcmCopyingPointersOffset + 8) - arm9Offset;
+ int itcmSizeOffset = oldDestPointersOffset + 4;
+ int oldITCMSize = FileFunctions.readFullInt(arm9, itcmSizeOffset);
+
+ int oldDTCMOffset = itcmSrcOffset + oldITCMSize;
+
+ byte[] newARM9 = Arrays.copyOf(arm9, arm9.length + extendBy);
+
+ // Change:
+ // 1. Pointer to destination pointers/sizes
+ // 2. ARM9 size
+ // 3. Size of the area copied to ITCM
+ FileFunctions.writeFullInt(newARM9, tcmCopyingPointersOffset,
+ oldDestPointersOffset + extendBy + arm9Offset);
+ FileFunctions.writeFullInt(newARM9, tcmCopyingPointersOffset + 4,
+ newARM9.length + arm9Offset);
+ FileFunctions.writeFullInt(newARM9, itcmSizeOffset, oldITCMSize + extendBy);
+
+ // Finally, shift everything
+ System.arraycopy(newARM9, oldDTCMOffset, newARM9, oldDTCMOffset + extendBy,
+ arm9.length - oldDTCMOffset);
+
+ arm9Extended = true;
+
+ return newARM9;
+ }
+
+}