summaryrefslogtreecommitdiff
path: root/src/com/sneed/pkrandom/ctr
diff options
context:
space:
mode:
Diffstat (limited to 'src/com/sneed/pkrandom/ctr')
-rw-r--r--src/com/sneed/pkrandom/ctr/AMX.java227
-rw-r--r--src/com/sneed/pkrandom/ctr/BFLIM.java203
-rw-r--r--src/com/sneed/pkrandom/ctr/GARCArchive.java388
-rw-r--r--src/com/sneed/pkrandom/ctr/Mini.java102
-rw-r--r--src/com/sneed/pkrandom/ctr/NCCH.java1024
-rw-r--r--src/com/sneed/pkrandom/ctr/RomfsFile.java121
-rw-r--r--src/com/sneed/pkrandom/ctr/SMDH.java118
7 files changed, 0 insertions, 2183 deletions
diff --git a/src/com/sneed/pkrandom/ctr/AMX.java b/src/com/sneed/pkrandom/ctr/AMX.java
deleted file mode 100644
index 41687eb..0000000
--- a/src/com/sneed/pkrandom/ctr/AMX.java
+++ /dev/null
@@ -1,227 +0,0 @@
-package com.sneed.pkrandom.ctr;
-
-/*----------------------------------------------------------------------------*/
-/*-- AMX.java - class for handling AMX script archives --*/
-/*-- --*/
-/*-- Contains code based on "pk3DS", copyright (C) Kaphotics --*/
-/*-- Contains code based on "pkNX", copyright (C) Kaphotics --*/
-/*-- Contains code based on "poketools", copyright (C) FireyFly --*/
-/*-- Additional contributions by the UPR-ZX team --*/
-/*-- --*/
-/*-- 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 com.sneed.pkrandom.FileFunctions;
-import com.sneed.pkrandom.exceptions.RandomizerIOException;
-
-import java.io.ByteArrayOutputStream;
-import java.io.IOException;
-import java.nio.ByteBuffer;
-import java.nio.ByteOrder;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.List;
-
-public class AMX {
-
- public byte[] decData;
- public int scriptOffset = 0;
-
- private int amxMagic = 0x0A0AF1E0;
- private int amxMagicDebug = 0x0A0AF1EF;
- private long mask = 0xFF;
-
- private int length;
-
- private int scriptInstrStart;
- private int scriptMovementStart;
- private int finalOffset;
- private int allocatedMemory;
-
- private int compLength;
- private int decompLength;
-
- private int ptrOffset;
- private int ptrCount;
-
- private byte[] extraData;
-
- public AMX(byte[] data, int scriptNum) throws IOException {
- int found = 0;
- for (int i = 0; i < data.length - 3; i++) {
- int val = FileFunctions.readFullInt(data,i);
- if (val == amxMagic) {
- if (found == scriptNum) {
- int length = FileFunctions.readFullInt(data,i-4);
- readHeaderAndDecompress(Arrays.copyOfRange(data,i-4,i-4+length));
- scriptOffset = i-4;
- break;
- } else {
- found++;
- }
- }
- }
- }
-
- public AMX(byte[] encData) throws IOException {
- readHeaderAndDecompress(encData);
- }
-
- // Credit to the creators of pk3DS (Kaphotics et al)
- private void readHeaderAndDecompress(byte[] encData) throws IOException {
- length = FileFunctions.readFullInt(encData,0);
- int magic = FileFunctions.readFullInt(encData,4);
- if (magic != amxMagic) {
- throw new IOException();
- }
-
- ptrOffset = FileFunctions.read2ByteInt(encData,8);
- ptrCount = FileFunctions.read2ByteInt(encData,0xA);
-
- scriptInstrStart = FileFunctions.readFullInt(encData,0xC);
- scriptMovementStart = FileFunctions.readFullInt(encData,0x10);
- finalOffset = FileFunctions.readFullInt(encData,0x14);
- allocatedMemory = FileFunctions.readFullInt(encData,0x18);
-
- compLength = length - scriptInstrStart;
- byte[] compressedBytes = Arrays.copyOfRange(encData,scriptInstrStart,length);
- decompLength = finalOffset - scriptInstrStart;
-
- decData = decompressBytes(compressedBytes, decompLength);
- extraData = Arrays.copyOfRange(encData,0x1C,scriptInstrStart);
- }
-
- // Credit to FireyFly
- private byte[] decompressBytes(byte[] data, int length) {
- byte[] code = new byte[length];
- int i = 0, j = 0, x = 0, f = 0;
- while (i < code.length) {
- int b = data[f++];
- int v = b & 0x7F;
- if (++j == 1) {
- x = ((((v >>> 6 == 0 ? 1 : 0) - 1 ) << 6) | v);
- } else {
- x = (x << 7) | (v & 0xFF);
- }
- if ((b & 0x80) != 0) continue;
- code[i++] = (byte)(x & 0xFF);
- code[i++] = (byte)((x >>> 8) & 0xFF);
- code[i++] = (byte)((x >>> 16) & 0xFF);
- code[i++] = (byte)((x >>> 24) & 0xFF);
- j = 0;
- }
- return code;
- }
-
- public byte[] getBytes() {
-
- ByteBuffer bbuf = ByteBuffer.allocate(length*2);
-
- bbuf.order(ByteOrder.LITTLE_ENDIAN);
-
- bbuf.putInt(length);
- bbuf.putInt(amxMagic);
- bbuf.putShort((short)ptrOffset);
- bbuf.putShort((short)ptrCount);
- bbuf.putInt(scriptInstrStart);
- bbuf.putInt(scriptMovementStart);
- bbuf.putInt(finalOffset);
- bbuf.putInt(allocatedMemory);
- bbuf.put(extraData);
- bbuf.put(compressScript(decData));
- bbuf.flip();
- bbuf.putInt(bbuf.limit());
-
- return Arrays.copyOfRange(bbuf.array(),0,bbuf.limit());
- }
-
- private byte[] compressScript(byte[] data) {
- if (data == null || data.length % 4 != 0) {
- return null;
- }
- ByteBuffer inBuf = ByteBuffer.wrap(data);
- inBuf.order(ByteOrder.LITTLE_ENDIAN);
-
- ByteArrayOutputStream out = new ByteArrayOutputStream(compLength);
-
- try {
- while (inBuf.position() < data.length) {
- compressBytes(inBuf, out);
- }
- } catch (IOException e) {
- throw new RandomizerIOException(e);
- }
-
- return out.toByteArray();
- }
-
- // Modified version of the AMX script compression algorithm from pkNX
- private void compressBytes(ByteBuffer inBuf, ByteArrayOutputStream out) throws IOException {
- List<Byte> bytes = new ArrayList<>();
- int instructionTemp = inBuf.getInt(inBuf.position());
- long instruction = Integer.toUnsignedLong(instructionTemp);
- boolean sign = (instruction & 0x80000000) > 0;
-
- // Signed (negative) values are handled opposite of unsigned (positive) values.
- // Positive values are "done" when we've shifted the value down to zero, but
- // we don't need to store the highest 1s in a signed value. We handle this by
- // tracking the loop via a NOTed shadow copy of the instruction if it's signed.
- int shadowTemp = sign ? ~instructionTemp : instructionTemp;
- long shadow = Integer.toUnsignedLong(shadowTemp);
- do
- {
- long least7 = instruction & 0b01111111;
- byte byteVal = (byte)least7;
-
- if (bytes.size() > 0)
- {
- // Continuation bit on all but the lowest byte
- byteVal |= 0x80;
- }
-
- bytes.add(byteVal);
-
- instruction >>= 7;
- shadow >>= 7;
- }
- while (shadow != 0);
-
- if (bytes.size() < 5)
- {
- // Ensure "sign bit" (bit just to the right of highest continuation bit) is
- // correct. Add an extra empty continuation byte if we need to. Values can't
- // be longer than 5 bytes, though.
-
- int signBit = sign ? 0x40 : 0x00;
-
- if ((bytes.get(bytes.size() - 1) & 0x40) != signBit)
- bytes.add((byte)(sign ? 0xFF : 0x80));
- }
-
- // Reverse for endianess
- for (int i = 0; i < bytes.size() / 2; i++) {
- byte temp = bytes.get(i);
- bytes.set(i, bytes.get(bytes.size() - i - 1));
- bytes.set(bytes.size() - i - 1, temp);
- }
-
- byte[] ret = new byte[bytes.size()];
- for (int i = 0; i < ret.length; i++) {
- ret[i] = bytes.get(i);
- }
-
- inBuf.position(inBuf.position() + 4);
- out.write(ret);
- }
-}
diff --git a/src/com/sneed/pkrandom/ctr/BFLIM.java b/src/com/sneed/pkrandom/ctr/BFLIM.java
deleted file mode 100644
index 9447e00..0000000
--- a/src/com/sneed/pkrandom/ctr/BFLIM.java
+++ /dev/null
@@ -1,203 +0,0 @@
-package com.sneed.pkrandom.ctr;
-
-/*----------------------------------------------------------------------------*/
-/*-- BFLIM.java - class for reading/parsing BFLIM images. --*/
-/*-- Note that this class is optimized around handling Gen 7 --*/
-/*-- Pokemon icons, and won't work for all types of BFLIMs --*/
-/*-- --*/
-/*-- Code based on "Switch Toolbox", copyright (C) KillzXGaming --*/
-/*-- --*/
-/*-- Ported to Java by UPR-ZX Team 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 com.sneed.pkrandom.FileFunctions;
-
-import java.awt.image.BufferedImage;
-
-public class BFLIM {
-
- private int width;
- private int height;
- private byte[] imageData;
- private Header header;
- private Image image;
-
- public BFLIM(byte[] bflimBytes) {
- if (bflimBytes.length < 0x28) {
- throw new IllegalArgumentException("Invalid BFLIM: not long enough to contain a header");
- }
- header = new Header(bflimBytes);
- image = new Image(bflimBytes);
- width = image.width;
- height = image.height;
- imageData = new byte[image.imageSize];
- System.arraycopy(bflimBytes, 0, imageData, 0, image.imageSize);
- }
-
- @SuppressWarnings("SuspiciousNameCombination")
- public BufferedImage getImage() {
- // Swap width and height, because the image is rendered on its side
- int swappedWidth = height;
- int swappedHeight = width;
- int[] decodedImageData = decodeBlock(imageData, swappedWidth, swappedHeight);
- int[] colorData = convertToColorData(decodedImageData);
- int[] correctedColorData = rearrangeImage(colorData, width, height);
- BufferedImage image = new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB);
- for (int y = 0; y < height; y++) {
- for (int x = 0; x < width; x++) {
- int color = correctedColorData[x + (y * this.width)];
- image.setRGB(x, y, color);
- }
- }
- return image;
- }
-
- private static int[] SwizzleLUT = {
- 0, 1, 8, 9, 2, 3, 10, 11,
- 16, 17, 24, 25, 18, 19, 26, 27,
- 4, 5, 12, 13, 6, 7, 14, 15,
- 20, 21, 28, 29, 22, 23, 30, 31,
- 32, 33, 40, 41, 34, 35, 42, 43,
- 48, 49, 56, 57, 50, 51, 58, 59,
- 36, 37, 44, 45, 38, 39, 46, 47,
- 52, 53, 60, 61, 54, 55, 62, 63
- };
-
- private int[] decodeBlock(byte[] data, int width, int height) {
- int[] output = new int[width * height * 4];
- int inputOffset = 0;
- for (int ty = 0; ty < height; ty += 8) {
- for (int tx = 0; tx < width; tx += 8) {
- for (int px = 0; px < 64; px++) {
- int x = SwizzleLUT[px] & 7;
- int y = (SwizzleLUT[px] - x) >> 3;
- int outputOffset = (tx + x + ((height - 1 - (ty + y)) * width)) * 4;
- int value = FileFunctions.read2ByteInt(data, inputOffset);
- if (image.format == 7) {
- decodeRGBA5551(output, outputOffset, value);
- } else if (image.format == 8) {
- decodeRGBA4(output, outputOffset, value);
- } else {
- throw new IllegalArgumentException("Unsupported BFLIM: unsupported image format");
- }
- inputOffset += 2;
- }
- }
- }
- return output;
- }
-
- private int[] convertToColorData(int[] decodedImageData) {
- int[] output = new int[decodedImageData.length / 4];
- for (int i = 0; i < decodedImageData.length; i += 4) {
- int a = decodedImageData[i];
- int b = decodedImageData[i + 1];
- int g = decodedImageData[i + 2];
- int r = decodedImageData[i + 3];
- int color = (a << 24) | (b << 16) | (g << 8) | r;
- output[i / 4] = color;
- }
- return output;
- }
-
- private int[] rearrangeImage(int[] colorData, int width, int height) {
- int[] output = new int[colorData.length];
- for (int destY = 0; destY < height; destY++) {
- for (int destX = 0; destX < width; destX++) {
- int srcX = height - destY - 1;
- int srcY = width - destX - 1;
- int srcIndex = srcX + (srcY * height);
- int destIndex = destX + (destY * width);
- output[destIndex] = colorData[srcIndex];
- }
- }
- return output;
- }
-
- private static void decodeRGBA5551(int[] output, int outputOffset, int value) {
- int R = ((value >> 1) & 0x1f) << 3;
- int G = ((value >> 6) & 0x1f) << 3;
- int B = ((value >> 11) & 0x1f) << 3;
- int A = (value & 1) * 0xFF;
- R = R | (R >> 5);
- G = G | (G >> 5);
- B = B | (B >> 5);
- output[outputOffset] = A;
- output[outputOffset + 1] = B;
- output[outputOffset + 2] = G;
- output[outputOffset + 3] = R;
- }
-
- private static void decodeRGBA4(int[] output, int outputOffset, int value) {
- int R = ((value >> 4) & 0xf);
- int G = ((value >> 8) & 0xf);
- int B = ((value >> 12) & 0xf);
- int A = (value & 1) | (value << 4);
- R = R | (R << 4);
- G = G | (G << 4);
- B = B | (B << 4);
- output[outputOffset] = A;
- output[outputOffset + 1] = B;
- output[outputOffset + 2] = G;
- output[outputOffset + 3] = R;
- }
-
- private class Header {
- public int version;
-
- public Header(byte[] bflimBytes) {
- int headerOffset = bflimBytes.length - 0x28;
- int signature = FileFunctions.readFullIntBigEndian(bflimBytes, headerOffset);
- if (signature != 0x464C494D) {
- throw new IllegalArgumentException("Invalid BFLIM: cannot find FLIM header");
- }
- boolean bigEndian = FileFunctions.read2ByteInt(bflimBytes, headerOffset + 4) == 0xFFFE;
- if (bigEndian) {
- throw new IllegalArgumentException("Unsupported BFLIM: this is a big endian BFLIM");
- }
- int headerSize = FileFunctions.read2ByteInt(bflimBytes, headerOffset + 6);
- if (headerSize != 0x14) {
- throw new IllegalArgumentException("Invalid BFLIM: header length does not equal 0x14");
- }
- version = FileFunctions.readFullInt(bflimBytes, headerOffset + 8);
- }
- }
-
- private class Image {
- public int size;
- public short width;
- public short height;
- public short alignment;
- public byte format;
- public byte flags;
- public int imageSize;
-
- public Image(byte[] bflimBytes) {
- int imageHeaderOffset = bflimBytes.length - 0x14;
- int signature = FileFunctions.readFullIntBigEndian(bflimBytes, imageHeaderOffset);
- if (signature != 0x696D6167) {
- throw new IllegalArgumentException("Invalid BFLIM: cannot find imag header");
- }
- size = FileFunctions.readFullInt(bflimBytes, imageHeaderOffset + 4);
- width = (short) FileFunctions.read2ByteInt(bflimBytes, imageHeaderOffset + 8);
- height = (short) FileFunctions.read2ByteInt(bflimBytes, imageHeaderOffset + 10);
- alignment = (short) FileFunctions.read2ByteInt(bflimBytes, imageHeaderOffset + 12);
- format = bflimBytes[imageHeaderOffset + 14];
- flags = bflimBytes[imageHeaderOffset + 15];
- imageSize = FileFunctions.readFullInt(bflimBytes, imageHeaderOffset + 16);
- }
- }
-}
diff --git a/src/com/sneed/pkrandom/ctr/GARCArchive.java b/src/com/sneed/pkrandom/ctr/GARCArchive.java
deleted file mode 100644
index 387c23d..0000000
--- a/src/com/sneed/pkrandom/ctr/GARCArchive.java
+++ /dev/null
@@ -1,388 +0,0 @@
-package com.sneed.pkrandom.ctr;
-
-/*----------------------------------------------------------------------------*/
-/*-- GARCArchive.java - class for packing/unpacking GARC archives --*/
-/*-- --*/
-/*-- Code based on "pk3DS", copyright (C) Kaphotics --*/
-/*-- --*/
-/*-- Ported to Java by UPR-ZX Team 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 cuecompressors.BLZCoder;
-
-import java.io.ByteArrayOutputStream;
-import java.io.File;
-import java.io.FileOutputStream;
-import java.io.IOException;
-import java.nio.ByteBuffer;
-import java.nio.ByteOrder;
-import java.util.*;
-
-public class GARCArchive {
-
- private final int VER_4 = 0x0400;
- private final int VER_6 = 0x0600;
- private int version;
- private final int garcHeaderSize_4 = 0x1C;
- private final int garcHeaderSize_6 = 0x24;
- private final String garcMagic = "CRAG";
- private final String fatoMagic = "OTAF";
- private final String fatbMagic = "BTAF";
- private final String fimbMagic = "BMIF";
- private boolean skipDecompression = true;
-
- public List<Map<Integer,byte[]>> files = new ArrayList<>();
- private Map<Integer,Boolean> isCompressed = new TreeMap<>();
- private List<Boolean> compressThese = null;
-
- private GARCFrame garc;
- private FATOFrame fato;
- private FATBFrame fatb;
- private FIMBFrame fimb;
-
- public GARCArchive() {
-
- }
-
- public GARCArchive(byte[] data, boolean skipDecompression) throws IOException {
- this.skipDecompression = skipDecompression;
- boolean success = readFrames(data);
- if (!success) {
- throw new IOException("Invalid GARC file");
- }
- files = fimb.files;
- }
-
- public GARCArchive(byte[] data, List<Boolean> compressedThese) throws IOException {
- this.compressThese = compressedThese;
- boolean success = readFrames(data);
- if (!success) {
- throw new IOException("Invalid GARC file");
- }
- files = fimb.files;
- }
-
- private boolean readFrames(byte[] data) {
- if (data.length <= 0) {
- System.out.println("Empty GARC");
- return false;
- }
- ByteBuffer bbuf = ByteBuffer.wrap(data);
- bbuf.order(ByteOrder.LITTLE_ENDIAN);
- // GARC
- byte[] magicBuf = new byte[4];
- bbuf.get(magicBuf);
- String magic = new String(magicBuf);
- if (!magic.equals(garcMagic)) {
- return false;
- }
- garc = new GARCFrame();
- garc.headerSize = bbuf.getInt();
- garc.endianness = bbuf.getShort();
- garc.version = bbuf.getShort();
- int frameCount = bbuf.getInt();
- if (frameCount != 4) {
- return false;
- }
- garc.dataOffset = bbuf.getInt();
- garc.fileSize = bbuf.getInt();
- if (garc.version == VER_4) {
- garc.contentLargestUnpadded = bbuf.getInt();
- garc.contentPadToNearest = 4;
- version = 4;
- } else if (garc.version == VER_6) {
- garc.contentLargestPadded = bbuf.getInt();
- garc.contentLargestUnpadded = bbuf.getInt();
- garc.contentPadToNearest = bbuf.getInt();
- version = 6;
- } else {
- return false;
- }
-
- // FATO
- fato = new FATOFrame();
- bbuf.get(magicBuf);
- magic = new String(magicBuf);
- if (!magic.equals(fatoMagic)) {
- return false;
- }
- fato.headerSize = bbuf.getInt();
- fato.entryCount = bbuf.getShort();
- fato.padding = bbuf.getShort();
- fato.entries = new int[fato.entryCount];
- for (int i = 0; i < fato.entryCount; i++) {
- fato.entries[i] = bbuf.getInt();
- }
-
- // FATB
- fatb = new FATBFrame();
- bbuf.get(magicBuf);
- magic = new String(magicBuf);
- if (!magic.equals(fatbMagic)) {
- return false;
- }
- fatb.headerSize = bbuf.getInt();
- fatb.fileCount = bbuf.getInt();
- fatb.entries = new FATBEntry[fatb.fileCount];
- for (int i = 0; i < fatb.fileCount; i++) {
- fatb.entries[i] = new FATBEntry();
- fatb.entries[i].vector = bbuf.getInt();
- fatb.entries[i].subEntries = new TreeMap<>();
- int bitVector = fatb.entries[i].vector;
- int counter = 0;
- for (int b = 0; b < 32; b++) {
- boolean exists = (bitVector & 1) == 1;
- bitVector >>>= 1;
- if (!exists) continue;
- FATBSubEntry subEntry = new FATBSubEntry();
- subEntry.start = bbuf.getInt();
- subEntry.end = bbuf.getInt();
- subEntry.length = bbuf.getInt();
- fatb.entries[i].subEntries.put(b,subEntry);
- counter++;
- }
- fatb.entries[i].isFolder = counter > 1;
- }
-
- // FIMB
- fimb = new FIMBFrame();
- bbuf.get(magicBuf);
- magic = new String(magicBuf);
- if (!magic.equals(fimbMagic)) {
- return false;
- }
- fimb.headerSize = bbuf.getInt();
- fimb.dataSize = bbuf.getInt();
- fimb.files = new ArrayList<>();
- for (int i = 0; i < fatb.fileCount; i++) {
- FATBEntry entry = fatb.entries[i];
- Map<Integer,byte[]> files = new TreeMap<>();
- for (int k: entry.subEntries.keySet()) {
- FATBSubEntry subEntry = entry.subEntries.get(k);
- bbuf.position(garc.dataOffset + subEntry.start);
- byte[] file = new byte[subEntry.length];
- boolean compressed = compressThese == null ?
- bbuf.get(bbuf.position()) == 0x11 && !skipDecompression :
- bbuf.get(bbuf.position()) == 0x11 && compressThese.get(i);
- bbuf.get(file);
- if (compressed) {
- try {
- files.put(k,new BLZCoder(null).BLZ_DecodePub(file,"GARC"));
- isCompressed.put(i,true);
- } catch (Exception e) {
- e.printStackTrace();
- return false;
- }
- } else {
- files.put(k,file);
- isCompressed.put(i,false);
- }
- }
- fimb.files.add(files);
- }
- return true;
- }
-
- public void updateFiles(List<Map<Integer,byte[]>> files) {
- fimb.files = files;
- }
-
- public byte[] getBytes() throws IOException {
- int garcHeaderSize = garc.version == VER_4 ? garcHeaderSize_4 : garcHeaderSize_6;
- ByteBuffer garcBuf = ByteBuffer.allocate(garcHeaderSize);
- garcBuf.order(ByteOrder.LITTLE_ENDIAN);
- garcBuf.put(garcMagic.getBytes());
- garcBuf.putInt(garcHeaderSize);
- garcBuf.putShort((short)0xFEFF);
- garcBuf.putShort(version == 4 ? (short)VER_4 : (short)VER_6);
- garcBuf.putInt(4);
-
- ByteBuffer fatoBuf = ByteBuffer.allocate(fato.headerSize);
- fatoBuf.order(ByteOrder.LITTLE_ENDIAN);
- fatoBuf.put(fatoMagic.getBytes());
- fatoBuf.putInt(fato.headerSize);
- fatoBuf.putShort((short)fato.entryCount);
- fatoBuf.putShort((short)fato.padding);
-
- ByteBuffer fatbBuf = ByteBuffer.allocate(fatb.headerSize);
- fatbBuf.order(ByteOrder.LITTLE_ENDIAN);
- fatbBuf.put(fatbMagic.getBytes());
- fatbBuf.putInt(fatb.headerSize);
- fatbBuf.putInt(fatb.fileCount);
-
- ByteBuffer fimbHeaderBuf = ByteBuffer.allocate(fimb.headerSize);
- fimbHeaderBuf.order(ByteOrder.LITTLE_ENDIAN);
- fimbHeaderBuf.put(fimbMagic.getBytes());
- fimbHeaderBuf.putInt(fimb.headerSize);
-
- ByteArrayOutputStream fimbPayloadStream = new ByteArrayOutputStream(); // Unknown size, can't use ByteBuffer
-
- int fimbOffset = 0;
- int largestSize = 0;
- int largestPadded = 0;
- for (int i = 0; i < fimb.files.size(); i++) {
- Map<Integer,byte[]> directory = fimb.files.get(i);
- int bitVector = 0;
- int totalLength = 0;
- for (int k: directory.keySet()) {
- bitVector |= (1 << k);
- byte[] file = directory.get(k);
- if (isCompressed.get(i)) {
- file = new BLZCoder(null).BLZ_EncodePub(file,false,false,"GARC");
- }
- fimbPayloadStream.write(file);
- totalLength += file.length;
- }
-
- int paddingRequired = totalLength % garc.contentPadToNearest;
- if (paddingRequired != 0) {
- paddingRequired = garc.contentPadToNearest - paddingRequired;
- }
-
- if (totalLength > largestSize) {
- largestSize = totalLength;
- }
- if (totalLength + paddingRequired > largestPadded) {
- largestPadded = totalLength + paddingRequired;
- }
-
- for (int j = 0; j < paddingRequired; j++) {
- fimbPayloadStream.write(fato.padding & 0xFF);
- }
-
- fatoBuf.putInt(fatbBuf.position() - 12);
-
- fatbBuf.putInt(bitVector);
- fatbBuf.putInt(fimbOffset);
- fimbOffset = fimbPayloadStream.size();
- fatbBuf.putInt(fimbOffset);
- fatbBuf.putInt(totalLength);
- }
-
- int dataOffset = garcHeaderSize + fatoBuf.position() + fatbBuf.position() + fimb.headerSize;
- garcBuf.putInt(dataOffset);
- garcBuf.putInt(dataOffset + fimbOffset);
- if (garc.version == VER_4) {
- garcBuf.putInt(largestSize);
- } else if (garc.version == VER_6) {
- garcBuf.putInt(largestPadded);
- garcBuf.putInt(largestSize);
- garcBuf.putInt(garc.contentPadToNearest);
- }
- fimbHeaderBuf.putInt(fimbPayloadStream.size());
-
- garcBuf.flip();
- fatoBuf.flip();
- fatbBuf.flip();
- fimbHeaderBuf.flip();
-
- byte[] fullArray = new byte[garcBuf.limit() + fatoBuf.limit() + fatbBuf.limit() + fimbHeaderBuf.limit() + fimbPayloadStream.size()];
- System.arraycopy(garcBuf.array(),
- 0,
- fullArray,
- 0,
- garcBuf.limit());
- System.arraycopy(fatoBuf.array(),
- 0,
- fullArray,
- garcBuf.limit(),
- fatoBuf.limit());
- System.arraycopy(fatbBuf.array(),
- 0,
- fullArray,
- garcBuf.limit()+fatoBuf.limit(),
- fatbBuf.limit());
- System.arraycopy(fimbHeaderBuf.array(),
- 0,
- fullArray,
- garcBuf.limit()+fatoBuf.limit()+fatbBuf.limit(),
- fimbHeaderBuf.limit());
-// garcBuf.get(fullArray);
-// fatoBuf.get(fullArray,garcBuf.limit(),fatoBuf.limit());
-// fatbBuf.get(fullArray,garcBuf.limit()+fatoBuf.limit(),fatbBuf.limit());
-// fimbHeaderBuf.get(fullArray,garcBuf.limit()+fatoBuf.limit()+fatbBuf.limit(),fimbHeaderBuf.limit());
- System.arraycopy(fimbPayloadStream.toByteArray(),
- 0,
- fullArray,
- garcBuf.limit()+fatoBuf.limit()+fatbBuf.limit()+fimbHeaderBuf.limit(),
- fimbPayloadStream.size());
- return fullArray;
- }
-
-
-
- public byte[] getFile(int index) {
- return fimb.files.get(index).get(0);
- }
-
- public byte[] getFile(int index, int subIndex) {
- return fimb.files.get(index).get(subIndex);
- }
-
- public void setFile(int index, byte[] data) {
- fimb.files.get(index).put(0,data);
- }
-
- public Map<Integer,byte[]> getDirectory(int index) {
- return fimb.files.get(index);
- }
-
- private class GARCFrame {
- int headerSize;
- int endianness;
- int version;
- int dataOffset;
- int fileSize;
-
- int contentLargestPadded;
- int contentLargestUnpadded;
- int contentPadToNearest;
- }
-
- private class FATOFrame {
- int headerSize;
- int entryCount;
- int padding;
-
- int[] entries;
- }
-
- private class FATBFrame {
- int headerSize;
- int fileCount;
- FATBEntry[] entries;
- }
-
- private class FATBEntry {
- int vector;
- boolean isFolder;
- Map<Integer,FATBSubEntry> subEntries;
- }
-
- private class FATBSubEntry {
- boolean exists;
- int start;
- int end;
- int length;
- int padding;
- }
-
- private class FIMBFrame {
- int headerSize;
- int dataSize;
- List<Map<Integer,byte[]>> files;
- }
-}
diff --git a/src/com/sneed/pkrandom/ctr/Mini.java b/src/com/sneed/pkrandom/ctr/Mini.java
deleted file mode 100644
index 64e94ba..0000000
--- a/src/com/sneed/pkrandom/ctr/Mini.java
+++ /dev/null
@@ -1,102 +0,0 @@
-package com.sneed.pkrandom.ctr;
-
-/*----------------------------------------------------------------------------*/
-/*-- Mini.java - class for packing/unpacking Mini archives --*/
-/*-- --*/
-/*-- Code based on "pk3DS", copyright (C) Kaphotics --*/
-/*-- --*/
-/*-- Ported to Java by UPR-ZX Team 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 com.sneed.pkrandom.FileFunctions;
-
-import java.io.ByteArrayOutputStream;
-import java.io.IOException;
-import java.nio.ByteBuffer;
-import java.nio.ByteOrder;
-
-public class Mini {
- public static byte[] PackMini(byte[][] fileData, String identifier) throws IOException {
- // Create new Binary with the relevant header bytes
- byte[] data = new byte[4];
- data[0] = (byte) identifier.charAt(0);
- data[1] = (byte) identifier.charAt(1);
- ByteBuffer buf = ByteBuffer.allocate(2);
- buf.order(ByteOrder.LITTLE_ENDIAN);
- buf.putShort((short) fileData.length);
- System.arraycopy(buf.array(), 0, data, 2, 2);
-
- int count = fileData.length;
- int dataOffset = 4 + 4 + (count * 4);
-
- // Start the data filling
- ByteArrayOutputStream dataOut = new ByteArrayOutputStream();
- ByteArrayOutputStream offsetMap = new ByteArrayOutputStream();
- // For each file...
- for (int i = 0; i < count; i++) {
- int fileOffset = dataOut.size() + dataOffset;
- buf = ByteBuffer.allocate(4);
- buf.order(ByteOrder.LITTLE_ENDIAN);
- buf.putInt(fileOffset);
- offsetMap.write(buf.array());
- dataOut.write(fileData[i]);
-
- // Pad with zeroes until len % 4 == 0
- while (dataOut.size() % 4 != 0) {
- dataOut.write((byte) 0);
- }
- }
- // Cap the file
- buf = ByteBuffer.allocate(4);
- buf.order(ByteOrder.LITTLE_ENDIAN);
- buf.putInt(dataOut.size() + dataOffset);
- offsetMap.write(buf.array());
-
- ByteArrayOutputStream newPack = new ByteArrayOutputStream();
- ByteArrayOutputStream header = new ByteArrayOutputStream();
- header.write(data);
- header.writeTo(newPack);
- offsetMap.writeTo(newPack);
- dataOut.writeTo(newPack);
- return newPack.toByteArray();
- }
-
- public static byte[][] UnpackMini(byte[] fileData, String identifier) {
- if (fileData == null || fileData.length < 4) {
- return null;
- }
-
- if (identifier.charAt(0) != fileData[0] || identifier.charAt(1) != fileData[1]) {
- return null;
- }
-
- int count = FileFunctions.read2ByteInt(fileData, 2);
- int ctr = 4;
- int start = FileFunctions.readFullInt(fileData, ctr);
- ctr += 4;
- byte[][] returnData = new byte[count][];
- for (int i = 0; i < count; i++) {
- int end = FileFunctions.readFullInt(fileData, ctr);
- ctr += 4;
- int len = end - start;
- byte[] data = new byte[len];
- System.arraycopy(fileData, start, data, 0, len);
- returnData[i] = data;
- start = end;
- }
- return returnData;
- }
-}
diff --git a/src/com/sneed/pkrandom/ctr/NCCH.java b/src/com/sneed/pkrandom/ctr/NCCH.java
deleted file mode 100644
index 8db5a4e..0000000
--- a/src/com/sneed/pkrandom/ctr/NCCH.java
+++ /dev/null
@@ -1,1024 +0,0 @@
-package com.sneed.pkrandom.ctr;
-
-/*----------------------------------------------------------------------------*/
-/*-- NCCH.java - a base class for dealing with 3DS NCCH ROM images. --*/
-/*-- --*/
-/*-- 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. --*/
-/*-- --*/
-/*-- 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 com.sneed.pkrandom.FileFunctions;
-import com.sneed.pkrandom.SysConstants;
-import com.sneed.pkrandom.exceptions.CannotWriteToLocationException;
-import com.sneed.pkrandom.exceptions.EncryptedROMException;
-import com.sneed.pkrandom.exceptions.RandomizerIOException;
-import cuecompressors.BLZCoder;
-
-import java.io.*;
-import java.nio.charset.StandardCharsets;
-import java.nio.file.Path;
-import java.nio.file.Paths;
-import java.security.*;
-import java.util.*;
-
-public class NCCH {
- private String romFilename;
- private RandomAccessFile baseRom;
- private long ncchStartingOffset;
- private String productCode;
- private String titleId;
- private int version;
- private long exefsOffset, romfsOffset, fileDataOffset;
- private ExefsFileHeader codeFileHeader;
- private SMDH smdh;
- private List<ExefsFileHeader> extraExefsFiles;
- private List<FileMetadata> fileMetadataList;
- private Map<String, RomfsFile> romfsFiles;
- private boolean romOpen;
- private String tmpFolder;
- private boolean writingEnabled;
- private boolean codeCompressed, codeOpen, codeChanged;
- private byte[] codeRamstored;
-
- // Public so the base game can read it from the game update NCCH
- public long originalCodeCRC, originalRomfsHeaderCRC;
-
- private static final int media_unit_size = 0x200;
- private static final int header_and_exheader_size = 0xA00;
- private static final int ncsd_magic = 0x4E435344;
- private static final int cia_header_size = 0x2020;
- private static final int ncch_magic = 0x4E434348;
- private static final int ncch_and_ncsd_magic_offset = 0x100;
- private static final int exefs_header_size = 0x200;
- private static final int romfs_header_size = 0x5C;
- private static final int romfs_magic_1 = 0x49564643;
- private static final int romfs_magic_2 = 0x00000100;
- private static final int level3_header_size = 0x28;
- private static final int metadata_unused = 0xFFFFFFFF;
-
- public NCCH(String filename, String productCode, String titleId) throws IOException {
- this.romFilename = filename;
- this.baseRom = new RandomAccessFile(filename, "r");
- this.ncchStartingOffset = NCCH.getCXIOffsetInFile(filename);
- this.productCode = productCode;
- this.titleId = titleId;
- this.romOpen = true;
-
- if (this.ncchStartingOffset != -1) {
- this.version = this.readVersionFromFile();
- }
-
- // TMP folder?
- String rawFilename = new File(filename).getName();
- String dataFolder = "tmp_" + rawFilename.substring(0, rawFilename.lastIndexOf('.'));
- // remove nonsensical chars
- dataFolder = dataFolder.replaceAll("[^A-Za-z0-9_]+", "");
- File tmpFolder = new File(SysConstants.ROOT_PATH + dataFolder);
- tmpFolder.mkdirs();
- if (tmpFolder.canWrite()) {
- writingEnabled = true;
- this.tmpFolder = SysConstants.ROOT_PATH + dataFolder + File.separator;
- tmpFolder.deleteOnExit();
- } else {
- writingEnabled = false;
- }
-
- // The below code handles things "wrong" with regards to encrypted ROMs. We just
- // blindly treat the ROM as decrypted and try to parse all of its data, when we
- // *should* be looking at the header of the ROM to determine if the ROM is encrypted.
- // Unfortunately, many people have poorly-decrypted ROMs that do not properly set
- // the bytes on the NCCH header, so we can't assume that the header is telling the
- // truth. If we read the whole ROM without crashing, then it's probably decrypted.
- try {
- readFileSystem();
- } catch (Exception ex) {
- if (!this.isDecrypted()) {
- throw new EncryptedROMException(ex);
- } else {
- throw ex;
- }
- }
- }
-
- public void reopenROM() throws IOException {
- if (!this.romOpen) {
- baseRom = new RandomAccessFile(this.romFilename, "r");
- romOpen = true;
- }
- }
-
- public void closeROM() throws IOException {
- if (this.romOpen && baseRom != null) {
- baseRom.close();
- baseRom = null;
- romOpen = false;
- }
- }
-
- private void readFileSystem() throws IOException {
- exefsOffset = ncchStartingOffset + FileFunctions.readIntFromFile(baseRom, ncchStartingOffset + 0x1A0) * media_unit_size;
- romfsOffset = ncchStartingOffset + FileFunctions.readIntFromFile(baseRom, ncchStartingOffset + 0x1B0) * media_unit_size;
- baseRom.seek(ncchStartingOffset + 0x20D);
- byte systemControlInfoFlags = baseRom.readByte();
- codeCompressed = (systemControlInfoFlags & 0x01) != 0;
- readExefs();
- readRomfs();
- }
-
- private void readExefs() throws IOException {
- System.out.println("NCCH: Reading exefs...");
- byte[] exefsHeaderData = new byte[exefs_header_size];
- baseRom.seek(exefsOffset);
- baseRom.readFully(exefsHeaderData);
-
- ExefsFileHeader[] fileHeaders = new ExefsFileHeader[10];
- for (int i = 0; i < 10; i++) {
- fileHeaders[i] = new ExefsFileHeader(exefsHeaderData, i * 0x10);
- }
-
- extraExefsFiles = new ArrayList<>();
- for (ExefsFileHeader fileHeader : fileHeaders) {
- if (fileHeader.isValid() && fileHeader.filename.equals(".code")) {
- codeFileHeader = fileHeader;
- } else if (fileHeader.isValid()) {
- extraExefsFiles.add(fileHeader);
- }
-
- if (fileHeader.isValid() && fileHeader.filename.equals("icon")) {
- byte[] smdhBytes = new byte[fileHeader.size];
- baseRom.seek(exefsOffset + 0x200 + fileHeader.offset);
- baseRom.readFully(smdhBytes);
- smdh = new SMDH(smdhBytes);
- }
- }
- System.out.println("NCCH: Done reading exefs");
- }
-
- private void readRomfs() throws IOException {
- System.out.println("NCCH: Reading romfs...");
- byte[] romfsHeaderData = new byte[romfs_header_size];
- baseRom.seek(romfsOffset);
- baseRom.readFully(romfsHeaderData);
- originalRomfsHeaderCRC = FileFunctions.getCRC32(romfsHeaderData);
- int magic1 = FileFunctions.readFullIntBigEndian(romfsHeaderData, 0x00);
- int magic2 = FileFunctions.readFullIntBigEndian(romfsHeaderData, 0x04);
- if (magic1 != romfs_magic_1 || magic2 != romfs_magic_2) {
- System.err.println("NCCH: romfs does not contain magic values");
- // Not a valid romfs
- return;
- }
- int masterHashSize = FileFunctions.readFullInt(romfsHeaderData, 0x08);
- int level3HashBlockSize = 1 << FileFunctions.readFullInt(romfsHeaderData, 0x4C);
- long level3Offset = romfsOffset + alignLong(0x60 + masterHashSize, level3HashBlockSize);
-
- byte[] level3HeaderData = new byte[level3_header_size];
- baseRom.seek(level3Offset);
- baseRom.readFully(level3HeaderData);
- int headerLength = FileFunctions.readFullInt(level3HeaderData, 0x00);
- if (headerLength != level3_header_size) {
- // Not a valid romfs
- System.err.println("NCCH: romfs does not have a proper level 3 header");
- return;
- }
- int directoryMetadataOffset = FileFunctions.readFullInt(level3HeaderData, 0x0C);
- int directoryMetadataLength = FileFunctions.readFullInt(level3HeaderData, 0x10);
- int fileMetadataOffset = FileFunctions.readFullInt(level3HeaderData, 0x1c);
- int fileMetadataLength = FileFunctions.readFullInt(level3HeaderData, 0x20);
- int fileDataOffsetFromHeaderStart = FileFunctions.readFullInt(level3HeaderData, 0x24);
- fileDataOffset = level3Offset + fileDataOffsetFromHeaderStart;
-
- byte[] directoryMetadataBlock = new byte[directoryMetadataLength];
- baseRom.seek(level3Offset + directoryMetadataOffset);
- baseRom.readFully(directoryMetadataBlock);
- byte[] fileMetadataBlock = new byte[fileMetadataLength];
- baseRom.seek(level3Offset + fileMetadataOffset);
- baseRom.readFully(fileMetadataBlock);
- fileMetadataList = new ArrayList<>();
- romfsFiles = new TreeMap<>();
- visitDirectory(0, "", directoryMetadataBlock, fileMetadataBlock);
- System.out.println("NCCH: Done reading romfs");
- }
-
- private void visitDirectory(int offset, String rootPath, byte[] directoryMetadataBlock, byte[] fileMetadataBlock) {
- DirectoryMetadata metadata = new DirectoryMetadata(directoryMetadataBlock, offset);
- String currentPath = rootPath;
- if (!metadata.name.equals("")) {
- currentPath = rootPath + metadata.name + "/";
- }
-
- if (metadata.firstFileOffset != metadata_unused) {
- visitFile(metadata.firstFileOffset, currentPath, fileMetadataBlock);
- }
- if (metadata.firstChildDirectoryOffset != metadata_unused) {
- visitDirectory(metadata.firstChildDirectoryOffset, currentPath, directoryMetadataBlock, fileMetadataBlock);
- }
- if (metadata.siblingDirectoryOffset != metadata_unused) {
- visitDirectory(metadata.siblingDirectoryOffset, rootPath, directoryMetadataBlock, fileMetadataBlock);
- }
- }
-
- private void visitFile(int offset, String rootPath, byte[] fileMetadataBlock) {
- FileMetadata metadata = new FileMetadata(fileMetadataBlock, offset);
- String currentPath = rootPath + metadata.name;
- System.out.println("NCCH: Visiting file " + currentPath);
- RomfsFile file = new RomfsFile(this);
- file.offset = fileDataOffset + metadata.fileDataOffset;
- file.size = (int) metadata.fileDataLength; // no Pokemon game has a file larger than unsigned int max
- file.fullPath = currentPath;
- metadata.file = file;
- fileMetadataList.add(metadata);
- romfsFiles.put(currentPath, file);
- if (metadata.siblingFileOffset != metadata_unused) {
- visitFile(metadata.siblingFileOffset, rootPath, fileMetadataBlock);
- }
- }
-
- public void saveAsNCCH(String filename, String gameAcronym, long seed) throws IOException, NoSuchAlgorithmException {
- this.reopenROM();
-
- // Initialize new ROM
- RandomAccessFile fNew = new RandomAccessFile(filename, "rw");
-
- // Read the header and exheader and write it to the output ROM
- byte[] header = new byte[header_and_exheader_size];
- baseRom.seek(ncchStartingOffset);
- baseRom.readFully(header);
- fNew.write(header);
-
- // Just in case they were set wrong in the original header, let's correctly set the
- // bytes in the header to indicate the output ROM is decrypted
- byte[] flags = new byte[8];
- baseRom.seek(ncchStartingOffset + 0x188);
- baseRom.readFully(flags);
- flags[3] = 0;
- flags[7] = 4;
- fNew.seek(0x188);
- fNew.write(flags);
-
- // The logo is small enough (8KB) to just read the whole thing into memory. Write it to the new ROM directly
- // after the header, then update the new ROM's logo offset
- long logoOffset = ncchStartingOffset + FileFunctions.readIntFromFile(baseRom, ncchStartingOffset + 0x198) * media_unit_size;
- long logoLength = FileFunctions.readIntFromFile(baseRom, ncchStartingOffset + 0x19C) * media_unit_size;
- if (logoLength > 0) {
- byte[] logo = new byte[(int) logoLength];
- baseRom.seek(logoOffset);
- baseRom.readFully(logo);
- long newLogoOffset = header_and_exheader_size;
- fNew.seek(newLogoOffset);
- fNew.write(logo);
- fNew.seek(0x198);
- fNew.write((int) newLogoOffset / media_unit_size);
- }
-
- // The plain region is even smaller (1KB) so repeat the same process
- long plainOffset = ncchStartingOffset + FileFunctions.readIntFromFile(baseRom, ncchStartingOffset + 0x190) * media_unit_size;
- long plainLength = FileFunctions.readIntFromFile(baseRom, ncchStartingOffset + 0x194) * media_unit_size;
- if (plainLength > 0) {
- byte[] plain = new byte[(int) plainLength];
- baseRom.seek(plainOffset);
- baseRom.readFully(plain);
- long newPlainOffset = header_and_exheader_size + logoLength;
- fNew.seek(newPlainOffset);
- fNew.write(plain);
- fNew.seek(0x190);
- fNew.write((int) newPlainOffset / media_unit_size);
- }
-
- // Update the SMDH so that Citra displays the seed in the title
- smdh.setAllDescriptions(gameAcronym + " randomizer seed: " + seed);
- smdh.setAllPublishers("Universal Pokemon Randomizer ZX");
-
- // Now, reconstruct the exefs based on our new version of .code and our new SMDH
- long newExefsOffset = header_and_exheader_size + logoLength + plainLength;
- long newExefsLength = rebuildExefs(fNew, newExefsOffset);
- fNew.seek(0x1A0);
- fNew.write((int) newExefsOffset / media_unit_size);
- fNew.seek(0x1A4);
- fNew.write((int) newExefsLength / media_unit_size);
-
- // Then, reconstruct the romfs
- // TODO: Fix the yet-unsolved alignment issues in rebuildRomfs when you remove this align
- long newRomfsOffset = alignLong(header_and_exheader_size + logoLength + plainLength + newExefsLength, 4096);
- long newRomfsLength = rebuildRomfs(fNew, newRomfsOffset);
- fNew.seek(0x1B0);
- fNew.write((int) newRomfsOffset / media_unit_size);
- fNew.seek(0x1B4);
- fNew.write((int) newRomfsLength / media_unit_size);
-
- // Lastly, reconstruct the superblock hashes
- MessageDigest digest = MessageDigest.getInstance("SHA-256");
- int exefsHashRegionSize = FileFunctions.readIntFromFile(baseRom, ncchStartingOffset + 0x1A8) * media_unit_size;
- byte[] exefsDataToHash = new byte[exefsHashRegionSize];
- fNew.seek(newExefsOffset);
- fNew.readFully(exefsDataToHash);
- byte[] exefsSuperblockHash = digest.digest(exefsDataToHash);
- fNew.seek(0x1C0);
- fNew.write(exefsSuperblockHash);
- int romfsHashRegionSize = FileFunctions.readIntFromFile(baseRom, ncchStartingOffset + 0x1B8) * media_unit_size;
- byte[] romfsDataToHash = new byte[romfsHashRegionSize];
- fNew.seek(newRomfsOffset);
- fNew.readFully(romfsDataToHash);
- byte[] romfsSuperblockHash = digest.digest(romfsDataToHash);
- fNew.seek(0x1E0);
- fNew.write(romfsSuperblockHash);
-
- // While totally optional, let's zero out the NCCH signature so that
- // it's clear this isn't a properly-signed ROM
- byte[] zeroedSignature = new byte[0x100];
- fNew.seek(0x0);
- fNew.write(zeroedSignature);
- fNew.close();
- }
-
- private long rebuildExefs(RandomAccessFile fNew, long newExefsOffset) throws IOException, NoSuchAlgorithmException {
- System.out.println("NCCH: Rebuilding exefs...");
- byte[] code = getCode();
- if (codeCompressed) {
- code = new BLZCoder(null).BLZ_EncodePub(code, false, true, ".code");
- }
-
- // Create a new ExefsFileHeader for our updated .code
- ExefsFileHeader newCodeHeader = new ExefsFileHeader();
- newCodeHeader.filename = codeFileHeader.filename;
- newCodeHeader.size = code.length;
- newCodeHeader.offset = 0;
-
- // For all the file headers, write them to the new ROM and store them in order for hashing later
- ExefsFileHeader[] newHeaders = new ExefsFileHeader[10];
- newHeaders[0] = newCodeHeader;
- fNew.seek(newExefsOffset);
- fNew.write(newCodeHeader.asBytes());
- for (int i = 0; i < extraExefsFiles.size(); i++) {
- ExefsFileHeader header = extraExefsFiles.get(i);
- newHeaders[i + 1] = header;
- fNew.write(header.asBytes());
- }
-
- // Write the file data, then hash the data and write the hashes in reverse order
- MessageDigest digest = MessageDigest.getInstance("SHA-256");
- long endingOffset = 0;
- for (int i = 0; i < newHeaders.length; i++) {
- ExefsFileHeader header = newHeaders[i];
- if (header != null) {
- byte[] data;
- if (header.filename.equals(".code")) {
- data = code;
- } else if (header.filename.equals("icon")) {
- data = smdh.getBytes();
- } else {
- long dataOffset = exefsOffset + 0x200 + header.offset;
- data = new byte[header.size];
- baseRom.seek(dataOffset);
- baseRom.readFully(data);
- }
- fNew.seek(newExefsOffset + 0x200 + header.offset);
- fNew.write(data);
- byte[] hash = digest.digest(data);
- fNew.seek(newExefsOffset + 0x200 - ((i + 1) * 0x20));
- fNew.write(hash);
- endingOffset = newExefsOffset + 0x200 + header.offset + header.size;
- }
- }
-
- // Pad to media unit size
- fNew.seek(endingOffset);
- long exefsLength = endingOffset - newExefsOffset;
- while (exefsLength % media_unit_size != 0) {
- fNew.writeByte(0);
- exefsLength++;
- }
-
- System.out.println("NCCH: Done rebuilding exefs");
- return exefsLength;
- }
-
- private long rebuildRomfs(RandomAccessFile fNew, long newRomfsOffset) throws IOException, NoSuchAlgorithmException {
- System.out.println("NCCH: Rebuilding romfs...");
-
- // Start by copying the romfs header straight from the original ROM. We'll update the
- // header as we continue to build the romfs
- byte[] romfsHeaderData = new byte[romfs_header_size];
- baseRom.seek(romfsOffset);
- baseRom.readFully(romfsHeaderData);
- fNew.seek(newRomfsOffset);
- fNew.write(romfsHeaderData);
-
- // Now find the level 3 (file data) offset, since the first thing we need to do is write the
- // updated file data. We're assuming here that the master hash size is smaller than the level 3
- // hash block size, which it almost certainly will because we're not adding large amounts of data
- // to the romfs
- int masterHashSize = FileFunctions.readFullInt(romfsHeaderData, 0x08);
- int level3HashBlockSize = 1 << FileFunctions.readFullInt(romfsHeaderData, 0x4C);
- long level3Offset = romfsOffset + alignLong(0x60 + masterHashSize, level3HashBlockSize);
- long newLevel3Offset = newRomfsOffset + alignLong(0x60 + masterHashSize, level3HashBlockSize);
-
- // Copy the level 3 header straight from the original ROM. Since we're not adding or
- // removing any files, the File/Directory tables should have the same offsets and lengths
- byte[] level3HeaderData = new byte[level3_header_size];
- baseRom.seek(level3Offset);
- baseRom.readFully(level3HeaderData);
- fNew.seek(newLevel3Offset);
- fNew.write(level3HeaderData);
-
- // Write out both hash tables and the directory metadata table. Since we're not adding or removing
- // any files/directories, we can just use what's in the base ROM for this.
- int directoryHashTableOffset = FileFunctions.readFullInt(level3HeaderData, 0x04);
- int directoryHashTableLength = FileFunctions.readFullInt(level3HeaderData, 0x08);
- int directoryMetadataTableOffset = FileFunctions.readFullInt(level3HeaderData, 0x0C);
- int directoryMetadataTableLength = FileFunctions.readFullInt(level3HeaderData, 0x10);
- int fileHashTableOffset = FileFunctions.readFullInt(level3HeaderData, 0x14);
- int fileHashTableLength = FileFunctions.readFullInt(level3HeaderData, 0x18);
- byte[] directoryHashTable = new byte[directoryHashTableLength];
- baseRom.seek(level3Offset + directoryHashTableOffset);
- baseRom.readFully(directoryHashTable);
- fNew.seek(newLevel3Offset + directoryHashTableOffset);
- fNew.write(directoryHashTable);
- byte[] directoryMetadataTable = new byte[directoryMetadataTableLength];
- baseRom.seek(level3Offset + directoryMetadataTableOffset);
- baseRom.readFully(directoryMetadataTable);
- fNew.seek(newLevel3Offset + directoryMetadataTableOffset);
- fNew.write(directoryMetadataTable);
- byte[] fileHashTable = new byte[fileHashTableLength];
- baseRom.seek(level3Offset + fileHashTableOffset);
- baseRom.readFully(fileHashTable);
- fNew.seek(newLevel3Offset + fileHashTableOffset);
- fNew.write(fileHashTable);
-
- // Now reconstruct the file metadata table. It may need to be changed if any file grew or shrunk
- int fileMetadataTableOffset = FileFunctions.readFullInt(level3HeaderData, 0x1C);
- int fileMetadataTableLength = FileFunctions.readFullInt(level3HeaderData, 0x20);
- byte[] newFileMetadataTable = updateFileMetadataTable(fileMetadataTableLength);
- fNew.seek(newLevel3Offset + fileMetadataTableOffset);
- fNew.write(newFileMetadataTable);
-
- // Using the new file metadata table, output the file data
- int fileDataOffset = FileFunctions.readFullInt(level3HeaderData, 0x24);
- long endOfFileDataOffset = 0;
- for (FileMetadata metadata : fileMetadataList) {
- System.out.println("NCCH: Writing file " + metadata.file.fullPath + " to romfs");
- // Users have sent us bug reports with really bizarre errors here that seem to indicate
- // broken metadata; do this in a try-catch solely so we can log the metadata if we fail
- try {
- byte[] fileData;
- if (metadata.file.fileChanged) {
- fileData = metadata.file.getOverrideContents();
- } else {
- fileData = new byte[metadata.file.size];
- baseRom.seek(metadata.file.offset);
- baseRom.readFully(fileData);
- }
- long currentDataOffset = newLevel3Offset + fileDataOffset + metadata.fileDataOffset;
- fNew.seek(currentDataOffset);
- fNew.write(fileData);
- endOfFileDataOffset = currentDataOffset + fileData.length;
- } catch (Exception e) {
- String message = String.format("Error when building romfs: File: %s, offset: %s, size: %s",
- metadata.file.fullPath, metadata.offset, metadata.file.size);
- throw new RandomizerIOException(message, e);
- }
- }
-
- // Now that level 3 (file data) is done, construct level 2 (hashes of file data)
- // Note that in the ROM, level 1 comes *before* level 2, so we need to calculate
- // level 1 length and offset as well.
- long newLevel3EndingOffset = endOfFileDataOffset;
- long newLevel3HashdataSize = newLevel3EndingOffset - newLevel3Offset;
- long numberOfLevel3HashBlocks = alignLong(newLevel3HashdataSize, level3HashBlockSize) / level3HashBlockSize;
- int level2HashBlockSize = 1 << FileFunctions.readFullInt(romfsHeaderData, 0x34);
- long newLevel2HashdataSize = numberOfLevel3HashBlocks * 0x20;
- long numberOfLevel2HashBlocks = alignLong(newLevel2HashdataSize, level2HashBlockSize) / level2HashBlockSize;
- int level1HashBlockSize = 1 << FileFunctions.readFullInt(romfsHeaderData, 0x1C);
- long newLevel1HashdataSize = numberOfLevel2HashBlocks * 0x20;
- long newLevel1Offset = newLevel3Offset + alignLong(newLevel3HashdataSize, level3HashBlockSize);
- long newLevel2Offset = newLevel1Offset + alignLong(newLevel1HashdataSize, level1HashBlockSize);
- long newFileEndingOffset = alignLong(newLevel2Offset + newLevel2HashdataSize, level2HashBlockSize);
- MessageDigest digest = MessageDigest.getInstance("SHA-256");
- byte[] dataToHash = new byte[level3HashBlockSize];
- for (long i = 0; i < numberOfLevel3HashBlocks; i++) {
- fNew.seek(newLevel3Offset + (i * level3HashBlockSize));
- fNew.readFully(dataToHash);
- byte[] hash = digest.digest(dataToHash);
- fNew.seek(newLevel2Offset + (i * 0x20));
- fNew.write(hash);
- }
- while (fNew.getFilePointer() != newFileEndingOffset) {
- fNew.writeByte(0);
- }
-
- // Now that level 2 (hashes of file data) is done, construct level 1 (hashes of
- // hashes of file data) and the master hash/level 0 (hashes of level 1)
- dataToHash = new byte[level2HashBlockSize];
- for (long i = 0; i < numberOfLevel2HashBlocks; i++) {
- fNew.seek(newLevel2Offset + (i * level2HashBlockSize));
- fNew.readFully(dataToHash);
- byte[] hash = digest.digest(dataToHash);
- fNew.seek(newLevel1Offset + (i * 0x20));
- fNew.write(hash);
- }
- long numberOfLevel1HashBlocks = alignLong(newLevel1HashdataSize, level1HashBlockSize) / level1HashBlockSize;
- dataToHash = new byte[level1HashBlockSize];
- for (long i = 0; i < numberOfLevel1HashBlocks; i++) {
- fNew.seek(newLevel1Offset + (i * level1HashBlockSize));
- fNew.readFully(dataToHash);
- byte[] hash = digest.digest(dataToHash);
- fNew.seek(newRomfsOffset + 0x60 + (i * 0x20));
- fNew.write(hash);
- }
-
- // Lastly, update the header and return the size of the new romfs
- long level1LogicalOffset = 0;
- long level2LogicalOffset = alignLong(newLevel1HashdataSize, level1HashBlockSize);
- long level3LogicalOffset = alignLong(level2LogicalOffset + newLevel2HashdataSize, level2HashBlockSize);
- FileFunctions.writeFullInt(romfsHeaderData, 0x08, (int) numberOfLevel1HashBlocks * 0x20);
- FileFunctions.writeFullLong(romfsHeaderData, 0x0C, level1LogicalOffset);
- FileFunctions.writeFullLong(romfsHeaderData, 0x14, newLevel1HashdataSize);
- FileFunctions.writeFullLong(romfsHeaderData, 0x24, level2LogicalOffset);
- FileFunctions.writeFullLong(romfsHeaderData, 0x2C, newLevel2HashdataSize);
- FileFunctions.writeFullLong(romfsHeaderData, 0x3C, level3LogicalOffset);
- FileFunctions.writeFullLong(romfsHeaderData, 0x44, newLevel3HashdataSize);
- fNew.seek(newRomfsOffset);
- fNew.write(romfsHeaderData);
- long currentLength = newFileEndingOffset - newRomfsOffset;
- long newRomfsLength = alignLong(currentLength, media_unit_size);
- fNew.seek(newFileEndingOffset);
- while (fNew.getFilePointer() < newRomfsOffset + newRomfsLength) {
- fNew.writeByte(0);
- }
-
- System.out.println("NCCH: Done rebuilding romfs");
- return newRomfsLength;
- }
-
- private byte[] updateFileMetadataTable(int fileMetadataTableLength) {
- fileMetadataList.sort((FileMetadata f1, FileMetadata f2) -> (int) (f1.fileDataOffset - f2.fileDataOffset));
- byte[] fileMetadataTable = new byte[fileMetadataTableLength];
- int currentTableOffset = 0;
- long currentFileDataOffset = 0;
- for (FileMetadata metadata : fileMetadataList) {
- metadata.fileDataOffset = currentFileDataOffset;
- if (metadata.file.fileChanged) {
- metadata.fileDataLength = metadata.file.size;
- }
- byte[] metadataBytes = metadata.asBytes();
- System.arraycopy(metadataBytes, 0, fileMetadataTable, currentTableOffset, metadataBytes.length);
- currentTableOffset += metadataBytes.length;
- currentFileDataOffset += metadata.fileDataLength;
- }
- return fileMetadataTable;
- }
-
- public void saveAsLayeredFS(String outputPath) throws IOException {
- String layeredFSRootPath = outputPath + File.separator + titleId + File.separator;
- File layeredFSRootDir = new File(layeredFSRootPath);
- if (!layeredFSRootDir.exists()) {
- layeredFSRootDir.mkdirs();
- } else {
- purgeDirectory(layeredFSRootDir);
- }
- String romfsRootPath = layeredFSRootPath + "romfs" + File.separator;
- File romfsDir = new File(romfsRootPath);
- if (!romfsDir.exists()) {
- romfsDir.mkdirs();
- }
-
- if (codeChanged) {
- byte[] code = getCode();
- FileOutputStream fos = new FileOutputStream(new File(layeredFSRootPath + "code.bin"));
- fos.write(code);
- fos.close();
- }
-
- for (Map.Entry<String, RomfsFile> entry : romfsFiles.entrySet()) {
- RomfsFile file = entry.getValue();
- if (file.fileChanged) {
- writeRomfsFileToLayeredFS(file, romfsRootPath);
- }
- }
- }
-
- private void purgeDirectory(File directory) {
- for (File file : directory.listFiles()) {
- if (file.isDirectory()) {
- purgeDirectory(file);
- }
- file.delete();
- }
- }
-
- private void writeRomfsFileToLayeredFS(RomfsFile file, String layeredFSRootPath) throws IOException {
- String[] romfsPathComponents = file.fullPath.split("/");
- StringBuffer buffer = new StringBuffer(layeredFSRootPath);
- for (int i = 0; i < romfsPathComponents.length - 1; i++) {
- buffer.append(romfsPathComponents[i]);
- buffer.append(File.separator);
- File currentDir = new File(buffer.toString());
- if (!currentDir.exists()) {
- currentDir.mkdirs();
- }
- }
- buffer.append(romfsPathComponents[romfsPathComponents.length - 1]);
- String romfsFilePath = buffer.toString();
- FileOutputStream fos = new FileOutputStream(new File(romfsFilePath));
- fos.write(file.getOverrideContents());
- fos.close();
- }
-
- public boolean isDecrypted() throws IOException {
- // This is the way you're *supposed* to tell if a ROM is decrypted. Specifically, this
- // is checking the noCrypto flag on the NCCH bitflags.
- long ncchFlagOffset = ncchStartingOffset + 0x188;
- byte[] ncchFlags = new byte[8];
- baseRom.seek(ncchFlagOffset);
- baseRom.readFully(ncchFlags);
- if ((ncchFlags[7] & 0x4) != 0) {
- return true;
- }
-
- // However, some poorly-decrypted ROMs don't set this flag. So our heuristic for detecting
- // if they're decrypted is to check whether the battle CRO exists, since all 3DS Pokemon
- // games and updates have this file. If the game is *really* encrypted, then the odds of us
- // successfully extracting this exact name from the metadata tables is like one in a billion.
- return romfsFiles != null && (romfsFiles.containsKey("DllBattle.cro") || romfsFiles.containsKey("Battle.cro"));
- }
-
- // Retrieves a decompressed version of .code (the game's executable).
- // The first time this is called, it will retrieve it straight from the
- // exefs. Future calls will rely on a cached version to speed things up.
- // If writing is enabled, it will cache the decompressed version to the
- // tmpFolder; otherwise, it will store it in RAM.
- public byte[] getCode() throws IOException {
- if (!codeOpen) {
- codeOpen = true;
- byte[] code = new byte[codeFileHeader.size];
-
- // File header offsets are from the start of the exefs but *exclude* the
- // size of the exefs header, so we need to add it back ourselves.
- baseRom.seek(exefsOffset + exefs_header_size + codeFileHeader.offset);
- baseRom.readFully(code);
- originalCodeCRC = FileFunctions.getCRC32(code);
-
- if (codeCompressed) {
- code = new BLZCoder(null).BLZ_DecodePub(code, ".code");
- }
-
- // Now actually make the copy or w/e
- if (writingEnabled) {
- File arm9file = new File(tmpFolder + ".code");
- FileOutputStream fos = new FileOutputStream(arm9file);
- fos.write(code);
- fos.close();
- arm9file.deleteOnExit();
- this.codeRamstored = null;
- return code;
- } else {
- this.codeRamstored = code;
- byte[] newcopy = new byte[code.length];
- System.arraycopy(code, 0, newcopy, 0, code.length);
- return newcopy;
- }
- } else {
- if (writingEnabled) {
- return FileFunctions.readFileFullyIntoBuffer(tmpFolder + ".code");
- } else {
- byte[] newcopy = new byte[this.codeRamstored.length];
- System.arraycopy(this.codeRamstored, 0, newcopy, 0, this.codeRamstored.length);
- return newcopy;
- }
- }
- }
-
- public void writeCode(byte[] code) throws IOException {
- if (!codeOpen) {
- getCode();
- }
- codeChanged = true;
- if (writingEnabled) {
- FileOutputStream fos = new FileOutputStream(new File(tmpFolder + ".code"));
- fos.write(code);
- fos.close();
- } else {
- if (this.codeRamstored.length == code.length) {
- // copy new in
- System.arraycopy(code, 0, this.codeRamstored, 0, code.length);
- } else {
- // make new array
- this.codeRamstored = null;
- this.codeRamstored = new byte[code.length];
- System.arraycopy(code, 0, this.codeRamstored, 0, code.length);
- }
- }
- }
-
- public boolean hasFile(String filename) {
- return romfsFiles.containsKey(filename);
- }
-
- // returns null if file doesn't exist
- public byte[] getFile(String filename) throws IOException {
- if (romfsFiles.containsKey(filename)) {
- return romfsFiles.get(filename).getContents();
- } else {
- return null;
- }
- }
-
- public void writeFile(String filename, byte[] data) throws IOException {
- if (romfsFiles.containsKey(filename)) {
- romfsFiles.get(filename).writeOverride(data);
- }
- }
-
- public void printRomDiagnostics(PrintStream logStream, NCCH gameUpdate) {
- Path p = Paths.get(this.romFilename);
- logStream.println("File name: " + p.getFileName().toString());
- if (gameUpdate == null) {
- logStream.println(".code: " + String.format("%08X", this.originalCodeCRC));
- } else {
- logStream.println(".code: " + String.format("%08X", gameUpdate.originalCodeCRC));
- }
- logStream.println("romfs header: " + String.format("%08X", this.originalRomfsHeaderCRC));
- if (gameUpdate != null) {
- logStream.println("romfs header (game update): " + String.format("%08X", gameUpdate.originalRomfsHeaderCRC));
- }
- List<String> fileList = new ArrayList<>();
- Map<String, String> baseRomfsFileDiagnostics = this.getRomfsFilesDiagnostics();
- Map<String, String> updateRomfsFileDiagnostics = new HashMap<>();
- if (gameUpdate != null) {
- updateRomfsFileDiagnostics = gameUpdate.getRomfsFilesDiagnostics();
- }
- for (Map.Entry<String, String> entry : updateRomfsFileDiagnostics.entrySet()) {
- baseRomfsFileDiagnostics.remove(entry.getKey());
- fileList.add(entry.getValue());
- }
- for (Map.Entry<String, String> entry : baseRomfsFileDiagnostics.entrySet()) {
- fileList.add(entry.getValue());
- }
- Collections.sort(fileList);
- for (String fileLog : fileList) {
- logStream.println(fileLog);
- }
- }
-
- public Map<String, String> getRomfsFilesDiagnostics() {
- Map<String, String> fileDiagnostics = new HashMap<>();
- for (Map.Entry<String, RomfsFile> entry : romfsFiles.entrySet()) {
- if (entry.getValue().originalCRC != 0) {
- fileDiagnostics.put(entry.getKey(), entry.getKey() + ": " + String.format("%08X", entry.getValue().originalCRC));
- }
- }
- return fileDiagnostics;
- }
-
- public String getTmpFolder() {
- return tmpFolder;
- }
-
- public RandomAccessFile getBaseRom() {
- return baseRom;
- }
-
- public boolean isWritingEnabled() {
- return writingEnabled;
- }
-
- public String getProductCode() {
- return productCode;
- }
-
- public String getTitleId() {
- return titleId;
- }
-
- public int getVersion() {
- return version;
- }
-
- public static int alignInt(int num, int alignment) {
- int mask = ~(alignment - 1);
- return (num + (alignment - 1)) & mask;
- }
-
- public static long alignLong(long num, long alignment) {
- long mask = ~(alignment - 1);
- return (num + (alignment - 1)) & mask;
- }
-
- private int readVersionFromFile() {
- try {
- // Only CIAs can define a version in their TMD. If this is a different ROM type,
- // just exit out early.
- int magic = FileFunctions.readBigEndianIntFromFile(this.baseRom, ncch_and_ncsd_magic_offset);
- if (magic == ncch_magic || magic == ncsd_magic) {
- return 0;
- }
-
- // For CIAs, we need to read the title metadata (TMD) in order to retrieve the version.
- // The TMD is after the certificate chain and ticket.
- int certChainSize = FileFunctions.readIntFromFile(this.baseRom, 0x08);
- int ticketSize = FileFunctions.readIntFromFile(this.baseRom, 0x0C);
- long certChainOffset = NCCH.alignLong(cia_header_size, 64);
- long ticketOffset = NCCH.alignLong(certChainOffset + certChainSize, 64);
- long tmdOffset = NCCH.alignLong(ticketOffset + ticketSize, 64);
-
- // At the start of the TMD is a signature whose length varies based on what type of signature it is.
- int signatureType = FileFunctions.readBigEndianIntFromFile(this.baseRom, tmdOffset);
- int signatureSize, paddingSize;
- switch (signatureType) {
- case 0x010003:
- signatureSize = 0x200;
- paddingSize = 0x3C;
- break;
- case 0x010004:
- signatureSize = 0x100;
- paddingSize = 0x3C;
- break;
- case 0x010005:
- signatureSize = 0x3C;
- paddingSize = 0x40;
- break;
- default:
- signatureSize = -1;
- paddingSize = -1;
- break;
- }
- if (signatureSize == -1) {
- // This shouldn't happen in practice, since all used and valid signature types are represented
- // in the above switch. However, if we can't find the right signature type, then it's probably
- // an invalid CIA anyway, so we're unlikely to get good version information out of it.
- return 0;
- }
-
- // After the signature is the TMD header, which actually contains the version information.
- long tmdHeaderOffset = tmdOffset + 4 + signatureSize + paddingSize;
- return FileFunctions.read2ByteBigEndianIntFromFile(this.baseRom, tmdHeaderOffset + 0x9C);
- } catch (IOException e) {
- throw new RandomizerIOException(e);
- }
- }
-
- // At the bare minimum, a 3DS game consists of what's known as a CXI file, which
- // is just an NCCH that contains executable code. However, 3DS games are packaged
- // in various containers that can hold other NCCH files like the game manual and
- // firmware updates, among other things. This function's determines the location
- // of the CXI regardless of the container.
- public static long getCXIOffsetInFile(String filename) {
- try {
- RandomAccessFile rom = new RandomAccessFile(filename, "r");
- int ciaHeaderSize = FileFunctions.readIntFromFile(rom, 0x00);
- if (ciaHeaderSize == cia_header_size) {
- // This *might* be a CIA; let's do our best effort to try to get
- // a CXI out of this.
- int certChainSize = FileFunctions.readIntFromFile(rom, 0x08);
- int ticketSize = FileFunctions.readIntFromFile(rom, 0x0C);
- int tmdFileSize = FileFunctions.readIntFromFile(rom, 0x10);
-
- // If this is *really* a CIA, we'll find our CXI at the beginning of the
- // content section, which is after the certificate chain, ticket, and TMD
- long certChainOffset = NCCH.alignLong(ciaHeaderSize, 64);
- long ticketOffset = NCCH.alignLong(certChainOffset + certChainSize, 64);
- long tmdOffset = NCCH.alignLong(ticketOffset + ticketSize, 64);
- long contentOffset = NCCH.alignLong(tmdOffset + tmdFileSize, 64);
- int magic = FileFunctions.readBigEndianIntFromFile(rom, contentOffset + ncch_and_ncsd_magic_offset);
- if (magic == ncch_magic) {
- // This CIA's content contains a valid CXI!
- return contentOffset;
- }
- }
-
- // We don't put the following code in an else-block because there *might*
- // exist a totally-valid CXI or CCI whose first four bytes just so
- // *happen* to be the same as the first four bytes of a CIA file.
- int magic = FileFunctions.readBigEndianIntFromFile(rom, ncch_and_ncsd_magic_offset);
- rom.close();
- if (magic == ncch_magic) {
- // Magic is NCCH, so this just a straight-up NCCH/CXI; there is no container
- // around the game data. Thus, the CXI offset is the beginning of the file.
- return 0;
- } else if (magic == ncsd_magic) {
- // Magic is NCSD, so this is almost certainly a CCI. The CXI is always
- // a fixed distance away from the start.
- return 0x4000;
- } else {
- // This doesn't seem to be a valid 3DS file.
- return -1;
- }
- } catch (IOException e) {
- throw new RandomizerIOException(e);
- }
- }
-
- private class ExefsFileHeader {
- public String filename;
- public int offset;
- public int size;
-
- public ExefsFileHeader() { }
-
- public ExefsFileHeader(byte[] exefsHeaderData, int fileHeaderOffset) {
- byte[] filenameBytes = new byte[0x8];
- System.arraycopy(exefsHeaderData, fileHeaderOffset, filenameBytes, 0, 0x8);
- this.filename = new String(filenameBytes, StandardCharsets.UTF_8).trim();
- this.offset = FileFunctions.readFullInt(exefsHeaderData, fileHeaderOffset + 0x08);
- this.size = FileFunctions.readFullInt(exefsHeaderData, fileHeaderOffset + 0x0C);
- }
-
- public boolean isValid() {
- return this.filename != "" && this.size != 0;
- }
-
- public byte[] asBytes() {
- byte[] output = new byte[0x10];
- byte[] filenameBytes = this.filename.getBytes(StandardCharsets.UTF_8);
- System.arraycopy(filenameBytes, 0, output, 0, filenameBytes.length);
- FileFunctions.writeFullInt(output, 0x08, this.offset);
- FileFunctions.writeFullInt(output, 0x0C, this.size);
- return output;
- }
- }
-
- private class DirectoryMetadata {
- public int parentDirectoryOffset;
- public int siblingDirectoryOffset;
- public int firstChildDirectoryOffset;
- public int firstFileOffset;
- public int nextDirectoryInHashBucketOffset;
- public int nameLength;
- public String name;
-
- public DirectoryMetadata(byte[] directoryMetadataBlock, int offset) {
- parentDirectoryOffset = FileFunctions.readFullInt(directoryMetadataBlock, offset);
- siblingDirectoryOffset = FileFunctions.readFullInt(directoryMetadataBlock, offset + 0x04);
- firstChildDirectoryOffset = FileFunctions.readFullInt(directoryMetadataBlock, offset + 0x08);
- firstFileOffset = FileFunctions.readFullInt(directoryMetadataBlock, offset + 0x0C);
- nextDirectoryInHashBucketOffset = FileFunctions.readFullInt(directoryMetadataBlock, offset + 0x10);
- nameLength = FileFunctions.readFullInt(directoryMetadataBlock, offset + 0x14);
- name = "";
- if (nameLength != metadata_unused) {
- byte[] nameBytes = new byte[nameLength];
- System.arraycopy(directoryMetadataBlock, offset + 0x18, nameBytes, 0, nameLength);
- name = new String(nameBytes, StandardCharsets.UTF_16LE).trim();
- }
- }
- }
-
- private class FileMetadata {
- public int offset;
- public int parentDirectoryOffset;
- public int siblingFileOffset;
- public long fileDataOffset;
- public long fileDataLength;
- public int nextFileInHashBucketOffset;
- public int nameLength;
- public String name;
- public RomfsFile file; // used only for rebuilding CXI
-
- public FileMetadata(byte[] fileMetadataBlock, int offset) {
- this.offset = offset;
- parentDirectoryOffset = FileFunctions.readFullInt(fileMetadataBlock, offset);
- siblingFileOffset = FileFunctions.readFullInt(fileMetadataBlock, offset + 0x04);
- fileDataOffset = FileFunctions.readFullLong(fileMetadataBlock, offset + 0x08);
- fileDataLength = FileFunctions.readFullLong(fileMetadataBlock, offset + 0x10);
- nextFileInHashBucketOffset = FileFunctions.readFullInt(fileMetadataBlock, offset + 0x18);
- nameLength = FileFunctions.readFullInt(fileMetadataBlock, offset + 0x1C);
- name = "";
- if (nameLength != metadata_unused) {
- byte[] nameBytes = new byte[nameLength];
- System.arraycopy(fileMetadataBlock, offset + 0x20, nameBytes, 0, nameLength);
- name = new String(nameBytes, StandardCharsets.UTF_16LE).trim();
- }
- }
-
- public byte[] asBytes() {
- int metadataLength = 0x20;
- if (nameLength != metadata_unused) {
- metadataLength += alignInt(nameLength, 4);
- }
- byte[] output = new byte[metadataLength];
- FileFunctions.writeFullInt(output, 0x00, this.parentDirectoryOffset);
- FileFunctions.writeFullInt(output, 0x04, this.siblingFileOffset);
- FileFunctions.writeFullLong(output, 0x08, this.fileDataOffset);
- FileFunctions.writeFullLong(output, 0x10, this.fileDataLength);
- FileFunctions.writeFullInt(output, 0x18, this.nextFileInHashBucketOffset);
- FileFunctions.writeFullInt(output, 0x1C, this.nameLength);
- if (!name.equals("")) {
- byte[] nameBytes = name.getBytes(StandardCharsets.UTF_16LE);
- System.arraycopy(nameBytes, 0, output, 0x20, nameBytes.length);
- }
- return output;
- }
- }
-}
diff --git a/src/com/sneed/pkrandom/ctr/RomfsFile.java b/src/com/sneed/pkrandom/ctr/RomfsFile.java
deleted file mode 100644
index 63caccc..0000000
--- a/src/com/sneed/pkrandom/ctr/RomfsFile.java
+++ /dev/null
@@ -1,121 +0,0 @@
-package com.sneed.pkrandom.ctr;
-
-/*----------------------------------------------------------------------------*/
-/*-- RomfsFile.java - an entry in the romfs filesystem --*/
-/*-- --*/
-/*-- 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. --*/
-/*-- --*/
-/*-- 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 com.sneed.pkrandom.FileFunctions;
-
-import java.io.File;
-import java.io.FileOutputStream;
-import java.io.IOException;
-import java.io.RandomAccessFile;
-
-public class RomfsFile {
-
- private NCCH parent;
- public long offset;
- public int size;
- public String fullPath;
- private Extracted status = Extracted.NOT;
- private String extFilename;
- public byte[] data;
- public boolean fileChanged = false;
- public long originalCRC;
-
- public RomfsFile(NCCH parent) {
- this.parent = parent;
- }
-
- public byte[] getContents() throws IOException {
- if (this.status == Extracted.NOT) {
- // extract file
- parent.reopenROM();
- RandomAccessFile rom = parent.getBaseRom();
- byte[] buf = new byte[this.size];
- rom.seek(this.offset);
- rom.readFully(buf);
- originalCRC = FileFunctions.getCRC32(buf);
- if (parent.isWritingEnabled()) {
- // make a file
- String tmpDir = parent.getTmpFolder();
- this.extFilename = fullPath.replaceAll("[^A-Za-z0-9_\\.]+", "");
- File tmpFile = new File(tmpDir + extFilename);
- FileOutputStream fos = new FileOutputStream(tmpFile);
- fos.write(buf);
- fos.close();
- tmpFile.deleteOnExit();
- this.status = Extracted.TO_FILE;
- this.data = null;
- return buf;
- } else {
- this.status = Extracted.TO_RAM;
- this.data = buf;
- byte[] newcopy = new byte[buf.length];
- System.arraycopy(buf, 0, newcopy, 0, buf.length);
- return newcopy;
- }
- } else if (this.status == Extracted.TO_RAM) {
- byte[] newcopy = new byte[this.data.length];
- System.arraycopy(this.data, 0, newcopy, 0, this.data.length);
- return newcopy;
- } else {
- String tmpDir = parent.getTmpFolder();
- return FileFunctions.readFileFullyIntoBuffer(tmpDir + this.extFilename);
- }
- }
-
- public void writeOverride(byte[] data) throws IOException {
- if (status == Extracted.NOT) {
- // temp extract
- getContents();
- }
- fileChanged = true;
- size = data.length;
- if (status == Extracted.TO_FILE) {
- String tmpDir = parent.getTmpFolder();
- FileOutputStream fos = new FileOutputStream(new File(tmpDir + this.extFilename));
- fos.write(data);
- fos.close();
- } else {
- if (this.data.length == data.length) {
- // copy new in
- System.arraycopy(data, 0, this.data, 0, data.length);
- } else {
- // make new array
- this.data = null;
- this.data = new byte[data.length];
- System.arraycopy(data, 0, this.data, 0, data.length);
- }
- }
- }
-
- // returns null if no override
- public byte[] getOverrideContents() throws IOException {
- if (status == Extracted.NOT) {
- return null;
- }
- return getContents();
- }
-
- private enum Extracted {
- NOT, TO_FILE, TO_RAM
- }
-}
diff --git a/src/com/sneed/pkrandom/ctr/SMDH.java b/src/com/sneed/pkrandom/ctr/SMDH.java
deleted file mode 100644
index 28442ba..0000000
--- a/src/com/sneed/pkrandom/ctr/SMDH.java
+++ /dev/null
@@ -1,118 +0,0 @@
-package com.sneed.pkrandom.ctr;
-
-/*----------------------------------------------------------------------------*/
-/*-- NCCH.java - a base class for dealing with 3DS SMDH (icon.bin) files. --*/
-/*-- --*/
-/*-- 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. --*/
-/*-- --*/
-/*-- 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 com.sneed.pkrandom.FileFunctions;
-
-import java.nio.charset.StandardCharsets;
-
-public class SMDH {
-
- private byte[] data;
- private String[] shortDescriptions = new String[12];
- private String[] longDescriptions = new String[12];
- private String[] publishers = new String[12];
-
- private static final int smdh_magic = 0x48444D53;
- private static final int length_of_title = 0x200;
- private static final int short_description_length = 0x80;
- private static final int long_description_length = 0x100;
- private static final int publisher_length = 0x80;
-
- public SMDH(byte[] smdhData) {
- data = smdhData;
- if (this.isValid()) {
- readDescriptionsAndPublishers();
- }
- }
-
- public byte[] getBytes() {
- return data;
- }
-
- public void setAllDescriptions(String newDescription) {
- byte[] newDescriptionBytes = newDescription.getBytes(StandardCharsets.UTF_16LE);
- if (newDescriptionBytes.length <= short_description_length) {
- for (int i = 0; i < 12; i++) {
- shortDescriptions[i] = newDescription;
- longDescriptions[i] = newDescription;
- }
- writeDescriptionsAndPublishers();
- }
- }
-
- public void setAllPublishers(String newPublisher) {
- byte[] newPublisherBytes = newPublisher.getBytes(StandardCharsets.UTF_16LE);
- if (newPublisherBytes.length <= publisher_length) {
- for (int i = 0; i < 12; i++) {
- publishers[i] = newPublisher;
- }
- writeDescriptionsAndPublishers();
- }
- }
-
- private boolean isValid() {
- int magic = FileFunctions.readFullInt(data, 0x0);
- return magic == smdh_magic;
- }
-
- private void readDescriptionsAndPublishers() {
- for (int i = 0; i < 12; i++) {
- int shortDescriptionOffset = 0x08 + (length_of_title * i);
- byte[] shortDescriptionBytes = new byte[short_description_length];
- System.arraycopy(data, shortDescriptionOffset, shortDescriptionBytes, 0, short_description_length);
- shortDescriptions[i] = new String(shortDescriptionBytes, StandardCharsets.UTF_16LE).trim();
-
- int longDescriptionOffset = 0x88 + (length_of_title * i);
- byte[] longDescriptionBytes = new byte[long_description_length];
- System.arraycopy(data, longDescriptionOffset, longDescriptionBytes, 0, long_description_length);
- longDescriptions[i] = new String(longDescriptionBytes, StandardCharsets.UTF_16LE).trim();
-
- int publisherOffset = 0x188 + (length_of_title * i);
- byte[] publisherBytes = new byte[publisher_length];
- System.arraycopy(data, publisherOffset, publisherBytes, 0, publisher_length);
- publishers[i] = new String(publisherBytes, StandardCharsets.UTF_16LE).trim();
- }
- }
-
- private void writeDescriptionsAndPublishers() {
- for (int i = 0; i < 12; i++) {
- byte[] emptyShortDescription = new byte[short_description_length];
- int shortDescriptionOffset = 0x08 + (length_of_title * i);
- byte[] shortDescriptionBytes = shortDescriptions[i].getBytes(StandardCharsets.UTF_16LE);
- System.arraycopy(emptyShortDescription, 0, data, shortDescriptionOffset, short_description_length);
- System.arraycopy(shortDescriptionBytes, 0, data, shortDescriptionOffset, shortDescriptionBytes.length);
-
- byte[] emptyLongDescription = new byte[long_description_length];
- int longDescriptionOffset = 0x88 + (length_of_title * i);
- byte[] longDescriptionBytes = longDescriptions[i].getBytes(StandardCharsets.UTF_16LE);
- System.arraycopy(emptyLongDescription, 0, data, longDescriptionOffset, long_description_length);
- System.arraycopy(longDescriptionBytes, 0, data, longDescriptionOffset, longDescriptionBytes.length);
-
- byte[] emptyPublisher = new byte[publisher_length];
- int publisherOffset = 0x188 + (length_of_title * i);
- byte[] publisherBytes = publishers[i].getBytes(StandardCharsets.UTF_16LE);
- System.arraycopy(emptyPublisher, 0, data, publisherOffset, publisher_length);
- System.arraycopy(publisherBytes, 0, data, publisherOffset, publisherBytes.length);
- }
- }
-}