summaryrefslogtreecommitdiff
path: root/src/com/pkrandom/FileFunctions.java
diff options
context:
space:
mode:
Diffstat (limited to 'src/com/pkrandom/FileFunctions.java')
-rwxr-xr-xsrc/com/pkrandom/FileFunctions.java352
1 files changed, 352 insertions, 0 deletions
diff --git a/src/com/pkrandom/FileFunctions.java b/src/com/pkrandom/FileFunctions.java
new file mode 100755
index 0000000..83b2112
--- /dev/null
+++ b/src/com/pkrandom/FileFunctions.java
@@ -0,0 +1,352 @@
+package com.pkrandom;
+
+/*----------------------------------------------------------------------------*/
+/*-- FileFunctions.java - functions relating to file I/O. --*/
+/*-- --*/
+/*-- 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.*;
+import java.net.URL;
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import java.util.Scanner;
+import java.util.zip.CRC32;
+
+public class FileFunctions {
+
+ public static File fixFilename(File original, String defaultExtension) {
+ return fixFilename(original, defaultExtension, new ArrayList<>());
+ }
+
+ // Behavior:
+ // if file has no extension, add defaultExtension
+ // if there are banned extensions & file has a banned extension, replace
+ // with defaultExtension
+ // else, leave as is
+ public static File fixFilename(File original, String defaultExtension, List<String> bannedExtensions) {
+ String absolutePath = original.getAbsolutePath();
+ for (String bannedExtension: bannedExtensions) {
+ if (absolutePath.endsWith("." + bannedExtension)) {
+ absolutePath = absolutePath.substring(0, absolutePath.lastIndexOf('.') + 1) + defaultExtension;
+ break;
+ }
+ }
+ if (!absolutePath.endsWith("." + defaultExtension)) {
+ absolutePath += "." + defaultExtension;
+ }
+ return new File(absolutePath);
+ }
+
+ private static List<String> overrideFiles = Arrays.asList(SysConstants.customNamesFile,
+ SysConstants.tclassesFile, SysConstants.tnamesFile, SysConstants.nnamesFile);
+
+ public static boolean configExists(String filename) {
+ if (overrideFiles.contains(filename)) {
+ File fh = new File(SysConstants.ROOT_PATH + filename);
+ if (fh.exists() && fh.canRead()) {
+ return true;
+ }
+ fh = new File("./" + filename);
+ if (fh.exists() && fh.canRead()) {
+ return true;
+ }
+ }
+ return FileFunctions.class.getResource("/com/pkrandom/config/" + filename) != null;
+ }
+
+ public static InputStream openConfig(String filename) throws FileNotFoundException {
+ if (overrideFiles.contains(filename)) {
+ File fh = new File(SysConstants.ROOT_PATH + filename);
+ if (fh.exists() && fh.canRead()) {
+ return new FileInputStream(fh);
+ }
+ fh = new File("./" + filename);
+ if (fh.exists() && fh.canRead()) {
+ return new FileInputStream(fh);
+ }
+ }
+ return FileFunctions.class.getResourceAsStream("/com/pkrandom/config/" + filename);
+ }
+
+ public static CustomNamesSet getCustomNames() throws IOException {
+ InputStream is = openConfig(SysConstants.customNamesFile);
+ CustomNamesSet cns = new CustomNamesSet(is);
+ is.close();
+ return cns;
+ }
+
+ public static long readFullLong(byte[] data, int offset) {
+ ByteBuffer buf = ByteBuffer.allocate(8);
+ buf.order(ByteOrder.LITTLE_ENDIAN);
+ buf.put(data, offset, 8);
+ buf.rewind();
+ return buf.getLong();
+ }
+
+ public static int readFullInt(byte[] data, int offset) {
+ ByteBuffer buf = ByteBuffer.allocate(4);
+ buf.order(ByteOrder.LITTLE_ENDIAN);
+ buf.put(data, offset, 4);
+ buf.rewind();
+ return buf.getInt();
+ }
+
+ public static int readFullIntBigEndian(byte[] data, int offset) {
+ ByteBuffer buf = ByteBuffer.allocate(4).put(data, offset, 4);
+ buf.rewind();
+ return buf.getInt();
+ }
+
+ public static int read2ByteIntBigEndian(byte[] data, int index) {
+ return (data[index + 1] & 0xFF) | ((data[index] & 0xFF) << 8);
+ }
+
+ public static int read2ByteInt(byte[] data, int index) {
+ return (data[index] & 0xFF) | ((data[index + 1] & 0xFF) << 8);
+ }
+
+ public static void write2ByteInt(byte[] data, int offset, int value) {
+ data[offset] = (byte) (value & 0xFF);
+ data[offset + 1] = (byte) ((value >> 8) & 0xFF);
+ }
+
+ public static void writeFullInt(byte[] data, int offset, int value) {
+ byte[] valueBytes = ByteBuffer.allocate(4).order(ByteOrder.LITTLE_ENDIAN).putInt(value).array();
+ System.arraycopy(valueBytes, 0, data, offset, 4);
+ }
+
+ public static void writeFullIntBigEndian(byte[] data, int offset, int value) {
+ byte[] valueBytes = ByteBuffer.allocate(4).putInt(value).array();
+ System.arraycopy(valueBytes, 0, data, offset, 4);
+ }
+
+ public static void writeFullLong(byte[] data, int offset, long value) {
+ byte[] valueBytes = ByteBuffer.allocate(8).order(ByteOrder.LITTLE_ENDIAN).putLong(value).array();
+ System.arraycopy(valueBytes, 0, data, offset, 8);
+ }
+
+ public static byte[] readFileFullyIntoBuffer(String filename) throws IOException {
+ File fh = new File(filename);
+ if (!fh.exists() || !fh.isFile() || !fh.canRead()) {
+ throw new FileNotFoundException(filename);
+ }
+ long fileSize = fh.length();
+ if (fileSize > Integer.MAX_VALUE) {
+ throw new IOException(filename + " is too long to read in as a byte-array.");
+ }
+ FileInputStream fis = new FileInputStream(filename);
+ byte[] buf = readFullyIntoBuffer(fis, (int) fileSize);
+ fis.close();
+ return buf;
+ }
+
+ public static byte[] readFullyIntoBuffer(InputStream in, int bytes) throws IOException {
+ byte[] buf = new byte[bytes];
+ readFully(in, buf, 0, bytes);
+ return buf;
+ }
+
+ private static void readFully(InputStream in, byte[] buf, int offset, int length) throws IOException {
+ int offs = 0, read;
+ while (offs < length && (read = in.read(buf, offs + offset, length - offs)) != -1) {
+ offs += read;
+ }
+ }
+
+ public static int read2ByteBigEndianIntFromFile(RandomAccessFile file, long offset) throws IOException {
+ byte[] buf = new byte[2];
+ file.seek(offset);
+ file.readFully(buf);
+ return read2ByteIntBigEndian(buf, 0);
+ }
+
+ public static int readBigEndianIntFromFile(RandomAccessFile file, long offset) throws IOException {
+ byte[] buf = new byte[4];
+ file.seek(offset);
+ file.readFully(buf);
+ return readFullIntBigEndian(buf, 0);
+ }
+
+ public static int readIntFromFile(RandomAccessFile file, long offset) throws IOException {
+ byte[] buf = new byte[4];
+ file.seek(offset);
+ file.readFully(buf);
+ return readFullInt(buf, 0);
+ }
+
+ public static void writeBytesToFile(String filename, byte[] data) throws IOException {
+ FileOutputStream fos = new FileOutputStream(filename);
+ fos.write(data);
+ fos.close();
+ }
+
+ public static byte[] getConfigAsBytes(String filename) throws IOException {
+ InputStream in = openConfig(filename);
+ byte[] buf = readFullyIntoBuffer(in, in.available());
+ in.close();
+ return buf;
+ }
+
+ public static int getFileChecksum(String filename) {
+ try {
+ return getFileChecksum(openConfig(filename));
+ } catch (IOException e) {
+ return 0;
+ }
+ }
+
+ private static int getFileChecksum(InputStream stream) {
+ try {
+ Scanner sc = new Scanner(stream, "UTF-8");
+ CRC32 checksum = new CRC32();
+ while (sc.hasNextLine()) {
+ String line = sc.nextLine().trim();
+ if (!line.isEmpty()) {
+ checksum.update(line.getBytes("UTF-8"));
+ }
+ }
+ sc.close();
+ return (int) checksum.getValue();
+ } catch (IOException e) {
+ return 0;
+ }
+ }
+
+ public static boolean checkOtherCRC(byte[] data, int byteIndex, int switchIndex, String filename, int offsetInData) {
+ // If the switch at data[byteIndex].switchIndex is on, then check that
+ // the CRC at data[offsetInData] ... data[offsetInData+3] matches the
+ // CRC of filename.
+ // If not, return false.
+ // If any other case, return true.
+ int switches = data[byteIndex] & 0xFF;
+ if (((switches >> switchIndex) & 0x01) == 0x01) {
+ // have to check the CRC
+ int crc = readFullIntBigEndian(data, offsetInData);
+
+ return getFileChecksum(filename) == crc;
+ }
+ return true;
+ }
+
+ public static long getCRC32(byte[] data) {
+ CRC32 checksum = new CRC32();
+ checksum.update(data);
+ return checksum.getValue();
+ }
+
+ private static byte[] getCodeTweakFile(String filename) throws IOException {
+ InputStream is = FileFunctions.class.getResourceAsStream("/com/pkrandom/patches/" + filename);
+ byte[] buf = readFullyIntoBuffer(is, is.available());
+ is.close();
+ return buf;
+ }
+
+ public static byte[] downloadFile(String url) throws IOException {
+ BufferedInputStream in = new BufferedInputStream(new URL(url).openStream());
+ ByteArrayOutputStream out = new ByteArrayOutputStream();
+ byte[] buf = new byte[1024];
+ int count;
+ while ((count = in.read(buf, 0, 1024)) != -1) {
+ out.write(buf, 0, count);
+ }
+ in.close();
+ return out.toByteArray();
+ }
+
+ public static void applyPatch(byte[] rom, String patchName) throws IOException {
+ byte[] patch = getCodeTweakFile(patchName + ".ips");
+
+ // check sig
+ int patchlen = patch.length;
+ if (patchlen < 8 || patch[0] != 'P' || patch[1] != 'A' || patch[2] != 'T' || patch[3] != 'C' || patch[4] != 'H') {
+ throw new IOException("not a valid IPS file");
+ }
+
+ // records
+ int offset = 5;
+ while (offset + 2 < patchlen) {
+ int writeOffset = readIPSOffset(patch, offset);
+ if (writeOffset == 0x454f46) {
+ // eof, done
+ return;
+ }
+ offset += 3;
+ if (offset + 1 >= patchlen) {
+ // error
+ throw new IOException("abrupt ending to IPS file, entry cut off before size");
+ }
+ int size = readIPSSize(patch, offset);
+ offset += 2;
+ if (size == 0) {
+ // RLE
+ if (offset + 1 >= patchlen) {
+ // error
+ throw new IOException("abrupt ending to IPS file, entry cut off before RLE size");
+ }
+ int rleSize = readIPSSize(patch, offset);
+ if (writeOffset + rleSize > rom.length) {
+ // error
+ throw new IOException("trying to patch data past the end of the ROM file");
+ }
+ offset += 2;
+ if (offset >= patchlen) {
+ // error
+ throw new IOException("abrupt ending to IPS file, entry cut off before RLE byte");
+ }
+ byte rleByte = patch[offset++];
+ for (int i = writeOffset; i < writeOffset + rleSize; i++) {
+ rom[i] = rleByte;
+ }
+ } else {
+ if (offset + size > patchlen) {
+ // error
+ throw new IOException("abrupt ending to IPS file, entry cut off before end of data block");
+ }
+ if (writeOffset + size > rom.length) {
+ // error
+ throw new IOException("trying to patch data past the end of the ROM file");
+ }
+ System.arraycopy(patch, offset, rom, writeOffset, size);
+ offset += size;
+ }
+ }
+ throw new IOException("improperly terminated IPS file");
+ }
+
+ private static int readIPSOffset(byte[] data, int offset) {
+ return ((data[offset] & 0xFF) << 16) | ((data[offset + 1] & 0xFF) << 8) | (data[offset + 2] & 0xFF);
+ }
+
+ private static int readIPSSize(byte[] data, int offset) {
+ return ((data[offset] & 0xFF) << 8) | (data[offset + 1] & 0xFF);
+ }
+
+ public static byte[] convIntArrToByteArr(int[] arg) {
+ byte[] out = new byte[arg.length];
+ for (int i = 0; i < arg.length; i++) {
+ out[i] = (byte) arg[i];
+ }
+ return out;
+ }
+} \ No newline at end of file