From 26601b3e0130d24ef5b9c692b797864ec98db063 Mon Sep 17 00:00:00 2001 From: NathanFallet Date: Thu, 23 Mar 2023 19:47:00 +0100 Subject: [PATCH 1/2] Adding support for Linux --- .gitignore | 5 + src/Gen4/RomHandler.cpp | 20 +- src/Gen4/RomHandler.h | 24 +- src/Gen4/main.cpp | 2 +- src/Gen4/make.bat | 3 +- src/Gen5/RomHandler.cpp | 30 +-- src/Gen5/RomHandler.h | 30 +-- src/Gen5/main.cpp | 2 +- src/arm9dec/blz.c | 55 +++++ src/arm9dec/blz.h | 10 + src/arm9dec/common.h | 10 + src/arm9dec/main.c | 44 ++++ src/arm9dec/make.bat | 1 + src/blz/blz.c | 485 ++++++++++++++++++++++++++++++++++++ src/blz/make.bat | 1 + src/knarc/Narc.cpp | 536 ++++++++++++++++++++++++++++++++++++++++ src/knarc/Narc.h | 86 +++++++ src/knarc/Source.cpp | 109 ++++++++ src/knarc/make.bat | 1 + src/ndstool/README.md | 1 + src/ndstool/copy.sh | 2 + 21 files changed, 1404 insertions(+), 53 deletions(-) mode change 100644 => 100755 src/Gen4/make.bat create mode 100644 src/arm9dec/blz.c create mode 100644 src/arm9dec/blz.h create mode 100644 src/arm9dec/common.h create mode 100644 src/arm9dec/main.c create mode 100755 src/arm9dec/make.bat create mode 100644 src/blz/blz.c create mode 100755 src/blz/make.bat create mode 100644 src/knarc/Narc.cpp create mode 100644 src/knarc/Narc.h create mode 100644 src/knarc/Source.cpp create mode 100755 src/knarc/make.bat create mode 100644 src/ndstool/README.md create mode 100755 src/ndstool/copy.sh diff --git a/.gitignore b/.gitignore index cd85d66..1b0b0de 100644 --- a/.gitignore +++ b/.gitignore @@ -5,8 +5,13 @@ temp/* data/* //Development tools +.vscode *replace* +//Linux/MacOS generated files (at compilation time) +*.dSYM +.DS_Store + //=) files/bw2_changes/ow_sprites/* */2_00000616.bin \ No newline at end of file diff --git a/src/Gen4/RomHandler.cpp b/src/Gen4/RomHandler.cpp index ac21afe..ee3dced 100644 --- a/src/Gen4/RomHandler.cpp +++ b/src/Gen4/RomHandler.cpp @@ -16,18 +16,18 @@ inline string csvGetNext(string str) { } void UnpackRom(string romPath, string arm9) { - string command = string(NDS_TOOL) + " -x \"" + romPath + "\" -9 temp\\arm9.bin -7 temp\\arm7.bin -y9 temp\\y9.bin -y7 temp\\y7.bin -d data -y temp\\overlay -t temp\\banner.bin -h temp\\header.bin"; + string command = string(NDS_TOOL) + " -x \"" + romPath + "\" -9 temp/arm9.bin -7 temp/arm7.bin -y9 temp/y9.bin -y7 temp/y7.bin -d data -y temp/overlay -t temp/banner.bin -h temp/header.bin"; cout << command << endl; system(command.c_str()); //Updating some of the files... - command = string(ARM9_DEC) + " temp\\arm9.bin " + " temp\\arm9_.bin"; + command = string(ARM9_DEC) + " temp/arm9.bin " + " temp/arm9_.bin"; system(command.c_str()); fstream bin; - bin.open("temp\\arm9_.bin", ios::binary|ios::in|ios::out|ios::ate); + bin.open("temp/arm9_.bin", ios::binary|ios::in|ios::out|ios::ate); unsigned char lang = 2; @@ -83,33 +83,33 @@ void UnpackRom(string romPath, string arm9) { changes.close(); bin.close(); - command = "copy files\\scr_seq.narc data\\a\\0\\1\\2"; + command = string(COPY_CMD) + " files/scr_seq.narc data/a/0/1/2"; system(command.c_str()); } void UnpackFieldNarc() { - string command = string(KNARC) + " -d temp\\event_data -u data\\a\\0\\3\\2"; + string command = string(KNARC) + " -d temp/event_data -u data/a/0/3/2"; system(command.c_str()); //Updating some select files: for(const auto & file : directory_iterator(EVENT_DATA)) { string pstr = file.path().string(); - command = "copy " + file.path().string(); - pstr = pstr.substr(pstr.find_last_of("\\") + 1, pstr.length()); - command += " temp\\event_data\\" + pstr; + command = string(COPY_CMD) + " " + file.path().string(); + pstr = pstr.substr(pstr.find_last_of("/") + 1, pstr.length()); + command += " temp/event_data/" + pstr; system(command.c_str()); } } void PackFieldNarc() { - string command = string(KNARC) + " -d temp\\event_data -p data\\a\\0\\3\\2"; + string command = string(KNARC) + " -d temp/event_data -p data/a/0/3/2"; system(command.c_str()); } void RepackRom(string output_name) { cout << output_name << endl; - string command = string(NDS_TOOL) + " -c " + "\"" + string(OUT_PATH) + output_name + "\"" + " -9 temp\\arm9_.bin -7 temp\\arm7.bin -y9 temp\\y9.bin -y7 temp\\y7.bin -d data -y temp\\overlay -t temp\\banner.bin -h temp\\header.bin"; + string command = string(NDS_TOOL) + " -c " + "\"" + string(OUT_PATH) + output_name + "\"" + " -9 temp/arm9_.bin -7 temp/arm7.bin -y9 temp/y9.bin -y7 temp/y7.bin -d data -y temp/overlay -t temp/banner.bin -h temp/header.bin"; system(command.c_str()); } diff --git a/src/Gen4/RomHandler.h b/src/Gen4/RomHandler.h index 9def77d..9bb4b64 100644 --- a/src/Gen4/RomHandler.h +++ b/src/Gen4/RomHandler.h @@ -6,17 +6,23 @@ using std::string; #define HGSS_COMMON "./files/Blocks_HGSS_Shared/" -#define SS_PATH "soulsilver_rom\\" -#define SS_ARM9 "files\\hgss_script.csv" -#define HG_ARM9 "files\\hgss_script.csv" -#define HG_PATH "heartgold_rom\\" -#define OUT_PATH "randomized_roms\\" +#define SS_PATH "soulsilver_rom/" +#define SS_ARM9 "files/hgss_script.csv" +#define HG_ARM9 "files/hgss_script.csv" +#define HG_PATH "heartgold_rom/" +#define OUT_PATH "randomized_roms/" #define HG_DATA "./files/Blocks_HG/" #define SS_DATA "./files/Blocks_SS/" -#define NDS_TOOL "tools\\ndstool.exe" -#define KNARC "tools\\knarc.exe" -#define ARM9_DEC "tools\\arm9dec.exe" -#define EVENT_DATA "files\\event_data" +#define NDS_TOOL "tools/ndstool.exe" +#define KNARC "tools/knarc.exe" +#define ARM9_DEC "tools/arm9dec.exe" +#define EVENT_DATA "files/event_data" + +#ifdef __CYGWIN__ +#define COPY_CMD "copy" +#else +#define COPY_CMD "cp" +#endif void UnpackRom(string path, string arm9); void UnpackFieldNarc(); diff --git a/src/Gen4/main.cpp b/src/Gen4/main.cpp index 5bd6884..1ff6218 100644 --- a/src/Gen4/main.cpp +++ b/src/Gen4/main.cpp @@ -66,7 +66,7 @@ static void HandleRandomization(string directory, string data, string arm9) { ClearData(); } cout << "Rom randomized with seed: " << seed << "." << endl; - pstr = pstr.substr(pstr.find_last_of("\\") + 1, pstr.length()-4) + "_map_randomized"; + pstr = pstr.substr(pstr.find_last_of("/") + 1, pstr.length()-4) + "_map_randomized"; GenerateLogFile(OUT_PATH + pstr + ".log"); SetWarps(); PackFieldNarc(); diff --git a/src/Gen4/make.bat b/src/Gen4/make.bat old mode 100644 new mode 100755 index 57b37b7..6ff610a --- a/src/Gen4/make.bat +++ b/src/Gen4/make.bat @@ -1,2 +1 @@ -g++ -g main.cpp RomHandler.cpp Randomizer.cpp -o ../../map_randomizer.exe -pause \ No newline at end of file +g++ -std=c++17 -g main.cpp RomHandler.cpp Randomizer.cpp -o ../../map_randomizer.exe \ No newline at end of file diff --git a/src/Gen5/RomHandler.cpp b/src/Gen5/RomHandler.cpp index 806d0b6..5edf2df 100644 --- a/src/Gen5/RomHandler.cpp +++ b/src/Gen5/RomHandler.cpp @@ -21,7 +21,7 @@ void ApplyChanges(string changesPath) { changesPath += langExt; fstream bin; - bin.open("temp\\arm9.bin", ios::binary|ios::in|ios::out|ios::ate); + bin.open("temp/arm9.bin", ios::binary|ios::in|ios::out|ios::ate); ifstream changes; changes.open(changesPath); @@ -46,7 +46,7 @@ void LockSeason(string changesPath, const char season) { changesPath += langExt; fstream bin; - bin.open("temp\\arm9.bin", ios::binary|ios::in|ios::out|ios::ate); + bin.open("temp/arm9.bin", ios::binary|ios::in|ios::out|ios::ate); ifstream changes; changes.open(changesPath); @@ -76,17 +76,17 @@ void LockSeason(string changesPath, const char season) { } void UnpackRom(string romPath, string arm9) { - string command = string(NDS_TOOL) + " -x \"" + romPath + "\" -9 temp\\arm9.bin -7 temp\\arm7.bin -y9 temp\\y9.bin -y7 temp\\y7.bin -d data -y temp\\overlay -t temp\\banner.bin -h temp\\header.bin"; + string command = string(NDS_TOOL) + " -x \"" + romPath + "\" -9 temp/arm9.bin -7 temp/arm7.bin -y9 temp/y9.bin -y7 temp/y7.bin -d data -y temp/overlay -t temp/banner.bin -h temp/header.bin"; cout << command << endl; system(command.c_str()); - command = string(ARM9_DEC) + " -d temp\\arm9.bin"; + command = string(ARM9_DEC) + " -d temp/arm9.bin"; system(command.c_str()); ApplyChanges(string(BW2_UNIVERSAL)); fstream bin; - bin.open("temp\\arm9.bin", ios::binary|ios::in|ios::out|ios::ate); + bin.open("temp/arm9.bin", ios::binary|ios::in|ios::out|ios::ate); unsigned char lang; @@ -133,22 +133,22 @@ void UnpackFieldNarc() { //Updating some select files: //Event Data - CopyFiles("data\\a\\1\\2\\6", "temp\\event_data", EVENT_DATA); + CopyFiles("data/a/1/2/6", "temp/event_data", EVENT_DATA); //Trainer Data - CopyFiles("data\\a\\0\\9\\1", "temp\\trainer_data", TRAINER_DATA); + CopyFiles("data/a/0/9/1", "temp/trainer_data", TRAINER_DATA); //Trainer Poke - CopyFiles("data\\a\\0\\9\\2", "temp\\trainer_poke", TRAINER_POKE); + CopyFiles("data/a/0/9/2", "temp/trainer_poke", TRAINER_POKE); //Map Scripts - CopyFiles("data\\a\\0\\5\\6", "temp\\map_script", MAP_FUNCTIONS); + CopyFiles("data/a/0/5/6", "temp/map_script", MAP_FUNCTIONS); //Map headers - string command = string(KNARC) + " -d temp\\map_header -u data\\a\\0\\1\\2"; + string command = string(KNARC) + " -d temp/map_header -u data/a/0/1/2"; system(command.c_str()); - for(const auto & file : directory_iterator("temp\\map_header")) { + for(const auto & file : directory_iterator("temp/map_header")) { fstream bin; bin.open(file.path(), ios::binary|ios::in|ios::out|ios::ate); bin.seekg(0, ios::end); @@ -171,22 +171,22 @@ void UnpackFieldNarc() { bin.close(); } - command = string(KNARC) + " -d temp\\map_header -p data\\a\\0\\1\\2"; + command = string(KNARC) + " -d temp/map_header -p data/a/0/1/2"; system(command.c_str()); //=) - CopyFiles("data\\a\\0\\4\\8", "temp\\ow_spr", OW_SPRITES); + CopyFiles("data/a/0/4/8", "temp/ow_spr", OW_SPRITES); system("echo on"); } void PackFieldNarc() { - string command = string(KNARC) + " -d temp\\event_data -p data\\a\\1\\2\\6"; + string command = string(KNARC) + " -d temp/event_data -p data/a/1/2/6"; system(command.c_str()); } void RepackRom(string output_name) { cout << output_name << endl; - string command = string(NDS_TOOL) + " -c " + "\"" + string(OUT_PATH) + output_name + "\"" + " -9 temp\\arm9.bin -7 temp\\arm7.bin -y9 temp\\y9.bin -y7 temp\\y7.bin -d data -y temp\\overlay -t temp\\banner.bin -h temp\\header.bin"; + string command = string(NDS_TOOL) + " -c " + "\"" + string(OUT_PATH) + output_name + "\"" + " -9 temp/arm9.bin -7 temp/arm7.bin -y9 temp/y9.bin -y7 temp/y7.bin -d data -y temp/overlay -t temp/banner.bin -h temp/header.bin"; system(command.c_str()); } diff --git a/src/Gen5/RomHandler.h b/src/Gen5/RomHandler.h index 8b717eb..9ab6de0 100644 --- a/src/Gen5/RomHandler.h +++ b/src/Gen5/RomHandler.h @@ -5,24 +5,24 @@ using std::string; -#define ARM9_DEC "tools\\blz.exe" -#define W2_PATH "white2_rom\\" -#define B2_PATH "black2_rom\\" -#define OUT_PATH "randomized_roms\\" +#define ARM9_DEC "tools/blz.exe" +#define W2_PATH "white2_rom/" +#define B2_PATH "black2_rom/" +#define OUT_PATH "randomized_roms/" #define W2_DATA "./files/Blocks_W2/" #define B2_DATA "./files/Blocks_B2/" #define BW2_DATA_SHARED "./files/Blocks_BW2_Shared/" #define SPRINGSUMMER_DATA "./files/Blocks_BW2_Seasons/SpringSummer/" #define AUTUMN_DATA "./files/Blocks_BW2_Seasons/Autumn/" #define WINTER_DATA "./files/Blocks_BW2_Seasons/Winter/" -#define NDS_TOOL "tools\\ndstool.exe" -#define KNARC "tools\\knarc.exe" -#define EVENT_DATA "files\\bw2_changes\\event_data_bw2" -#define TRAINER_DATA "files\\bw2_changes\\tr_data_bw2" -#define TRAINER_POKE "files\\bw2_changes\\tr_poke_bw2" -#define MAP_FUNCTIONS "files\\bw2_changes\\map_functions_bw2" -#define OW_SPRITES "files\\bw2_changes\\ow_sprites" -#define MUGSHOTS "files\\bw2_changes\\mugshots" +#define NDS_TOOL "tools/ndstool.exe" +#define KNARC "tools/knarc.exe" +#define EVENT_DATA "files/bw2_changes/event_data_bw2" +#define TRAINER_DATA "files/bw2_changes/tr_data_bw2" +#define TRAINER_POKE "files/bw2_changes/tr_poke_bw2" +#define MAP_FUNCTIONS "files/bw2_changes/map_functions_bw2" +#define OW_SPRITES "files/bw2_changes/ow_sprites" +#define MUGSHOTS "files/bw2_changes/mugshots" //Season consts enum Seasons { @@ -33,9 +33,9 @@ enum Seasons { }; //File change locations -#define BW2_UNIVERSAL "files\\bw2_script_universal.csv" -#define SEASON_LOCK_BASE_W2 "files\\Blocks_BW2_Seasons\\SeasonScripts\\W2SeasonLock" -#define SEASON_LOCK_BASE_B2 "files\\Blocks_BW2_Seasons\\SeasonScripts\\B2SeasonLock" +#define BW2_UNIVERSAL "files/bw2_script_universal.csv" +#define SEASON_LOCK_BASE_W2 "files/Blocks_BW2_Seasons/SeasonScripts/W2SeasonLock" +#define SEASON_LOCK_BASE_B2 "files/Blocks_BW2_Seasons/SeasonScripts/B2SeasonLock" void LockSeason(string changesPath, const char season); void UnpackRom(string path, string arm9); diff --git a/src/Gen5/main.cpp b/src/Gen5/main.cpp index 3723055..82c05c4 100644 --- a/src/Gen5/main.cpp +++ b/src/Gen5/main.cpp @@ -116,7 +116,7 @@ static void HandleRandomization(string directory, string data, string season_pat ClearData(); } LockSeason(season_path, season); - pstr = pstr.substr(pstr.find_last_of("\\") + 1, pstr.length()-4) + "_map_randomized"; + pstr = pstr.substr(pstr.find_last_of("/") + 1, pstr.length()-4) + "_map_randomized"; GenerateLogFile(OUT_PATH + pstr + ".log"); SetWarps(); PackFieldNarc(); diff --git a/src/arm9dec/blz.c b/src/arm9dec/blz.c new file mode 100644 index 0000000..bc20856 --- /dev/null +++ b/src/arm9dec/blz.c @@ -0,0 +1,55 @@ +#include "blz.h" + +static int get_decompressed_length(u8 *end, u32 input_len) +{ + return input_len + *(u32 *)&end[-4]; +} + +static void decompress(u8 *end) +{ + footer f = *(footer *)&end[-8]; + + u8 *data = end - f.total_size; + + u32 cmpSize = f.total_size - f.hdr_size, + outSize = f.total_size + f.ext_size; + + while (outSize) + { + u32 flags = data[--cmpSize]; + + for (u32 i = 0; i < 8; i++) + { + if ((flags << i) & 0x80) + { + cmpSize -= 2; + + u16 op = *(u16 *)&data[cmpSize]; + + u32 len = (op >> 12) + 3, + ofs = (op & 0xFFF) + 3; + + outSize -= len; + + while (len--) + data[outSize + len] = data[outSize + len + ofs]; + } + else + data[--outSize] = data[--cmpSize]; + + if (!outSize) + break; + } + } +} + +u8 *decompress_blz_buffer(u8 *in, u32 length_in, u32 *length_out) +{ + *length_out = get_decompressed_length(in + length_in, length_in); + + u8 *buf = malloc(*length_out); + memcpy(buf, in, length_in); + decompress(buf + length_in); + + return buf; +} \ No newline at end of file diff --git a/src/arm9dec/blz.h b/src/arm9dec/blz.h new file mode 100644 index 0000000..3058829 --- /dev/null +++ b/src/arm9dec/blz.h @@ -0,0 +1,10 @@ +#include "common.h" + +typedef struct footer +{ + u32 total_size : 24; + u32 hdr_size : 8; + u32 ext_size; +} footer; + +u8 *decompress_blz_buffer(u8 *in, u32 length_in, u32 *length_out); \ No newline at end of file diff --git a/src/arm9dec/common.h b/src/arm9dec/common.h new file mode 100644 index 0000000..c751f97 --- /dev/null +++ b/src/arm9dec/common.h @@ -0,0 +1,10 @@ +#pragma once + +#include +#include +#include +#include + +typedef uint8_t u8; +typedef uint16_t u16; +typedef uint32_t u32; \ No newline at end of file diff --git a/src/arm9dec/main.c b/src/arm9dec/main.c new file mode 100644 index 0000000..e8fd368 --- /dev/null +++ b/src/arm9dec/main.c @@ -0,0 +1,44 @@ +#include "blz.h" + +int main(int argc, char **argv) +{ + if (argc != 3) + { + printf("Usage: %s arm9.bin output\n", argv[0]); + return 1; + } + + printf("Saving %s to %s...\n", argv[1], argv[2]); + + FILE *in = fopen(argv[1], "rb"); + FILE *out = fopen(argv[2], "wb"); + + fseek(in, 0, SEEK_END); + int sz = ftell(in) - 12; + rewind(in); + + u8 *buf = malloc(sz); + + puts("Reading input..."); + + fread(buf, 1, sz, in); + fclose(in); + + puts("Decompressing..."); + + u32 len = 0; + u8 *outbuf = decompress_blz_buffer(buf, sz, &len); + + printf("Successfully decompressed 0x%x bytes to 0x%x bytes...\n", sz, len); + puts("Writing output..."); + + fwrite(outbuf, 1, len, out); + fclose(out); + + free(buf); + free(outbuf); + + puts("Done!"); + + return 0; +} \ No newline at end of file diff --git a/src/arm9dec/make.bat b/src/arm9dec/make.bat new file mode 100755 index 0000000..6ac4470 --- /dev/null +++ b/src/arm9dec/make.bat @@ -0,0 +1 @@ +gcc -g main.c blz.c -o ../../tools/arm9dec.exe \ No newline at end of file diff --git a/src/blz/blz.c b/src/blz/blz.c new file mode 100644 index 0000000..26a3dc6 --- /dev/null +++ b/src/blz/blz.c @@ -0,0 +1,485 @@ +/*----------------------------------------------------------------------------*/ +/*-- blz.c - Bottom LZ coding for Nintendo GBA/DS --*/ +/*-- Copyright (C) 2011 CUE --*/ +/*-- --*/ +/*-- 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 . --*/ +/*----------------------------------------------------------------------------*/ + +/*----------------------------------------------------------------------------*/ +#include +#include +#include + +/*----------------------------------------------------------------------------*/ +#define CMD_DECODE 0x00 // decode +#define CMD_ENCODE 0x01 // encode + +#define BLZ_NORMAL 0 // normal mode +#define BLZ_BEST 1 // best mode + +#define BLZ_SHIFT 1 // bits to shift +#define BLZ_MASK 0x80 // bits to check: + // ((((1 << BLZ_SHIFT) - 1) << (8 - BLZ_SHIFT) + +#define BLZ_THRESHOLD 2 // max number of bytes to not encode +#define BLZ_N 0x1002 // max offset ((1 << 12) + 2) +#define BLZ_F 0x12 // max coded ((1 << 4) + BLZ_THRESHOLD) + +#define RAW_MINIM 0x00000000 // empty file, 0 bytes +#define RAW_MAXIM 0x00FFFFFF // 3-bytes length, 16MB - 1 + +#define BLZ_MINIM 0x00000004 // header only (empty RAW file) +#define BLZ_MAXIM 0x01400000 // 0x0120000A, padded to 20MB: + // * length, RAW_MAXIM + // * flags, (RAW_MAXIM + 7) / 8 + // * header, 11 + // 0x00FFFFFF + 0x00200000 + 12 + padding + +/*----------------------------------------------------------------------------*/ +unsigned int arm9; + +/*----------------------------------------------------------------------------*/ +#define BREAK(text) { printf(text); return; } +#define EXIT(text) { printf(text); exit(-1); } + +/*----------------------------------------------------------------------------*/ +void Title(void); +void Usage(void); + +char *Load(char *filename, int *length, int min, int max); +void Save(char *filename, char *buffer, int length); +char *Memory(int length, int size); + +void BLZ_Decode(char *filename); +void BLZ_Encode(char *filename, int mode); +char *BLZ_Code(unsigned char *raw_buffer, int raw_len, int *new_len, int best); +void BLZ_Invert(char *buffer, int length); +short BLZ_CRC16(unsigned char *buffer, unsigned int length); + +/*----------------------------------------------------------------------------*/ +int main(int argc, char **argv) { + int cmd, mode; + int arg; + + Title(); + + if (argc < 2) Usage(); + if (!strcmp(argv[1], "-d")) { cmd = CMD_DECODE; } + else if (!strcmp(argv[1], "-en")) { cmd = CMD_ENCODE; mode = BLZ_NORMAL; } + else if (!strcmp(argv[1], "-eo")) { cmd = CMD_ENCODE; mode = BLZ_BEST; } + else if (!strcmp(argv[1], "-en9")) { cmd = CMD_ENCODE; mode = BLZ_NORMAL; } + else if (!strcmp(argv[1], "-eo9")) { cmd = CMD_ENCODE; mode = BLZ_BEST; } + else EXIT("Command not supported\n"); + if (argc < 3) EXIT("Filename not specified\n"); + + switch (cmd) { + case CMD_DECODE: + for (arg = 2; arg < argc; arg++) BLZ_Decode(argv[arg]); + break; + case CMD_ENCODE: + arm9 = argv[1][3] == '9' ? 1 : 0; + for (arg = 2; arg < argc; arg++) BLZ_Encode(argv[arg], mode); + break; + default: + break; + } + + printf("\nDone\n"); + + return(0); +} + +/*----------------------------------------------------------------------------*/ +void Title(void) { + printf( + "\n" + "BLZ - (c) CUE 2011\n" + "Bottom LZ coding for Nintendo GBA/DS\n" + "\n" + ); +} + +/*----------------------------------------------------------------------------*/ +void Usage(void) { + EXIT( + "Usage: BLZ command filename [filename [...]]\n" + "\n" + "command:\n" + " -d ....... decode 'filename'\n" + " -en[9] ... encode 'filename', normal mode\n" + " -eo[9] ... encode 'filename', optimal mode (LZ-CUE)\n" + "\n" + "* '9' compress an ARM9 file with 0x4000 bytes decoded\n" + "* multiple filenames and wildcards are permitted\n" + "* the original file is overwritten with the new file\n" + "* this codification is used in the DS overlay files\n" + ); +} + +int fsize(FILE *fp){ + int prev=ftell(fp); + fseek(fp, 0L, SEEK_END); + int sz=ftell(fp); + fseek(fp,prev,SEEK_SET); + return sz; +} + +/*----------------------------------------------------------------------------*/ +char *Load(char *filename, int *length, int min, int max) { + FILE *fp; + int fs; + char *fb; + + if ((fp = fopen(filename, "rb")) == NULL) EXIT("\nFile open error\n"); + fs = fsize(fp); + if ((fs < min) || (fs > max)) EXIT("\nFile size error\n"); + fb = Memory(fs + 3, sizeof(char)); + if (fread(fb, 1, fs, fp) != fs) EXIT("\nFile read error\n"); + if (fclose(fp) == EOF) EXIT("\nFile close error\n"); + + *length = fs; + + return(fb); +} + +/*----------------------------------------------------------------------------*/ +void Save(char *filename, char *buffer, int length) { + FILE *fp; + + if ((fp = fopen(filename, "wb")) == NULL) EXIT("\nFile create error\n"); + if (fwrite(buffer, 1, length, fp) != length) EXIT("\nFile write error\n"); + if (fclose(fp) == EOF) EXIT("\nFile close error\n"); +} + +/*----------------------------------------------------------------------------*/ +char *Memory(int length, int size) { + char *fb; + + fb = (char *) calloc(length, size); + if (fb == NULL) EXIT("\nMemory error\n"); + + return(fb); +} + +/*----------------------------------------------------------------------------*/ +void BLZ_Decode(char *filename) { + unsigned char *pak_buffer, *raw_buffer, *pak, *raw, *pak_end, *raw_end; + unsigned int pak_len, raw_len, len, pos, inc_len, hdr_len, enc_len, dec_len; + unsigned char flags, mask; + + printf("- decoding '%s'", filename); + + pak_buffer = Load(filename, &pak_len, BLZ_MINIM, BLZ_MAXIM); + + inc_len = *(unsigned int *)(pak_buffer + pak_len - 4); + if (!inc_len) { + printf(", WARNING: not coded file!"); + enc_len = 0; + dec_len = pak_len; + pak_len = 0; + raw_len = dec_len; + } else { + if (pak_len < 8) EXIT("\nFile has a bad header\n"); + hdr_len = pak_buffer[pak_len - 5]; + if ((hdr_len < 0x08) || (hdr_len > 0x0B)) EXIT("\nBad header length\n"); + if (pak_len <= hdr_len) EXIT("\nBad length\n"); + enc_len = *(unsigned int *)(pak_buffer + pak_len - 8) & 0x00FFFFFF; + dec_len = pak_len - enc_len; + pak_len = enc_len - hdr_len; + raw_len = dec_len + enc_len + inc_len; + if (raw_len > RAW_MAXIM) EXIT("\nBad decoded length\n"); + } + + raw_buffer = (unsigned char *) Memory(raw_len, sizeof(char)); + + pak = pak_buffer; + raw = raw_buffer; + pak_end = pak_buffer + dec_len + pak_len; + raw_end = raw_buffer + raw_len; + + for (len = 0; len < dec_len; len++) *raw++ = *pak++; + + BLZ_Invert(pak_buffer + dec_len, pak_len); + + mask = 0; + + while (raw < raw_end) { + if (!(mask >>= BLZ_SHIFT)) { + if (pak == pak_end) break; + flags = *pak++; + mask = BLZ_MASK; + } + + if (!(flags & mask)) { + if (pak == pak_end) break; + *raw++ = *pak++; + } else { + if (pak + 1 >= pak_end) break; + pos = *pak++ << 8; + pos |= *pak++; + len = (pos >> 12) + BLZ_THRESHOLD + 1; + if (raw + len > raw_end) { + printf(", WARNING: wrong decoded length!"); + len = raw_end - raw; + } + pos = (pos & 0xFFF) + 3; + while (len--) *raw++ = *(raw - pos); + } + } + + BLZ_Invert(raw_buffer + dec_len, raw_len - dec_len); + + raw_len = raw - raw_buffer; + + if (raw != raw_end) printf(", WARNING: unexpected end of encoded file!"); + + Save(filename, raw_buffer, raw_len); + + free(raw_buffer); + free(pak_buffer); + + printf("\n"); +} + +/*----------------------------------------------------------------------------*/ +void BLZ_Encode(char *filename, int mode) { + unsigned char *raw_buffer, *pak_buffer, *new_buffer; + unsigned int raw_len, pak_len, new_len; + + printf("- encoding '%s'", filename); + + raw_buffer = Load(filename, &raw_len, RAW_MINIM, RAW_MAXIM); + + pak_buffer = NULL; + pak_len = BLZ_MAXIM + 1; + + new_buffer = BLZ_Code(raw_buffer, raw_len, &new_len, mode); + if (new_len < pak_len) { + if (pak_buffer != NULL) free(pak_buffer); + pak_buffer = new_buffer; + pak_len = new_len; + } + + Save(filename, pak_buffer, pak_len); + + free(pak_buffer); + free(raw_buffer); + + printf("\n"); +} + +/*----------------------------------------------------------------------------*/ +char *BLZ_Code(unsigned char *raw_buffer, int raw_len, int *new_len, int best) { + unsigned char *pak_buffer, *pak, *raw, *raw_end, *flg, *tmp; + unsigned int pak_len, inc_len, hdr_len, enc_len, len, pos, max; + unsigned int len_best, pos_best, len_next, pos_next, len_post, pos_post; + unsigned int pak_tmp, raw_tmp, raw_new; + unsigned short crc; + unsigned char mask; + +#define SEARCH(l,p) { \ + l = BLZ_THRESHOLD; \ + \ + max = raw - raw_buffer >= BLZ_N ? BLZ_N : raw - raw_buffer; \ + for (pos = 3; pos <= max; pos++) { \ + for (len = 0; len < BLZ_F; len++) { \ + if (raw + len == raw_end) break; \ + if (len >= pos) break; \ + if (*(raw + len) != *(raw + len - pos)) break; \ + } \ + \ + if (len > l) { \ + p = pos; \ + if ((l = len) == BLZ_F) break; \ + } \ + } \ +} + + pak_tmp = 0; + raw_tmp = raw_len; + + pak_len = raw_len + ((raw_len + 7) / 8) + 11; + pak_buffer = (unsigned char *) Memory(pak_len, sizeof(char)); + + raw_new = raw_len; + if (arm9) { + if (raw_len < 0x4000) { + printf(", WARNING: ARM9 must be greater as 16KB, switch [9] disabled"); + } else if ( + (*(unsigned int *)(raw_buffer + 0x0) != 0xE7FFDEFF) || + (*(unsigned int *)(raw_buffer + 0x4) != 0xE7FFDEFF) || + (*(unsigned int *)(raw_buffer + 0x8) != 0xE7FFDEFF) || + (*(unsigned short *)(raw_buffer + 0xC) != 0xDEFF) + ) { + printf(", WARNING: invalid Secure Area ID, switch [9] disabled"); + } else if (*(short *)(raw_buffer + 0x7FE)) { + printf(", WARNING: invalid Secure Area 2KB end, switch [9] disabled"); + } else { + crc = (unsigned short)BLZ_CRC16(raw_buffer + 0x10, 0x07F0); + if (*(unsigned short *)(raw_buffer + 0x0E) != crc) { + printf(", WARNING: CRC16 Secure Area 2KB do not match"); + *(unsigned short *)(raw_buffer + 0x0E) = crc; + } + raw_new -= 0x4000; + } + } + + BLZ_Invert(raw_buffer, raw_len); + + pak = pak_buffer; + raw = raw_buffer; + raw_end = raw_buffer + raw_new; + + mask = 0; + + while (raw < raw_end) { + if (!(mask >>= BLZ_SHIFT)) { + *(flg = pak++) = 0; + mask = BLZ_MASK; + } + + SEARCH(len_best, pos_best); + + // LZ-CUE optimization start + if (best) { + if (len_best > BLZ_THRESHOLD) { + if (raw + len_best < raw_end) { + raw += len_best; + SEARCH(len_next, pos_next); + raw -= len_best - 1; + SEARCH(len_post, pos_post); + raw--; + + if (len_next <= BLZ_THRESHOLD) len_next = 1; + if (len_post <= BLZ_THRESHOLD) len_post = 1; + + if (len_best + len_next <= 1 + len_post) len_best = 1; + } + } + } + // LZ-CUE optimization end + + *flg <<= 1; + if (len_best > BLZ_THRESHOLD) { + raw += len_best; + *flg |= 1; + *pak++ = ((len_best - (BLZ_THRESHOLD+1)) << 4) | ((pos_best - 3) >> 8); + *pak++ = (pos_best - 3) & 0xFF; + } else { + *pak++ = *raw++; + } + +#if 1 + if (pak - pak_buffer + raw_len - (raw - raw_buffer) < pak_tmp + raw_tmp) { +#else + if ( + (((pak - pak_buffer + raw_len - (raw - raw_buffer)) + 3) & -4) + < + pak_tmp + raw_tmp + ) { +#endif + pak_tmp = pak - pak_buffer; + raw_tmp = raw_len - (raw - raw_buffer); + } + } + + while (mask && (mask != 1)) { + mask >>= BLZ_SHIFT; + *flg <<= 1; + } + + pak_len = pak - pak_buffer; + + BLZ_Invert(raw_buffer, raw_len); + BLZ_Invert(pak_buffer, pak_len); + + if (!pak_tmp || (raw_len + 4 < ((pak_tmp + raw_tmp + 3) & -4) + 8)) { + pak = pak_buffer; + raw = raw_buffer; + raw_end = raw_buffer + raw_len; + + while (raw < raw_end) *pak++ = *raw++; + + while ((pak - pak_buffer) & 3) *pak++ = 0; + + *(unsigned int *)pak = 0; pak += 4; + } else { + tmp = (unsigned char *) Memory(raw_tmp + pak_tmp + 11, sizeof(char)); + + for (len = 0; len < raw_tmp; len++) + tmp[len] = raw_buffer[len]; + + for (len = 0; len < pak_tmp; len++) + tmp[raw_tmp + len] = pak_buffer[len + pak_len - pak_tmp]; + + pak = pak_buffer; + pak_buffer = tmp; + + free(pak); + + pak = pak_buffer + raw_tmp + pak_tmp; + + enc_len = pak_tmp; + hdr_len = 8; + inc_len = raw_len - pak_tmp - raw_tmp; + + while ((pak - pak_buffer) & 3) { + *pak++ = 0xFF; + hdr_len++; + } + + *(unsigned int *)pak = enc_len + hdr_len; pak += 3; + *pak++ = hdr_len; + *(unsigned int *)pak = inc_len - hdr_len; pak += 4; + } + + *new_len = pak - pak_buffer; + + return(pak_buffer); +} + +/*----------------------------------------------------------------------------*/ +void BLZ_Invert(char *buffer, int length) { + char *bottom, ch; + + bottom = buffer + length - 1; + + while (buffer < bottom) { + ch = *buffer; + *buffer++ = *bottom; + *bottom-- = ch; + } +} + +/*----------------------------------------------------------------------------*/ +short BLZ_CRC16(unsigned char *buffer, unsigned int length) { + unsigned short crc; + unsigned int nbits; + + crc = 0xFFFF; + while (length--) { + crc ^= *buffer++; + nbits = 8; + while (nbits--) { + if (crc & 1) { crc = (crc >> 1) ^ 0xA001; } + else crc = crc >> 1; + } + } + + return(crc); +} + +/*----------------------------------------------------------------------------*/ +/*-- EOF Copyright (C) 2011 CUE --*/ +/*----------------------------------------------------------------------------*/ diff --git a/src/blz/make.bat b/src/blz/make.bat new file mode 100755 index 0000000..d5acb52 --- /dev/null +++ b/src/blz/make.bat @@ -0,0 +1 @@ +gcc -g blz.c -o ../../tools/blz.exe \ No newline at end of file diff --git a/src/knarc/Narc.cpp b/src/knarc/Narc.cpp new file mode 100644 index 0000000..88b1421 --- /dev/null +++ b/src/knarc/Narc.cpp @@ -0,0 +1,536 @@ +#include "Narc.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +using namespace std; + +void Narc::AlignDword(ofstream& ofs, uint8_t paddingChar) +{ + if ((ofs.tellp() % 4) != 0) + { + for (int i = 4 - (ofs.tellp() % 4); i-- > 0; ) + { + ofs.write(reinterpret_cast(&paddingChar), sizeof(uint8_t)); + } + } +} + +bool Narc::Cleanup(ifstream& ifs, const NarcError& e) +{ + ifs.close(); + + error = e; + + return false; +} + +bool Narc::Cleanup(ofstream& ofs, const NarcError& e) +{ + ofs.close(); + + error = e; + + return false; +} + +vector Narc::OrderedDirectoryIterator(const filesystem::path& path, bool recursive) const +{ + vector v; + + for (const auto& de : filesystem::directory_iterator(path)) + { + v.push_back(de); + } + + sort(v.begin(), v.end(), [](const filesystem::directory_entry& a, const filesystem::directory_entry& b) + { + // I fucking hate C++ + string aStr = a.path().filename().string(); + string bStr = b.path().filename().string(); + + for (size_t i = 0; i < aStr.size(); ++i) + { + aStr[i] = tolower(aStr[i]); + } + + for (size_t i = 0; i < bStr.size(); ++i) + { + bStr[i] = tolower(bStr[i]); + } + + return aStr < bStr; + }); + + if (recursive) + { + size_t vSize = v.size(); + + for (size_t i = 0; i < vSize; ++i) + { + if (v[i].is_directory()) + { + vector temp = OrderedDirectoryIterator(v[i], true); + + v.insert(v.end(), temp.begin(), temp.end()); + } + } + } + + return v; +} + +NarcError Narc::GetError() const +{ + return error; +} + +bool Narc::Pack(const filesystem::path& fileName, const filesystem::path& directory) +{ + ofstream ofs(fileName, ios::binary); + + if (!ofs.good()) { return Cleanup(ofs, NarcError::InvalidOutputFile); } + + vector fatEntries; + uint16_t directoryCounter = 1; + + for (const auto& de : OrderedDirectoryIterator(directory, true)) + { + if (de.is_directory()) + { + ++directoryCounter; + } + else + { + fatEntries.push_back(FileAllocationTableEntry + { + .Start = 0x0, + .End = 0x0 + }); + + if (fatEntries.size() > 1) + { + fatEntries.back().Start = fatEntries.rbegin()[1].End; + + if ((fatEntries.rbegin()[1].End % 4) != 0) + { + fatEntries.back().Start += 4 - (fatEntries.rbegin()[1].End % 4); + } + } + + fatEntries.back().End = fatEntries.back().Start + static_cast(de.file_size()); + } + } + + FileAllocationTable fat + { + .Id = 0x46415442, + .ChunkSize = sizeof(FileAllocationTable) + (static_cast(fatEntries.size()) * sizeof(FileAllocationTableEntry)), + .FileCount = static_cast(fatEntries.size()), + .Reserved = 0x0 + }; + + map subTables; + vector paths; + + directoryCounter = 0; + + for (const auto& de : OrderedDirectoryIterator(directory, true)) + { + if (!subTables.count(de.path().parent_path())) + { + subTables.insert({ de.path().parent_path(), "" }); + paths.push_back(de.path().parent_path()); + } + + if (de.is_directory()) + { + ++directoryCounter; + + subTables[de.path().parent_path()] += static_cast(0x80 + de.path().filename().string().size()); + subTables[de.path().parent_path()] += de.path().filename().string(); + subTables[de.path().parent_path()] += (0xF000 + directoryCounter) & 0xFF; + subTables[de.path().parent_path()] += (0xF000 + directoryCounter) >> 8; + } + else + { + subTables[de.path().parent_path()] += static_cast(de.path().filename().string().size()); + subTables[de.path().parent_path()] += de.path().filename().string(); + } + } + + for (auto& subTable : subTables) + { + subTable.second += '\0'; + } + + vector fntEntries; + + if (!regex_match(filesystem::directory_iterator(directory)->path().string(), regex(".*_/d{4,8}/.bin"))) + { + fntEntries.push_back( + { + .Offset = (directoryCounter + 1) * sizeof(FileNameTableEntry), + .FirstFileId = 0x0, + .Utility = static_cast(directoryCounter + 1) + }); + + for (uint16_t i = 0; i < directoryCounter; ++i) + { + fntEntries.push_back( + { + .Offset = fntEntries.back().Offset + subTables[paths[i]].size(), + .FirstFileId = fntEntries.back().FirstFileId, + .Utility = 0x0 + }); + + for (size_t j = 0; j < (subTables[paths[i]].size() - 1); ++j) + { + if (static_cast(subTables[paths[i]][j]) <= 0x7F) + { + j += static_cast(subTables[paths[i]][j]); + ++fntEntries.back().FirstFileId; + } + else if (static_cast(subTables[paths[i]][j]) <= 0xFF) + { + j += static_cast(subTables[paths[i]][j]) - 0x80 + 0x2; + } + } + + fntEntries.back().Utility = 0xF000 + (find(paths.begin(), paths.end(), paths[i + 1].parent_path()) - paths.begin()); + } + } + else + { + fntEntries.push_back( + { + .Offset = 0x4, + .FirstFileId = 0x0, + .Utility = 0x1 + }); + } + + FileNameTable fnt + { + .Id = 0x464E5442, + .ChunkSize = sizeof(FileNameTable) + (fntEntries.size() * sizeof(FileNameTableEntry)) + }; + + if (!regex_match(filesystem::directory_iterator(directory)->path().string(), regex(".*_/d{4,8}/.bin"))) + { + for (const auto& subTable : subTables) + { + fnt.ChunkSize += subTable.second.size(); + } + } + + if ((fnt.ChunkSize % 4) != 0) + { + fnt.ChunkSize += 4 - (fnt.ChunkSize % 4); + } + + FileImages fi + { + .Id = 0x46494D47, + .ChunkSize = sizeof(FileImages) + fatEntries.back().End + }; + + if ((fi.ChunkSize % 4) != 0) + { + fi.ChunkSize += 4 - (fi.ChunkSize % 4); + } + + Header header + { + .Id = 0x4352414E, + .ByteOrderMark = 0xFFFE, + .Version = 0x100, + .FileSize = sizeof(Header) + fat.ChunkSize + fnt.ChunkSize + fi.ChunkSize, + .ChunkSize = sizeof(Header), + .ChunkCount = 0x3 + }; + + ofs.write(reinterpret_cast(&header), sizeof(Header)); + ofs.write(reinterpret_cast(&fat), sizeof(FileAllocationTable)); + + for (auto& entry : fatEntries) + { + ofs.write(reinterpret_cast(&entry), sizeof(FileAllocationTableEntry)); + } + + ofs.write(reinterpret_cast(&fnt), sizeof(FileNameTable)); + + for (auto& entry : fntEntries) + { + ofs.write(reinterpret_cast(&entry), sizeof(FileNameTableEntry)); + } + + if (!regex_match(filesystem::directory_iterator(directory)->path().string(), regex(".*_/d{4,8}/.bin"))) + { + for (const auto& path : paths) + { + ofs << subTables[path]; + } + } + + AlignDword(ofs, 0xFF); + + ofs.write(reinterpret_cast(&fi), sizeof(FileImages)); + + for (const auto& de : OrderedDirectoryIterator(directory, true)) + { + if (de.is_directory()) + { + continue; + } + + ifstream ifs(de.path(), ios::binary | ios::ate); + + if (!ifs.good()) + { + ifs.close(); + + return Cleanup(ofs, NarcError::InvalidInputFile); + } + + streampos length = ifs.tellg(); + unique_ptr buffer = make_unique(static_cast(length)); + + ifs.seekg(0); + ifs.read(buffer.get(), length); + ifs.close(); + + ofs.write(buffer.get(), length); + + AlignDword(ofs, 0xFF); + } + + ofs.close(); + + return error == NarcError::None; +} + +bool Narc::Unpack(const filesystem::path& fileName, const filesystem::path& directory) +{ + ifstream ifs(fileName, ios::binary); + + if (!ifs.good()) { return Cleanup(ifs, NarcError::InvalidInputFile); } + + Header header; + ifs.read(reinterpret_cast(&header), sizeof(Header)); + + if (header.Id != 0x4352414E) { return Cleanup(ifs, NarcError::InvalidHeaderId); } + if (header.ByteOrderMark != 0xFFFE) { return Cleanup(ifs, NarcError::InvalidByteOrderMark); } + if ((header.Version != 0x0100) && (header.Version != 0x0000)) { return Cleanup(ifs, NarcError::InvalidVersion); } + if (header.ChunkSize != 0x10) { return Cleanup(ifs, NarcError::InvalidHeaderSize); } + if (header.ChunkCount != 0x3) { return Cleanup(ifs, NarcError::InvalidChunkCount); } + + FileAllocationTable fat; + ifs.read(reinterpret_cast(&fat), sizeof(FileAllocationTable)); + + if (fat.Id != 0x46415442) { return Cleanup(ifs, NarcError::InvalidFileAllocationTableId); } + if (fat.Reserved != 0x0) { return Cleanup(ifs, NarcError::InvalidFileAllocationTableReserved); } + + unique_ptr fatEntries = make_unique(fat.FileCount); + + for (uint16_t i = 0; i < fat.FileCount; ++i) + { + ifs.read(reinterpret_cast(&fatEntries.get()[i]), sizeof(FileAllocationTableEntry)); + } + + FileNameTable fnt; + vector FileNameTableEntries; + ifs.read(reinterpret_cast(&fnt), sizeof(FileNameTable)); + + if (fnt.Id != 0x464E5442) { return Cleanup(ifs, NarcError::InvalidFileNameTableId); } + + vector fntEntries; + + do + { + fntEntries.push_back(FileNameTableEntry()); + + ifs.read(reinterpret_cast(&fntEntries.back().Offset), sizeof(uint32_t)); + ifs.read(reinterpret_cast(&fntEntries.back().FirstFileId), sizeof(uint16_t)); + ifs.read(reinterpret_cast(&fntEntries.back().Utility), sizeof(uint16_t)); + } while (static_cast(ifs.tellg()) < (header.ChunkSize + fat.ChunkSize + sizeof(FileNameTable) + fntEntries[0].Offset)); + + unique_ptr fileNames = make_unique(0xFFFF); + + for (size_t i = 0; i < fntEntries.size(); ++i) + { + ifs.seekg(static_cast(header.ChunkSize) + fat.ChunkSize + sizeof(FileNameTable) + fntEntries[i].Offset); + + uint16_t fileId = 0x0000; + + for (uint8_t length = 0x80; length != 0x00; ifs.read(reinterpret_cast(&length), sizeof(uint8_t))) + { + if (length <= 0x7F) + { + for (uint8_t j = 0; j < length; ++j) + { + uint8_t c; + ifs.read(reinterpret_cast(&c), sizeof(uint8_t)); + + fileNames.get()[fntEntries[i].FirstFileId + fileId] += c; + } + + ++fileId; + } + else if (length == 0x80) + { + // Reserved + } + else if (length <= 0xFF) + { + length -= 0x80; + string directoryName; + + for (uint8_t j = 0; j < length; ++j) + { + uint8_t c; + ifs.read(reinterpret_cast(&c), sizeof(uint8_t)); + + directoryName += c; + } + + uint16_t directoryId; + ifs.read(reinterpret_cast(&directoryId), sizeof(uint16_t)); + + fileNames.get()[directoryId] = directoryName; + } + else + { + return Cleanup(ifs, NarcError::InvalidFileNameTableEntryId); + } + } + } + + if ((ifs.tellg() % 4) != 0) + { + ifs.seekg(4 - (ifs.tellg() % 4), ios::cur); + } + + FileImages fi; + ifs.read(reinterpret_cast(&fi), sizeof(FileImages)); + + if (fi.Id != 0x46494D47) { return Cleanup(ifs, NarcError::InvalidFileImagesId); } + + filesystem::create_directory(directory); + filesystem::current_path(directory); + + if (fnt.ChunkSize == 0x10) + { + for (uint16_t i = 0; i < fat.FileCount; ++i) + { + ifs.seekg(static_cast(header.ChunkSize) + fat.ChunkSize + fnt.ChunkSize + 8 + fatEntries.get()[i].Start); + + unique_ptr buffer = make_unique(fatEntries.get()[i].End - fatEntries.get()[i].Start); + ifs.read(buffer.get(), fatEntries.get()[i].End - fatEntries.get()[i].Start); + + ostringstream oss; + oss << fileName.stem().string() << "_" << setfill('0') << setw(8) << i << ".bin"; + + ofstream ofs(oss.str(), ios::binary); + + if (!ofs.good()) + { + ofs.close(); + + return Cleanup(ifs, NarcError::InvalidOutputFile); + } + + ofs.write(buffer.get(), fatEntries.get()[i].End - fatEntries.get()[i].Start); + ofs.close(); + } + } + else + { + filesystem::path absolutePath = filesystem::absolute(filesystem::current_path()); + + for (size_t i = 0; i < fntEntries.size(); ++i) + { + filesystem::current_path(absolutePath); + stack directories; + + for (uint16_t j = fntEntries[i].Utility; j > 0xF000; j = fntEntries[j - 0xF000].Utility) + { + directories.push(fileNames.get()[j]); + } + + for (; !directories.empty(); directories.pop()) + { + filesystem::create_directory(directories.top()); + filesystem::current_path(directories.top()); + } + + if (fntEntries[i].Utility >= 0xF000) + { + filesystem::create_directory(fileNames.get()[0xF000 + i]); + filesystem::current_path(fileNames.get()[0xF000 + i]); + } + + ifs.seekg(static_cast(header.ChunkSize) + fat.ChunkSize + sizeof(FileNameTable) + fntEntries[i].Offset); + + uint16_t fileId = 0x0000; + + for (uint8_t length = 0x80; length != 0x00; ifs.read(reinterpret_cast(&length), sizeof(uint8_t))) + { + if (length <= 0x7F) + { + streampos savedPosition = ifs.tellg(); + + ifs.seekg(static_cast(header.ChunkSize) + fat.ChunkSize + fnt.ChunkSize + 8 + fatEntries.get()[fntEntries[i].FirstFileId + fileId].Start); + + unique_ptr buffer = make_unique(fatEntries.get()[fntEntries[i].FirstFileId + fileId].End - fatEntries.get()[fntEntries[i].FirstFileId + fileId].Start); + ifs.read(buffer.get(), fatEntries.get()[fntEntries[i].FirstFileId + fileId].End - fatEntries.get()[fntEntries[i].FirstFileId + fileId].Start); + + ofstream ofs(fileNames.get()[fntEntries[i].FirstFileId + fileId], ios::binary); + + if (!ofs.good()) + { + ofs.close(); + + return Cleanup(ifs, NarcError::InvalidOutputFile); + } + + ofs.write(buffer.get(), fatEntries.get()[fntEntries[i].FirstFileId + fileId].End - fatEntries.get()[fntEntries[i].FirstFileId + fileId].Start); + ofs.close(); + + ifs.seekg(savedPosition); + ifs.seekg(length, ios::cur); + + ++fileId; + } + else if (length == 0x80) + { + // Reserved + } + else if (length <= 0xFF) + { + ifs.seekg(static_cast(length) - 0x80 + 0x2, ios::cur); + } + else + { + return Cleanup(ifs, NarcError::InvalidFileNameTableEntryId); + } + } + } + } + + ifs.close(); + + return error == NarcError::None; +} diff --git a/src/knarc/Narc.h b/src/knarc/Narc.h new file mode 100644 index 0000000..d9de52b --- /dev/null +++ b/src/knarc/Narc.h @@ -0,0 +1,86 @@ +#pragma once + +#include +#include +#include +#include +#include + +enum class NarcError +{ + None, + InvalidInputFile, + InvalidHeaderId, + InvalidByteOrderMark, + InvalidVersion, + InvalidHeaderSize, + InvalidChunkCount, + InvalidFileAllocationTableId, + InvalidFileAllocationTableReserved, + InvalidFileNameTableId, + InvalidFileNameTableEntryId, + InvalidFileImagesId, + InvalidOutputFile +}; + +struct Header +{ + uint32_t Id; + uint16_t ByteOrderMark; + uint16_t Version; + uint32_t FileSize; + uint16_t ChunkSize; + uint16_t ChunkCount; +}; + +struct FileAllocationTable +{ + uint32_t Id; + uint32_t ChunkSize; + uint16_t FileCount; + uint16_t Reserved; +}; + +struct FileAllocationTableEntry +{ + uint32_t Start; + uint32_t End; +}; + +struct FileNameTable +{ + uint32_t Id; + uint32_t ChunkSize; +}; + +struct FileNameTableEntry +{ + uint32_t Offset; + uint16_t FirstFileId; + uint16_t Utility; +}; + +struct FileImages +{ + uint32_t Id; + uint32_t ChunkSize; +}; + +class Narc +{ + public: + NarcError GetError() const; + + bool Pack(const std::filesystem::path& fileName, const std::filesystem::path& directory); + bool Unpack(const std::filesystem::path& fileName, const std::filesystem::path& directory); + + private: + NarcError error = NarcError::None; + + void AlignDword(std::ofstream& ofs, uint8_t paddingChar); + + bool Cleanup(std::ifstream& ifs, const NarcError& e); + bool Cleanup(std::ofstream& ofs, const NarcError& e); + + std::vector OrderedDirectoryIterator(const std::filesystem::path& path, bool recursive) const; +}; diff --git a/src/knarc/Source.cpp b/src/knarc/Source.cpp new file mode 100644 index 0000000..3678997 --- /dev/null +++ b/src/knarc/Source.cpp @@ -0,0 +1,109 @@ +#include +#include +#include +#include + +#include "Narc.h" + +using namespace std; + +void PrintError(NarcError error) +{ + switch (error) + { + case NarcError::None: cout << "ERROR: No error???" << endl; break; + case NarcError::InvalidInputFile: cout << "ERROR: Invalid input file" << endl; break; + case NarcError::InvalidHeaderId: cout << "ERROR: Invalid header ID" << endl; break; + case NarcError::InvalidByteOrderMark: cout << "ERROR: Invalid byte order mark" << endl; break; + case NarcError::InvalidVersion: cout << "ERROR: Invalid NARC version" << endl; break; + case NarcError::InvalidHeaderSize: cout << "ERROR: Invalid header size" << endl; break; + case NarcError::InvalidChunkCount: cout << "ERROR: Invalid chunk count" << endl; break; + case NarcError::InvalidFileAllocationTableId: cout << "ERROR: Invalid file allocation table ID" << endl; break; + case NarcError::InvalidFileAllocationTableReserved: cout << "ERROR: Invalid file allocation table reserved section" << endl; break; + case NarcError::InvalidFileNameTableId: cout << "ERROR: Invalid file name table ID" << endl; break; + case NarcError::InvalidFileNameTableEntryId: cout << "ERROR: Invalid file name table entry ID" << endl; break; + case NarcError::InvalidFileImagesId: cout << "ERROR: Invalid file images ID" << endl; break; + case NarcError::InvalidOutputFile: cout << "ERROR: Invalid output file" << endl; break; + default: cout << "ERROR: Unknown error???" << endl; break; + } +} + +int main(int argc, char* argv[]) +{ + if (argc != 5) + { + cout << "OVERVIEW: Knarc" << endl << endl; + cout << "USAGE: knarc [options] " << endl << endl; + cout << "OPTIONS:" << endl; + cout << "\t-d\tDirectory to pack from/unpack to" << endl; + cout << "\t-p\tPack" << endl; + cout << "\t-u\tUnpack" << endl; + + return 1; + } + + string directory = ""; + string fileName = ""; + bool pack = false; + + for (int i = 1; i < argc; ++i) + { + if (!strcmp(argv[i], "-d")) + { + if (i == (argc - 1)) + { + cerr << "ERROR: No directory specified" << endl; + + return 1; + } + + directory = argv[++i]; + } + else if (!strcmp(argv[i], "-p")) + { + if (i == (argc - 1)) + { + cerr << "ERROR: No NARC specified to pack to" << endl; + + return 1; + } + + fileName = argv[++i]; + pack = true; + } + else if (!strcmp(argv[i], "-u")) + { + if (i == (argc - 1)) + { + cerr << "ERROR: No NARC specified to unpack from" << endl; + + return 1; + } + + fileName = argv[++i]; + } + } + + Narc narc; + + if (pack) + { + if (!narc.Pack(fileName, directory)) + { + PrintError(narc.GetError()); + + return 1; + } + } + else + { + if (!narc.Unpack(fileName, directory)) + { + PrintError(narc.GetError()); + + return 1; + } + } + + return 0; +} diff --git a/src/knarc/make.bat b/src/knarc/make.bat new file mode 100755 index 0000000..efa36ce --- /dev/null +++ b/src/knarc/make.bat @@ -0,0 +1 @@ +g++ -std=c++17 -Wno-narrowing -g Narc.cpp Source.cpp -o ../../tools/knarc.exe \ No newline at end of file diff --git a/src/ndstool/README.md b/src/ndstool/README.md new file mode 100644 index 0000000..71d8312 --- /dev/null +++ b/src/ndstool/README.md @@ -0,0 +1 @@ +ndstool can be installed with devkitPro pacman. Then use the script to copy it to the tools folder. \ No newline at end of file diff --git a/src/ndstool/copy.sh b/src/ndstool/copy.sh new file mode 100755 index 0000000..3edeccb --- /dev/null +++ b/src/ndstool/copy.sh @@ -0,0 +1,2 @@ +LOC=`which ndstool` +cp $LOC ../../tools/ndstool.exe \ No newline at end of file From a1b0cad126f9926885410913c14f0591e9fd0c3e Mon Sep 17 00:00:00 2001 From: NathanFallet Date: Thu, 23 Mar 2023 20:00:09 +0100 Subject: [PATCH 2/2] Same for BW2 --- src/Gen5/RomHandler.cpp | 3 ++- src/Gen5/RomHandler.h | 6 ++++++ src/Gen5/make.bat | 3 +-- 3 files changed, 9 insertions(+), 3 deletions(-) mode change 100644 => 100755 src/Gen5/make.bat diff --git a/src/Gen5/RomHandler.cpp b/src/Gen5/RomHandler.cpp index 5edf2df..da072fe 100644 --- a/src/Gen5/RomHandler.cpp +++ b/src/Gen5/RomHandler.cpp @@ -2,6 +2,7 @@ #include #include #include +#include using namespace std; using std::filesystem::directory_iterator; @@ -120,7 +121,7 @@ inline void CopyFiles(string narc_path, string temp_path, string data_path) { string command = string(KNARC) + " -d " + temp_path + " -u " + narc_path; system(command.c_str()); - command = "copy " + string(data_path); + command = string(COPY_CMD) + " " + string(data_path); command += " " + temp_path; system(command.c_str()); diff --git a/src/Gen5/RomHandler.h b/src/Gen5/RomHandler.h index 9ab6de0..71ab10d 100644 --- a/src/Gen5/RomHandler.h +++ b/src/Gen5/RomHandler.h @@ -24,6 +24,12 @@ using std::string; #define OW_SPRITES "files/bw2_changes/ow_sprites" #define MUGSHOTS "files/bw2_changes/mugshots" +#ifdef __CYGWIN__ +#define COPY_CMD "copy" +#else +#define COPY_CMD "cp -r" +#endif + //Season consts enum Seasons { SEASON_SPRING, diff --git a/src/Gen5/make.bat b/src/Gen5/make.bat old mode 100644 new mode 100755 index 9536344..4d8cbff --- a/src/Gen5/make.bat +++ b/src/Gen5/make.bat @@ -1,2 +1 @@ -g++ -g main.cpp RomHandler.cpp Randomizer.cpp -o ../../map_randomizer_bw2.exe -pause \ No newline at end of file +g++ -std=c++17 -g main.cpp RomHandler.cpp Randomizer.cpp -o ../../map_randomizer_bw2.exe \ No newline at end of file