summaryrefslogtreecommitdiff
path: root/tools/src
diff options
context:
space:
mode:
authorHenny022p <info@henny022.de>2022-01-31 17:30:51 +0100
committerHenny022p <info@henny022.de>2022-01-31 17:30:51 +0100
commit2a53ee4bf2ee7eea617280118c2188bffec77ae5 (patch)
tree85ed631ab330f9ca3734bb77253a374e3ef0de21 /tools/src
parent9b60d9c5e24ed592524c25a9dc013417d6e7ee0c (diff)
parent2b8d880ffc0a4548d25dc613d53a908d36362d84 (diff)
merge master
Diffstat (limited to 'tools/src')
-rw-r--r--tools/src/CMakeLists.txt13
-rw-r--r--tools/src/agb2mid/CMakeLists.txt6
-rwxr-xr-xtools/src/agb2mid/LICENSE19
-rwxr-xr-xtools/src/agb2mid/agb.cpp728
-rwxr-xr-xtools/src/agb2mid/agb.h36
-rwxr-xr-xtools/src/agb2mid/error.cpp35
-rwxr-xr-xtools/src/agb2mid/error.h26
-rwxr-xr-xtools/src/agb2mid/main.cpp294
-rwxr-xr-xtools/src/agb2mid/main.h51
-rwxr-xr-xtools/src/agb2mid/midi.cpp375
-rwxr-xr-xtools/src/agb2mid/midi.h78
-rwxr-xr-xtools/src/agb2mid/tables.cpp267
-rwxr-xr-xtools/src/agb2mid/tables.h29
-rw-r--r--tools/src/aif2pcm/CMakeLists.txt7
-rwxr-xr-xtools/src/aif2pcm/LICENSE20
-rw-r--r--tools/src/aif2pcm/extended.c164
-rw-r--r--tools/src/aif2pcm/main.c799
-rw-r--r--tools/src/asset_processor/CMakeLists.txt8
-rw-r--r--tools/src/asset_processor/assets/aif.cpp27
-rw-r--r--tools/src/asset_processor/assets/aif.h11
-rw-r--r--tools/src/asset_processor/assets/animation.cpp32
-rw-r--r--tools/src/asset_processor/assets/animation.h7
-rw-r--r--tools/src/asset_processor/assets/asset.cpp7
-rw-r--r--tools/src/asset_processor/assets/asset.h79
-rw-r--r--tools/src/asset_processor/assets/exitlist.cpp43
-rw-r--r--tools/src/asset_processor/assets/exitlist.h7
-rw-r--r--tools/src/asset_processor/assets/frameobjlists.cpp75
-rw-r--r--tools/src/asset_processor/assets/frameobjlists.h7
-rw-r--r--tools/src/asset_processor/assets/gfx.cpp38
-rw-r--r--tools/src/asset_processor/assets/gfx.h11
-rw-r--r--tools/src/asset_processor/assets/macroasm.cpp20
-rw-r--r--tools/src/asset_processor/assets/macroasm.h17
-rw-r--r--tools/src/asset_processor/assets/midi.cpp147
-rw-r--r--tools/src/asset_processor/assets/midi.h14
-rw-r--r--tools/src/asset_processor/assets/palette.cpp27
-rw-r--r--tools/src/asset_processor/assets/palette.h11
-rw-r--r--tools/src/asset_processor/assets/spriteframe.cpp20
-rw-r--r--tools/src/asset_processor/assets/spriteframe.h7
-rw-r--r--tools/src/asset_processor/assets/tileset.cpp33
-rw-r--r--tools/src/asset_processor/assets/tileset.h11
-rw-r--r--tools/src/asset_processor/main.cpp342
-rw-r--r--tools/src/asset_processor/main.h24
-rw-r--r--tools/src/asset_processor/offsets.cpp9
-rw-r--r--tools/src/asset_processor/offsets.h25
-rw-r--r--tools/src/asset_processor/reader.h38
-rw-r--r--tools/src/asset_processor/util.cpp28
-rw-r--r--tools/src/asset_processor/util.h15
-rw-r--r--tools/src/bin2c/CMakeLists.txt6
-rwxr-xr-xtools/src/bin2c/LICENSE19
-rw-r--r--tools/src/bin2c/bin2c.c173
-rw-r--r--tools/src/gbafix/CMakeLists.txt6
-rwxr-xr-xtools/src/gbafix/COPYING674
-rwxr-xr-xtools/src/gbafix/Makefile13
-rw-r--r--tools/src/gbafix/elf.h2913
-rw-r--r--tools/src/gbafix/gbafix.c333
-rw-r--r--tools/src/gbagfx/CMakeLists.txt12
-rwxr-xr-xtools/src/gbagfx/LICENSE19
-rw-r--r--tools/src/gbagfx/convert_png.c243
-rw-r--r--tools/src/gbagfx/convert_png.h12
-rw-r--r--tools/src/gbagfx/font.c315
-rw-r--r--tools/src/gbagfx/font.h16
-rw-r--r--tools/src/gbagfx/gfx.c358
-rw-r--r--tools/src/gbagfx/gfx.h38
-rw-r--r--tools/src/gbagfx/global.h31
-rw-r--r--tools/src/gbagfx/huff.c398
-rw-r--r--tools/src/gbagfx/huff.h38
-rw-r--r--tools/src/gbagfx/jasc_pal.c165
-rw-r--r--tools/src/gbagfx/jasc_pal.h9
-rw-r--r--tools/src/gbagfx/link.pal19
-rw-r--r--tools/src/gbagfx/lz.c150
-rw-r--r--tools/src/gbagfx/lz.h9
-rw-r--r--tools/src/gbagfx/main.c478
-rw-r--r--tools/src/gbagfx/options.h24
-rw-r--r--tools/src/gbagfx/rl.c134
-rw-r--r--tools/src/gbagfx/rl.h9
-rw-r--r--tools/src/gbagfx/util.c119
-rw-r--r--tools/src/gbagfx/util.h14
-rw-r--r--tools/src/mid2agb/CMakeLists.txt6
-rwxr-xr-xtools/src/mid2agb/LICENSE19
-rw-r--r--tools/src/mid2agb/agb.cpp510
-rwxr-xr-xtools/src/mid2agb/agb.h33
-rw-r--r--tools/src/mid2agb/error.cpp35
-rwxr-xr-xtools/src/mid2agb/error.h26
-rw-r--r--tools/src/mid2agb/main.cpp215
-rwxr-xr-xtools/src/mid2agb/main.h39
-rw-r--r--tools/src/mid2agb/midi.cpp880
-rw-r--r--tools/src/mid2agb/midi.h75
-rw-r--r--tools/src/mid2agb/tables.cpp262
-rwxr-xr-xtools/src/mid2agb/tables.h29
-rw-r--r--tools/src/preproc/CMakeLists.txt6
-rwxr-xr-xtools/src/preproc/LICENSE19
-rw-r--r--tools/src/preproc/asm_file.cpp439
-rw-r--r--tools/src/preproc/asm_file.h65
-rw-r--r--tools/src/preproc/c_file.cpp377
-rw-r--r--tools/src/preproc/c_file.h57
-rw-r--r--tools/src/preproc/char_util.h61
-rw-r--r--tools/src/preproc/charmap.cpp356
-rw-r--r--tools/src/preproc/charmap.h61
-rw-r--r--tools/src/preproc/preproc.cpp141
-rw-r--r--tools/src/preproc/preproc.h53
-rw-r--r--tools/src/preproc/string_parser.cpp314
-rw-r--r--tools/src/preproc/string_parser.h54
-rw-r--r--tools/src/preproc/utf8.cpp94
-rw-r--r--tools/src/preproc/utf8.h33
-rw-r--r--tools/src/scaninc/CMakeLists.txt7
-rwxr-xr-xtools/src/scaninc/LICENSE19
-rw-r--r--tools/src/scaninc/asm_file.cpp165
-rw-r--r--tools/src/scaninc/asm_file.h105
-rw-r--r--tools/src/scaninc/c_file.cpp264
-rw-r--r--tools/src/scaninc/c_file.h60
-rw-r--r--tools/src/scaninc/scaninc.cpp113
-rw-r--r--tools/src/scaninc/scaninc.h38
-rw-r--r--tools/src/scaninc/source_file.cpp108
-rw-r--r--tools/src/scaninc/source_file.h62
-rw-r--r--tools/src/tmc_strings/CMakeLists.txt6
-rw-r--r--tools/src/tmc_strings/main.cpp614
-rw-r--r--tools/src/util/CMakeLists.txt5
-rw-r--r--tools/src/util/dummy.cpp5
-rw-r--r--tools/src/util/util/file.h25
-rw-r--r--tools/src/util/util/types.h15
120 files changed, 16704 insertions, 0 deletions
diff --git a/tools/src/CMakeLists.txt b/tools/src/CMakeLists.txt
new file mode 100644
index 00000000..2b3043d4
--- /dev/null
+++ b/tools/src/CMakeLists.txt
@@ -0,0 +1,13 @@
+# libraries
+add_subdirectory(util)
+# binaries
+add_subdirectory(agb2mid)
+add_subdirectory(aif2pcm)
+add_subdirectory(asset_processor)
+add_subdirectory(bin2c)
+add_subdirectory(gbafix)
+add_subdirectory(gbagfx)
+add_subdirectory(mid2agb)
+add_subdirectory(preproc)
+add_subdirectory(scaninc)
+add_subdirectory(tmc_strings)
diff --git a/tools/src/agb2mid/CMakeLists.txt b/tools/src/agb2mid/CMakeLists.txt
new file mode 100644
index 00000000..e1bbdc8f
--- /dev/null
+++ b/tools/src/agb2mid/CMakeLists.txt
@@ -0,0 +1,6 @@
+file(GLOB_RECURSE sources *.cpp)
+
+add_executable(agb2mid ${sources})
+target_include_directories(agb2mid PRIVATE .)
+
+install(TARGETS agb2mid RUNTIME DESTINATION bin)
diff --git a/tools/src/agb2mid/LICENSE b/tools/src/agb2mid/LICENSE
new file mode 100755
index 00000000..534d1534
--- /dev/null
+++ b/tools/src/agb2mid/LICENSE
@@ -0,0 +1,19 @@
+Copyright (c) 2016 YamaArashi
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in
+all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE.
diff --git a/tools/src/agb2mid/agb.cpp b/tools/src/agb2mid/agb.cpp
new file mode 100755
index 00000000..ce4b946d
--- /dev/null
+++ b/tools/src/agb2mid/agb.cpp
@@ -0,0 +1,728 @@
+// Copyright(c) 2016 YamaArashi
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+
+#include <cstdio>
+#include <cstdarg>
+#include <cstring>
+#include <vector>
+#include <cmath>
+#include <iostream>
+#include "agb.h"
+#include "main.h"
+#include "midi.h"
+#include "tables.h"
+//////// ------
+
+// TODO move functions to correct files
+std::uint32_t ReadInt8();
+std::uint32_t lReadInt32() {
+ std::uint32_t val = 0;
+ val |= ReadInt8();
+ val |= ReadInt8() << 8;
+ val |= ReadInt8() << 16;
+ val |= ReadInt8() << 24;
+ return val;
+}
+void Seek(long offset);
+
+std::vector<std::uint32_t> trackPointers;
+
+const unsigned int REDUCE_POINTERS = 0x8000000;
+
+extern int g_fileStartOffset; // TODO move to header
+
+void ReadAgbSong() {
+ Seek(g_fileStartOffset);
+
+ int numTracks = ReadInt8();
+ int numBlocks = ReadInt8();
+ int priority = ReadInt8();
+ int reverb = ReadInt8();
+ int sound = lReadInt32();
+ for (int i = 0; i < numTracks; i++) {
+ trackPointers.push_back(lReadInt32());
+ if (g_verbose)
+ std::printf("Track: %X\n", trackPointers.back());
+ }
+ if (g_verbose) {
+ std::printf("numTracks %d\n", numTracks);
+ std::printf("numBlocks %d\n", numBlocks);
+ std::printf("priority %d\n", priority);
+ std::printf("reverb %d\n", reverb);
+ std::printf("sound %X\n", sound);
+ }
+}
+const int FINE = 0xb1;
+const int GOTO = 0xb2;
+const int PATT = 0xb3;
+const int PEND = 0xb4;
+const int TEMPO = 0xbb;
+const int KEYSH = 0xBC;
+const int VOICE = 0xbd;
+const int VOL = 0xbe;
+const int PAN = 0xbf;
+const int BEND = 0xc0;
+const int BENDR = 0xc1;
+const int LFOS = 0xc2;
+const int MOD = 0xc4;
+const int TUNE = 0xc8;
+const int XCMD = 0xcd;
+const int EOT = 0xce;
+const int TIE = 0xcf;
+const int MODT = 0xc5;
+const int LFODL = 0xc3;
+
+std::vector<std::vector<Event>> trackEvents;
+std::vector<Event> metaEvents;
+
+int convertTime(int time) {
+ return ((float)time * g_midiTimeDiv) / (24 * g_clocksPerBeat);
+}
+
+extern void Skip(long offset);
+
+std::uint32_t GetCurrentPtr() {
+ return std::ftell(g_inputFile);
+}
+
+std::uint32_t PtrToFileAddr(std::uint32_t ptr) {
+ return ptr - REDUCE_POINTERS;
+}
+
+void insertAtCorrectTimeFromEnd(std::vector<Event>& events, const Event& event) {
+ for (auto it = events.rbegin(); it != events.rend(); it++) {
+ if ((*it).time <= event.time) {
+ events.insert(it.base(), event);
+ return;
+ }
+ }
+ events.insert(events.begin(), event);
+}
+
+void insertAtCorrectTimeFromStart(std::vector<Event>& events, const Event& event) {
+ for (auto it = events.begin(); it != events.end(); it++) {
+ if ((*it).time >= event.time) {
+ events.insert(it, event);
+ return;
+ }
+ }
+ events.push_back(event);
+}
+
+void ReadAgbTracks() {
+ size_t count = 0;
+ int addedPadding = 8;
+
+ // TODO configurable???
+ g_midiTimeDiv = 24;
+
+ // Add initial time signature (only one irrespective of how many tracks there are)
+ // TODO TimeSignature events in original midi were converted by changing waits? (InsertTimingEvents?)
+ Event event;
+ event.type = EventType::TimeSignature;
+ event.param1 = g_nominator;
+ event.param2 = g_denominatorExp;
+ metaEvents.push_back(event);
+
+ for (uint32_t trackPointer : trackPointers) {
+
+ std::vector<Event> events;
+ count++;
+ if (g_verbose)
+ std::printf("\n\ntrackPointer: %X -> %X %ld\n", trackPointer, trackPointer - REDUCE_POINTERS, count);
+
+ // Search for loop in this trac
+ bool hasLoop = false;
+ std::uint32_t loopAddress = 0;
+ bool foundLoop = false;
+ int loopStartTime = 0;
+
+ unsigned int trackEnd =
+ (trackPointers.size() > count
+ ? trackPointers[count]
+ : (g_fileStartOffset + REDUCE_POINTERS)); // Use offset to header as end for last track
+ if (g_verbose)
+ std::printf("End of track: %X\n", trackEnd);
+ Seek(trackEnd - REDUCE_POINTERS);
+
+ // search for a few bytes whether there is a loop end
+ for (int i = 5; i < 10 + addedPadding; i++) {
+ if (trackEnd - i < trackPointer) {
+ // Ignore GOTOs from the previous track.
+ continue;
+ }
+ Seek(trackEnd - REDUCE_POINTERS - i);
+ if (ReadInt8() == GOTO) {
+ if (g_verbose)
+ std::printf("Has loop: %d\n", i);
+ loopAddress = lReadInt32() - REDUCE_POINTERS;
+ if (loopAddress > 0x1000000) {
+ // The 0xB1 was probably part of the pointer or something.
+ continue;
+ }
+ hasLoop = true;
+ if (g_verbose) {
+ std::printf("Addr: %X\n", GetCurrentPtr());
+ std::printf("Addr: %X\n", loopAddress);
+ }
+ break;
+ }
+ }
+ if (g_verbose)
+ std::printf("Has loop: %d, %X\n", hasLoop, loopAddress);
+
+ // Read all track events
+ Seek(trackPointer - REDUCE_POINTERS);
+
+ int type = ReadInt8();
+
+ // VOL?
+ if (type == VOL) {
+ int val = ReadInt8();
+
+ if (g_verbose)
+ std::printf("ignore vol: %X\n", val);
+ type = ReadInt8();
+ }
+
+ int keyShift = 0;
+
+ // WAIT?
+ // KEYSH
+ if (type == KEYSH) {
+ keyShift = ReadInt8();
+ if (g_verbose)
+ std::printf("KEYSH: %d\n", keyShift);
+ // TODO initial shift is always 0? Or is this set as the whole key shift?
+ } else {
+ std::printf("Error: initial type %X not implemented\n", type);
+ return;
+ }
+
+ std::vector<Event> currentlyPlayingNotes;
+
+ bool endOfTrack = false;
+ int lastCommand = -1;
+ int lastNote = -1;
+ int lastVelocity = -1;
+ int currentTime = 0;
+ int loopEndTime = -1;
+
+ int patternDuration = 0;
+
+ type = ReadInt8();
+
+ // Did we jump into a pattern
+ bool inPattern = false;
+ int returnPtr = 0;
+
+ while (!endOfTrack) {
+ if (hasLoop && !foundLoop && GetCurrentPtr() - 1 == loopAddress) {
+ if (g_verbose)
+ std::printf("<<<< inserted loop start\n");
+ // foundLoop = true;
+ loopStartTime = currentTime;
+ Event event;
+ event.time = loopStartTime;
+ event.type = EventType::LoopBegin;
+ events.push_back(event);
+ }
+
+ if (g_verbose)
+ std::printf("%X|@%d\t ", GetCurrentPtr() - 1, currentTime);
+ if (type < 0x80) {
+ // Repeat last command
+ Skip(-1);
+ type = lastCommand;
+ if (g_verbose)
+ std::printf("(repeat cmd) ");
+ } else {
+ if (type > 0xb4 && type != 0xbb) { // type is no repeatable command (not WAIT, GOTO, TEMPO etc.)
+ lastCommand = type;
+ }
+ }
+
+ switch (type) {
+ case TEMPO: {
+ int val = ReadInt8();
+ if (g_verbose)
+ std::printf("TEMPO: %d\n", val * 2);
+
+ Event event;
+ event.time = currentTime;
+ event.type = EventType::Tempo;
+ event.param2 = 60000000 / (val * 2);
+ metaEvents.push_back(event);
+
+ break;
+ }
+ case VOICE: {
+ int val = ReadInt8();
+ if (g_verbose)
+ std::printf("VOICE: %d\n", val);
+ Event event;
+ event.time = currentTime;
+ event.type = EventType::InstrumentChange;
+ event.param1 = val;
+ events.push_back(event);
+ break;
+ }
+ case VOL: {
+ const int MVL = 128;
+ const int MXV = 0x7F;
+
+ int val = ReadInt8();
+ if (g_verbose)
+ std::printf("VOL: %d\n", val);
+ Event event;
+ event.time = currentTime;
+ event.type = EventType::Controller;
+ event.param1 = 0x7;
+ event.param2 = std::ceil((float)val * MXV / MVL);
+ events.push_back(event);
+ break;
+ }
+ case PAN: {
+ int val = ReadInt8();
+ if (g_verbose)
+ std::printf("PAN: %X\n", val);
+
+ event.time = currentTime;
+ event.type = EventType::Controller;
+ event.param1 = 0xa;
+ event.param2 = val;
+ events.push_back(event);
+ break;
+ }
+ case LFOS: {
+ int val = ReadInt8();
+ if (g_verbose)
+ std::printf("LFOS: %X\n", val);
+ event.time = currentTime;
+ event.type = EventType::Controller;
+ event.param1 = 0x15;
+ event.param2 = val;
+ events.push_back(event);
+ break;
+ }
+ case MOD: {
+ int val = ReadInt8();
+ if (g_verbose)
+ std::printf("MOD: %X\n", val);
+
+ event.time = currentTime;
+ event.type = EventType::Controller;
+ event.param1 = 0x1;
+ event.param2 = val;
+ events.push_back(event);
+ break;
+ }
+ case PEND: {
+ if (inPattern) {
+ if (g_verbose) {
+ std::printf("P--END\n");
+ std::printf(">>>>>> Returning to %X (duration: %d)\n", returnPtr, patternDuration);
+ }
+ inPattern = false;
+ Seek(returnPtr);
+ } else {
+ if (g_verbose)
+ std::printf("PEND\n");
+ }
+ break;
+ }
+ case PATT: {
+ std::uint32_t val = lReadInt32();
+ if (g_verbose) {
+ std::printf("PATT\n");
+ std::printf(">>>>>> Jumping to: %X\n", val);
+ }
+ patternDuration = 0;
+ returnPtr = GetCurrentPtr();
+ inPattern = true;
+
+ Seek(PtrToFileAddr(val));
+ break;
+ }
+ case GOTO: {
+ int val = lReadInt32();
+ if (g_verbose)
+ std::printf("GOTO: %X\n", val);
+
+ // Already handled
+ loopEndTime = currentTime;
+ break;
+ }
+ case EOT: {
+ bool lastNoteUsed = false;
+ int note = ReadInt8();
+ if (note < 0x80) { // a valid note
+ type = ReadInt8();
+ } else {
+ type = note;
+ note = lastNote;
+ lastNoteUsed = true;
+ }
+
+ char noteBuf[16];
+ if (note >= 24)
+ std::snprintf(noteBuf, sizeof(noteBuf), g_noteTable[note % 12], note / 12 - 2);
+ else
+ std::snprintf(noteBuf, sizeof(noteBuf), g_minusNoteTable[note % 12], note / -12 + 2);
+
+ if (lastNoteUsed) {
+ if (g_verbose)
+ std::printf("END Tie: (%s) \n", noteBuf);
+ } else {
+ if (g_verbose)
+ std::printf("END Tie: %s\n", noteBuf);
+ }
+
+ Event event;
+ event.time = currentTime;
+ event.type = EventType::NoteOff;
+ event.note = note + keyShift;
+ events.push_back(event);
+ continue; // Next type was already read
+ }
+ case TIE: {
+ int note = ReadInt8();
+ int velocity = 0;
+ bool lastNoteUsed = false;
+ bool lastVelocityUsed = false;
+ if (note < 0x80) { // a valid note
+ lastNote = note;
+ velocity = ReadInt8();
+ if (velocity < 0x80) { // a valid velocity
+ lastVelocity = velocity;
+ type = ReadInt8();
+ } else {
+ type = velocity;
+ velocity = lastVelocity;
+ lastVelocityUsed = true;
+ }
+ } else {
+ type = note;
+ note = lastNote;
+ velocity = lastVelocity;
+ lastNoteUsed = true;
+ lastVelocityUsed = true;
+ }
+
+ char noteBuf[16];
+ if (note >= 24)
+ std::snprintf(noteBuf, sizeof(noteBuf), g_noteTable[note % 12], note / 12 - 2);
+ else
+ std::snprintf(noteBuf, sizeof(noteBuf), g_minusNoteTable[note % 12], note / -12 + 2);
+
+ if (lastNoteUsed) {
+ if (lastVelocityUsed) {
+ if (g_verbose)
+ std::printf("TIE: (%s) (v%d)\n", noteBuf, velocity);
+ } else {
+ if (g_verbose)
+ std::printf("TIE: (%s) v%d\n", noteBuf, velocity);
+ }
+ } else {
+ if (lastVelocityUsed) {
+ if (g_verbose)
+ std::printf("TIE: %s(v%d)\n", noteBuf, velocity);
+ } else {
+ if (g_verbose)
+ std::printf("TIE: %sv%d\n", noteBuf, velocity);
+ }
+ }
+
+ Event event;
+ event.time = currentTime;
+ event.type = EventType::Note;
+ event.note = note + keyShift;
+ event.param1 = velocity;
+ events.push_back(event);
+ continue; // Next type was already read
+ }
+ case BEND: {
+ int val = ReadInt8();
+ if (g_verbose)
+ std::printf("BEND %X\n", val);
+ Event event;
+ event.time = currentTime;
+ event.type = EventType::PitchBend;
+ event.param2 = val;
+ events.push_back(event);
+ break;
+ }
+ case BENDR: {
+ int val = ReadInt8();
+ if (g_verbose)
+ std::printf("BENDR %X\n", val);
+
+ event.time = currentTime;
+ event.type = EventType::Controller;
+ event.param1 = 0x14;
+ event.param2 = val;
+ events.push_back(event);
+ break;
+ }
+ case TUNE: {
+ int val = ReadInt8();
+ if (g_verbose)
+ std::printf("TUNE %X\n", val);
+
+ event.time = currentTime;
+ event.type = EventType::Controller;
+ event.param1 = 0x18;
+ event.param2 = val;
+ events.push_back(event);
+ break;
+ }
+ case MODT: {
+ int val = ReadInt8();
+ if (g_verbose)
+ std::printf("MODT %X\n", val);
+
+ event.time = currentTime;
+ event.type = EventType::Controller;
+ event.param1 = 0x16;
+ event.param2 = val;
+ events.push_back(event);
+ break;
+ }
+ case LFODL: {
+ int val = ReadInt8();
+ if (g_verbose)
+ std::printf("LFODL %X\n", val);
+
+ event.time = currentTime;
+ event.type = EventType::Controller;
+ event.param1 = 0x1A;
+ event.param2 = val;
+ events.push_back(event);
+ break;
+ }
+ case XCMD: {
+ // TODO does XCMD always have four params or can xIECV or xIECL stand alone?
+ int c1 = ReadInt8();
+ int v1 = ReadInt8();
+ int c2 = ReadInt8();
+ int v2 = ReadInt8();
+ if (g_verbose)
+ std::printf("XCMD %X %X %X %X\n", c1, v1, c2, v2);
+
+ Event event;
+ event.time = currentTime;
+ event.type = EventType::Extended;
+ event.note = 0x1d;
+ event.param1 = c1;
+ event.param2 = v1;
+ events.push_back(event);
+ event.note = 0x1f;
+ event.param1 = c2;
+ event.param2 = v2;
+ events.push_back(event);
+ break;
+ }
+ case KEYSH: {
+ int val = ReadInt8();
+ if (g_verbose)
+ std::printf("KEYSH: %d\n", val);
+ keyShift = val;
+ break;
+ }
+ case FINE: {
+ if (g_verbose)
+ std::printf("--- FINE ---\n");
+ endOfTrack = true;
+ break;
+ }
+ default:
+ if (type >= 0x80 && type <= 0x80 + 48) {
+ const int lenTbl[] = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16,
+ 17, 18, 19, 20, 21, 22, 23, 24, 28, 30, 32, 36, 40, 42, 44, 48, 52,
+ 54, 56, 60, 64, 66, 68, 72, 76, 78, 80, 84, 88, 90, 92, 96 };
+
+ int wait = lenTbl[type - 0x80];
+ if (g_verbose)
+ std::printf("WAIT: %d\n", wait);
+
+ patternDuration += wait;
+
+ if (!currentlyPlayingNotes.empty()) {
+ for (auto it = currentlyPlayingNotes.begin(); it != currentlyPlayingNotes.end();) {
+ Event& note = *it; // Modify the note in the list if we don't remove it now.
+ note.time -= wait;
+ if (note.time <= 0) {
+ note.time += currentTime + wait;
+ insertAtCorrectTimeFromEnd(events, note);
+ it = currentlyPlayingNotes.erase(it);
+ } else {
+ ++it;
+ }
+ }
+ }
+ currentTime += wait;
+ } else if (type >= 0xD0 && type <= 0xD0 + 47) {
+ // Handle note with a fixed length
+ int noteType = type;
+ int note = ReadInt8();
+ int velocity = 0;
+ int length = 0;
+ bool usedLastNote = false;
+ bool usedLastVelocity = false;
+ if (note < 0x80) { // a valid note
+ lastNote = note;
+ velocity = ReadInt8();
+ if (velocity < 0x80) { // a valid velocity
+ lastVelocity = velocity;
+ length = ReadInt8();
+ if (length < 0x80) { // a valid length
+ type = ReadInt8();
+ } else {
+ type = length;
+ length = 0;
+ }
+ } else {
+ type = velocity;
+ velocity = lastVelocity;
+ usedLastVelocity = true;
+ }
+ } else {
+ type = note;
+ note = lastNote;
+ velocity = lastVelocity;
+ usedLastNote = true;
+ usedLastVelocity = true;
+ }
+
+ Event event;
+ event.time = currentTime;
+ event.type = EventType::Note;
+ event.note = note + keyShift;
+ event.param1 = velocity;
+ // Length table for notes and rests // TODO move to tables.cpp
+ const int lenTbl[] = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16,
+ 17, 18, 19, 20, 21, 22, 23, 24, 28, 30, 32, 36, 40, 42, 44, 48, 52,
+ 54, 56, 60, 64, 66, 68, 72, 76, 78, 80, 84, 88, 90, 92, 96 };
+
+ int wait = lenTbl[noteType - 0xd0 + 1] + length;
+
+ // DEBUG
+ if (g_verbose) {
+ char noteBuf[16];
+
+ if (note >= 24)
+ std::snprintf(noteBuf, sizeof(noteBuf), g_noteTable[note % 12], note / 12 - 2);
+ else
+ std::snprintf(noteBuf, sizeof(noteBuf), g_minusNoteTable[note % 12], note / -12 + 2);
+
+ std::printf("NOTE: %d ", wait - length);
+ if (usedLastNote) {
+ std::printf("(%s) ", noteBuf);
+ } else {
+ std::printf("%s", noteBuf);
+ }
+ if (usedLastVelocity) {
+ std::printf("(v%03u)", velocity);
+ } else {
+ std::printf("v%03u", velocity);
+ }
+
+ if (length != 0) {
+ std::printf(" +l:%d", length);
+ }
+ std::printf("\n");
+ }
+ events.push_back(event);
+
+ event.type = EventType::NoteOff;
+ event.time = wait;
+ currentlyPlayingNotes.push_back(event);
+ continue; // Next type was already read
+ } else {
+ std::printf("ERROR: Unhandled type %X\n", type);
+ return;
+ }
+ break;
+ }
+
+ // TODO notes, waits,
+ type = ReadInt8();
+ }
+
+ if (currentlyPlayingNotes.size() > 0) {
+ if (g_verbose) {
+ std::printf("Need to finish %lu notes.\n", currentlyPlayingNotes.size());
+ std::cin.ignore();
+ }
+
+ int latestTime = currentTime;
+ for (auto& note : currentlyPlayingNotes) {
+ note.time += currentTime;
+ if (note.time > latestTime) {
+ latestTime = note.time;
+ }
+ insertAtCorrectTimeFromEnd(events, note);
+ }
+ if (g_verbose)
+ std::printf("Found notes from %d up to %d.\n", currentTime, latestTime);
+ currentTime = latestTime;
+ if (g_verbose)
+ std::cin.ignore();
+ }
+
+ if (hasLoop) {
+ Event event;
+ event.type = EventType::LoopEnd;
+ event.time = loopEndTime;
+ insertAtCorrectTimeFromEnd(events, event);
+ }
+
+ Event event;
+ event.type = EventType::EndOfTrack;
+ event.time = currentTime;
+ events.push_back(event);
+ if (g_verbose)
+ std::printf("END OF TRACK: %d\n", currentTime);
+
+ trackEvents.push_back(events);
+ }
+
+ // Insert manual time signature change event
+ for (const auto& change : timeSignatureChanges) {
+ bool inserted = false;
+ event.type = EventType::TimeSignature;
+ event.time = change.time;
+ event.param1 = change.nominator;
+ event.param2 = change.denominator;
+
+ for (size_t i = 0; i < metaEvents.size() - 1; i++) {
+ int prevTime = metaEvents[i].time;
+ int nextTime = metaEvents[i + 1].time;
+ if (event.time > prevTime && event.time <= nextTime) {
+ metaEvents.insert(metaEvents.begin() + i + 1, event);
+ inserted = true;
+ break;
+ }
+ }
+ if (!inserted) {
+ metaEvents.push_back(event);
+ }
+ }
+}
diff --git a/tools/src/agb2mid/agb.h b/tools/src/agb2mid/agb.h
new file mode 100755
index 00000000..6833168e
--- /dev/null
+++ b/tools/src/agb2mid/agb.h
@@ -0,0 +1,36 @@
+// Copyright(c) 2016 YamaArashi
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+
+#ifndef AGB_H
+#define AGB_H
+
+#include <vector>
+#include "midi.h"
+
+void ReadAgbSong();
+void ReadAgbTracks();
+
+extern int g_agbTrack;
+extern std::vector<std::vector<Event>> trackEvents;
+extern std::vector<Event> metaEvents;
+
+void insertAtCorrectTimeFromStart(std::vector<Event>& events, const Event& event);
+
+#endif // AGB_H
diff --git a/tools/src/agb2mid/error.cpp b/tools/src/agb2mid/error.cpp
new file mode 100755
index 00000000..d824ae86
--- /dev/null
+++ b/tools/src/agb2mid/error.cpp
@@ -0,0 +1,35 @@
+// Copyright(c) 2016 YamaArashi
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+
+#include <cstdio>
+#include <cstdlib>
+#include <cstdarg>
+
+// Reports an error diagnostic and terminates the program.
+[[noreturn]] void RaiseError(const char* format, ...) {
+ const int bufferSize = 1024;
+ char buffer[bufferSize];
+ std::va_list args;
+ va_start(args, format);
+ std::vsnprintf(buffer, bufferSize, format, args);
+ std::fprintf(stderr, "error: %s\n", buffer);
+ va_end(args);
+ std::exit(1);
+}
diff --git a/tools/src/agb2mid/error.h b/tools/src/agb2mid/error.h
new file mode 100755
index 00000000..da4f0114
--- /dev/null
+++ b/tools/src/agb2mid/error.h
@@ -0,0 +1,26 @@
+// Copyright(c) 2016 YamaArashi
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+
+#ifndef ERROR_H
+#define ERROR_H
+
+[[noreturn]] void RaiseError(const char* format, ...);
+
+#endif // ERROR_H
diff --git a/tools/src/agb2mid/main.cpp b/tools/src/agb2mid/main.cpp
new file mode 100755
index 00000000..8c057021
--- /dev/null
+++ b/tools/src/agb2mid/main.cpp
@@ -0,0 +1,294 @@
+// Copyright(c) 2016 YamaArashi
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+
+#include <cstdio>
+#include <cstdlib>
+#include <cstring>
+#include <cctype>
+#include <cassert>
+#include <string>
+#include <set>
+#include <vector>
+#include "main.h"
+#include "error.h"
+#include "midi.h"
+#include "agb.h"
+
+FILE* g_inputFile = nullptr;
+FILE* g_outputFile = nullptr;
+
+int g_fileStartOffset = 0;
+
+std::string g_asmLabel;
+int g_masterVolume = 127;
+int g_voiceGroup = 0;
+int g_priority = 0;
+int g_reverb = -1;
+int g_clocksPerBeat = 1;
+bool g_exactGateTime = false;
+bool g_compressionEnabled = true;
+int g_nominator = 4;
+int g_denominatorExp = 2;
+bool g_verbose = false;
+std::vector<TimeSignatureChange> timeSignatureChanges;
+
+[[noreturn]] static void PrintUsage() {
+ std::printf("Usage: AGB2MID baserom start_offset input_file output_file [options]\n"
+ "\n"
+ " baserom path to the baserom\n"
+ " start_offset offset to the sound header\n"
+ " input_file filename(.s) for AGB file\n"
+ " output_file filename(.mid) of MIDI file (default:input_file)\n"
+ "\n"
+ "options -L??? label for assembler (default:output_file)\n"
+ " -V??? master volume (default:127)\n"
+ " -G??? voice group number (default:0)\n"
+ " -P??? priority (default:0)\n"
+ " -R??? reverb (default:off)\n"
+ " -X 48 clocks/beat (default:24 clocks/beat)\n"
+ " -E exact gate-time\n"
+ " -N no compression\n"
+ " -n??? midi time nominator\n"
+ " -d??? midi time denominator\n"
+ " -t ? ? ? time signature change\n"
+ " -v verbose\n");
+ std::exit(1);
+}
+
+static std::string StripExtension(std::string s) {
+ std::size_t pos = s.find_last_of('.');
+
+ if (pos > 0 && pos != std::string::npos) {
+ s = s.substr(0, pos);
+ }
+
+ return s;
+}
+
+static std::string GetExtension(std::string s) {
+ std::size_t pos = s.find_last_of('.');
+
+ if (pos > 0 && pos != std::string::npos) {
+ return s.substr(pos + 1);
+ }
+ return "";
+}
+
+static std::string BaseName(std::string s) {
+ std::size_t posAfterSlash = s.find_last_of("/\\");
+
+ if (posAfterSlash == std::string::npos)
+ posAfterSlash = 0;
+ else
+ posAfterSlash++;
+
+ std::size_t dotPos = s.find_first_of('.', posAfterSlash);
+ if (dotPos > posAfterSlash && dotPos != std::string::npos)
+ s = s.substr(posAfterSlash, dotPos - posAfterSlash);
+
+ return s;
+}
+
+static const char* GetArgument(int argc, char** argv, int& index) {
+ assert(index >= 0 && index < argc);
+
+ const char* option = argv[index];
+
+ assert(option != nullptr);
+ assert(option[0] == '-');
+
+ // If there is text following the letter, return that.
+ if (std::strlen(option) >= 3)
+ return option + 2;
+
+ // Otherwise, try to get the next arg.
+ if (index + 1 < argc) {
+ index++;
+ return argv[index];
+ } else {
+ return nullptr;
+ }
+}
+
+int main(int argc, char** argv) {
+ std::string baseromPath;
+ std::string startOffset;
+ std::string inputFilename;
+ std::string outputFilename;
+
+ for (int i = 1; i < argc; i++) {
+ const char* option = argv[i];
+
+ if (option[0] == '-' && option[1] != '\0') {
+ const char* arg;
+
+ switch (option[1]) {
+ case 'E':
+ g_exactGateTime = true;
+ break;
+ case 'G':
+ arg = GetArgument(argc, argv, i);
+ if (arg == nullptr)
+ PrintUsage();
+ g_voiceGroup = std::stoi(arg);
+ break;
+ case 'L':
+ arg = GetArgument(argc, argv, i);
+ if (arg == nullptr)
+ PrintUsage();
+ g_asmLabel = arg;
+ break;
+ case 'N':
+ g_compressionEnabled = false;
+ break;
+ case 'P':
+ arg = GetArgument(argc, argv, i);
+ if (arg == nullptr)
+ PrintUsage();
+ g_priority = std::stoi(arg);
+ break;
+ case 'R':
+ arg = GetArgument(argc, argv, i);
+ if (arg == nullptr)
+ PrintUsage();
+ g_reverb = std::stoi(arg);
+ break;
+ case 'V':
+ arg = GetArgument(argc, argv, i);
+ if (arg == nullptr)
+ PrintUsage();
+ g_masterVolume = std::stoi(arg);
+ break;
+ case 'X':
+ g_clocksPerBeat = 2;
+ break;
+ case 'n':
+ arg = GetArgument(argc, argv, i);
+ if (arg == nullptr)
+ PrintUsage();
+ g_nominator = std::stoi(arg);
+ break;
+ case 'd': {
+ arg = GetArgument(argc, argv, i);
+ if (arg == nullptr)
+ PrintUsage();
+ int denominator = std::stoi(arg);
+ if (denominator == 1) {
+ g_denominatorExp = 0;
+ } else if (denominator == 2) {
+ g_denominatorExp = 1;
+ } else if (denominator == 4) {
+ g_denominatorExp = 2;
+ } else if (denominator == 8) {
+ g_denominatorExp = 3;
+ } else if (denominator == 16) {
+ g_denominatorExp = 4;
+ } else if (denominator == 32) {
+ g_denominatorExp = 5;
+ } else {
+ std::printf("ERROR: denominator %d\n", denominator);
+ exit(1);
+ }
+ break;
+ }
+ case 't': {
+ int nominator = std::stoi(argv[i + 1]);
+ int denominator = std::stoi(argv[i + 2]);
+ if (denominator == 1) {
+ denominator = 0;
+ } else if (denominator == 2) {
+ denominator = 1;
+ } else if (denominator == 4) {
+ denominator = 2;
+ } else if (denominator == 8) {
+ denominator = 3;
+ } else if (denominator == 16) {
+ denominator = 4;
+ } else if (denominator == 32) {
+ denominator = 5;
+ } else {
+ std::printf("ERROR: denominator %d\n", denominator);
+ exit(1);
+ }
+ int time = std::stoi(argv[i + 3]);
+ i += 3;
+ TimeSignatureChange change;
+ change.nominator = nominator;
+ change.denominator = denominator;
+ change.time = time;
+
+ // TODO sort by time?
+ timeSignatureChanges.push_back(change);
+ break;
+ }
+ case 'v': {
+ g_verbose = true;
+ break;
+ }
+ default:
+ PrintUsage();
+ }
+ } else {
+ if (baseromPath.empty())
+ baseromPath = argv[i];
+ else if (startOffset.empty())
+ startOffset = argv[i];
+ else if (inputFilename.empty())
+ inputFilename = argv[i];
+ else if (outputFilename.empty())
+ outputFilename = argv[i];
+ else
+ PrintUsage();
+ }
+ }
+
+ if (inputFilename.empty())
+ PrintUsage();
+
+ if (outputFilename.empty())
+ outputFilename = StripExtension(inputFilename) + ".mid";
+
+ if (GetExtension(outputFilename) != "mid")
+ RaiseError("output filename extension is not \"mid\"");
+
+ if (g_asmLabel.empty())
+ g_asmLabel = BaseName(outputFilename);
+
+ g_inputFile = std::fopen(inputFilename.c_str(), "rb");
+
+ if (g_inputFile == nullptr)
+ RaiseError("failed to open \"%s\" for reading", inputFilename.c_str());
+
+ g_outputFile = std::fopen(outputFilename.c_str(), "w");
+
+ if (g_outputFile == nullptr)
+ RaiseError("failed to open \"%s\" for writing", outputFilename.c_str());
+
+ g_fileStartOffset = std::stoul(startOffset, nullptr, 16);
+
+ ReadAgbSong();
+ ReadAgbTracks();
+ WriteMidiFile();
+
+ std::fclose(g_inputFile);
+ std::fclose(g_outputFile);
+
+ return 0;
+}
diff --git a/tools/src/agb2mid/main.h b/tools/src/agb2mid/main.h
new file mode 100755
index 00000000..f37dbba5
--- /dev/null
+++ b/tools/src/agb2mid/main.h
@@ -0,0 +1,51 @@
+// Copyright(c) 2016 YamaArashi
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+
+#ifndef MAIN_H
+#define MAIN_H
+
+#include <cstdio>
+#include <string>
+
+extern FILE* g_inputFile;
+extern FILE* g_outputFile;
+
+extern std::string g_asmLabel;
+extern int g_masterVolume;
+extern int g_voiceGroup;
+extern int g_priority;
+extern int g_reverb;
+extern int g_clocksPerBeat;
+extern bool g_exactGateTime;
+extern bool g_compressionEnabled;
+extern int g_nominator;
+extern int g_denominatorExp;
+extern int g_timeSignatureChange;
+extern bool g_nonmatching;
+extern bool g_verbose;
+
+struct TimeSignatureChange {
+ int nominator;
+ int denominator;
+ int time;
+};
+extern std::vector<TimeSignatureChange> timeSignatureChanges;
+
+#endif // MAIN_H
diff --git a/tools/src/agb2mid/midi.cpp b/tools/src/agb2mid/midi.cpp
new file mode 100755
index 00000000..03b07ee5
--- /dev/null
+++ b/tools/src/agb2mid/midi.cpp
@@ -0,0 +1,375 @@
+// Copyright(c) 2016 YamaArashi
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+
+// https://github.com/jpmac26/gba-mus-ripper
+
+#include <cstdio>
+#include <cassert>
+#include <string>
+#include <vector>
+#include <algorithm>
+#include <memory>
+#include "midi.h"
+#include "main.h"
+#include "error.h"
+#include "agb.h"
+#include "tables.h"
+
+enum class MidiEventCategory {
+ Control,
+ SysEx,
+ Meta,
+ Invalid,
+};
+
+MidiFormat g_midiFormat;
+std::int_fast32_t g_midiTrackCount;
+std::int16_t g_midiTimeDiv;
+
+int g_midiChan;
+std::int32_t g_initialWait;
+
+void Seek(long offset) {
+ if (std::fseek(g_inputFile, offset, SEEK_SET) != 0)
+ RaiseError("failed to seek to %x", offset);
+}
+
+void Skip(long offset) {
+ if (std::fseek(g_inputFile, offset, SEEK_CUR) != 0)
+ RaiseError("failed to skip %l bytes", offset);
+}
+
+std::string ReadSignature() {
+ char signature[4];
+
+ if (std::fread(signature, 4, 1, g_inputFile) != 1)
+ RaiseError("failed to read signature");
+
+ return std::string(signature, 4);
+}
+
+std::uint32_t ReadInt8() {
+ int c = std::fgetc(g_inputFile);
+
+ if (c < 0)
+ RaiseError("unexpected EOF");
+
+ return c;
+}
+
+std::uint32_t ReadInt16() {
+ std::uint32_t val = 0;
+ val |= ReadInt8() << 8;
+ val |= ReadInt8();
+ return val;
+}
+
+std::uint32_t ReadInt24() {
+ std::uint32_t val = 0;
+ val |= ReadInt8() << 16;
+ val |= ReadInt8() << 8;
+ val |= ReadInt8();
+ return val;
+}
+
+std::uint32_t ReadInt32() {
+ std::uint32_t val = 0;
+ val |= ReadInt8() << 24;
+ val |= ReadInt8() << 16;
+ val |= ReadInt8() << 8;
+ val |= ReadInt8();
+ return val;
+}
+
+std::uint32_t ReadVLQ() {
+ std::uint32_t val = 0;
+ std::uint32_t c;
+
+ do {
+ c = ReadInt8();
+ val <<= 7;
+ val |= (c & 0x7F);
+ } while (c & 0x80);
+
+ return val;
+}
+
+size_t lendian_fwrite(const void* ptr, size_t size, size_t nmemb, FILE* stream) {
+ if (size != 4) {
+ /* Warn and exit */
+ }
+
+ int x = 1;
+
+ if (*((char*)&x) == 1) {
+ /* Little endian machine, use fwrite directly */
+ return fwrite(ptr, size, nmemb, stream);
+ } else {
+ /* Big endian machine, pre-process first */
+
+ unsigned char* buffer = (unsigned char*)ptr;
+
+ for (size_t i = 0; i < nmemb; i++) {
+ unsigned char a = buffer[4 * i];
+ unsigned char b = buffer[4 * i + 1];
+
+ buffer[4 * i] = buffer[4 * i + 3];
+ buffer[4 * i + 1] = buffer[4 * i + 2];
+ buffer[4 * i + 2] = b;
+ buffer[4 * i + 3] = a;
+ }
+
+ return fwrite(ptr, size, nmemb, stream);
+ }
+}
+void WriteInt32(int value) {
+ union {
+ char bytes[4];
+ long ul;
+ } data;
+ data.ul = value;
+ fwrite(&data.bytes[3], 1, 1, g_outputFile);
+ fwrite(&data.bytes[2], 1, 1, g_outputFile);
+ fwrite(&data.bytes[1], 1, 1, g_outputFile);
+ fwrite(&data.bytes[0], 1, 1, g_outputFile);
+}
+void WriteInt16(short value) {
+ union {
+ char bytes[2];
+ short us;
+ } data;
+ data.us = value;
+ fwrite(&data.bytes[1], 1, 1, g_outputFile);
+ fwrite(&data.bytes[0], 1, 1, g_outputFile);
+}
+void WriteInt8(char value) {
+ fwrite(&value, 1, 1, g_outputFile);
+}
+
+void writeDeltaTime(std::vector<char>& data, long value) {
+ char bytes[4] = { 0 };
+
+ if ((unsigned long)value >= (1 << 28)) {
+ // TODO error number too large
+ value = 0x0FFFffff;
+ }
+
+ bytes[0] = (char)(((long)value >> 21) & 0x7f); // most significant 7 bits
+ bytes[1] = (char)(((long)value >> 14) & 0x7f);
+ bytes[2] = (char)(((long)value >> 7) & 0x7f);
+ bytes[3] = (char)(((long)value) & 0x7f); // least significant 7 bits
+
+ int start = 0;
+ while ((start < 4) && (bytes[start] == 0))
+ start++;
+
+ for (int i = start; i < 3; i++) {
+ bytes[i] = bytes[i] | 0x80;
+ data.push_back(bytes[i]);
+ }
+ data.push_back(bytes[3]);
+}
+
+void writeInt32(std::vector<char>& data, int value) {
+ union {
+ char bytes[4];
+ int ul;
+ } onion;
+ onion.ul = value;
+ data.push_back(onion.bytes[3]);
+ data.push_back(onion.bytes[2]);
+ data.push_back(onion.bytes[1]);
+ data.push_back(onion.bytes[0]);
+}
+
+void writeInt24(std::vector<char>& data, int value) {
+ union {
+ char bytes[4];
+ int ul;
+ } onion;
+ onion.ul = value;
+ data.push_back(onion.bytes[2]);
+ data.push_back(onion.bytes[1]);
+ data.push_back(onion.bytes[0]);
+}
+
+void WriteTrack(int channelId, const std::vector<Event>& events) {
+ std::vector<char> data; // TODO reuse same data vector
+ data.reserve(0x1000);
+ data.clear();
+
+ // int previousTime = 0;
+
+ int currentTime = 0;
+
+ bool DEBUG = g_verbose && false;
+
+ for (const auto& event : events) {
+ if (DEBUG) {
+ std::printf("TIME: %d\n", event.time);
+ }
+ writeDeltaTime(data, event.time - currentTime);
+ currentTime = event.time;
+ switch (event.type) {
+ case EventType::InstrumentChange:
+ if (DEBUG) {
+ std::printf("InstrumentChange\n");
+ }
+ data.push_back(0xC0 + channelId); // type
+ data.push_back(event.param1); // instrument
+ break;
+ case EventType::Controller:
+ if (DEBUG) {
+ std::printf("Controller\n");
+ }
+ data.push_back(0xB0 + channelId); // type
+ data.push_back(event.param1); // controller index
+ data.push_back(event.param2); // value
+ break;
+ case EventType::Note:
+ if (DEBUG) {
+ std::printf("Note\n");
+ }
+ data.push_back(0x90 + channelId); // type
+ data.push_back(event.note); // note
+ data.push_back(event.param1); // velocity
+ break;
+ case EventType::NoteOff:
+ if (DEBUG) {
+ std::printf("NoteOff\n");
+ }
+ // Wait and note_end
+ data.push_back(0x80 + channelId); // type
+ data.push_back(event.note); // note
+ data.push_back(0); // Ignored velocity
+ break;
+ case EventType::TimeSignature: {
+ if (DEBUG) {
+ std::printf("TimeSignature\n");
+ }
+ // One time meta event at the beginning of the meta track.
+ data.push_back(0xff); // meta
+ data.push_back(88); // TIME_SIGNATURE
+ writeDeltaTime(data, 4); // m_length
+ data.push_back(event.param1); // numerator
+ data.push_back(event.param2); // denominator
+ data.push_back(24 * g_clocksPerBeat); // clocksPerClick
+ data.push_back(8); // 32ndPer4th
+ break;
+ }
+ case EventType::Tempo:
+ if (DEBUG) {
+ std::printf("Tempo\n");
+ }
+ data.push_back(0xff); // meta
+ data.push_back(81); // META_TEMPO
+ writeDeltaTime(data, 3); // only valid tempo size is 3
+ writeInt24(data, event.param2); // usecPerQuarterNote
+ break;
+ case EventType::Extended:
+ if (DEBUG) {
+ std::printf("Extended\n");
+ }
+ if (event.note == 0x1d) {
+ // for some reason the first one is in a meta change?
+ data.push_back(0xb0);
+ }
+ data.push_back(0x1e); // extended param change
+ data.push_back(event.param1);
+
+ writeDeltaTime(data, 0); // Next midi event
+ data.push_back(0x1d); // TODO why twice 0x1d? event.note);
+ data.push_back(event.param2);
+ break;
+ case EventType::PitchBend:
+ if (DEBUG) {
+ std::printf("PitchBend\n");
+ }
+ data.push_back(0xe0 + channelId); // petch bend
+ data.push_back(0); // lsb
+ data.push_back(event.param2);
+ break;
+ case EventType::EndOfTrack:
+ if (DEBUG) {
+ std::printf("EndOfTrack\n");
+ }
+ // End of track
+ data.push_back(0xff); // meta
+ data.push_back(47); // META_END_OF_TRACK
+ writeDeltaTime(data, 0); // length
+ break;
+ case EventType::LoopBegin:
+ if (DEBUG) {
+ std::printf("LoopBegin\n");
+ }
+ data.push_back(0xff); // meta
+ data.push_back(0x6); // META_MARKER
+ data.push_back(0x1); // length
+ data.push_back(0x5B); // [
+ break;
+ case EventType::LoopEnd:
+ if (DEBUG) {
+ std::printf("LoopEnd\n");
+ }
+ data.push_back(0xff); // meta
+ data.push_back(0x6); // META_MARKER
+ data.push_back(0x1); // length
+ data.push_back(0x5D); // ]
+ break;
+ }
+ }
+
+ // MidiTrack header
+ std::fprintf(g_outputFile, "MTrk");
+ // length of track data
+ WriteInt32(data.size());
+
+ fwrite(data.data(), data.size(), 1, g_outputFile);
+}
+
+void WriteMidiFile() {
+ // MidiHeader
+ std::fprintf(g_outputFile, "MThd");
+ // u32 length of header data = 6
+ WriteInt32(6);
+ // u16 format = 1
+ WriteInt16(1);
+ // u16 numberTracks
+ WriteInt16(trackEvents.size() + 1);
+ // u16 midiTimeDiv (24 in example)
+ WriteInt16(g_midiTimeDiv);
+
+ // Add end of track to meta events
+ Event event;
+ event.type = EventType::EndOfTrack;
+ // EndOfTrack event needs to be at the last meta event (can be earlier than the end of other tracks) for
+ // bgmVaatiMotif to work
+ event.time = metaEvents.back().time;
+ metaEvents.push_back(event);
+
+ // Track for meta events
+ WriteTrack(-1, metaEvents);
+
+ int i = 0;
+ for (const auto& events : trackEvents) {
+ WriteTrack(i, events);
+ i++;
+ }
+} \ No newline at end of file
diff --git a/tools/src/agb2mid/midi.h b/tools/src/agb2mid/midi.h
new file mode 100755
index 00000000..f69cf382
--- /dev/null
+++ b/tools/src/agb2mid/midi.h
@@ -0,0 +1,78 @@
+// Copyright(c) 2016 YamaArashi
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+
+#ifndef MIDI_H
+#define MIDI_H
+
+#include <cstdint>
+
+enum class MidiFormat { SingleTrack, MultiTrack };
+
+enum class EventType {
+ EndOfTie = 0x01,
+ Label = 0x11,
+ LoopEnd = 0x12,
+ LoopEndBegin = 0x13,
+ LoopBegin = 0x14,
+ OriginalTimeSignature = 0x15,
+ WholeNoteMark = 0x16,
+ Pattern = 0x17,
+ TimeSignature = 0x18,
+ Tempo = 0x19,
+ InstrumentChange = 0x21,
+ Controller = 0x22,
+ PitchBend = 0x23,
+ KeyShift = 0x31,
+ Note = 0x40,
+ TimeSplit = 0xFE,
+ EndOfTrack = 0xFF,
+ Extended,
+ Wait,
+ NoteOff,
+};
+
+struct Event {
+ std::int32_t time = 0;
+ EventType type;
+ std::uint8_t note = 0;
+ std::uint8_t param1 = 0;
+ std::int32_t param2 = 0;
+
+ bool operator==(const Event& other) {
+ return (time == other.time && type == other.type && note == other.note && param1 == other.param1 &&
+ param2 == other.param2);
+ }
+
+ bool operator!=(const Event& other) {
+ return !(*this == other);
+ }
+};
+
+void WriteMidiFile();
+
+extern int g_midiChan;
+extern std::int32_t g_initialWait;
+
+inline bool IsPatternBoundary(EventType type) {
+ return type == EventType::EndOfTrack || (int)type <= 0x17;
+}
+extern std::int16_t g_midiTimeDiv;
+
+#endif // MIDI_H
diff --git a/tools/src/agb2mid/tables.cpp b/tools/src/agb2mid/tables.cpp
new file mode 100755
index 00000000..7d3902dc
--- /dev/null
+++ b/tools/src/agb2mid/tables.cpp
@@ -0,0 +1,267 @@
+// Copyright(c) 2016 YamaArashi
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+
+#include "tables.h"
+
+const int g_noteDurationLUT[] = {
+ 0, // 0
+ 1, // 1
+ 2, // 2
+ 3, // 3
+ 4, // 4
+ 5, // 5
+ 6, // 6
+ 7, // 7
+ 8, // 8
+ 9, // 9
+ 10, // 10
+ 11, // 11
+ 12, // 12
+ 13, // 13
+ 14, // 14
+ 15, // 15
+ 16, // 16
+ 17, // 17
+ 18, // 18
+ 19, // 19
+ 20, // 20
+ 21, // 21
+ 22, // 22
+ 23, // 23
+ 24, // 24
+ 24, // 25
+ 24, // 26
+ 24, // 27
+ 28, // 28
+ 28, // 29
+ 30, // 30
+ 30, // 31
+ 32, // 32
+ 32, // 33
+ 32, // 34
+ 32, // 35
+ 36, // 36
+ 36, // 37
+ 36, // 38
+ 36, // 39
+ 40, // 40
+ 40, // 41
+ 42, // 42
+ 42, // 43
+ 44, // 44
+ 44, // 45
+ 44, // 46
+ 44, // 47
+ 48, // 48
+ 48, // 49
+ 48, // 50
+ 48, // 51
+ 52, // 52
+ 52, // 53
+ 54, // 54
+ 54, // 55
+ 56, // 56
+ 56, // 57
+ 56, // 58
+ 56, // 59
+ 60, // 60
+ 60, // 61
+ 60, // 62
+ 60, // 63
+ 64, // 64
+ 64, // 65
+ 66, // 66
+ 66, // 67
+ 68, // 68
+ 68, // 69
+ 68, // 70
+ 68, // 71
+ 72, // 72
+ 72, // 73
+ 72, // 74
+ 72, // 75
+ 76, // 76
+ 76, // 77
+ 78, // 78
+ 78, // 79
+ 80, // 80
+ 80, // 81
+ 80, // 82
+ 80, // 83
+ 84, // 84
+ 84, // 85
+ 84, // 86
+ 84, // 87
+ 88, // 88
+ 88, // 89
+ 90, // 90
+ 90, // 91
+ 92, // 92
+ 92, // 93
+ 92, // 94
+ 92, // 95
+ 96, // 96
+};
+
+const int g_noteVelocityLUT[] = {
+ 0, // 0
+ 4, // 1
+ 4, // 2
+ 4, // 3
+ 4, // 4
+ 8, // 5
+ 8, // 6
+ 8, // 7
+ 8, // 8
+ 12, // 9
+ 12, // 10
+ 12, // 11
+ 12, // 12
+ 16, // 13
+ 16, // 14
+ 16, // 15
+ 16, // 16
+ 20, // 17
+ 20, // 18
+ 20, // 19
+ 20, // 20
+ 24, // 21
+ 24, // 22
+ 24, // 23
+ 24, // 24
+ 28, // 25
+ 28, // 26
+ 28, // 27
+ 28, // 28
+ 32, // 29
+ 32, // 30
+ 32, // 31
+ 32, // 32
+ 36, // 33
+ 36, // 34
+ 36, // 35
+ 36, // 36
+ 40, // 37
+ 40, // 38
+ 40, // 39
+ 40, // 40
+ 44, // 41
+ 44, // 42
+ 44, // 43
+ 44, // 44
+ 48, // 45
+ 48, // 46
+ 48, // 47
+ 48, // 48
+ 52, // 49
+ 52, // 50
+ 52, // 51
+ 52, // 52
+ 56, // 53
+ 56, // 54
+ 56, // 55
+ 56, // 56
+ 60, // 57
+ 60, // 58
+ 60, // 59
+ 60, // 60
+ 64, // 61
+ 64, // 62
+ 64, // 63
+ 64, // 64
+ 68, // 65
+ 68, // 66
+ 68, // 67
+ 68, // 68
+ 72, // 69
+ 72, // 70
+ 72, // 71
+ 72, // 72
+ 76, // 73
+ 76, // 74
+ 76, // 75
+ 76, // 76
+ 80, // 77
+ 80, // 78
+ 80, // 79
+ 80, // 80
+ 84, // 81
+ 84, // 82
+ 84, // 83
+ 84, // 84
+ 88, // 85
+ 88, // 86
+ 88, // 87
+ 88, // 88
+ 92, // 89
+ 92, // 90
+ 92, // 91
+ 92, // 92
+ 96, // 93
+ 96, // 94
+ 96, // 95
+ 96, // 96
+ 100, // 97
+ 100, // 98
+ 100, // 99
+ 100, // 100
+ 104, // 101
+ 104, // 102
+ 104, // 103
+ 104, // 104
+ 108, // 105
+ 108, // 106
+ 108, // 107
+ 108, // 108
+ 112, // 109
+ 112, // 110
+ 112, // 111
+ 112, // 112
+ 116, // 113
+ 116, // 114
+ 116, // 115
+ 116, // 116
+ 120, // 117
+ 120, // 118
+ 120, // 119
+ 120, // 120
+ 124, // 121
+ 124, // 122
+ 124, // 123
+ 124, // 124
+ 127, // 125
+ 127, // 126
+ 127, // 127
+};
+
+const char* g_noteTable[] = {
+ "Cn%01u ", "Cs%01u ", "Dn%01u ", "Ds%01u ", "En%01u ", "Fn%01u ",
+ "Fs%01u ", "Gn%01u ", "Gs%01u ", "An%01u ", "As%01u ", "Bn%01u ",
+};
+
+const char* g_minusNoteTable[] = {
+ "CnM%01u", "CsM%01u", "DnM%01u", "DsM%01u", "EnM%01u", "FnM%01u",
+ "FsM%01u", "GnM%01u", "GsM%01u", "AnM%01u", "AsM%01u", "BnM%01u",
+};
+
+const int g_noteDurationInverseLUT[] = {
+ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24,
+ 28, 30, 32, 36, 40, 42, 44, 48, 52, 54, 56, 60, 64, 66, 68, 72, 76, 78, 80, 84, 88, 90, 92, 96,
+}; \ No newline at end of file
diff --git a/tools/src/agb2mid/tables.h b/tools/src/agb2mid/tables.h
new file mode 100755
index 00000000..72f3ac85
--- /dev/null
+++ b/tools/src/agb2mid/tables.h
@@ -0,0 +1,29 @@
+// Copyright(c) 2016 YamaArashi
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+
+#ifndef TABLES_H
+#define TABLES_H
+
+extern const int g_noteDurationLUT[97];
+extern const int g_noteVelocityLUT[];
+extern const char* g_noteTable[];
+extern const char* g_minusNoteTable[];
+extern const int g_noteDurationInverseLUT[];
+#endif // TABLES_H
diff --git a/tools/src/aif2pcm/CMakeLists.txt b/tools/src/aif2pcm/CMakeLists.txt
new file mode 100644
index 00000000..d01668d6
--- /dev/null
+++ b/tools/src/aif2pcm/CMakeLists.txt
@@ -0,0 +1,7 @@
+file(GLOB_RECURSE sources *.c)
+
+add_executable(aif2pcm ${sources})
+target_include_directories(aif2pcm PRIVATE .)
+target_link_libraries(aif2pcm PRIVATE m)
+
+install(TARGETS aif2pcm RUNTIME DESTINATION bin)
diff --git a/tools/src/aif2pcm/LICENSE b/tools/src/aif2pcm/LICENSE
new file mode 100755
index 00000000..966b92bd
--- /dev/null
+++ b/tools/src/aif2pcm/LICENSE
@@ -0,0 +1,20 @@
+Copyright (c) 2016 huderlem
+Copyright (c) 2005, 2006 by Marco Trillo <marcotrillo@gmail.com>
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in
+all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE.
diff --git a/tools/src/aif2pcm/extended.c b/tools/src/aif2pcm/extended.c
new file mode 100644
index 00000000..33911c01
--- /dev/null
+++ b/tools/src/aif2pcm/extended.c
@@ -0,0 +1,164 @@
+/* $Id: extended.c,v 1.8 2006/12/23 11:17:49 toad32767 Exp $ */
+/*-
+ * Copyright (c) 2005, 2006 by Marco Trillo <marcotrillo@gmail.com>
+ *
+ * Permission is hereby granted, free of charge, to any
+ * person obtaining a copy of this software and associated
+ * documentation files (the "Software"), to deal in the
+ * Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the
+ * Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice
+ * shall be included in all copies or substantial portions of
+ * the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY
+ * KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
+ * WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
+ * PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS
+ * OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR
+ * OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
+ * OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
+ * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+
+#include <math.h>
+#include <string.h>
+#include <stdint.h>
+
+/*
+ * Infinite & NAN values
+ * for non-IEEE systems
+ */
+#ifndef HUGE_VAL
+#ifdef HUGE
+#define INFINITE_VALUE HUGE
+#define NAN_VALUE HUGE
+#endif
+#else
+#define INFINITE_VALUE HUGE_VAL
+#define NAN_VALUE HUGE_VAL
+#endif
+
+/*
+ * IEEE 754 Extended Precision
+ *
+ * Implementation here is the 80-bit extended precision
+ * format of Motorola 68881, Motorola 68882 and Motorola
+ * 68040 FPUs, as well as Intel 80x87 FPUs.
+ *
+ * See:
+ * http://www.freescale.com/files/32bit/doc/fact_sheet/BR509.pdf
+ */
+/*
+ * Exponent range: [-16383,16383]
+ * Precision for mantissa: 64 bits with no hidden bit
+ * Bias: 16383
+ */
+
+/*
+ * Write IEEE Extended Precision Numbers
+ */
+void ieee754_write_extended(double in, uint8_t* out) {
+ int sgn, exp, shift;
+ double fraction, t;
+ unsigned int lexp, hexp;
+ unsigned long low, high;
+
+ if (in == 0.0) {
+ memset(out, 0, 10);
+ return;
+ }
+ if (in < 0.0) {
+ in = fabs(in);
+ sgn = 1;
+ } else
+ sgn = 0;
+
+ fraction = frexp(in, &exp);
+
+ if (exp == 0 || exp > 16384) {
+ if (exp > 16384) /* infinite value */
+ low = high = 0;
+ else {
+ low = 0x80000000;
+ high = 0;
+ }
+ exp = 32767;
+ goto done;
+ }
+ fraction = ldexp(fraction, 32);
+ t = floor(fraction);
+ low = (unsigned long)t;
+ fraction -= t;
+ t = floor(ldexp(fraction, 32));
+ high = (unsigned long)t;
+
+ /* Convert exponents < -16382 to -16382 (then they will be
+ * stored as -16383) */
+ if (exp < -16382) {
+ shift = 0 - exp - 16382;
+ high >>= shift;
+ high |= (low << (32 - shift));
+ low >>= shift;
+ exp = -16382;
+ }
+ exp += 16383 - 1; /* bias */
+
+done:
+ lexp = ((unsigned int)exp) >> 8;
+ hexp = ((unsigned int)exp) & 0xFF;
+
+ /* big endian */
+ out[0] = ((uint8_t)sgn) << 7;
+ out[0] |= (uint8_t)lexp;
+ out[1] = (uint8_t)hexp;
+ out[2] = (uint8_t)(low >> 24);
+ out[3] = (uint8_t)((low >> 16) & 0xFF);
+ out[4] = (uint8_t)((low >> 8) & 0xFF);
+ out[5] = (uint8_t)(low & 0xFF);
+ out[6] = (uint8_t)(high >> 24);
+ out[7] = (uint8_t)((high >> 16) & 0xFF);
+ out[8] = (uint8_t)((high >> 8) & 0xFF);
+ out[9] = (uint8_t)(high & 0xFF);
+
+ return;
+}
+
+/*
+ * Read IEEE Extended Precision Numbers
+ */
+double ieee754_read_extended(uint8_t* in) {
+ int sgn, exp;
+ unsigned long low, high;
+ double out;
+
+ /* Extract the components from the big endian buffer */
+ sgn = (int)(in[0] >> 7);
+ exp = ((int)(in[0] & 0x7F) << 8) | ((int)in[1]);
+ low = (((unsigned long)in[2]) << 24) | (((unsigned long)in[3]) << 16) | (((unsigned long)in[4]) << 8) |
+ (unsigned long)in[5];
+ high = (((unsigned long)in[6]) << 24) | (((unsigned long)in[7]) << 16) | (((unsigned long)in[8]) << 8) |
+ (unsigned long)in[9];
+
+ if (exp == 0 && low == 0 && high == 0)
+ return (sgn ? -0.0 : 0.0);
+
+ switch (exp) {
+ case 32767:
+ if (low == 0 && high == 0)
+ return (sgn ? -INFINITE_VALUE : INFINITE_VALUE);
+ else
+ return (sgn ? -NAN_VALUE : NAN_VALUE);
+ default:
+ exp -= 16383; /* unbias exponent */
+ }
+
+ out = ldexp((double)low, -31 + exp);
+ out += ldexp((double)high, -63 + exp);
+
+ return (sgn ? -out : out);
+}
diff --git a/tools/src/aif2pcm/main.c b/tools/src/aif2pcm/main.c
new file mode 100644
index 00000000..50616519
--- /dev/null
+++ b/tools/src/aif2pcm/main.c
@@ -0,0 +1,799 @@
+// Copyright(c) 2016 huderlem
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <stdbool.h>
+#include <stdint.h>
+#include <limits.h>
+
+/* extended.c */
+void ieee754_write_extended(double, uint8_t*);
+double ieee754_read_extended(uint8_t*);
+
+#ifdef _MSC_VER
+
+#define FATAL_ERROR(format, ...) \
+ do { \
+ fprintf(stderr, format, __VA_ARGS__); \
+ exit(1); \
+ } while (0)
+
+#else
+
+#define FATAL_ERROR(format, ...) \
+ do { \
+ fprintf(stderr, format, ##__VA_ARGS__); \
+ exit(1); \
+ } while (0)
+
+#endif // _MSC_VER
+
+typedef struct {
+ unsigned long num_samples;
+ uint8_t* samples;
+ uint8_t midi_note;
+ bool has_loop;
+ unsigned long loop_offset;
+ double sample_rate;
+ unsigned long real_num_samples;
+} AifData;
+
+struct Bytes {
+ unsigned long length;
+ uint8_t* data;
+};
+
+struct Marker {
+ unsigned short id;
+ unsigned long position;
+ // don't care about the name
+};
+
+struct Bytes* read_bytearray(const char* filename) {
+ struct Bytes* bytes = malloc(sizeof(struct Bytes));
+ FILE* f = fopen(filename, "rb");
+ if (!f) {
+ FATAL_ERROR("Failed to open '%s' for reading!\n", filename);
+ }
+ fseek(f, 0, SEEK_END);
+ bytes->length = ftell(f);
+ fseek(f, 0, SEEK_SET);
+ bytes->data = malloc(bytes->length);
+ unsigned long read = fread(bytes->data, bytes->length, 1, f);
+ fclose(f);
+ if (read <= 0) {
+ FATAL_ERROR("Failed to read data from '%s'!\n", filename);
+ }
+ return bytes;
+}
+
+void write_bytearray(const char* filename, struct Bytes* bytes) {
+ FILE* f = fopen(filename, "wb");
+ if (!f) {
+ FATAL_ERROR("Failed to open '%s' for writing!\n", filename);
+ }
+ fwrite(bytes->data, bytes->length, 1, f);
+ fclose(f);
+}
+
+void free_bytearray(struct Bytes* bytes) {
+ free(bytes->data);
+ free(bytes);
+}
+
+char* get_file_extension(char* filename) {
+ char* index = strrchr(filename, '.');
+ if (!index || index == filename) {
+ return NULL;
+ }
+ return index + 1;
+}
+
+char* new_file_extension(char* filename, char* ext) {
+ char* index = strrchr(filename, '.');
+ if (!index || index == filename) {
+ index = filename + strlen(filename);
+ }
+ int length = index - filename;
+ char* new_filename = malloc(length + 1 + strlen(ext) + 1);
+ if (new_filename) {
+ strcpy(new_filename, filename);
+ new_filename[length] = '.';
+ strcpy(new_filename + length + 1, ext);
+ }
+ return new_filename;
+}
+
+void read_aif(struct Bytes* aif, AifData* aif_data) {
+ aif_data->has_loop = false;
+ aif_data->num_samples = 0;
+
+ unsigned long pos = 0;
+ char chunk_name[5];
+ chunk_name[4] = '\0';
+ char chunk_type[5];
+ chunk_type[4] = '\0';
+
+ // Check for FORM Chunk
+ memcpy(chunk_name, &aif->data[pos], 4);
+ pos += 4;
+ if (strcmp(chunk_name, "FORM") != 0) {
+ FATAL_ERROR("Input .aif file has invalid header Chunk '%s'!\n", chunk_name);
+ }
+
+ // Read size of whole file.
+ unsigned long whole_chunk_size = aif->data[pos++] << 24;
+ whole_chunk_size |= (aif->data[pos++] << 16);
+ whole_chunk_size |= (aif->data[pos++] << 8);
+ whole_chunk_size |= (uint8_t)aif->data[pos++];
+
+ unsigned long expected_whole_chunk_size = aif->length - 8;
+ if (whole_chunk_size != expected_whole_chunk_size) {
+ FATAL_ERROR("FORM Chunk ckSize '%lu' doesn't match actual size '%lu'!\n", whole_chunk_size,
+ expected_whole_chunk_size);
+ }
+
+ // Check for AIFF Form Type
+ memcpy(chunk_type, &aif->data[pos], 4);
+ pos += 4;
+ if (strcmp(chunk_type, "AIFF") != 0) {
+ FATAL_ERROR("FORM Type is '%s', but it must be AIFF!", chunk_type);
+ }
+
+ struct Marker* markers = NULL;
+ unsigned short num_markers = 0, loop_start = 0, loop_end = 0;
+ unsigned long num_sample_frames = 0;
+
+ // Read all the Chunks to populate the AifData struct.
+ while ((pos + 8) < aif->length) {
+ // Read Chunk id
+ memcpy(chunk_name, &aif->data[pos], 4);
+ pos += 4;
+
+ unsigned long chunk_size = (aif->data[pos++] << 24);
+ chunk_size |= (aif->data[pos++] << 16);
+ chunk_size |= (aif->data[pos++] << 8);
+ chunk_size |= aif->data[pos++];
+
+ if ((pos + chunk_size) > aif->length) {
+ FATAL_ERROR("%s chunk at 0x%lx reached end of file before finishing\n", chunk_name, pos);
+ }
+
+ if (strcmp(chunk_name, "COMM") == 0) {
+ short num_channels = (aif->data[pos++] << 8);
+ num_channels |= (uint8_t)aif->data[pos++];
+ if (num_channels != 1) {
+ FATAL_ERROR("numChannels (%d) in the COMM Chunk must be 1!\n", num_channels);
+ }
+
+ num_sample_frames = (aif->data[pos++] << 24);
+ num_sample_frames |= (aif->data[pos++] << 16);
+ num_sample_frames |= (aif->data[pos++] << 8);
+ num_sample_frames |= (uint8_t)aif->data[pos++];
+
+ short sample_size = (aif->data[pos++] << 8);
+ sample_size |= (uint8_t)aif->data[pos++];
+ if (sample_size != 8) {
+ FATAL_ERROR("sampleSize (%d) in the COMM Chunk must be 8!\n", sample_size);
+ }
+
+ double sample_rate = ieee754_read_extended((uint8_t*)(aif->data + pos));
+ pos += 10;
+
+ aif_data->sample_rate = sample_rate;
+
+ if (aif_data->num_samples == 0) {
+ aif_data->num_samples = num_sample_frames;
+ }
+ } else if (strcmp(chunk_name, "MARK") == 0) {
+ num_markers = (aif->data[pos++] << 8);
+ num_markers |= (uint8_t)aif->data[pos++];
+
+ if (markers) {
+ FATAL_ERROR("More than one MARK Chunk in file!\n");
+ }
+
+ markers = calloc(num_markers, sizeof(struct Marker));
+
+ // Read each marker.
+ for (int i = 0; i < num_markers; i++) {
+ unsigned short marker_id = (aif->data[pos++] << 8);
+ marker_id |= (uint8_t)aif->data[pos++];
+
+ unsigned long marker_position = (aif->data[pos++] << 24);
+ marker_position |= (aif->data[pos++] << 16);
+ marker_position |= (aif->data[pos++] << 8);
+ marker_position |= (uint8_t)aif->data[pos++];
+
+ // Marker name is a Pascal-style string.
+ uint8_t marker_name_size = aif->data[pos++];
+ // We don't actually need the marker name for anything anymore.
+ /*char *marker_name = (char *)malloc((marker_name_size + 1) * sizeof(char));
+ memcpy(marker_name, &aif->data[pos], marker_name_size);
+ marker_name[marker_name_size] = '\0';*/
+ pos += marker_name_size + !(marker_name_size & 1);
+
+ markers[i].id = marker_id;
+ markers[i].position = marker_position;
+ }
+ } else if (strcmp(chunk_name, "INST") == 0) {
+ uint8_t midi_note = (uint8_t)aif->data[pos++];
+
+ aif_data->midi_note = midi_note;
+
+ // Skip over data we don't need.
+ pos += 7;
+
+ unsigned short loop_type = (aif->data[pos++] << 8);
+ loop_type |= (uint8_t)aif->data[pos++];
+
+ if (loop_type) {
+ loop_start = (aif->data[pos++] << 8);
+ loop_start |= (uint8_t)aif->data[pos++];
+
+ loop_end = (aif->data[pos++] << 8);
+ loop_end |= (uint8_t)aif->data[pos++];
+ } else {
+ // Skip NoLooping sustain loop.
+ pos += 4;
+ }
+
+ // Skip release loop, we don't need it.
+ pos += 6;
+ } else if (strcmp(chunk_name, "SSND") == 0) {
+ // Skip offset and blockSize
+ pos += 8;
+
+ unsigned long num_samples = chunk_size - 8;
+ uint8_t* sample_data = (uint8_t*)malloc(num_samples * sizeof(uint8_t));
+ memcpy(sample_data, &aif->data[pos], num_samples);
+
+ aif_data->samples = sample_data;
+ aif_data->real_num_samples = num_samples;
+ pos += chunk_size - 8;
+ } else {
+ // Skip over unsupported chunks.
+ pos += chunk_size;
+ }
+ }
+
+ if (markers) {
+ // Resolve loop points.
+ struct Marker* cur_marker = markers;
+
+ // Grab loop start point.
+ for (int i = 0; i < num_markers; i++, cur_marker++) {
+ if (cur_marker->id == loop_start) {
+ aif_data->loop_offset = cur_marker->position;
+ aif_data->has_loop = true;
+ break;
+ }
+ }
+
+ cur_marker = markers;
+
+ // Grab loop end point.
+ for (int i = 0; i < num_markers; i++, cur_marker++) {
+ if (cur_marker->id == loop_end) {
+ if (cur_marker->position < aif_data->loop_offset) {
+ aif_data->loop_offset = cur_marker->position;
+ aif_data->has_loop = true;
+ }
+ aif_data->num_samples = cur_marker->position;
+ break;
+ }
+ }
+
+ free(markers);
+ }
+}
+
+// This is a table of deltas between sample values in compressed PCM data.
+const int gDeltaEncodingTable[] = {
+ 0, 1, 4, 9, 16, 25, 36, 49, -64, -49, -36, -25, -16, -9, -4, -1,
+};
+
+struct Bytes* delta_decompress(struct Bytes* delta, unsigned int expected_length) {
+ struct Bytes* pcm = malloc(sizeof(struct Bytes));
+ pcm->length = expected_length;
+ pcm->data = malloc(pcm->length + 0x40);
+
+ uint8_t hi, lo;
+ unsigned int i = 0;
+ unsigned int j = 0;
+ int k;
+ int8_t base;
+ while (i < delta->length) {
+ base = (int8_t)delta->data[i++];
+ pcm->data[j++] = (uint8_t)base;
+ if (i >= delta->length) {
+ break;
+ }
+ if (j >= pcm->length) {
+ break;
+ }
+ lo = delta->data[i] & 0xf;
+ base += gDeltaEncodingTable[lo];
+ pcm->data[j++] = base;
+ i++;
+ if (i >= delta->length) {
+ break;
+ }
+ if (j >= pcm->length) {
+ break;
+ }
+ for (k = 0; k < 31; k++) {
+ hi = (delta->data[i] >> 4) & 0xf;
+ base += gDeltaEncodingTable[hi];
+ pcm->data[j++] = base;
+ if (j >= pcm->length) {
+ break;
+ }
+ lo = delta->data[i] & 0xf;
+ base += gDeltaEncodingTable[lo];
+ pcm->data[j++] = base;
+ i++;
+ if (i >= delta->length) {
+ break;
+ }
+ if (j >= pcm->length) {
+ break;
+ }
+ }
+ if (j >= pcm->length) {
+ break;
+ }
+ }
+
+ pcm->length = j;
+ return pcm;
+}
+
+int get_delta_index(uint8_t sample, uint8_t prev_sample) {
+ int best_error = INT_MAX;
+ int best_index = -1;
+
+ for (int i = 0; i < 16; i++) {
+ uint8_t new_sample = prev_sample + gDeltaEncodingTable[i];
+ int error = sample > new_sample ? sample - new_sample : new_sample - sample;
+
+ if (error < best_error) {
+ best_error = error;
+ best_index = i;
+ }
+ }
+
+ return best_index;
+}
+
+struct Bytes* delta_compress(struct Bytes* pcm) {
+ struct Bytes* delta = malloc(sizeof(struct Bytes));
+ // estimate the length so we can malloc
+ int num_blocks = pcm->length / 64;
+ delta->length = num_blocks * 33;
+
+ int extra = pcm->length % 64;
+ if (extra) {
+ delta->length += 1;
+ extra -= 1;
+ }
+ if (extra) {
+ delta->length += 1;
+ extra -= 1;
+ }
+ if (extra) {
+ delta->length += (extra + 1) / 2;
+ }
+
+ delta->data = malloc(delta->length + 33);
+
+ unsigned int i = 0;
+ unsigned int j = 0;
+ int k;
+ uint8_t base;
+ int delta_index;
+
+ while (i < pcm->length) {
+ base = pcm->data[i++];
+ delta->data[j++] = base;
+
+ if (i >= pcm->length) {
+ break;
+ }
+ delta_index = get_delta_index(pcm->data[i++], base);
+ base += gDeltaEncodingTable[delta_index];
+ delta->data[j++] = delta_index;
+
+ for (k = 0; k < 31; k++) {
+ if (i >= pcm->length) {
+ break;
+ }
+ delta_index = get_delta_index(pcm->data[i++], base);
+ base += gDeltaEncodingTable[delta_index];
+ delta->data[j] = (delta_index << 4);
+
+ if (i >= pcm->length) {
+ break;
+ }
+ delta_index = get_delta_index(pcm->data[i++], base);
+ base += gDeltaEncodingTable[delta_index];
+ delta->data[j++] |= delta_index;
+ }
+ }
+
+ delta->length = j;
+
+ return delta;
+}
+
+#define STORE_U32_LE(dest, value) \
+ do { \
+ *(dest) = (value)&0xff; \
+ *((dest) + 1) = ((value) >> 8) & 0xff; \
+ *((dest) + 2) = ((value) >> 16) & 0xff; \
+ *((dest) + 3) = ((value) >> 24) & 0xff; \
+ } while (0)
+
+#define LOAD_U32_LE(var, src) \
+ do { \
+ (var) = *(src); \
+ (var) |= (*((src) + 1) << 8); \
+ (var) |= (*((src) + 2) << 16); \
+ (var) |= (*((src) + 3) << 24); \
+ } while (0)
+
+// Reads an .aif file and produces a .pcm file containing an array of 8-bit samples.
+void aif2pcm(const char* aif_filename, const char* pcm_filename, bool compress) {
+ struct Bytes* aif = read_bytearray(aif_filename);
+ AifData aif_data = { 0, 0, 0, 0, 0, 0, 0 };
+ read_aif(aif, &aif_data);
+
+ int header_size = 0x10;
+ struct Bytes* pcm;
+ struct Bytes output = { 0, 0 };
+
+ if (compress) {
+ struct Bytes* input = malloc(sizeof(struct Bytes));
+ input->data = aif_data.samples;
+ input->length = aif_data.real_num_samples;
+ pcm = delta_compress(input);
+ free(input);
+ } else {
+ pcm = malloc(sizeof(struct Bytes));
+ pcm->data = aif_data.samples;
+ pcm->length = aif_data.real_num_samples;
+ }
+ output.length = header_size + pcm->length;
+ output.data = malloc(output.length);
+
+ uint32_t pitch_adjust = (uint32_t)(aif_data.sample_rate * 1024);
+ uint32_t loop_offset = (uint32_t)(aif_data.loop_offset);
+ uint32_t adjusted_num_samples = (uint32_t)(aif_data.num_samples - 1);
+ uint32_t flags = 0;
+ if (aif_data.has_loop)
+ flags |= 0x40000000;
+ if (compress)
+ flags |= 1;
+ STORE_U32_LE(output.data + 0, flags);
+ STORE_U32_LE(output.data + 4, pitch_adjust);
+ STORE_U32_LE(output.data + 8, loop_offset);
+ STORE_U32_LE(output.data + 12, adjusted_num_samples);
+ memcpy(&output.data[header_size], pcm->data, pcm->length);
+ write_bytearray(pcm_filename, &output);
+
+ free(aif->data);
+ free(aif);
+ free(pcm);
+ free(output.data);
+ free(aif_data.samples);
+}
+
+// Reads a .pcm file containing an array of 8-bit samples and produces an .aif file.
+// See http://www-mmsp.ece.mcgill.ca/documents/audioformats/aiff/Docs/AIFF-1.3.pdf for .aif file specification.
+void pcm2aif(const char* pcm_filename, const char* aif_filename, uint32_t base_note) {
+ struct Bytes* pcm = read_bytearray(pcm_filename);
+
+ AifData* aif_data = malloc(sizeof(AifData));
+
+ uint32_t flags;
+ LOAD_U32_LE(flags, pcm->data + 0);
+ aif_data->has_loop = flags & 0x40000000;
+ bool compressed = flags & 1;
+
+ uint32_t pitch_adjust;
+ LOAD_U32_LE(pitch_adjust, pcm->data + 4);
+ aif_data->sample_rate = pitch_adjust / 1024.0;
+
+ LOAD_U32_LE(aif_data->loop_offset, pcm->data + 8);
+ LOAD_U32_LE(aif_data->num_samples, pcm->data + 12);
+ aif_data->num_samples += 1;
+
+ if (compressed) {
+ struct Bytes* delta = pcm;
+ uint8_t* pcm_data = pcm->data;
+ delta->length -= 0x10;
+ delta->data += 0x10;
+ pcm = delta_decompress(delta, aif_data->num_samples);
+ free(pcm_data);
+ free(delta);
+ } else {
+ pcm->length -= 0x10;
+ pcm->data += 0x10;
+ }
+
+ aif_data->samples = malloc(pcm->length);
+ memcpy(aif_data->samples, pcm->data, pcm->length);
+
+ struct Bytes* aif = malloc(sizeof(struct Bytes));
+ aif->length = 54 + 60 + pcm->length;
+ aif->data = malloc(aif->length);
+
+ long pos = 0;
+
+ // First, write the FORM header chunk.
+ // FORM Chunk ckID
+ aif->data[pos++] = 'F';
+ aif->data[pos++] = 'O';
+ aif->data[pos++] = 'R';
+ aif->data[pos++] = 'M';
+
+ // FORM Chunk ckSize
+ unsigned long form_size = pos;
+ unsigned long data_size = aif->length - 8;
+ aif->data[pos++] = ((data_size >> 24) & 0xFF);
+ aif->data[pos++] = ((data_size >> 16) & 0xFF);
+ aif->data[pos++] = ((data_size >> 8) & 0xFF);
+ aif->data[pos++] = (data_size & 0xFF);
+
+ // FORM Chunk formType
+ aif->data[pos++] = 'A';
+ aif->data[pos++] = 'I';
+ aif->data[pos++] = 'F';
+ aif->data[pos++] = 'F';
+
+ // Next, write the Common Chunk
+ // Common Chunk ckID
+ aif->data[pos++] = 'C';
+ aif->data[pos++] = 'O';
+ aif->data[pos++] = 'M';
+ aif->data[pos++] = 'M';
+
+ // Common Chunk ckSize
+ aif->data[pos++] = 0;
+ aif->data[pos++] = 0;
+ aif->data[pos++] = 0;
+ aif->data[pos++] = 18;
+
+ // Common Chunk numChannels
+ aif->data[pos++] = 0;
+ aif->data[pos++] = 1; // 1 channel
+
+ // Common Chunk numSampleFrames
+ aif->data[pos++] = ((aif_data->num_samples >> 24) & 0xFF);
+ aif->data[pos++] = ((aif_data->num_samples >> 16) & 0xFF);
+ aif->data[pos++] = ((aif_data->num_samples >> 8) & 0xFF);
+ aif->data[pos++] = (aif_data->num_samples & 0xFF);
+
+ // Common Chunk sampleSize
+ aif->data[pos++] = 0;
+ aif->data[pos++] = 8; // 8 bits per sample
+
+ // Common Chunk sampleRate
+ // double sample_rate = pitch_adjust / 1024.0;
+ uint8_t sample_rate_buffer[10];
+ ieee754_write_extended(aif_data->sample_rate, sample_rate_buffer);
+ for (int i = 0; i < 10; i++) {
+ aif->data[pos++] = sample_rate_buffer[i];
+ }
+
+ if (aif_data->has_loop) {
+
+ // Marker Chunk ckID
+ aif->data[pos++] = 'M';
+ aif->data[pos++] = 'A';
+ aif->data[pos++] = 'R';
+ aif->data[pos++] = 'K';
+
+ // Marker Chunk ckSize
+ aif->data[pos++] = 0;
+ aif->data[pos++] = 0;
+ aif->data[pos++] = 0;
+ aif->data[pos++] = 12 + (aif_data->has_loop ? 12 : 0);
+
+ // Marker Chunk numMarkers
+ aif->data[pos++] = 0;
+ aif->data[pos++] = (aif_data->has_loop ? 2 : 1);
+
+ // Marker loop start
+ aif->data[pos++] = 0;
+ aif->data[pos++] = 1; // id = 1
+
+ long loop_start = aif_data->loop_offset;
+ aif->data[pos++] = ((loop_start >> 24) & 0xFF);
+ aif->data[pos++] = ((loop_start >> 16) & 0xFF);
+ aif->data[pos++] = ((loop_start >> 8) & 0xFF);
+ aif->data[pos++] = (loop_start & 0xFF); // position
+
+ aif->data[pos++] = 5; // pascal-style string length
+ aif->data[pos++] = 'S';
+ aif->data[pos++] = 'T';
+ aif->data[pos++] = 'A';
+ aif->data[pos++] = 'R';
+ aif->data[pos++] = 'T'; // markerName
+
+ // Marker loop end
+ aif->data[pos++] = 0;
+ aif->data[pos++] = (aif_data->has_loop ? 2 : 1); // id = 2
+
+ long loop_end = aif_data->num_samples;
+ aif->data[pos++] = ((loop_end >> 24) & 0xFF);
+ aif->data[pos++] = ((loop_end >> 16) & 0xFF);
+ aif->data[pos++] = ((loop_end >> 8) & 0xFF);
+ aif->data[pos++] = (loop_end & 0xFF); // position
+
+ aif->data[pos++] = 3; // pascal-style string length
+ aif->data[pos++] = 'E';
+ aif->data[pos++] = 'N';
+ aif->data[pos++] = 'D';
+ }
+
+ // Instrument Chunk ckID
+ aif->data[pos++] = 'I';
+ aif->data[pos++] = 'N';
+ aif->data[pos++] = 'S';
+ aif->data[pos++] = 'T';
+
+ // Instrument Chunk ckSize
+ aif->data[pos++] = 0;
+ aif->data[pos++] = 0;
+ aif->data[pos++] = 0;
+ aif->data[pos++] = 20;
+
+ aif->data[pos++] = base_note; // baseNote
+ aif->data[pos++] = 0; // detune
+ aif->data[pos++] = 0; // lowNote
+ aif->data[pos++] = 127; // highNote
+ aif->data[pos++] = 1; // lowVelocity
+ aif->data[pos++] = 127; // highVelocity
+ aif->data[pos++] = 0; // gain (hi)
+ aif->data[pos++] = 0; // gain (lo)
+
+ // Instrument Chunk sustainLoop
+ aif->data[pos++] = 0;
+ aif->data[pos++] = 1; // playMode = ForwardLooping
+
+ aif->data[pos++] = 0;
+ aif->data[pos++] = 1; // beginLoop marker id
+
+ aif->data[pos++] = 0;
+ aif->data[pos++] = 2; // endLoop marker id
+
+ // Instrument Chunk releaseLoop
+ aif->data[pos++] = 0;
+ aif->data[pos++] = 1; // playMode = ForwardLooping
+
+ aif->data[pos++] = 0;
+ aif->data[pos++] = 1; // beginLoop marker id
+
+ aif->data[pos++] = 0;
+ aif->data[pos++] = 2; // endLoop marker id
+
+ // Finally, write the Sound Data Chunk
+ // Sound Data Chunk ckID
+ aif->data[pos++] = 'S';
+ aif->data[pos++] = 'S';
+ aif->data[pos++] = 'N';
+ aif->data[pos++] = 'D';
+
+ // Sound Data Chunk ckSize
+ unsigned long sound_data_size = pcm->length + 8;
+ aif->data[pos++] = ((sound_data_size >> 24) & 0xFF);
+ aif->data[pos++] = ((sound_data_size >> 16) & 0xFF);
+ aif->data[pos++] = ((sound_data_size >> 8) & 0xFF);
+ aif->data[pos++] = (sound_data_size & 0xFF);
+
+ // Sound Data Chunk offset
+ aif->data[pos++] = 0;
+ aif->data[pos++] = 0;
+ aif->data[pos++] = 0;
+ aif->data[pos++] = 0;
+
+ // Sound Data Chunk blockSize
+ aif->data[pos++] = 0;
+ aif->data[pos++] = 0;
+ aif->data[pos++] = 0;
+ aif->data[pos++] = 0;
+
+ // Sound Data Chunk soundData
+ for (unsigned int i = 0; i < aif_data->loop_offset; i++) {
+ aif->data[pos++] = aif_data->samples[i];
+ }
+
+ int j = 0;
+ for (unsigned int i = aif_data->loop_offset; i < pcm->length; i++) {
+ int pcm_index = aif_data->loop_offset + (j++ % (pcm->length - aif_data->loop_offset));
+ aif->data[pos++] = aif_data->samples[pcm_index];
+ }
+
+ aif->length = pos;
+
+ // Go back and rewrite ckSize
+ data_size = aif->length - 8;
+ aif->data[form_size + 0] = ((data_size >> 24) & 0xFF);
+ aif->data[form_size + 1] = ((data_size >> 16) & 0xFF);
+ aif->data[form_size + 2] = ((data_size >> 8) & 0xFF);
+ aif->data[form_size + 3] = (data_size & 0xFF);
+
+ write_bytearray(aif_filename, aif);
+
+ free(aif->data);
+ free(aif);
+}
+
+void usage(void) {
+ fprintf(stderr, "Usage: aif2pcm bin_file [aif_file]\n");
+ fprintf(stderr, " aif2pcm aif_file [bin_file] [--compress]\n");
+}
+
+int main(int argc, char** argv) {
+ if (argc < 2) {
+ usage();
+ exit(1);
+ }
+
+ char* input_file = argv[1];
+ char* extension = get_file_extension(input_file);
+ char* output_file;
+ bool compressed = false;
+
+ if (argc > 3) {
+ for (int i = 3; i < argc; i++) {
+ if (strcmp(argv[i], "--compress") == 0) {
+ compressed = true;
+ }
+ }
+ }
+
+ if (strcmp(extension, "aif") == 0 || strcmp(extension, "aiff") == 0) {
+ if (argc >= 3) {
+ output_file = argv[2];
+ aif2pcm(input_file, output_file, compressed);
+ } else {
+ output_file = new_file_extension(input_file, "bin");
+ aif2pcm(input_file, output_file, compressed);
+ free(output_file);
+ }
+ } else if (strcmp(extension, "bin") == 0) {
+ if (argc >= 3) {
+ output_file = argv[2];
+ pcm2aif(input_file, output_file, 60);
+ } else {
+ output_file = new_file_extension(input_file, "aif");
+ pcm2aif(input_file, output_file, 60);
+ free(output_file);
+ }
+ } else {
+ FATAL_ERROR("Input file must be .aif or .bin: '%s'\n", input_file);
+ }
+
+ return 0;
+}
diff --git a/tools/src/asset_processor/CMakeLists.txt b/tools/src/asset_processor/CMakeLists.txt
new file mode 100644
index 00000000..4709cd52
--- /dev/null
+++ b/tools/src/asset_processor/CMakeLists.txt
@@ -0,0 +1,8 @@
+file(GLOB_RECURSE sources *.cpp)
+
+add_executable(asset_processor ${sources})
+target_include_directories(asset_processor PRIVATE .)
+target_link_libraries(asset_processor PRIVATE project_settings)
+target_link_libraries(asset_processor PRIVATE fmt::fmt nlohmann_json::nlohmann_json filesystem util)
+
+install(TARGETS asset_processor RUNTIME DESTINATION bin)
diff --git a/tools/src/asset_processor/assets/aif.cpp b/tools/src/asset_processor/assets/aif.cpp
new file mode 100644
index 00000000..19324ac4
--- /dev/null
+++ b/tools/src/asset_processor/assets/aif.cpp
@@ -0,0 +1,27 @@
+#include "aif.h"
+#include "util.h"
+
+std::filesystem::path AifAsset::generateAssetPath() {
+ std::filesystem::path asset_path = path;
+ return asset_path.replace_extension(".aif");
+}
+
+void AifAsset::convertToHumanReadable(const std::vector<char>& baserom) {
+ (void)baserom;
+
+ std::filesystem::path toolsPath = "tools";
+ std::vector<std::string> cmd;
+ cmd.push_back(toolsPath / "bin" / "aif2pcm");
+ cmd.push_back(path);
+ cmd.push_back(assetPath);
+ check_call(cmd);
+}
+
+void AifAsset::buildToBinary() {
+ std::filesystem::path toolsPath = "tools";
+ std::vector<std::string> cmd;
+ cmd.push_back(toolsPath / "bin" / "aif2pcm");
+ cmd.push_back(assetPath);
+ cmd.push_back(path);
+ check_call(cmd);
+} \ No newline at end of file
diff --git a/tools/src/asset_processor/assets/aif.h b/tools/src/asset_processor/assets/aif.h
new file mode 100644
index 00000000..131e1260
--- /dev/null
+++ b/tools/src/asset_processor/assets/aif.h
@@ -0,0 +1,11 @@
+#include "asset.h"
+
+class AifAsset : public BaseAsset {
+ public:
+ using BaseAsset::BaseAsset;
+ virtual void convertToHumanReadable(const std::vector<char>& baserom);
+ virtual void buildToBinary();
+
+ private:
+ virtual std::filesystem::path generateAssetPath();
+}; \ No newline at end of file
diff --git a/tools/src/asset_processor/assets/animation.cpp b/tools/src/asset_processor/assets/animation.cpp
new file mode 100644
index 00000000..fb14fea4
--- /dev/null
+++ b/tools/src/asset_processor/assets/animation.cpp
@@ -0,0 +1,32 @@
+#include "animation.h"
+#include "reader.h"
+#include <fmt/format.h>
+#include <util/file.h>
+
+void AnimationAsset::convertToHumanReadable(const std::vector<char>& baserom) {
+ Reader reader(baserom, start, size);
+ bool end_of_animation = false;
+ auto file = util::open_file(assetPath, "w");
+ while (!end_of_animation && reader.cursor + 3 < size) {
+ u8 frame_index = reader.read_u8();
+ u8 keyframe_duration = reader.read_u8();
+ u8 bitfield = reader.read_u8();
+ u8 bitfield2 = reader.read_u8();
+ end_of_animation = (bitfield2 & 0x80) != 0;
+ auto line = fmt::format("\tkeyframe frame_index={}", frame_index);
+ line += opt_param(", duration={}", 0, keyframe_duration);
+ line += opt_param(", bitfield={:#x}", 0, bitfield);
+ line += opt_param(", bitfield2={:#x}", 0, bitfield2);
+ std::fputs(line.c_str(), file.get());
+ std::fputc('\n', file.get());
+ }
+
+ if (!end_of_animation) {
+ std::fputs("@ TODO why no terminator?\n", file.get());
+ }
+
+ while (reader.cursor < size) {
+ u8 keyframe_count = reader.read_u8();
+ fmt::print(file.get(), "\t.byte {} @ keyframe count\n", keyframe_count);
+ }
+}
diff --git a/tools/src/asset_processor/assets/animation.h b/tools/src/asset_processor/assets/animation.h
new file mode 100644
index 00000000..751ac8fe
--- /dev/null
+++ b/tools/src/asset_processor/assets/animation.h
@@ -0,0 +1,7 @@
+#include "macroasm.h"
+
+class AnimationAsset : public BaseMacroAsmAsset {
+ public:
+ using BaseMacroAsmAsset::BaseMacroAsmAsset;
+ virtual void convertToHumanReadable(const std::vector<char>& baserom);
+}; \ No newline at end of file
diff --git a/tools/src/asset_processor/assets/asset.cpp b/tools/src/asset_processor/assets/asset.cpp
new file mode 100644
index 00000000..ebb0d700
--- /dev/null
+++ b/tools/src/asset_processor/assets/asset.cpp
@@ -0,0 +1,7 @@
+#include "asset.h"
+#include <util/file.h>
+
+void BaseAsset::extractBinary(const std::vector<char>& baserom) {
+ auto file = util::open_file(path, "w");
+ std::fwrite(baserom.data() + start, 1, static_cast<size_t>(size), file.get());
+}
diff --git a/tools/src/asset_processor/assets/asset.h b/tools/src/asset_processor/assets/asset.h
new file mode 100644
index 00000000..f34485a2
--- /dev/null
+++ b/tools/src/asset_processor/assets/asset.h
@@ -0,0 +1,79 @@
+#ifndef ASSET_H
+#define ASSET_H
+
+#include "util.h"
+#include <filesystem>
+#include <nlohmann/json_fwd.hpp>
+#include <string>
+#include <iostream>
+#include <utility>
+
+class BaseAsset {
+ public:
+ BaseAsset(std::filesystem::path path_, int start_, int size_, const nlohmann::json& asset_)
+ : path(std::move(path_)), start(start_), size(size_), asset(asset_) {
+ }
+
+ virtual ~BaseAsset() = default;
+
+ void setup() {
+ // Cannot call virtual functions in constructor, so another function call is necessary
+ assetPath = generateAssetPath();
+ buildPath = generateBuildPath();
+ }
+
+ // Extract the binary segment for this asset from the baserom and store it in a separate file.
+ virtual void extractBinary(const std::vector<char>& baserom);
+
+ // Convert the binary data for this asset to a human readable form.
+ virtual void convertToHumanReadable([[maybe_unused]] const std::vector<char>& baserom) {
+ }
+
+ // Build the asset from the human readable form back to the binary.
+ virtual void buildToBinary() {
+ }
+
+ // Returns the path to the binary file extracted from the baserom.
+ [[nodiscard]] std::filesystem::path getPath() const {
+ return path;
+ }
+
+ // Returns the path to the human readable asset file.
+ [[nodiscard]] std::filesystem::path getAssetPath() const {
+ return assetPath;
+ }
+
+ // Returns the path to the resulting file after building.
+ [[nodiscard]] std::filesystem::path getBuildPath() const {
+ return buildPath;
+ }
+
+ // Returns the base of the filename of the asset.
+ [[nodiscard]] std::string getSymbol() const {
+ // Need to get the stem twice to remove both of the .4bpp.lz extensions.
+ return (path.stem()).stem();
+ }
+
+ // Returns the start address of this asset.
+ [[nodiscard]] int getStart() const {
+ return start;
+ }
+
+ protected:
+ std::filesystem::path path;
+ std::filesystem::path assetPath;
+ std::filesystem::path buildPath;
+ int start;
+ int size;
+ const nlohmann::json& asset;
+
+ private:
+ virtual std::filesystem::path generateAssetPath() {
+ return path;
+ }
+ virtual std::filesystem::path generateBuildPath() {
+ return path;
+ }
+};
+
+#endif \ No newline at end of file
diff --git a/tools/src/asset_processor/assets/exitlist.cpp b/tools/src/asset_processor/assets/exitlist.cpp
new file mode 100644
index 00000000..85051fd1
--- /dev/null
+++ b/tools/src/asset_processor/assets/exitlist.cpp
@@ -0,0 +1,43 @@
+#include "exitlist.h"
+#include "reader.h"
+#include <fmt/format.h>
+#include <util/file.h>
+
+void ExitListAsset::convertToHumanReadable(const std::vector<char>& baserom) {
+ Reader reader(baserom, start, size);
+ auto file = util::open_file(assetPath, "w");
+ while (reader.cursor < size) {
+ u16 transition_type = reader.read_u16();
+ u16 x_pos = reader.read_u16();
+ u16 y_pos = reader.read_u16();
+ u16 dest_x = reader.read_u16();
+ u16 dest_y = reader.read_u16();
+ u8 screen_edge = reader.read_u8();
+ u8 dest_area = reader.read_u8();
+ u8 dest_room = reader.read_u8();
+ u8 unknown_2 = reader.read_u8();
+ u8 unknown_3 = reader.read_u8();
+ u8 unknown_4 = reader.read_u8();
+ u16 unknown_5 = reader.read_u16();
+ u16 padding_1 = reader.read_u16();
+ if (transition_type == 0xffff) {
+ std::fputs("\texit_list_end\n", file.get());
+ break;
+ }
+ auto line = fmt::format("\texit transition={}", transition_type);
+ line += opt_param(", x={:#x}", 0, x_pos);
+ line += opt_param(", y={:#x}", 0, y_pos);
+ line += opt_param(", destX={:#x}", 0, dest_x);
+ line += opt_param(", destY={:#x}", 0, dest_y);
+ line += opt_param(", screenEdge={:#x}", 0, screen_edge);
+ line += opt_param(", destArea={:#x}", 0, dest_area);
+ line += opt_param(", destRoom={:#x}", 0, dest_room);
+ line += opt_param(", unknown={:#x}", 0, unknown_2);
+ line += opt_param(", unknown2={:#x}", 0, unknown_3);
+ line += opt_param(", unknown3={:#x}", 0, unknown_4);
+ line += opt_param(", unknown4={:#x}", 0, unknown_5);
+ line += opt_param(", padding={:#x}", 0, padding_1);
+ std::fputs(line.c_str(), file.get());
+ std::fputc('\n', file.get());
+ }
+} \ No newline at end of file
diff --git a/tools/src/asset_processor/assets/exitlist.h b/tools/src/asset_processor/assets/exitlist.h
new file mode 100644
index 00000000..2b039124
--- /dev/null
+++ b/tools/src/asset_processor/assets/exitlist.h
@@ -0,0 +1,7 @@
+#include "macroasm.h"
+
+class ExitListAsset : public BaseMacroAsmAsset {
+ public:
+ using BaseMacroAsmAsset::BaseMacroAsmAsset;
+ virtual void convertToHumanReadable(const std::vector<char>& baserom);
+}; \ No newline at end of file
diff --git a/tools/src/asset_processor/assets/frameobjlists.cpp b/tools/src/asset_processor/assets/frameobjlists.cpp
new file mode 100644
index 00000000..d2ac6b22
--- /dev/null
+++ b/tools/src/asset_processor/assets/frameobjlists.cpp
@@ -0,0 +1,75 @@
+#include "frameobjlists.h"
+#include "reader.h"
+#include <algorithm>
+#include <fmt/format.h>
+#include <util/file.h>
+
+void FrameObjListsAsset::convertToHumanReadable(const std::vector<char>& baserom) {
+ Reader reader(baserom, start, size);
+
+ std::vector<u32> first_level;
+ std::vector<u32> second_level;
+
+ auto file = util::open_file(assetPath, "w");
+ std::fputs("@ First level of offsets\n", file.get());
+
+ while (reader.cursor < size) {
+ if (std::find(first_level.begin(), first_level.end(), reader.cursor) != first_level.end()) {
+ // End of first level
+ break;
+ }
+
+ u32 pointer = reader.read_u32();
+ first_level.push_back(pointer);
+ fmt::print(file.get(), "\t.4byte {:#x}\n", pointer);
+ }
+
+ std::fputs("\n@ Second level of offsets\n", file.get());
+
+ while (reader.cursor < size) {
+ if (std::find(second_level.begin(), second_level.end(), reader.cursor) != second_level.end()) {
+ // End of second level
+ break;
+ }
+ u32 pointer = reader.read_u32();
+ second_level.push_back(pointer);
+ fmt::print(file.get(), "\t.4byte {:#x}\n", pointer);
+ }
+
+ u32 max_second_level = *std::max_element(second_level.begin(), second_level.end());
+
+ while (reader.cursor < size) {
+ if (static_cast<u32>(reader.cursor) > max_second_level) {
+ break;
+ }
+ if (std::find(second_level.begin(), second_level.end(), reader.cursor) == second_level.end()) {
+ // Find nearest next value that is in the second level
+ int next = -1;
+ for (const auto& i : second_level) {
+ if (i > static_cast<u32>(reader.cursor) && (next == -1 || i < static_cast<u32>(next))) {
+ next = static_cast<int>(i);
+ }
+ }
+ int diff = next - reader.cursor;
+ fmt::print(file.get(), "@ Skipping {} bytes\n", diff);
+ for (int i = 0; i < diff; i++) {
+ u8 byte = reader.read_u8();
+ fmt::print(file.get(), "\t.byte {}\n", byte);
+ }
+ }
+ u8 num_objects = reader.read_u8();
+ fmt::print(file.get(), "\t.byte {} @ num_objs\n", num_objects);
+
+ for (int i = 0; i < num_objects; i++) {
+ s8 x_offset = reader.read_s8();
+ s8 y_offset = reader.read_s8();
+ u8 bitfield = reader.read_u8();
+ u16 bitfield2 = reader.read_u16();
+ auto line = fmt::format("\tobj x={}, y={}", x_offset, y_offset);
+ line += opt_param(", bitfield={:#x}", 0, bitfield);
+ line += opt_param(", bitfield2={:#x}", 0, bitfield2);
+ std::fputs(line.c_str(), file.get());
+ std::fputc('\n', file.get());
+ }
+ }
+} \ No newline at end of file
diff --git a/tools/src/asset_processor/assets/frameobjlists.h b/tools/src/asset_processor/assets/frameobjlists.h
new file mode 100644
index 00000000..728e0229
--- /dev/null
+++ b/tools/src/asset_processor/assets/frameobjlists.h
@@ -0,0 +1,7 @@
+#include "macroasm.h"
+
+class FrameObjListsAsset : public BaseMacroAsmAsset {
+ public:
+ using BaseMacroAsmAsset::BaseMacroAsmAsset;
+ virtual void convertToHumanReadable(const std::vector<char>& baserom);
+}; \ No newline at end of file
diff --git a/tools/src/asset_processor/assets/gfx.cpp b/tools/src/asset_processor/assets/gfx.cpp
new file mode 100644
index 00000000..39a11e3c
--- /dev/null
+++ b/tools/src/asset_processor/assets/gfx.cpp
@@ -0,0 +1,38 @@
+#include "gfx.h"
+#include "util.h"
+#include <nlohmann/json.hpp>
+
+std::filesystem::path GfxAsset::generateAssetPath() {
+ std::filesystem::path pngPath = path;
+ if (pngPath.extension() == ".lz") {
+ pngPath.replace_extension("");
+ }
+ pngPath.replace_extension(".png");
+ return pngPath;
+}
+
+void GfxAsset::convertToHumanReadable(const std::vector<char>& baserom) {
+ (void)baserom;
+
+ std::filesystem::path toolsPath = "tools";
+ std::vector<std::string> cmd;
+ cmd.push_back(toolsPath / "bin" / "gbagfx");
+ cmd.push_back(path);
+ cmd.push_back(assetPath);
+ if (asset.contains("options")) {
+ for (const auto& it : asset["options"].items()) {
+ cmd.push_back("-" + it.key());
+ cmd.push_back(to_string(it.value()));
+ }
+ }
+ check_call(cmd);
+}
+
+void GfxAsset::buildToBinary() {
+ std::filesystem::path toolsPath = "tools";
+ std::vector<std::string> cmd;
+ cmd.push_back(toolsPath / "bin" / "gbagfx");
+ cmd.push_back(assetPath);
+ cmd.push_back(path);
+ check_call(cmd);
+} \ No newline at end of file
diff --git a/tools/src/asset_processor/assets/gfx.h b/tools/src/asset_processor/assets/gfx.h
new file mode 100644
index 00000000..394961ea
--- /dev/null
+++ b/tools/src/asset_processor/assets/gfx.h
@@ -0,0 +1,11 @@
+#include "asset.h"
+
+class GfxAsset : public BaseAsset {
+ public:
+ using BaseAsset::BaseAsset;
+ virtual void convertToHumanReadable(const std::vector<char>& baserom);
+ virtual void buildToBinary();
+
+ private:
+ virtual std::filesystem::path generateAssetPath();
+}; \ No newline at end of file
diff --git a/tools/src/asset_processor/assets/macroasm.cpp b/tools/src/asset_processor/assets/macroasm.cpp
new file mode 100644
index 00000000..c64df2e1
--- /dev/null
+++ b/tools/src/asset_processor/assets/macroasm.cpp
@@ -0,0 +1,20 @@
+#include "macroasm.h"
+#include <fmt/format.h>
+#include <util/file.h>
+
+std::filesystem::path BaseMacroAsmAsset::generateAssetPath() {
+ std::filesystem::path asset_path = path;
+ return asset_path.replace_extension(".s");
+}
+
+std::filesystem::path BaseMacroAsmAsset::generateBuildPath() {
+ std::filesystem::path build_path = path;
+ return build_path.replace_extension(".s");
+}
+
+void BaseMacroAsmAsset::extractBinary(const std::vector<char>& baserom) {
+ BaseAsset::extractBinary(baserom);
+ // Create dummy .s file that just incbins the .bin file.
+ auto file = util::open_file(assetPath, "w");
+ fmt::print(file.get(), "\t.incbin \"{}\"\n", path.native());
+} \ No newline at end of file
diff --git a/tools/src/asset_processor/assets/macroasm.h b/tools/src/asset_processor/assets/macroasm.h
new file mode 100644
index 00000000..ca1f4abf
--- /dev/null
+++ b/tools/src/asset_processor/assets/macroasm.h
@@ -0,0 +1,17 @@
+#ifndef MACROASM_H
+#define MACROASM_H
+
+#include "asset.h"
+
+// Common class for all assets that are converted to asm files consisting of macros.
+class BaseMacroAsmAsset : public BaseAsset {
+ using BaseAsset::BaseAsset;
+
+ public:
+ virtual void extractBinary(const std::vector<char>& baserom);
+
+ private:
+ virtual std::filesystem::path generateAssetPath();
+ virtual std::filesystem::path generateBuildPath();
+};
+#endif
diff --git a/tools/src/asset_processor/assets/midi.cpp b/tools/src/asset_processor/assets/midi.cpp
new file mode 100644
index 00000000..1bc575fb
--- /dev/null
+++ b/tools/src/asset_processor/assets/midi.cpp
@@ -0,0 +1,147 @@
+#include "midi.h"
+#include "reader.h"
+#include "util.h"
+#include <filesystem>
+#include <nlohmann/json.hpp>
+#include <fmt/format.h>
+#include <util/file.h>
+
+extern std::string gBaseromPath;
+
+std::filesystem::path MidiAsset::generateAssetPath() {
+ std::filesystem::path asset_path = path;
+ return asset_path.replace_extension(".mid");
+}
+
+std::filesystem::path MidiAsset::generateBuildPath() {
+ std::filesystem::path build_path = path;
+ return build_path.replace_extension(".s");
+}
+
+void MidiAsset::extractBinary(const std::vector<char>& baserom) {
+ // Custom extraction as we need a label in the middle.
+ std::string label = path.stem();
+
+ std::filesystem::path tracksPath = path;
+ tracksPath.replace_filename(label + "_tracks.bin");
+ std::filesystem::path headerPath = path;
+ headerPath.replace_filename(label + "_header.bin");
+
+ int headerOffset = asset["options"]["headerOffset"];
+
+ // Extract tracks
+ {
+ auto file = util::open_file(tracksPath, "w");
+ std::fwrite(baserom.data() + start, 1, static_cast<size_t>(headerOffset), file.get());
+ }
+ // Extract header
+ {
+ auto file = util::open_file(headerPath, "w");
+ std::fwrite(baserom.data() + start + headerOffset, 1, static_cast<size_t>(size - headerOffset), file.get());
+ }
+
+ // Create dummy .s file.
+ auto file = util::open_file(buildPath, "w");
+ fmt::print(file.get(), "\t.incbin \"{}\"\n", tracksPath.native());
+ fmt::print(file.get(), "{}::\n", label);
+ fmt::print(file.get(), "\t.incbin \"{}\"\n", headerPath.native());
+}
+
+void MidiAsset::parseOptions(std::vector<std::string>& commonParams, std::vector<std::string>& agb2midParams) {
+ bool exactGateTime = true;
+
+ for (const auto& it : asset["options"].items()) {
+ const std::string& key = it.key();
+ if (key == "group" || key == "G") {
+ commonParams.emplace_back("-G");
+ commonParams.push_back(to_string(it.value()));
+ } else if (key == "priority" || key == "P") {
+ commonParams.emplace_back("-P");
+ commonParams.push_back(to_string(it.value()));
+ } else if (key == "reverb" || key == "R") {
+ commonParams.emplace_back("-R");
+ commonParams.push_back(to_string(it.value()));
+ } else if (key == "nominator") {
+ agb2midParams.emplace_back("-n");
+ agb2midParams.push_back(to_string(it.value()));
+ } else if (key == "denominator") {
+ agb2midParams.emplace_back("-d");
+ agb2midParams.push_back(to_string(it.value()));
+ } else if (key == "timeChanges") {
+ const nlohmann::json& value = it.value();
+ if (value.is_array()) {
+ // Multiple time changes
+ for (const auto& change : value) {
+ agb2midParams.emplace_back("-t");
+ agb2midParams.push_back(to_string(change["nominator"]));
+ agb2midParams.push_back(to_string(change["denominator"]));
+ agb2midParams.push_back(to_string(change["time"]));
+ }
+ } else {
+ agb2midParams.emplace_back("-t");
+ agb2midParams.push_back(to_string(value["nominator"]));
+ agb2midParams.push_back(to_string(value["denominator"]));
+ agb2midParams.push_back(to_string(value["time"]));
+ }
+ } else if (key == "exactGateTime") {
+ if (it.value().get<int>() == 1) {
+ exactGateTime = true;
+ } else {
+ exactGateTime = false;
+ }
+ } else if (key == "headerOffset") {
+ // ignore here
+ } else {
+ commonParams.push_back("-" + key);
+ commonParams.push_back(to_string(it.value()));
+ }
+ }
+
+ if (exactGateTime) {
+ commonParams.emplace_back("-E");
+ }
+}
+
+void MidiAsset::convertToHumanReadable(const std::vector<char>& baserom) {
+ (void)baserom;
+
+ // Convert the options
+ std::vector<std::string> commonParams;
+ std::vector<std::string> agb2midParams;
+ parseOptions(commonParams, agb2midParams);
+
+ int headerOffset = asset["options"]["headerOffset"];
+
+ std::filesystem::path toolPath = "tools";
+ std::vector<std::string> cmd;
+ cmd.push_back(toolPath / "bin" / "agb2mid");
+ cmd.push_back(gBaseromPath);
+ cmd.push_back(fmt::format("{:#x}", start + headerOffset));
+ cmd.push_back(gBaseromPath); // TODO deduplicate?
+ cmd.push_back(assetPath);
+ cmd.insert(cmd.end(), commonParams.begin(), commonParams.end());
+ cmd.insert(cmd.end(), agb2midParams.begin(), agb2midParams.end());
+ check_call(cmd);
+
+ // We also need to build the mid to an s file here, so we get shiftability after converting.
+ cmd.clear();
+ cmd.push_back(toolPath / "bin" / "mid2agb");
+ cmd.push_back(assetPath);
+ cmd.push_back(buildPath);
+ cmd.insert(cmd.end(), commonParams.begin(), commonParams.end());
+ check_call(cmd);
+}
+
+void MidiAsset::buildToBinary() {
+ // Convert the options
+ std::vector<std::string> commonParams;
+ std::vector<std::string> agb2midParams;
+ parseOptions(commonParams, agb2midParams);
+ std::filesystem::path toolPath = "tools";
+ std::vector<std::string> cmd;
+ cmd.push_back(toolPath / "bin" / "mid2agb");
+ cmd.push_back(assetPath);
+ cmd.push_back(buildPath);
+ cmd.insert(cmd.end(), commonParams.begin(), commonParams.end());
+ check_call(cmd);
+} \ No newline at end of file
diff --git a/tools/src/asset_processor/assets/midi.h b/tools/src/asset_processor/assets/midi.h
new file mode 100644
index 00000000..4bd362c6
--- /dev/null
+++ b/tools/src/asset_processor/assets/midi.h
@@ -0,0 +1,14 @@
+#include "asset.h"
+
+class MidiAsset : public BaseAsset {
+ public:
+ using BaseAsset::BaseAsset;
+ virtual void extractBinary(const std::vector<char>& baserom);
+ virtual void convertToHumanReadable(const std::vector<char>& baserom);
+ virtual void buildToBinary();
+
+ private:
+ void parseOptions(std::vector<std::string>& commonParams, std::vector<std::string>& agb2midParams);
+ virtual std::filesystem::path generateAssetPath();
+ virtual std::filesystem::path generateBuildPath();
+}; \ No newline at end of file
diff --git a/tools/src/asset_processor/assets/palette.cpp b/tools/src/asset_processor/assets/palette.cpp
new file mode 100644
index 00000000..f37504e7
--- /dev/null
+++ b/tools/src/asset_processor/assets/palette.cpp
@@ -0,0 +1,27 @@
+#include "palette.h"
+#include "util.h"
+
+std::filesystem::path PaletteAsset::generateAssetPath() {
+ std::filesystem::path asset_path = path;
+ return asset_path.replace_extension(".pal");
+}
+
+void PaletteAsset::convertToHumanReadable(const std::vector<char>& baserom) {
+ (void)baserom;
+
+ std::filesystem::path toolsPath = "tools";
+ std::vector<std::string> cmd;
+ cmd.push_back(toolsPath / "bin" / "gbagfx");
+ cmd.push_back(path);
+ cmd.push_back(assetPath);
+ check_call(cmd);
+}
+
+void PaletteAsset::buildToBinary() {
+ std::filesystem::path toolsPath = "tools";
+ std::vector<std::string> cmd;
+ cmd.push_back(toolsPath / "bin" / "gbagfx");
+ cmd.push_back(assetPath);
+ cmd.push_back(path);
+ check_call(cmd);
+} \ No newline at end of file
diff --git a/tools/src/asset_processor/assets/palette.h b/tools/src/asset_processor/assets/palette.h
new file mode 100644
index 00000000..10c36ac2
--- /dev/null
+++ b/tools/src/asset_processor/assets/palette.h
@@ -0,0 +1,11 @@
+#include "asset.h"
+
+class PaletteAsset : public BaseAsset {
+ public:
+ using BaseAsset::BaseAsset;
+ virtual void convertToHumanReadable(const std::vector<char>& baserom);
+ virtual void buildToBinary();
+
+ private:
+ virtual std::filesystem::path generateAssetPath();
+}; \ No newline at end of file
diff --git a/tools/src/asset_processor/assets/spriteframe.cpp b/tools/src/asset_processor/assets/spriteframe.cpp
new file mode 100644
index 00000000..70a40f61
--- /dev/null
+++ b/tools/src/asset_processor/assets/spriteframe.cpp
@@ -0,0 +1,20 @@
+#include "spriteframe.h"
+#include "reader.h"
+#include <fmt/format.h>
+#include <util/file.h>
+
+void SpriteFrameAsset::convertToHumanReadable(const std::vector<char>& baserom) {
+ Reader reader(baserom, start, size);
+ auto file = util::open_file(assetPath, "w");
+ while (reader.cursor < size) {
+ u8 num_gfx_tiles = reader.read_u8();
+ u8 unk = reader.read_u8();
+ u16 first_gfx_tile_index = reader.read_u16();
+
+ auto line = fmt::format("\tsprite_frame first_tile_index={:#x}", first_gfx_tile_index);
+ line += opt_param(", num_tiles={}", 0, num_gfx_tiles);
+ line += opt_param(", unknown={:#x}", 0, unk);
+ std::fputs(line.c_str(), file.get());
+ std::fputc('\n', file.get());
+ }
+} \ No newline at end of file
diff --git a/tools/src/asset_processor/assets/spriteframe.h b/tools/src/asset_processor/assets/spriteframe.h
new file mode 100644
index 00000000..2022cc74
--- /dev/null
+++ b/tools/src/asset_processor/assets/spriteframe.h
@@ -0,0 +1,7 @@
+#include "macroasm.h"
+
+class SpriteFrameAsset : public BaseMacroAsmAsset {
+ public:
+ using BaseMacroAsmAsset::BaseMacroAsmAsset;
+ virtual void convertToHumanReadable(const std::vector<char>& baserom);
+}; \ No newline at end of file
diff --git a/tools/src/asset_processor/assets/tileset.cpp b/tools/src/asset_processor/assets/tileset.cpp
new file mode 100644
index 00000000..cd564f33
--- /dev/null
+++ b/tools/src/asset_processor/assets/tileset.cpp
@@ -0,0 +1,33 @@
+#include "tileset.h"
+#include "util.h"
+
+std::filesystem::path TilesetAsset::generateAssetPath() {
+ std::filesystem::path pngPath = path;
+ if (pngPath.extension() == ".lz") {
+ pngPath.replace_extension("");
+ }
+ pngPath.replace_extension(".png");
+ return pngPath;
+}
+
+void TilesetAsset::convertToHumanReadable(const std::vector<char>& baserom) {
+ (void)baserom;
+
+ std::filesystem::path toolsPath = "tools";
+ std::vector<std::string> cmd;
+ cmd.push_back(toolsPath / "bin" / "gbagfx");
+ cmd.push_back(path);
+ cmd.push_back(assetPath);
+ cmd.push_back("-mwidth");
+ cmd.push_back("32");
+ check_call(cmd);
+}
+
+void TilesetAsset::buildToBinary() {
+ std::filesystem::path toolsPath = "tools";
+ std::vector<std::string> cmd;
+ cmd.push_back(toolsPath / "bin" / "gbagfx");
+ cmd.push_back(assetPath);
+ cmd.push_back(path);
+ check_call(cmd);
+} \ No newline at end of file
diff --git a/tools/src/asset_processor/assets/tileset.h b/tools/src/asset_processor/assets/tileset.h
new file mode 100644
index 00000000..0affdf4d
--- /dev/null
+++ b/tools/src/asset_processor/assets/tileset.h
@@ -0,0 +1,11 @@
+#include "asset.h"
+
+class TilesetAsset : public BaseAsset {
+ public:
+ using BaseAsset::BaseAsset;
+ virtual void convertToHumanReadable(const std::vector<char>& baserom);
+ virtual void buildToBinary();
+
+ private:
+ virtual std::filesystem::path generateAssetPath();
+}; \ No newline at end of file
diff --git a/tools/src/asset_processor/main.cpp b/tools/src/asset_processor/main.cpp
new file mode 100644
index 00000000..1e6df2a0
--- /dev/null
+++ b/tools/src/asset_processor/main.cpp
@@ -0,0 +1,342 @@
+#include "main.h"
+#include "assets/aif.h"
+#include "assets/animation.h"
+#include "assets/exitlist.h"
+#include "assets/frameobjlists.h"
+#include "assets/gfx.h"
+#include "assets/midi.h"
+#include "assets/palette.h"
+#include "assets/spriteframe.h"
+#include "assets/tileset.h"
+#include "offsets.h"
+#include <filesystem>
+#include <fstream>
+#include <iostream>
+#include <nlohmann/json.hpp>
+#include <fmt/format.h>
+
+using nlohmann::json;
+
+// Does json array contain element? https://github.com/nlohmann/json/issues/399#issuecomment-844212174
+#define CONTAINS(list, value) (std::find(list.begin(), list.end(), value) != list.end())
+
+void usage() {
+ std::cout << "Usage: asset_processor [options] MODE VARIANT BUILD_PATH\n"
+ << "\n"
+ << "MODE Mode to execute\n"
+ << "VARIANT Variant to build. One of USA, JP, EU, DEMO_USA, DEMO_JP\n"
+ << "BUILD_PATH Path to the build folder\n"
+ << "\n"
+ << "Modes:\n"
+ << "extract Extract binary data from baserom\n"
+ << "convert Convert data to human readable format\n"
+ << "build Build binary data from assets\n"
+ << "\n"
+ << "Options:\n"
+ << "-v Print verbose output\n"
+ << "-h Show this help\n"
+ << "-f Force extraction of all assets (even if unmodified)\n";
+ std::exit(1);
+}
+
+bool gVerbose = false;
+bool gForceExtraction = false;
+Mode gMode;
+std::string gVariant;
+std::string gAssetsFolder;
+std::string gBaseromPath;
+
+static std::map<std::string, std::string> baseroms = { { "USA", "baserom.gba" },
+ { "EU", "baserom_eu.gba" },
+ { "JP", "baserom_jp.gba" },
+ { "DEMO_USA", "baserom_demo.gba" },
+ { "DEMO_JP", "baserom_demo_jp.gba" } };
+
+int main(int argc, char** argv) {
+
+ // Parse options.
+ int argCount = argc - 1; // Skip program name
+ char** args = &argv[1];
+
+ while (argCount > 0 && args[0][0] == '-') {
+ if (strcmp(args[0], "-v") == 0) {
+ gVerbose = true;
+ argCount--;
+ args++;
+ } else if (strcmp(args[0], "-h") == 0) {
+ argCount--;
+ args++;
+ usage();
+ } else if (strcmp(args[0], "-f") == 0) {
+ gForceExtraction = true;
+ argCount--;
+ args++;
+ } else {
+ std::cerr << "Error: Unrecognized argument `" << args[0] << "`" << std::endl;
+ std::exit(1);
+ }
+ }
+
+ if (argCount != 3) {
+ std::cerr << "Error: Not enough arguments. expected: 3, actual: " << argCount << std::endl;
+ usage();
+ }
+
+ if (strcmp(args[0], "extract") == 0) {
+ gMode = EXTRACT;
+ } else if (strcmp(args[0], "convert") == 0) {
+ gMode = CONVERT;
+ } else if (strcmp(args[0], "build") == 0) {
+ gMode = BUILD;
+ } else {
+ std::cerr << "Error: Unsupported mode `" << args[0] << "`" << std::endl;
+ std::exit(1);
+ }
+ gVariant = args[1];
+ if (gVariant != "USA" && gVariant != "EU" && gVariant != "JP" && gVariant != "DEMO_USA" && gVariant != "DEMO_JP") {
+ std::cerr << "Error: Unsupported variant `" << gVariant << "`" << std::endl;
+ std::exit(1);
+ }
+
+ gAssetsFolder = args[2];
+ gBaseromPath = baseroms[gVariant];
+
+ if (!std::filesystem::exists(gBaseromPath)) {
+ std::cerr << "Error: You need to provide a " << gVariant << " ROM as " << gBaseromPath << std::endl;
+ std::exit(1);
+ }
+
+ // Read baserom.
+ std::ifstream file(gBaseromPath, std::ios::binary | std::ios::ate);
+ auto size = file.tellg();
+ file.seekg(0, std::ios::beg);
+
+ std::vector<char> baserom(static_cast<size_t>(size));
+ if (!file.read(baserom.data(), size)) {
+ fmt::print(stderr, "Could not read baserom {}\n", gBaseromPath);
+ std::exit(1);
+ }
+ file.close();
+
+ // Gather all json configs from assets folder.
+ std::vector<std::filesystem::path> configs;
+ std::string configFolder = "assets";
+ for (const auto& entry : std::filesystem::directory_iterator(configFolder)) {
+ const auto& path = entry.path();
+ if (path.extension() == ".json") {
+ configs.push_back(path);
+ }
+ }
+ std::sort(configs.begin(), configs.end());
+
+ for (const auto& config : configs) {
+ if (gVerbose) {
+ std::cout << "Parsing " << config << "..." << std::endl;
+ }
+
+ std::ifstream input(config);
+ json assets;
+ input >> assets;
+
+ std::filesystem::file_time_type configModified = std::filesystem::last_write_time(config);
+
+ std::unique_ptr<OffsetCalculator> offsetCalculator;
+
+ int currentOffset = 0;
+ for (const auto& asset : assets) {
+ if (asset.contains("offsets")) { // Offset definition
+ if (asset["offsets"].contains(gVariant)) {
+ currentOffset = asset["offsets"][gVariant];
+ }
+ } else if (asset.contains("calculateOffsets")) { // Start offset calculation
+ if (gMode == EXTRACT || gMode == BUILD) {
+ std::filesystem::path path = gAssetsFolder;
+ path = path / asset["calculateOffsets"];
+ int baseOffset = 0;
+ // During build mode the offsets are calculated directly instead of from a base address.
+ if (gMode == EXTRACT) {
+ baseOffset = asset["start"].get<int>() + currentOffset;
+ }
+ offsetCalculator = std::make_unique<OffsetCalculator>(path, baseOffset);
+ }
+ } else if (asset.contains("path")) { // Asset definition
+
+ if (asset.contains("variants")) {
+ if (!CONTAINS(asset["variants"], gVariant)) {
+ // This asset is not used in the current variant
+ continue;
+ }
+ }
+
+ std::filesystem::path path = gAssetsFolder;
+ path = path / asset["path"];
+
+ switch (gMode) {
+ case EXTRACT: {
+ std::unique_ptr<BaseAsset> assetHandler = getAssetHandlerByType(path, asset, currentOffset);
+ if (shouldExtractAsset(path, configModified)) {
+ if (gVerbose) {
+ std::cout << "Extracting " << path << "..." << std::endl;
+ }
+
+ extractAsset(assetHandler, baserom);
+ }
+ if (offsetCalculator != nullptr) {
+ offsetCalculator->addAsset(assetHandler->getStart(), assetHandler->getSymbol());
+ }
+ break;
+ }
+ case CONVERT: {
+ std::unique_ptr<BaseAsset> assetHandler = getAssetHandlerByType(path, asset, currentOffset);
+ if (!std::filesystem::exists(assetHandler->getBuildPath())) {
+ std::cerr << "Error: Extracted binary file " << assetHandler->getBuildPath()
+ << " does not exist. Run `make` first." << std::endl;
+ std::exit(1);
+ }
+ if (shouldConvertAsset(assetHandler)) {
+ if (gVerbose) {
+ std::cout << "Converting " << assetHandler->getAssetPath() << "..." << std::endl;
+ }
+ convertAsset(assetHandler, baserom);
+ }
+ break;
+ }
+ case BUILD: {
+ std::unique_ptr<BaseAsset> assetHandler = getAssetHandlerByType(path, asset, currentOffset);
+ if (!std::filesystem::exists(assetHandler->getAssetPath())) {
+ std::cerr << "Error: Extracted asset file " << assetHandler->getAssetPath()
+ << " does not exist. Run `make extractassets` first." << std::endl;
+ std::exit(1);
+ }
+ if (shouldBuildAsset(assetHandler)) {
+ if (gVerbose) {
+ std::cout << "Building " << assetHandler->getAssetPath() << "..." << std::endl;
+ }
+ buildAsset(assetHandler);
+ }
+ if (offsetCalculator != nullptr) {
+ // New start is the end of the previous asset.
+ int start = offsetCalculator->getLastEnd();
+ // Get the size of the current asset and calculate the end position.
+ int filesize = static_cast<int>(std::filesystem::file_size(assetHandler->getBuildPath()));
+ // Align by four bytes.
+ if (filesize % 4 != 0) {
+ filesize += 4 - (filesize % 4);
+ }
+ offsetCalculator->setLastEnd(start + filesize);
+ offsetCalculator->addAsset(start, assetHandler->getSymbol());
+ }
+ break;
+ }
+ }
+ }
+ }
+ }
+
+ if (gVerbose) {
+ std::cout << "done" << std::endl;
+ }
+}
+
+std::unique_ptr<BaseAsset> getAssetHandlerByType(const std::filesystem::path& path, const json& asset,
+ const int& currentOffset) {
+
+ int start = 0;
+ if (asset.contains("start")) {
+ // Apply offset to the start of the USA variant
+ start = asset["start"].get<int>() + currentOffset;
+ } else if (asset.contains("starts")) {
+ // Use start for the current variant
+ start = asset["starts"][gVariant];
+ }
+
+ std::string type;
+ if (asset.contains("type")) {
+ type = asset["type"];
+ }
+
+ int size = 0;
+ if (asset.contains("size")) { // The asset has a size and want to be extracted first.
+ size = asset["size"]; // TODO can different sizes for the different variants ever occur?
+ }
+
+ std::unique_ptr<BaseAsset> assetHandler;
+ if (type == "tileset") {
+ assetHandler = std::make_unique<TilesetAsset>(path, start, size, asset);
+ } else if (type == "animation") {
+ assetHandler = std::make_unique<AnimationAsset>(path, start, size, asset);
+ } else if (type == "sprite_frame") {
+ assetHandler = std::make_unique<SpriteFrameAsset>(path, start, size, asset);
+ } else if (type == "exit_list") {
+ assetHandler = std::make_unique<ExitListAsset>(path, start, size, asset);
+ } else if (type == "frame_obj_lists") {
+ assetHandler = std::make_unique<FrameObjListsAsset>(path, start, size, asset);
+ } else if (type == "midi") {
+ assetHandler = std::make_unique<MidiAsset>(path, start, size, asset);
+ } else if (type == "aif") {
+ assetHandler = std::make_unique<AifAsset>(path, start, size, asset);
+ } else if (type == "gfx") {
+ assetHandler = std::make_unique<GfxAsset>(path, start, size, asset);
+ } else if (type == "palette") {
+ assetHandler = std::make_unique<PaletteAsset>(path, start, size, asset);
+ } else if (type == "map_gfx" || type == "map_layer1" || type == "map_layer2" || type == "metatiles_tile_types1" ||
+ type == "metatiles_tile_types2" || type == "metatiles_tileset1" || type == "metatiles_tileset2" ||
+ type == "map_mapping1" || type == "map_mapping2" || type == "tileset_mapping3" ||
+ type == "map_collision" || type == "unknown") {
+ // TODO implement conversions
+ assetHandler = std::make_unique<BaseAsset>(path, start, size, asset);
+ } else if (type.empty()) {
+ // Unknown binary asset
+ assetHandler = std::make_unique<BaseAsset>(path, start, size, asset);
+ } else {
+ fmt::print(stderr, "Error: Unimplemented asset type \"{}\"", type);
+ std::exit(1);
+ }
+ assetHandler->setup();
+ return assetHandler;
+}
+
+bool shouldExtractAsset(const std::filesystem::path& path, const std::filesystem::file_time_type& configModified) {
+ if (gForceExtraction) {
+ return true;
+ }
+ if (std::filesystem::is_regular_file(path)) {
+ std::filesystem::file_time_type fileModified = std::filesystem::last_write_time(path);
+ if (fileModified < configModified) {
+ // File was created before the config was modified, so it needs to be renewed.
+ return true;
+ } else {
+ return false;
+ }
+ } else {
+ // Target file does not yet exist -> extract
+ return true;
+ }
+}
+
+void extractAsset(std::unique_ptr<BaseAsset>& assetHandler, const std::vector<char>& baserom) {
+ // Create the parent directory
+ std::filesystem::path parentDir = assetHandler->getPath();
+ parentDir.remove_filename();
+ std::filesystem::create_directories(parentDir);
+
+ assetHandler->extractBinary(baserom);
+}
+
+bool shouldConvertAsset(const std::unique_ptr<BaseAsset>& assetHandler) {
+ (void)assetHandler;
+ return true; // TODO
+}
+
+void convertAsset(std::unique_ptr<BaseAsset>& assetHandler, const std::vector<char>& baserom) {
+ assetHandler->convertToHumanReadable(baserom);
+}
+
+bool shouldBuildAsset(const std::unique_ptr<BaseAsset>& assetHandler) {
+ (void)assetHandler;
+ return true; // TODO
+}
+
+void buildAsset(std::unique_ptr<BaseAsset>& assetHandler) {
+ assetHandler->buildToBinary();
+} \ No newline at end of file
diff --git a/tools/src/asset_processor/main.h b/tools/src/asset_processor/main.h
new file mode 100644
index 00000000..262a143d
--- /dev/null
+++ b/tools/src/asset_processor/main.h
@@ -0,0 +1,24 @@
+#ifndef MAIN_H
+#define MAIN_H
+
+#include "util.h"
+#include "assets/asset.h"
+
+std::unique_ptr<BaseAsset> getAssetHandlerByType(const std::filesystem::path& path, const nlohmann::json& asset,
+ const int& currentOffset);
+bool shouldExtractAsset(const std::filesystem::path& path, const std::filesystem::file_time_type& configModified);
+void extractAsset(std::unique_ptr<BaseAsset>& assetHandler, const std::vector<char>& baserom);
+bool shouldConvertAsset(const std::unique_ptr<BaseAsset>& assetHandler);
+void convertAsset(std::unique_ptr<BaseAsset>& assetHandler, const std::vector<char>& baserom);
+bool shouldBuildAsset(const std::unique_ptr<BaseAsset>& assetHandler);
+void buildAsset(std::unique_ptr<BaseAsset>& assetHandler);
+
+enum Mode { EXTRACT, CONVERT, BUILD };
+
+// Arguments
+extern bool gVerbose;
+extern Mode gMode;
+extern std::string gVariant;
+extern std::string gAssetsFolder;
+extern std::string gBaseromPath;
+#endif \ No newline at end of file
diff --git a/tools/src/asset_processor/offsets.cpp b/tools/src/asset_processor/offsets.cpp
new file mode 100644
index 00000000..827fe7a6
--- /dev/null
+++ b/tools/src/asset_processor/offsets.cpp
@@ -0,0 +1,9 @@
+#include "offsets.h"
+
+OffsetCalculator::OffsetCalculator(const std::filesystem::path& outputFile, int baseOffset_)
+ : output(outputFile), baseOffset(baseOffset_), lastEnd(0) {
+}
+
+void OffsetCalculator::addAsset(int start, const std::string& symbol) {
+ output << "\t.equiv offset_" << symbol << ", " << start - baseOffset << std::endl;
+} \ No newline at end of file
diff --git a/tools/src/asset_processor/offsets.h b/tools/src/asset_processor/offsets.h
new file mode 100644
index 00000000..83c41dac
--- /dev/null
+++ b/tools/src/asset_processor/offsets.h
@@ -0,0 +1,25 @@
+#ifndef OFFSETS_H
+#define OFFSETS_H
+#include <filesystem>
+#include <fstream>
+#include <string>
+
+class OffsetCalculator {
+
+ public:
+ OffsetCalculator(const std::filesystem::path& offsetsFile, int baseOffset_);
+ void addAsset(int start, const std::string& symbol);
+ [[nodiscard]] int getLastEnd() const {
+ return lastEnd;
+ }
+ void setLastEnd(int lastEnd_) {
+ this->lastEnd = lastEnd_;
+ }
+ private:
+ std::ofstream output;
+ int baseOffset;
+ // Store the end of the previously added asset
+ int lastEnd;
+};
+
+#endif \ No newline at end of file
diff --git a/tools/src/asset_processor/reader.h b/tools/src/asset_processor/reader.h
new file mode 100644
index 00000000..dc2159fc
--- /dev/null
+++ b/tools/src/asset_processor/reader.h
@@ -0,0 +1,38 @@
+#ifndef READER_H
+#define READER_H
+
+#include <vector>
+#include <util/types.h>
+
+class Reader {
+ public:
+ Reader(const std::vector<char>& baserom, int start, [[maybe_unused]]int size_)
+ : data(baserom.data() + start) //, size(size_)
+ {
+ }
+
+ [[nodiscard]] s8 read_s8() {
+ // TODO range check
+ return data[static_cast<unsigned long>(cursor++)];
+ }
+
+ [[nodiscard]] u8 read_u8() {
+ return static_cast<u8>(read_s8());
+ }
+
+ [[nodiscard]] u16 read_u16() {
+ return static_cast<u16>(read_u8() + (read_u8() << 8));
+ }
+
+ [[nodiscard]] u32 read_u32() {
+ return static_cast<u32>(read_u16() + (read_u16() << 16));
+ }
+
+ int cursor = 0;
+
+ private:
+ const char* data;
+ // const int size;
+};
+
+#endif \ No newline at end of file
diff --git a/tools/src/asset_processor/util.cpp b/tools/src/asset_processor/util.cpp
new file mode 100644
index 00000000..a0effa3c
--- /dev/null
+++ b/tools/src/asset_processor/util.cpp
@@ -0,0 +1,28 @@
+#include "util.h"
+#include <iostream>
+#include <fmt/format.h>
+
+void check_call(const std::vector<std::string>& cmd) {
+ std::string cmdstr;
+ bool first = true;
+ for (const auto& segment : cmd) {
+ if (first) {
+ first = false;
+ } else {
+ cmdstr += " ";
+ }
+ cmdstr += segment;
+ }
+ int code = system(cmdstr.c_str());
+ if (code != 0) {
+ std::cerr << cmdstr << " failed with return code " << code << std::endl;
+ std::exit(1);
+ }
+}
+
+std::string opt_param(const std::string& format, int defaultVal, int value) {
+ if (value != defaultVal) {
+ return fmt::format(format, value);
+ }
+ return "";
+}
diff --git a/tools/src/asset_processor/util.h b/tools/src/asset_processor/util.h
new file mode 100644
index 00000000..00781a20
--- /dev/null
+++ b/tools/src/asset_processor/util.h
@@ -0,0 +1,15 @@
+#ifndef UTIL_H
+#define UTIL_H
+
+#include <nlohmann/json_fwd.hpp>
+#include <memory>
+#include <stdexcept>
+#include <cstdlib>
+#include <string>
+#include <vector>
+
+void check_call(const std::vector<std::string>& cmd);
+
+std::string opt_param(const std::string& format, int defaultVal, int value);
+
+#endif \ No newline at end of file
diff --git a/tools/src/bin2c/CMakeLists.txt b/tools/src/bin2c/CMakeLists.txt
new file mode 100644
index 00000000..0e2f717f
--- /dev/null
+++ b/tools/src/bin2c/CMakeLists.txt
@@ -0,0 +1,6 @@
+file(GLOB_RECURSE sources *.c)
+
+add_executable(bin2c ${sources})
+target_include_directories(bin2c PRIVATE .)
+
+install(TARGETS bin2c RUNTIME DESTINATION bin)
diff --git a/tools/src/bin2c/LICENSE b/tools/src/bin2c/LICENSE
new file mode 100755
index 00000000..534d1534
--- /dev/null
+++ b/tools/src/bin2c/LICENSE
@@ -0,0 +1,19 @@
+Copyright (c) 2016 YamaArashi
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in
+all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE.
diff --git a/tools/src/bin2c/bin2c.c b/tools/src/bin2c/bin2c.c
new file mode 100644
index 00000000..18d2fd27
--- /dev/null
+++ b/tools/src/bin2c/bin2c.c
@@ -0,0 +1,173 @@
+// Copyright(c) 2015-2016 YamaArashi
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <stdbool.h>
+
+#ifdef _MSC_VER
+
+#define FATAL_ERROR(format, ...) \
+ do { \
+ fprintf(stderr, format, __VA_ARGS__); \
+ exit(1); \
+ } while (0)
+
+#else
+
+#define FATAL_ERROR(format, ...) \
+ do { \
+ fprintf(stderr, format, ##__VA_ARGS__); \
+ exit(1); \
+ } while (0)
+
+#endif // _MSC_VER
+
+unsigned char* ReadWholeFile(char* path, int* size) {
+ FILE* fp = fopen(path, "rb");
+
+ if (fp == NULL)
+ FATAL_ERROR("Failed to open \"%s\" for reading.\n", path);
+
+ fseek(fp, 0, SEEK_END);
+
+ *size = ftell(fp);
+
+ unsigned char* buffer = malloc(*size);
+
+ if (buffer == NULL)
+ FATAL_ERROR("Failed to allocate memory for reading \"%s\".\n", path);
+
+ rewind(fp);
+
+ if (fread(buffer, *size, 1, fp) != 1)
+ FATAL_ERROR("Failed to read \"%s\".\n", path);
+
+ fclose(fp);
+
+ return buffer;
+}
+
+int ExtractData(unsigned char* buffer, int offset, int size) {
+ switch (size) {
+ case 1:
+ return buffer[offset];
+ case 2:
+ return (buffer[offset + 1] << 8) | buffer[offset];
+ case 4:
+ return (buffer[offset + 3] << 24) | (buffer[offset + 2] << 16) | (buffer[offset + 1] << 8) | buffer[offset];
+ default:
+ FATAL_ERROR("Invalid size passed to ExtractData.\n");
+ }
+}
+
+int main(int argc, char** argv) {
+ if (argc < 3)
+ FATAL_ERROR("Usage: bin2c INPUT_FILE VAR_NAME [OPTIONS...]\n");
+
+ int fileSize;
+ unsigned char* buffer = ReadWholeFile(argv[1], &fileSize);
+ char* var_name = argv[2];
+ int col = 1;
+ int pad = 0;
+ int size = 1;
+ bool isSigned = false;
+ bool isStatic = false;
+ bool isDecimal = false;
+
+ for (int i = 3; i < argc; i++) {
+ if (!strcmp(argv[i], "-col")) {
+ i++;
+
+ if (i >= argc)
+ FATAL_ERROR("Missing argument after '-col'.\n");
+
+ col = atoi(argv[i]);
+ } else if (!strcmp(argv[i], "-pad")) {
+ i++;
+
+ if (i >= argc)
+ FATAL_ERROR("Missing argument after '-pad'.\n");
+
+ pad = atoi(argv[i]);
+ } else if (!strcmp(argv[i], "-size")) {
+ i++;
+
+ if (i >= argc)
+ FATAL_ERROR("Missing argument after '-size'.\n");
+
+ size = atoi(argv[i]);
+
+ if (size != 1 && size != 2 && size != 4)
+ FATAL_ERROR("Size must be 1, 2, or 4.\n");
+ } else if (!strcmp(argv[i], "-signed")) {
+ isSigned = true;
+ isDecimal = true;
+ } else if (!strcmp(argv[i], "-static")) {
+ isStatic = true;
+ } else if (!strcmp(argv[i], "-decimal")) {
+ isDecimal = true;
+ } else {
+ FATAL_ERROR("Unrecognized option '%s'.\n", argv[i]);
+ }
+ }
+
+ if ((fileSize & (size - 1)) != 0)
+ FATAL_ERROR("Size %d doesn't evenly divide file size %d.\n", size, fileSize);
+
+ printf("// Generated file. Do not edit.\n\n");
+
+ if (isStatic)
+ printf("static ");
+
+ printf("const ");
+
+ if (isSigned)
+ printf("s%d ", 8 * size);
+ else
+ printf("u%d ", 8 * size);
+
+ printf("%s[] =\n{", var_name);
+
+ int count = fileSize / size;
+ int offset = 0;
+
+ for (int i = 0; i < count; i++) {
+ if (i % col == 0)
+ printf("\n ");
+
+ int data = ExtractData(buffer, offset, size);
+ offset += size;
+
+ if (isDecimal) {
+ if (isSigned)
+ printf("%*d, ", pad, data);
+ else
+ printf("%*uu, ", pad, data);
+ } else {
+ printf("%#*xu, ", pad, data);
+ }
+ }
+
+ printf("\n};\n");
+
+ return 0;
+}
diff --git a/tools/src/gbafix/CMakeLists.txt b/tools/src/gbafix/CMakeLists.txt
new file mode 100644
index 00000000..986d0d27
--- /dev/null
+++ b/tools/src/gbafix/CMakeLists.txt
@@ -0,0 +1,6 @@
+file(GLOB_RECURSE sources *.c)
+
+add_executable(gbafix ${sources})
+target_include_directories(gbafix PRIVATE .)
+
+install(TARGETS gbafix RUNTIME DESTINATION bin)
diff --git a/tools/src/gbafix/COPYING b/tools/src/gbafix/COPYING
new file mode 100755
index 00000000..94a9ed02
--- /dev/null
+++ b/tools/src/gbafix/COPYING
@@ -0,0 +1,674 @@
+ GNU GENERAL PUBLIC LICENSE
+ Version 3, 29 June 2007
+
+ Copyright (C) 2007 Free Software Foundation, Inc. <http://fsf.org/>
+ Everyone is permitted to copy and distribute verbatim copies
+ of this license document, but changing it is not allowed.
+
+ Preamble
+
+ The GNU General Public License is a free, copyleft license for
+software and other kinds of works.
+
+ The licenses for most software and other practical works are designed
+to take away your freedom to share and change the works. By contrast,
+the GNU General Public License is intended to guarantee your freedom to
+share and change all versions of a program--to make sure it remains free
+software for all its users. We, the Free Software Foundation, use the
+GNU General Public License for most of our software; it applies also to
+any other work released this way by its authors. You can apply it to
+your programs, too.
+
+ When we speak of free software, we are referring to freedom, not
+price. Our General Public Licenses are designed to make sure that you
+have the freedom to distribute copies of free software (and charge for
+them if you wish), that you receive source code or can get it if you
+want it, that you can change the software or use pieces of it in new
+free programs, and that you know you can do these things.
+
+ To protect your rights, we need to prevent others from denying you
+these rights or asking you to surrender the rights. Therefore, you have
+certain responsibilities if you distribute copies of the software, or if
+you modify it: responsibilities to respect the freedom of others.
+
+ For example, if you distribute copies of such a program, whether
+gratis or for a fee, you must pass on to the recipients the same
+freedoms that you received. You must make sure that they, too, receive
+or can get the source code. And you must show them these terms so they
+know their rights.
+
+ Developers that use the GNU GPL protect your rights with two steps:
+(1) assert copyright on the software, and (2) offer you this License
+giving you legal permission to copy, distribute and/or modify it.
+
+ For the developers' and authors' protection, the GPL clearly explains
+that there is no warranty for this free software. For both users' and
+authors' sake, the GPL requires that modified versions be marked as
+changed, so that their problems will not be attributed erroneously to
+authors of previous versions.
+
+ Some devices are designed to deny users access to install or run
+modified versions of the software inside them, although the manufacturer
+can do so. This is fundamentally incompatible with the aim of
+protecting users' freedom to change the software. The systematic
+pattern of such abuse occurs in the area of products for individuals to
+use, which is precisely where it is most unacceptable. Therefore, we
+have designed this version of the GPL to prohibit the practice for those
+products. If such problems arise substantially in other domains, we
+stand ready to extend this provision to those domains in future versions
+of the GPL, as needed to protect the freedom of users.
+
+ Finally, every program is threatened constantly by software patents.
+States should not allow patents to restrict development and use of
+software on general-purpose computers, but in those that do, we wish to
+avoid the special danger that patents applied to a free program could
+make it effectively proprietary. To prevent this, the GPL assures that
+patents cannot be used to render the program non-free.
+
+ The precise terms and conditions for copying, distribution and
+modification follow.
+
+ TERMS AND CONDITIONS
+
+ 0. Definitions.
+
+ "This License" refers to version 3 of the GNU General Public License.
+
+ "Copyright" also means copyright-like laws that apply to other kinds of
+works, such as semiconductor masks.
+
+ "The Program" refers to any copyrightable work licensed under this
+License. Each licensee is addressed as "you". "Licensees" and
+"recipients" may be individuals or organizations.
+
+ To "modify" a work means to copy from or adapt all or part of the work
+in a fashion requiring copyright permission, other than the making of an
+exact copy. The resulting work is called a "modified version" of the
+earlier work or a work "based on" the earlier work.
+
+ A "covered work" means either the unmodified Program or a work based
+on the Program.
+
+ To "propagate" a work means to do anything with it that, without
+permission, would make you directly or secondarily liable for
+infringement under applicable copyright law, except executing it on a
+computer or modifying a private copy. Propagation includes copying,
+distribution (with or without modification), making available to the
+public, and in some countries other activities as well.
+
+ To "convey" a work means any kind of propagation that enables other
+parties to make or receive copies. Mere interaction with a user through
+a computer network, with no transfer of a copy, is not conveying.
+
+ An interactive user interface displays "Appropriate Legal Notices"
+to the extent that it includes a convenient and prominently visible
+feature that (1) displays an appropriate copyright notice, and (2)
+tells the user that there is no warranty for the work (except to the
+extent that warranties are provided), that licensees may convey the
+work under this License, and how to view a copy of this License. If
+the interface presents a list of user commands or options, such as a
+menu, a prominent item in the list meets this criterion.
+
+ 1. Source Code.
+
+ The "source code" for a work means the preferred form of the work
+for making modifications to it. "Object code" means any non-source
+form of a work.
+
+ A "Standard Interface" means an interface that either is an official
+standard defined by a recognized standards body, or, in the case of
+interfaces specified for a particular programming language, one that
+is widely used among developers working in that language.
+
+ The "System Libraries" of an executable work include anything, other
+than the work as a whole, that (a) is included in the normal form of
+packaging a Major Component, but which is not part of that Major
+Component, and (b) serves only to enable use of the work with that
+Major Component, or to implement a Standard Interface for which an
+implementation is available to the public in source code form. A
+"Major Component", in this context, means a major essential component
+(kernel, window system, and so on) of the specific operating system
+(if any) on which the executable work runs, or a compiler used to
+produce the work, or an object code interpreter used to run it.
+
+ The "Corresponding Source" for a work in object code form means all
+the source code needed to generate, install, and (for an executable
+work) run the object code and to modify the work, including scripts to
+control those activities. However, it does not include the work's
+System Libraries, or general-purpose tools or generally available free
+programs which are used unmodified in performing those activities but
+which are not part of the work. For example, Corresponding Source
+includes interface definition files associated with source files for
+the work, and the source code for shared libraries and dynamically
+linked subprograms that the work is specifically designed to require,
+such as by intimate data communication or control flow between those
+subprograms and other parts of the work.
+
+ The Corresponding Source need not include anything that users
+can regenerate automatically from other parts of the Corresponding
+Source.
+
+ The Corresponding Source for a work in source code form is that
+same work.
+
+ 2. Basic Permissions.
+
+ All rights granted under this License are granted for the term of
+copyright on the Program, and are irrevocable provided the stated
+conditions are met. This License explicitly affirms your unlimited
+permission to run the unmodified Program. The output from running a
+covered work is covered by this License only if the output, given its
+content, constitutes a covered work. This License acknowledges your
+rights of fair use or other equivalent, as provided by copyright law.
+
+ You may make, run and propagate covered works that you do not
+convey, without conditions so long as your license otherwise remains
+in force. You may convey covered works to others for the sole purpose
+of having them make modifications exclusively for you, or provide you
+with facilities for running those works, provided that you comply with
+the terms of this License in conveying all material for which you do
+not control copyright. Those thus making or running the covered works
+for you must do so exclusively on your behalf, under your direction
+and control, on terms that prohibit them from making any copies of
+your copyrighted material outside their relationship with you.
+
+ Conveying under any other circumstances is permitted solely under
+the conditions stated below. Sublicensing is not allowed; section 10
+makes it unnecessary.
+
+ 3. Protecting Users' Legal Rights From Anti-Circumvention Law.
+
+ No covered work shall be deemed part of an effective technological
+measure under any applicable law fulfilling obligations under article
+11 of the WIPO copyright treaty adopted on 20 December 1996, or
+similar laws prohibiting or restricting circumvention of such
+measures.
+
+ When you convey a covered work, you waive any legal power to forbid
+circumvention of technological measures to the extent such circumvention
+is effected by exercising rights under this License with respect to
+the covered work, and you disclaim any intention to limit operation or
+modification of the work as a means of enforcing, against the work's
+users, your or third parties' legal rights to forbid circumvention of
+technological measures.
+
+ 4. Conveying Verbatim Copies.
+
+ You may convey verbatim copies of the Program's source code as you
+receive it, in any medium, provided that you conspicuously and
+appropriately publish on each copy an appropriate copyright notice;
+keep intact all notices stating that this License and any
+non-permissive terms added in accord with section 7 apply to the code;
+keep intact all notices of the absence of any warranty; and give all
+recipients a copy of this License along with the Program.
+
+ You may charge any price or no price for each copy that you convey,
+and you may offer support or warranty protection for a fee.
+
+ 5. Conveying Modified Source Versions.
+
+ You may convey a work based on the Program, or the modifications to
+produce it from the Program, in the form of source code under the
+terms of section 4, provided that you also meet all of these conditions:
+
+ a) The work must carry prominent notices stating that you modified
+ it, and giving a relevant date.
+
+ b) The work must carry prominent notices stating that it is
+ released under this License and any conditions added under section
+ 7. This requirement modifies the requirement in section 4 to
+ "keep intact all notices".
+
+ c) You must license the entire work, as a whole, under this
+ License to anyone who comes into possession of a copy. This
+ License will therefore apply, along with any applicable section 7
+ additional terms, to the whole of the work, and all its parts,
+ regardless of how they are packaged. This License gives no
+ permission to license the work in any other way, but it does not
+ invalidate such permission if you have separately received it.
+
+ d) If the work has interactive user interfaces, each must display
+ Appropriate Legal Notices; however, if the Program has interactive
+ interfaces that do not display Appropriate Legal Notices, your
+ work need not make them do so.
+
+ A compilation of a covered work with other separate and independent
+works, which are not by their nature extensions of the covered work,
+and which are not combined with it such as to form a larger program,
+in or on a volume of a storage or distribution medium, is called an
+"aggregate" if the compilation and its resulting copyright are not
+used to limit the access or legal rights of the compilation's users
+beyond what the individual works permit. Inclusion of a covered work
+in an aggregate does not cause this License to apply to the other
+parts of the aggregate.
+
+ 6. Conveying Non-Source Forms.
+
+ You may convey a covered work in object code form under the terms
+of sections 4 and 5, provided that you also convey the
+machine-readable Corresponding Source under the terms of this License,
+in one of these ways:
+
+ a) Convey the object code in, or embodied in, a physical product
+ (including a physical distribution medium), accompanied by the
+ Corresponding Source fixed on a durable physical medium
+ customarily used for software interchange.
+
+ b) Convey the object code in, or embodied in, a physical product
+ (including a physical distribution medium), accompanied by a
+ written offer, valid for at least three years and valid for as
+ long as you offer spare parts or customer support for that product
+ model, to give anyone who possesses the object code either (1) a
+ copy of the Corresponding Source for all the software in the
+ product that is covered by this License, on a durable physical
+ medium customarily used for software interchange, for a price no
+ more than your reasonable cost of physically performing this
+ conveying of source, or (2) access to copy the
+ Corresponding Source from a network server at no charge.
+
+ c) Convey individual copies of the object code with a copy of the
+ written offer to provide the Corresponding Source. This
+ alternative is allowed only occasionally and noncommercially, and
+ only if you received the object code with such an offer, in accord
+ with subsection 6b.
+
+ d) Convey the object code by offering access from a designated
+ place (gratis or for a charge), and offer equivalent access to the
+ Corresponding Source in the same way through the same place at no
+ further charge. You need not require recipients to copy the
+ Corresponding Source along with the object code. If the place to
+ copy the object code is a network server, the Corresponding Source
+ may be on a different server (operated by you or a third party)
+ that supports equivalent copying facilities, provided you maintain
+ clear directions next to the object code saying where to find the
+ Corresponding Source. Regardless of what server hosts the
+ Corresponding Source, you remain obligated to ensure that it is
+ available for as long as needed to satisfy these requirements.
+
+ e) Convey the object code using peer-to-peer transmission, provided
+ you inform other peers where the object code and Corresponding
+ Source of the work are being offered to the general public at no
+ charge under subsection 6d.
+
+ A separable portion of the object code, whose source code is excluded
+from the Corresponding Source as a System Library, need not be
+included in conveying the object code work.
+
+ A "User Product" is either (1) a "consumer product", which means any
+tangible personal property which is normally used for personal, family,
+or household purposes, or (2) anything designed or sold for incorporation
+into a dwelling. In determining whether a product is a consumer product,
+doubtful cases shall be resolved in favor of coverage. For a particular
+product received by a particular user, "normally used" refers to a
+typical or common use of that class of product, regardless of the status
+of the particular user or of the way in which the particular user
+actually uses, or expects or is expected to use, the product. A product
+is a consumer product regardless of whether the product has substantial
+commercial, industrial or non-consumer uses, unless such uses represent
+the only significant mode of use of the product.
+
+ "Installation Information" for a User Product means any methods,
+procedures, authorization keys, or other information required to install
+and execute modified versions of a covered work in that User Product from
+a modified version of its Corresponding Source. The information must
+suffice to ensure that the continued functioning of the modified object
+code is in no case prevented or interfered with solely because
+modification has been made.
+
+ If you convey an object code work under this section in, or with, or
+specifically for use in, a User Product, and the conveying occurs as
+part of a transaction in which the right of possession and use of the
+User Product is transferred to the recipient in perpetuity or for a
+fixed term (regardless of how the transaction is characterized), the
+Corresponding Source conveyed under this section must be accompanied
+by the Installation Information. But this requirement does not apply
+if neither you nor any third party retains the ability to install
+modified object code on the User Product (for example, the work has
+been installed in ROM).
+
+ The requirement to provide Installation Information does not include a
+requirement to continue to provide support service, warranty, or updates
+for a work that has been modified or installed by the recipient, or for
+the User Product in which it has been modified or installed. Access to a
+network may be denied when the modification itself materially and
+adversely affects the operation of the network or violates the rules and
+protocols for communication across the network.
+
+ Corresponding Source conveyed, and Installation Information provided,
+in accord with this section must be in a format that is publicly
+documented (and with an implementation available to the public in
+source code form), and must require no special password or key for
+unpacking, reading or copying.
+
+ 7. Additional Terms.
+
+ "Additional permissions" are terms that supplement the terms of this
+License by making exceptions from one or more of its conditions.
+Additional permissions that are applicable to the entire Program shall
+be treated as though they were included in this License, to the extent
+that they are valid under applicable law. If additional permissions
+apply only to part of the Program, that part may be used separately
+under those permissions, but the entire Program remains governed by
+this License without regard to the additional permissions.
+
+ When you convey a copy of a covered work, you may at your option
+remove any additional permissions from that copy, or from any part of
+it. (Additional permissions may be written to require their own
+removal in certain cases when you modify the work.) You may place
+additional permissions on material, added by you to a covered work,
+for which you have or can give appropriate copyright permission.
+
+ Notwithstanding any other provision of this License, for material you
+add to a covered work, you may (if authorized by the copyright holders of
+that material) supplement the terms of this License with terms:
+
+ a) Disclaiming warranty or limiting liability differently from the
+ terms of sections 15 and 16 of this License; or
+
+ b) Requiring preservation of specified reasonable legal notices or
+ author attributions in that material or in the Appropriate Legal
+ Notices displayed by works containing it; or
+
+ c) Prohibiting misrepresentation of the origin of that material, or
+ requiring that modified versions of such material be marked in
+ reasonable ways as different from the original version; or
+
+ d) Limiting the use for publicity purposes of names of licensors or
+ authors of the material; or
+
+ e) Declining to grant rights under trademark law for use of some
+ trade names, trademarks, or service marks; or
+
+ f) Requiring indemnification of licensors and authors of that
+ material by anyone who conveys the material (or modified versions of
+ it) with contractual assumptions of liability to the recipient, for
+ any liability that these contractual assumptions directly impose on
+ those licensors and authors.
+
+ All other non-permissive additional terms are considered "further
+restrictions" within the meaning of section 10. If the Program as you
+received it, or any part of it, contains a notice stating that it is
+governed by this License along with a term that is a further
+restriction, you may remove that term. If a license document contains
+a further restriction but permits relicensing or conveying under this
+License, you may add to a covered work material governed by the terms
+of that license document, provided that the further restriction does
+not survive such relicensing or conveying.
+
+ If you add terms to a covered work in accord with this section, you
+must place, in the relevant source files, a statement of the
+additional terms that apply to those files, or a notice indicating
+where to find the applicable terms.
+
+ Additional terms, permissive or non-permissive, may be stated in the
+form of a separately written license, or stated as exceptions;
+the above requirements apply either way.
+
+ 8. Termination.
+
+ You may not propagate or modify a covered work except as expressly
+provided under this License. Any attempt otherwise to propagate or
+modify it is void, and will automatically terminate your rights under
+this License (including any patent licenses granted under the third
+paragraph of section 11).
+
+ However, if you cease all violation of this License, then your
+license from a particular copyright holder is reinstated (a)
+provisionally, unless and until the copyright holder explicitly and
+finally terminates your license, and (b) permanently, if the copyright
+holder fails to notify you of the violation by some reasonable means
+prior to 60 days after the cessation.
+
+ Moreover, your license from a particular copyright holder is
+reinstated permanently if the copyright holder notifies you of the
+violation by some reasonable means, this is the first time you have
+received notice of violation of this License (for any work) from that
+copyright holder, and you cure the violation prior to 30 days after
+your receipt of the notice.
+
+ Termination of your rights under this section does not terminate the
+licenses of parties who have received copies or rights from you under
+this License. If your rights have been terminated and not permanently
+reinstated, you do not qualify to receive new licenses for the same
+material under section 10.
+
+ 9. Acceptance Not Required for Having Copies.
+
+ You are not required to accept this License in order to receive or
+run a copy of the Program. Ancillary propagation of a covered work
+occurring solely as a consequence of using peer-to-peer transmission
+to receive a copy likewise does not require acceptance. However,
+nothing other than this License grants you permission to propagate or
+modify any covered work. These actions infringe copyright if you do
+not accept this License. Therefore, by modifying or propagating a
+covered work, you indicate your acceptance of this License to do so.
+
+ 10. Automatic Licensing of Downstream Recipients.
+
+ Each time you convey a covered work, the recipient automatically
+receives a license from the original licensors, to run, modify and
+propagate that work, subject to this License. You are not responsible
+for enforcing compliance by third parties with this License.
+
+ An "entity transaction" is a transaction transferring control of an
+organization, or substantially all assets of one, or subdividing an
+organization, or merging organizations. If propagation of a covered
+work results from an entity transaction, each party to that
+transaction who receives a copy of the work also receives whatever
+licenses to the work the party's predecessor in interest had or could
+give under the previous paragraph, plus a right to possession of the
+Corresponding Source of the work from the predecessor in interest, if
+the predecessor has it or can get it with reasonable efforts.
+
+ You may not impose any further restrictions on the exercise of the
+rights granted or affirmed under this License. For example, you may
+not impose a license fee, royalty, or other charge for exercise of
+rights granted under this License, and you may not initiate litigation
+(including a cross-claim or counterclaim in a lawsuit) alleging that
+any patent claim is infringed by making, using, selling, offering for
+sale, or importing the Program or any portion of it.
+
+ 11. Patents.
+
+ A "contributor" is a copyright holder who authorizes use under this
+License of the Program or a work on which the Program is based. The
+work thus licensed is called the contributor's "contributor version".
+
+ A contributor's "essential patent claims" are all patent claims
+owned or controlled by the contributor, whether already acquired or
+hereafter acquired, that would be infringed by some manner, permitted
+by this License, of making, using, or selling its contributor version,
+but do not include claims that would be infringed only as a
+consequence of further modification of the contributor version. For
+purposes of this definition, "control" includes the right to grant
+patent sublicenses in a manner consistent with the requirements of
+this License.
+
+ Each contributor grants you a non-exclusive, worldwide, royalty-free
+patent license under the contributor's essential patent claims, to
+make, use, sell, offer for sale, import and otherwise run, modify and
+propagate the contents of its contributor version.
+
+ In the following three paragraphs, a "patent license" is any express
+agreement or commitment, however denominated, not to enforce a patent
+(such as an express permission to practice a patent or covenant not to
+sue for patent infringement). To "grant" such a patent license to a
+party means to make such an agreement or commitment not to enforce a
+patent against the party.
+
+ If you convey a covered work, knowingly relying on a patent license,
+and the Corresponding Source of the work is not available for anyone
+to copy, free of charge and under the terms of this License, through a
+publicly available network server or other readily accessible means,
+then you must either (1) cause the Corresponding Source to be so
+available, or (2) arrange to deprive yourself of the benefit of the
+patent license for this particular work, or (3) arrange, in a manner
+consistent with the requirements of this License, to extend the patent
+license to downstream recipients. "Knowingly relying" means you have
+actual knowledge that, but for the patent license, your conveying the
+covered work in a country, or your recipient's use of the covered work
+in a country, would infringe one or more identifiable patents in that
+country that you have reason to believe are valid.
+
+ If, pursuant to or in connection with a single transaction or
+arrangement, you convey, or propagate by procuring conveyance of, a
+covered work, and grant a patent license to some of the parties
+receiving the covered work authorizing them to use, propagate, modify
+or convey a specific copy of the covered work, then the patent license
+you grant is automatically extended to all recipients of the covered
+work and works based on it.
+
+ A patent license is "discriminatory" if it does not include within
+the scope of its coverage, prohibits the exercise of, or is
+conditioned on the non-exercise of one or more of the rights that are
+specifically granted under this License. You may not convey a covered
+work if you are a party to an arrangement with a third party that is
+in the business of distributing software, under which you make payment
+to the third party based on the extent of your activity of conveying
+the work, and under which the third party grants, to any of the
+parties who would receive the covered work from you, a discriminatory
+patent license (a) in connection with copies of the covered work
+conveyed by you (or copies made from those copies), or (b) primarily
+for and in connection with specific products or compilations that
+contain the covered work, unless you entered into that arrangement,
+or that patent license was granted, prior to 28 March 2007.
+
+ Nothing in this License shall be construed as excluding or limiting
+any implied license or other defenses to infringement that may
+otherwise be available to you under applicable patent law.
+
+ 12. No Surrender of Others' Freedom.
+
+ If conditions are imposed on you (whether by court order, agreement or
+otherwise) that contradict the conditions of this License, they do not
+excuse you from the conditions of this License. If you cannot convey a
+covered work so as to satisfy simultaneously your obligations under this
+License and any other pertinent obligations, then as a consequence you may
+not convey it at all. For example, if you agree to terms that obligate you
+to collect a royalty for further conveying from those to whom you convey
+the Program, the only way you could satisfy both those terms and this
+License would be to refrain entirely from conveying the Program.
+
+ 13. Use with the GNU Affero General Public License.
+
+ Notwithstanding any other provision of this License, you have
+permission to link or combine any covered work with a work licensed
+under version 3 of the GNU Affero General Public License into a single
+combined work, and to convey the resulting work. The terms of this
+License will continue to apply to the part which is the covered work,
+but the special requirements of the GNU Affero General Public License,
+section 13, concerning interaction through a network will apply to the
+combination as such.
+
+ 14. Revised Versions of this License.
+
+ The Free Software Foundation may publish revised and/or new versions of
+the GNU General Public License from time to time. Such new versions will
+be similar in spirit to the present version, but may differ in detail to
+address new problems or concerns.
+
+ Each version is given a distinguishing version number. If the
+Program specifies that a certain numbered version of the GNU General
+Public License "or any later version" applies to it, you have the
+option of following the terms and conditions either of that numbered
+version or of any later version published by the Free Software
+Foundation. If the Program does not specify a version number of the
+GNU General Public License, you may choose any version ever published
+by the Free Software Foundation.
+
+ If the Program specifies that a proxy can decide which future
+versions of the GNU General Public License can be used, that proxy's
+public statement of acceptance of a version permanently authorizes you
+to choose that version for the Program.
+
+ Later license versions may give you additional or different
+permissions. However, no additional obligations are imposed on any
+author or copyright holder as a result of your choosing to follow a
+later version.
+
+ 15. Disclaimer of Warranty.
+
+ THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
+APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
+HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
+OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
+THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
+IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
+ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
+
+ 16. Limitation of Liability.
+
+ IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
+WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
+THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
+GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
+USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
+DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
+PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
+EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
+SUCH DAMAGES.
+
+ 17. Interpretation of Sections 15 and 16.
+
+ If the disclaimer of warranty and limitation of liability provided
+above cannot be given local legal effect according to their terms,
+reviewing courts shall apply local law that most closely approximates
+an absolute waiver of all civil liability in connection with the
+Program, unless a warranty or assumption of liability accompanies a
+copy of the Program in return for a fee.
+
+ END OF TERMS AND CONDITIONS
+
+ How to Apply These Terms to Your New Programs
+
+ If you develop a new program, and you want it to be of the greatest
+possible use to the public, the best way to achieve this is to make it
+free software which everyone can redistribute and change under these terms.
+
+ To do so, attach the following notices to the program. It is safest
+to attach them to the start of each source file to most effectively
+state the exclusion of warranty; and each file should have at least
+the "copyright" line and a pointer to where the full notice is found.
+
+ <one line to give the program's name and a brief idea of what it does.>
+ Copyright (C) <year> <name of author>
+
+ 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/>.
+
+Also add information on how to contact you by electronic and paper mail.
+
+ If the program does terminal interaction, make it output a short
+notice like this when it starts in an interactive mode:
+
+ <program> Copyright (C) <year> <name of author>
+ This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
+ This is free software, and you are welcome to redistribute it
+ under certain conditions; type `show c' for details.
+
+The hypothetical commands `show w' and `show c' should show the appropriate
+parts of the General Public License. Of course, your program's commands
+might be different; for a GUI interface, you would use an "about box".
+
+ You should also get your employer (if you work as a programmer) or school,
+if any, to sign a "copyright disclaimer" for the program, if necessary.
+For more information on this, and how to apply and follow the GNU GPL, see
+<http://www.gnu.org/licenses/>.
+
+ The GNU General Public License does not permit incorporating your program
+into proprietary programs. If your program is a subroutine library, you
+may consider it more useful to permit linking proprietary applications with
+the library. If this is what you want to do, use the GNU Lesser General
+Public License instead of this License. But first, please read
+<http://www.gnu.org/philosophy/why-not-lgpl.html>.
diff --git a/tools/src/gbafix/Makefile b/tools/src/gbafix/Makefile
new file mode 100755
index 00000000..5b410da0
--- /dev/null
+++ b/tools/src/gbafix/Makefile
@@ -0,0 +1,13 @@
+CC = gcc
+.PHONY: all clean
+
+SRCS = gbafix.c
+
+all: gbafix
+ @:
+
+gbafix: $(SRCS)
+ $(CC) $(SRCS) -o $@ $(LDFLAGS)
+
+clean:
+ $(RM) gbafix gbafix.exe
diff --git a/tools/src/gbafix/elf.h b/tools/src/gbafix/elf.h
new file mode 100644
index 00000000..73592286
--- /dev/null
+++ b/tools/src/gbafix/elf.h
@@ -0,0 +1,2913 @@
+/*
+From musl include/elf.h
+
+Copyright © 2005-2014 Rich Felker, et al.
+
+Permission is hereby granted, free of charge, to any person obtaining
+a copy of this software and associated documentation files (the
+"Software"), to deal in the Software without restriction, including
+without limitation the rights to use, copy, modify, merge, publish,
+distribute, sublicense, and/or sell copies of the Software, and to
+permit persons to whom the Software is furnished to do so, subject to
+the following conditions:
+
+The above copyright notice and this permission notice shall be
+included in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
+CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
+TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
+SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+*/
+
+#ifndef _ELF_H
+#define _ELF_H
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#include <stdint.h>
+
+typedef uint16_t Elf32_Half;
+typedef uint16_t Elf64_Half;
+
+typedef uint32_t Elf32_Word;
+typedef int32_t Elf32_Sword;
+typedef uint32_t Elf64_Word;
+typedef int32_t Elf64_Sword;
+
+typedef uint64_t Elf32_Xword;
+typedef int64_t Elf32_Sxword;
+typedef uint64_t Elf64_Xword;
+typedef int64_t Elf64_Sxword;
+
+typedef uint32_t Elf32_Addr;
+typedef uint64_t Elf64_Addr;
+
+typedef uint32_t Elf32_Off;
+typedef uint64_t Elf64_Off;
+
+typedef uint16_t Elf32_Section;
+typedef uint16_t Elf64_Section;
+
+typedef Elf32_Half Elf32_Versym;
+typedef Elf64_Half Elf64_Versym;
+
+#define EI_NIDENT (16)
+
+typedef struct {
+ unsigned char e_ident[EI_NIDENT];
+ Elf32_Half e_type;
+ Elf32_Half e_machine;
+ Elf32_Word e_version;
+ Elf32_Addr e_entry;
+ Elf32_Off e_phoff;
+ Elf32_Off e_shoff;
+ Elf32_Word e_flags;
+ Elf32_Half e_ehsize;
+ Elf32_Half e_phentsize;
+ Elf32_Half e_phnum;
+ Elf32_Half e_shentsize;
+ Elf32_Half e_shnum;
+ Elf32_Half e_shstrndx;
+} Elf32_Ehdr;
+
+typedef struct {
+ unsigned char e_ident[EI_NIDENT];
+ Elf64_Half e_type;
+ Elf64_Half e_machine;
+ Elf64_Word e_version;
+ Elf64_Addr e_entry;
+ Elf64_Off e_phoff;
+ Elf64_Off e_shoff;
+ Elf64_Word e_flags;
+ Elf64_Half e_ehsize;
+ Elf64_Half e_phentsize;
+ Elf64_Half e_phnum;
+ Elf64_Half e_shentsize;
+ Elf64_Half e_shnum;
+ Elf64_Half e_shstrndx;
+} Elf64_Ehdr;
+
+#define EI_MAG0 0
+#define ELFMAG0 0x7f
+
+#define EI_MAG1 1
+#define ELFMAG1 'E'
+
+#define EI_MAG2 2
+#define ELFMAG2 'L'
+
+#define EI_MAG3 3
+#define ELFMAG3 'F'
+
+#define ELFMAG "\177ELF"
+#define SELFMAG 4
+
+#define EI_CLASS 4
+#define ELFCLASSNONE 0
+#define ELFCLASS32 1
+#define ELFCLASS64 2
+#define ELFCLASSNUM 3
+
+#define EI_DATA 5
+#define ELFDATANONE 0
+#define ELFDATA2LSB 1
+#define ELFDATA2MSB 2
+#define ELFDATANUM 3
+
+#define EI_VERSION 6
+
+#define EI_OSABI 7
+#define ELFOSABI_NONE 0
+#define ELFOSABI_SYSV 0
+#define ELFOSABI_HPUX 1
+#define ELFOSABI_NETBSD 2
+#define ELFOSABI_LINUX 3
+#define ELFOSABI_GNU 3
+#define ELFOSABI_SOLARIS 6
+#define ELFOSABI_AIX 7
+#define ELFOSABI_IRIX 8
+#define ELFOSABI_FREEBSD 9
+#define ELFOSABI_TRU64 10
+#define ELFOSABI_MODESTO 11
+#define ELFOSABI_OPENBSD 12
+#define ELFOSABI_ARM 97
+#define ELFOSABI_STANDALONE 255
+
+#define EI_ABIVERSION 8
+
+#define EI_PAD 9
+
+#define ET_NONE 0
+#define ET_REL 1
+#define ET_EXEC 2
+#define ET_DYN 3
+#define ET_CORE 4
+#define ET_NUM 5
+#define ET_LOOS 0xfe00
+#define ET_HIOS 0xfeff
+#define ET_LOPROC 0xff00
+#define ET_HIPROC 0xffff
+
+#define EM_NONE 0
+#define EM_M32 1
+#define EM_SPARC 2
+#define EM_386 3
+#define EM_68K 4
+#define EM_88K 5
+#define EM_860 7
+#define EM_MIPS 8
+#define EM_S370 9
+#define EM_MIPS_RS3_LE 10
+
+#define EM_PARISC 15
+#define EM_VPP500 17
+#define EM_SPARC32PLUS 18
+#define EM_960 19
+#define EM_PPC 20
+#define EM_PPC64 21
+#define EM_S390 22
+
+#define EM_V800 36
+#define EM_FR20 37
+#define EM_RH32 38
+#define EM_RCE 39
+#define EM_ARM 40
+#define EM_FAKE_ALPHA 41
+#define EM_SH 42
+#define EM_SPARCV9 43
+#define EM_TRICORE 44
+#define EM_ARC 45
+#define EM_H8_300 46
+#define EM_H8_300H 47
+#define EM_H8S 48
+#define EM_H8_500 49
+#define EM_IA_64 50
+#define EM_MIPS_X 51
+#define EM_COLDFIRE 52
+#define EM_68HC12 53
+#define EM_MMA 54
+#define EM_PCP 55
+#define EM_NCPU 56
+#define EM_NDR1 57
+#define EM_STARCORE 58
+#define EM_ME16 59
+#define EM_ST100 60
+#define EM_TINYJ 61
+#define EM_X86_64 62
+#define EM_PDSP 63
+
+#define EM_FX66 66
+#define EM_ST9PLUS 67
+#define EM_ST7 68
+#define EM_68HC16 69
+#define EM_68HC11 70
+#define EM_68HC08 71
+#define EM_68HC05 72
+#define EM_SVX 73
+#define EM_ST19 74
+#define EM_VAX 75
+#define EM_CRIS 76
+#define EM_JAVELIN 77
+#define EM_FIREPATH 78
+#define EM_ZSP 79
+#define EM_MMIX 80
+#define EM_HUANY 81
+#define EM_PRISM 82
+#define EM_AVR 83
+#define EM_FR30 84
+#define EM_D10V 85
+#define EM_D30V 86
+#define EM_V850 87
+#define EM_M32R 88
+#define EM_MN10300 89
+#define EM_MN10200 90
+#define EM_PJ 91
+#define EM_OR1K 92
+#define EM_OPENRISC 92
+#define EM_ARC_A5 93
+#define EM_ARC_COMPACT 93
+#define EM_XTENSA 94
+#define EM_VIDEOCORE 95
+#define EM_TMM_GPP 96
+#define EM_NS32K 97
+#define EM_TPC 98
+#define EM_SNP1K 99
+#define EM_ST200 100
+#define EM_IP2K 101
+#define EM_MAX 102
+#define EM_CR 103
+#define EM_F2MC16 104
+#define EM_MSP430 105
+#define EM_BLACKFIN 106
+#define EM_SE_C33 107
+#define EM_SEP 108
+#define EM_ARCA 109
+#define EM_UNICORE 110
+#define EM_EXCESS 111
+#define EM_DXP 112
+#define EM_ALTERA_NIOS2 113
+#define EM_CRX 114
+#define EM_XGATE 115
+#define EM_C166 116
+#define EM_M16C 117
+#define EM_DSPIC30F 118
+#define EM_CE 119
+#define EM_M32C 120
+#define EM_TSK3000 131
+#define EM_RS08 132
+#define EM_SHARC 133
+#define EM_ECOG2 134
+#define EM_SCORE7 135
+#define EM_DSP24 136
+#define EM_VIDEOCORE3 137
+#define EM_LATTICEMICO32 138
+#define EM_SE_C17 139
+#define EM_TI_C6000 140
+#define EM_TI_C2000 141
+#define EM_TI_C5500 142
+#define EM_TI_ARP32 143
+#define EM_TI_PRU 144
+#define EM_MMDSP_PLUS 160
+#define EM_CYPRESS_M8C 161
+#define EM_R32C 162
+#define EM_TRIMEDIA 163
+#define EM_QDSP6 164
+#define EM_8051 165
+#define EM_STXP7X 166
+#define EM_NDS32 167
+#define EM_ECOG1X 168
+#define EM_MAXQ30 169
+#define EM_XIMO16 170
+#define EM_MANIK 171
+#define EM_CRAYNV2 172
+#define EM_RX 173
+#define EM_METAG 174
+#define EM_MCST_ELBRUS 175
+#define EM_ECOG16 176
+#define EM_CR16 177
+#define EM_ETPU 178
+#define EM_SLE9X 179
+#define EM_L10M 180
+#define EM_K10M 181
+#define EM_AARCH64 183
+#define EM_AVR32 185
+#define EM_STM8 186
+#define EM_TILE64 187
+#define EM_TILEPRO 188
+#define EM_MICROBLAZE 189
+#define EM_CUDA 190
+#define EM_TILEGX 191
+#define EM_CLOUDSHIELD 192
+#define EM_COREA_1ST 193
+#define EM_COREA_2ND 194
+#define EM_ARC_COMPACT2 195
+#define EM_OPEN8 196
+#define EM_RL78 197
+#define EM_VIDEOCORE5 198
+#define EM_78KOR 199
+#define EM_56800EX 200
+#define EM_BA1 201
+#define EM_BA2 202
+#define EM_XCORE 203
+#define EM_MCHP_PIC 204
+#define EM_KM32 210
+#define EM_KMX32 211
+#define EM_EMX16 212
+#define EM_EMX8 213
+#define EM_KVARC 214
+#define EM_CDP 215
+#define EM_COGE 216
+#define EM_COOL 217
+#define EM_NORC 218
+#define EM_CSR_KALIMBA 219
+#define EM_Z80 220
+#define EM_VISIUM 221
+#define EM_FT32 222
+#define EM_MOXIE 223
+#define EM_AMDGPU 224
+#define EM_RISCV 243
+#define EM_BPF 247
+#define EM_NUM 248
+
+#define EM_ALPHA 0x9026
+
+#define EV_NONE 0
+#define EV_CURRENT 1
+#define EV_NUM 2
+
+typedef struct {
+ Elf32_Word sh_name;
+ Elf32_Word sh_type;
+ Elf32_Word sh_flags;
+ Elf32_Addr sh_addr;
+ Elf32_Off sh_offset;
+ Elf32_Word sh_size;
+ Elf32_Word sh_link;
+ Elf32_Word sh_info;
+ Elf32_Word sh_addralign;
+ Elf32_Word sh_entsize;
+} Elf32_Shdr;
+
+typedef struct {
+ Elf64_Word sh_name;
+ Elf64_Word sh_type;
+ Elf64_Xword sh_flags;
+ Elf64_Addr sh_addr;
+ Elf64_Off sh_offset;
+ Elf64_Xword sh_size;
+ Elf64_Word sh_link;
+ Elf64_Word sh_info;
+ Elf64_Xword sh_addralign;
+ Elf64_Xword sh_entsize;
+} Elf64_Shdr;
+
+#define SHN_UNDEF 0
+#define SHN_LORESERVE 0xff00
+#define SHN_LOPROC 0xff00
+#define SHN_BEFORE 0xff00
+
+#define SHN_AFTER 0xff01
+
+#define SHN_HIPROC 0xff1f
+#define SHN_LOOS 0xff20
+#define SHN_HIOS 0xff3f
+#define SHN_ABS 0xfff1
+#define SHN_COMMON 0xfff2
+#define SHN_XINDEX 0xffff
+#define SHN_HIRESERVE 0xffff
+
+#define SHT_NULL 0
+#define SHT_PROGBITS 1
+#define SHT_SYMTAB 2
+#define SHT_STRTAB 3
+#define SHT_RELA 4
+#define SHT_HASH 5
+#define SHT_DYNAMIC 6
+#define SHT_NOTE 7
+#define SHT_NOBITS 8
+#define SHT_REL 9
+#define SHT_SHLIB 10
+#define SHT_DYNSYM 11
+#define SHT_INIT_ARRAY 14
+#define SHT_FINI_ARRAY 15
+#define SHT_PREINIT_ARRAY 16
+#define SHT_GROUP 17
+#define SHT_SYMTAB_SHNDX 18
+#define SHT_NUM 19
+#define SHT_LOOS 0x60000000
+#define SHT_GNU_ATTRIBUTES 0x6ffffff5
+#define SHT_GNU_HASH 0x6ffffff6
+#define SHT_GNU_LIBLIST 0x6ffffff7
+#define SHT_CHECKSUM 0x6ffffff8
+#define SHT_LOSUNW 0x6ffffffa
+#define SHT_SUNW_move 0x6ffffffa
+#define SHT_SUNW_COMDAT 0x6ffffffb
+#define SHT_SUNW_syminfo 0x6ffffffc
+#define SHT_GNU_verdef 0x6ffffffd
+#define SHT_GNU_verneed 0x6ffffffe
+#define SHT_GNU_versym 0x6fffffff
+#define SHT_HISUNW 0x6fffffff
+#define SHT_HIOS 0x6fffffff
+#define SHT_LOPROC 0x70000000
+#define SHT_HIPROC 0x7fffffff
+#define SHT_LOUSER 0x80000000
+#define SHT_HIUSER 0x8fffffff
+
+#define SHF_WRITE (1 << 0)
+#define SHF_ALLOC (1 << 1)
+#define SHF_EXECINSTR (1 << 2)
+#define SHF_MERGE (1 << 4)
+#define SHF_STRINGS (1 << 5)
+#define SHF_INFO_LINK (1 << 6)
+#define SHF_LINK_ORDER (1 << 7)
+#define SHF_OS_NONCONFORMING (1 << 8)
+
+#define SHF_GROUP (1 << 9)
+#define SHF_TLS (1 << 10)
+#define SHF_COMPRESSED (1 << 11)
+#define SHF_MASKOS 0x0ff00000
+#define SHF_MASKPROC 0xf0000000
+#define SHF_ORDERED (1 << 30)
+#define SHF_EXCLUDE (1U << 31)
+
+typedef struct {
+ Elf32_Word ch_type;
+ Elf32_Word ch_size;
+ Elf32_Word ch_addralign;
+} Elf32_Chdr;
+
+typedef struct {
+ Elf64_Word ch_type;
+ Elf64_Word ch_reserved;
+ Elf64_Xword ch_size;
+ Elf64_Xword ch_addralign;
+} Elf64_Chdr;
+
+#define ELFCOMPRESS_ZLIB 1
+#define ELFCOMPRESS_LOOS 0x60000000
+#define ELFCOMPRESS_HIOS 0x6fffffff
+#define ELFCOMPRESS_LOPROC 0x70000000
+#define ELFCOMPRESS_HIPROC 0x7fffffff
+
+#define GRP_COMDAT 0x1
+
+typedef struct {
+ Elf32_Word st_name;
+ Elf32_Addr st_value;
+ Elf32_Word st_size;
+ unsigned char st_info;
+ unsigned char st_other;
+ Elf32_Section st_shndx;
+} Elf32_Sym;
+
+typedef struct {
+ Elf64_Word st_name;
+ unsigned char st_info;
+ unsigned char st_other;
+ Elf64_Section st_shndx;
+ Elf64_Addr st_value;
+ Elf64_Xword st_size;
+} Elf64_Sym;
+
+typedef struct {
+ Elf32_Half si_boundto;
+ Elf32_Half si_flags;
+} Elf32_Syminfo;
+
+typedef struct {
+ Elf64_Half si_boundto;
+ Elf64_Half si_flags;
+} Elf64_Syminfo;
+
+#define SYMINFO_BT_SELF 0xffff
+#define SYMINFO_BT_PARENT 0xfffe
+#define SYMINFO_BT_LOWRESERVE 0xff00
+
+#define SYMINFO_FLG_DIRECT 0x0001
+#define SYMINFO_FLG_PASSTHRU 0x0002
+#define SYMINFO_FLG_COPY 0x0004
+#define SYMINFO_FLG_LAZYLOAD 0x0008
+
+#define SYMINFO_NONE 0
+#define SYMINFO_CURRENT 1
+#define SYMINFO_NUM 2
+
+#define ELF32_ST_BIND(val) (((unsigned char)(val)) >> 4)
+#define ELF32_ST_TYPE(val) ((val)&0xf)
+#define ELF32_ST_INFO(bind, type) (((bind) << 4) + ((type)&0xf))
+
+#define ELF64_ST_BIND(val) ELF32_ST_BIND(val)
+#define ELF64_ST_TYPE(val) ELF32_ST_TYPE(val)
+#define ELF64_ST_INFO(bind, type) ELF32_ST_INFO((bind), (type))
+
+#define STB_LOCAL 0
+#define STB_GLOBAL 1
+#define STB_WEAK 2
+#define STB_NUM 3
+#define STB_LOOS 10
+#define STB_GNU_UNIQUE 10
+#define STB_HIOS 12
+#define STB_LOPROC 13
+#define STB_HIPROC 15
+
+#define STT_NOTYPE 0
+#define STT_OBJECT 1
+#define STT_FUNC 2
+#define STT_SECTION 3
+#define STT_FILE 4
+#define STT_COMMON 5
+#define STT_TLS 6
+#define STT_NUM 7
+#define STT_LOOS 10
+#define STT_GNU_IFUNC 10
+#define STT_HIOS 12
+#define STT_LOPROC 13
+#define STT_HIPROC 15
+
+#define STN_UNDEF 0
+
+#define ELF32_ST_VISIBILITY(o) ((o)&0x03)
+#define ELF64_ST_VISIBILITY(o) ELF32_ST_VISIBILITY(o)
+
+#define STV_DEFAULT 0
+#define STV_INTERNAL 1
+#define STV_HIDDEN 2
+#define STV_PROTECTED 3
+
+typedef struct {
+ Elf32_Addr r_offset;
+ Elf32_Word r_info;
+} Elf32_Rel;
+
+typedef struct {
+ Elf64_Addr r_offset;
+ Elf64_Xword r_info;
+} Elf64_Rel;
+
+typedef struct {
+ Elf32_Addr r_offset;
+ Elf32_Word r_info;
+ Elf32_Sword r_addend;
+} Elf32_Rela;
+
+typedef struct {
+ Elf64_Addr r_offset;
+ Elf64_Xword r_info;
+ Elf64_Sxword r_addend;
+} Elf64_Rela;
+
+#define ELF32_R_SYM(val) ((val) >> 8)
+#define ELF32_R_TYPE(val) ((val)&0xff)
+#define ELF32_R_INFO(sym, type) (((sym) << 8) + ((type)&0xff))
+
+#define ELF64_R_SYM(i) ((i) >> 32)
+#define ELF64_R_TYPE(i) ((i)&0xffffffff)
+#define ELF64_R_INFO(sym, type) ((((Elf64_Xword)(sym)) << 32) + (type))
+
+typedef struct {
+ Elf32_Word p_type;
+ Elf32_Off p_offset;
+ Elf32_Addr p_vaddr;
+ Elf32_Addr p_paddr;
+ Elf32_Word p_filesz;
+ Elf32_Word p_memsz;
+ Elf32_Word p_flags;
+ Elf32_Word p_align;
+} Elf32_Phdr;
+
+typedef struct {
+ Elf64_Word p_type;
+ Elf64_Word p_flags;
+ Elf64_Off p_offset;
+ Elf64_Addr p_vaddr;
+ Elf64_Addr p_paddr;
+ Elf64_Xword p_filesz;
+ Elf64_Xword p_memsz;
+ Elf64_Xword p_align;
+} Elf64_Phdr;
+
+#define PT_NULL 0
+#define PT_LOAD 1
+#define PT_DYNAMIC 2
+#define PT_INTERP 3
+#define PT_NOTE 4
+#define PT_SHLIB 5
+#define PT_PHDR 6
+#define PT_TLS 7
+#define PT_NUM 8
+#define PT_LOOS 0x60000000
+#define PT_GNU_EH_FRAME 0x6474e550
+#define PT_GNU_STACK 0x6474e551
+#define PT_GNU_RELRO 0x6474e552
+#define PT_LOSUNW 0x6ffffffa
+#define PT_SUNWBSS 0x6ffffffa
+#define PT_SUNWSTACK 0x6ffffffb
+#define PT_HISUNW 0x6fffffff
+#define PT_HIOS 0x6fffffff
+#define PT_LOPROC 0x70000000
+#define PT_HIPROC 0x7fffffff
+
+#define PN_XNUM 0xffff
+
+#define PF_X (1 << 0)
+#define PF_W (1 << 1)
+#define PF_R (1 << 2)
+#define PF_MASKOS 0x0ff00000
+#define PF_MASKPROC 0xf0000000
+
+#define NT_PRSTATUS 1
+#define NT_FPREGSET 2
+#define NT_PRPSINFO 3
+#define NT_PRXREG 4
+#define NT_TASKSTRUCT 4
+#define NT_PLATFORM 5
+#define NT_AUXV 6
+#define NT_GWINDOWS 7
+#define NT_ASRS 8
+#define NT_PSTATUS 10
+#define NT_PSINFO 13
+#define NT_PRCRED 14
+#define NT_UTSNAME 15
+#define NT_LWPSTATUS 16
+#define NT_LWPSINFO 17
+#define NT_PRFPXREG 20
+#define NT_SIGINFO 0x53494749
+#define NT_FILE 0x46494c45
+#define NT_PRXFPREG 0x46e62b7f
+#define NT_PPC_VMX 0x100
+#define NT_PPC_SPE 0x101
+#define NT_PPC_VSX 0x102
+#define NT_386_TLS 0x200
+#define NT_386_IOPERM 0x201
+#define NT_X86_XSTATE 0x202
+#define NT_S390_HIGH_GPRS 0x300
+#define NT_S390_TIMER 0x301
+#define NT_S390_TODCMP 0x302
+#define NT_S390_TODPREG 0x303
+#define NT_S390_CTRS 0x304
+#define NT_S390_PREFIX 0x305
+#define NT_S390_LAST_BREAK 0x306
+#define NT_S390_SYSTEM_CALL 0x307
+#define NT_S390_TDB 0x308
+#define NT_ARM_VFP 0x400
+#define NT_ARM_TLS 0x401
+#define NT_ARM_HW_BREAK 0x402
+#define NT_ARM_HW_WATCH 0x403
+#define NT_ARM_SYSTEM_CALL 0x404
+#define NT_ARM_SVE 0x405
+#define NT_METAG_CBUF 0x500
+#define NT_METAG_RPIPE 0x501
+#define NT_METAG_TLS 0x502
+#define NT_VERSION 1
+
+typedef struct {
+ Elf32_Sword d_tag;
+ union {
+ Elf32_Word d_val;
+ Elf32_Addr d_ptr;
+ } d_un;
+} Elf32_Dyn;
+
+typedef struct {
+ Elf64_Sxword d_tag;
+ union {
+ Elf64_Xword d_val;
+ Elf64_Addr d_ptr;
+ } d_un;
+} Elf64_Dyn;
+
+#define DT_NULL 0
+#define DT_NEEDED 1
+#define DT_PLTRELSZ 2
+#define DT_PLTGOT 3
+#define DT_HASH 4
+#define DT_STRTAB 5
+#define DT_SYMTAB 6
+#define DT_RELA 7
+#define DT_RELASZ 8
+#define DT_RELAENT 9
+#define DT_STRSZ 10
+#define DT_SYMENT 11
+#define DT_INIT 12
+#define DT_FINI 13
+#define DT_SONAME 14
+#define DT_RPATH 15
+#define DT_SYMBOLIC 16
+#define DT_REL 17
+#define DT_RELSZ 18
+#define DT_RELENT 19
+#define DT_PLTREL 20
+#define DT_DEBUG 21
+#define DT_TEXTREL 22
+#define DT_JMPREL 23
+#define DT_BIND_NOW 24
+#define DT_INIT_ARRAY 25
+#define DT_FINI_ARRAY 26
+#define DT_INIT_ARRAYSZ 27
+#define DT_FINI_ARRAYSZ 28
+#define DT_RUNPATH 29
+#define DT_FLAGS 30
+#define DT_ENCODING 32
+#define DT_PREINIT_ARRAY 32
+#define DT_PREINIT_ARRAYSZ 33
+#define DT_NUM 34
+#define DT_LOOS 0x6000000d
+#define DT_HIOS 0x6ffff000
+#define DT_LOPROC 0x70000000
+#define DT_HIPROC 0x7fffffff
+#define DT_PROCNUM DT_MIPS_NUM
+
+#define DT_VALRNGLO 0x6ffffd00
+#define DT_GNU_PRELINKED 0x6ffffdf5
+#define DT_GNU_CONFLICTSZ 0x6ffffdf6
+#define DT_GNU_LIBLISTSZ 0x6ffffdf7
+#define DT_CHECKSUM 0x6ffffdf8
+#define DT_PLTPADSZ 0x6ffffdf9
+#define DT_MOVEENT 0x6ffffdfa
+#define DT_MOVESZ 0x6ffffdfb
+#define DT_FEATURE_1 0x6ffffdfc
+#define DT_POSFLAG_1 0x6ffffdfd
+
+#define DT_SYMINSZ 0x6ffffdfe
+#define DT_SYMINENT 0x6ffffdff
+#define DT_VALRNGHI 0x6ffffdff
+#define DT_VALTAGIDX(tag) (DT_VALRNGHI - (tag))
+#define DT_VALNUM 12
+
+#define DT_ADDRRNGLO 0x6ffffe00
+#define DT_GNU_HASH 0x6ffffef5
+#define DT_TLSDESC_PLT 0x6ffffef6
+#define DT_TLSDESC_GOT 0x6ffffef7
+#define DT_GNU_CONFLICT 0x6ffffef8
+#define DT_GNU_LIBLIST 0x6ffffef9
+#define DT_CONFIG 0x6ffffefa
+#define DT_DEPAUDIT 0x6ffffefb
+#define DT_AUDIT 0x6ffffefc
+#define DT_PLTPAD 0x6ffffefd
+#define DT_MOVETAB 0x6ffffefe
+#define DT_SYMINFO 0x6ffffeff
+#define DT_ADDRRNGHI 0x6ffffeff
+#define DT_ADDRTAGIDX(tag) (DT_ADDRRNGHI - (tag))
+#define DT_ADDRNUM 11
+
+#define DT_VERSYM 0x6ffffff0
+
+#define DT_RELACOUNT 0x6ffffff9
+#define DT_RELCOUNT 0x6ffffffa
+
+#define DT_FLAGS_1 0x6ffffffb
+#define DT_VERDEF 0x6ffffffc
+
+#define DT_VERDEFNUM 0x6ffffffd
+#define DT_VERNEED 0x6ffffffe
+
+#define DT_VERNEEDNUM 0x6fffffff
+#define DT_VERSIONTAGIDX(tag) (DT_VERNEEDNUM - (tag))
+#define DT_VERSIONTAGNUM 16
+
+#define DT_AUXILIARY 0x7ffffffd
+#define DT_FILTER 0x7fffffff
+#define DT_EXTRATAGIDX(tag) ((Elf32_Word) - ((Elf32_Sword)(tag) << 1 >> 1) - 1)
+#define DT_EXTRANUM 3
+
+#define DF_ORIGIN 0x00000001
+#define DF_SYMBOLIC 0x00000002
+#define DF_TEXTREL 0x00000004
+#define DF_BIND_NOW 0x00000008
+#define DF_STATIC_TLS 0x00000010
+
+#define DF_1_NOW 0x00000001
+#define DF_1_GLOBAL 0x00000002
+#define DF_1_GROUP 0x00000004
+#define DF_1_NODELETE 0x00000008
+#define DF_1_LOADFLTR 0x00000010
+#define DF_1_INITFIRST 0x00000020
+#define DF_1_NOOPEN 0x00000040
+#define DF_1_ORIGIN 0x00000080
+#define DF_1_DIRECT 0x00000100
+#define DF_1_TRANS 0x00000200
+#define DF_1_INTERPOSE 0x00000400
+#define DF_1_NODEFLIB 0x00000800
+#define DF_1_NODUMP 0x00001000
+#define DF_1_CONFALT 0x00002000
+#define DF_1_ENDFILTEE 0x00004000
+#define DF_1_DISPRELDNE 0x00008000
+#define DF_1_DISPRELPND 0x00010000
+#define DF_1_NODIRECT 0x00020000
+#define DF_1_IGNMULDEF 0x00040000
+#define DF_1_NOKSYMS 0x00080000
+#define DF_1_NOHDR 0x00100000
+#define DF_1_EDITED 0x00200000
+#define DF_1_NORELOC 0x00400000
+#define DF_1_SYMINTPOSE 0x00800000
+#define DF_1_GLOBAUDIT 0x01000000
+#define DF_1_SINGLETON 0x02000000
+
+#define DTF_1_PARINIT 0x00000001
+#define DTF_1_CONFEXP 0x00000002
+
+#define DF_P1_LAZYLOAD 0x00000001
+#define DF_P1_GROUPPERM 0x00000002
+
+typedef struct {
+ Elf32_Half vd_version;
+ Elf32_Half vd_flags;
+ Elf32_Half vd_ndx;
+ Elf32_Half vd_cnt;
+ Elf32_Word vd_hash;
+ Elf32_Word vd_aux;
+ Elf32_Word vd_next;
+} Elf32_Verdef;
+
+typedef struct {
+ Elf64_Half vd_version;
+ Elf64_Half vd_flags;
+ Elf64_Half vd_ndx;
+ Elf64_Half vd_cnt;
+ Elf64_Word vd_hash;
+ Elf64_Word vd_aux;
+ Elf64_Word vd_next;
+} Elf64_Verdef;
+
+#define VER_DEF_NONE 0
+#define VER_DEF_CURRENT 1
+#define VER_DEF_NUM 2
+
+#define VER_FLG_BASE 0x1
+#define VER_FLG_WEAK 0x2
+
+#define VER_NDX_LOCAL 0
+#define VER_NDX_GLOBAL 1
+#define VER_NDX_LORESERVE 0xff00
+#define VER_NDX_ELIMINATE 0xff01
+
+typedef struct {
+ Elf32_Word vda_name;
+ Elf32_Word vda_next;
+} Elf32_Verdaux;
+
+typedef struct {
+ Elf64_Word vda_name;
+ Elf64_Word vda_next;
+} Elf64_Verdaux;
+
+typedef struct {
+ Elf32_Half vn_version;
+ Elf32_Half vn_cnt;
+ Elf32_Word vn_file;
+ Elf32_Word vn_aux;
+ Elf32_Word vn_next;
+} Elf32_Verneed;
+
+typedef struct {
+ Elf64_Half vn_version;
+ Elf64_Half vn_cnt;
+ Elf64_Word vn_file;
+ Elf64_Word vn_aux;
+ Elf64_Word vn_next;
+} Elf64_Verneed;
+
+#define VER_NEED_NONE 0
+#define VER_NEED_CURRENT 1
+#define VER_NEED_NUM 2
+
+typedef struct {
+ Elf32_Word vna_hash;
+ Elf32_Half vna_flags;
+ Elf32_Half vna_other;
+ Elf32_Word vna_name;
+ Elf32_Word vna_next;
+} Elf32_Vernaux;
+
+typedef struct {
+ Elf64_Word vna_hash;
+ Elf64_Half vna_flags;
+ Elf64_Half vna_other;
+ Elf64_Word vna_name;
+ Elf64_Word vna_next;
+} Elf64_Vernaux;
+
+#define VER_FLG_WEAK 0x2
+
+typedef struct {
+ uint32_t a_type;
+ union {
+ uint32_t a_val;
+ } a_un;
+} Elf32_auxv_t;
+
+typedef struct {
+ uint64_t a_type;
+ union {
+ uint64_t a_val;
+ } a_un;
+} Elf64_auxv_t;
+
+#define AT_NULL 0
+#define AT_IGNORE 1
+#define AT_EXECFD 2
+#define AT_PHDR 3
+#define AT_PHENT 4
+#define AT_PHNUM 5
+#define AT_PAGESZ 6
+#define AT_BASE 7
+#define AT_FLAGS 8
+#define AT_ENTRY 9
+#define AT_NOTELF 10
+#define AT_UID 11
+#define AT_EUID 12
+#define AT_GID 13
+#define AT_EGID 14
+#define AT_CLKTCK 17
+
+#define AT_PLATFORM 15
+#define AT_HWCAP 16
+
+#define AT_FPUCW 18
+
+#define AT_DCACHEBSIZE 19
+#define AT_ICACHEBSIZE 20
+#define AT_UCACHEBSIZE 21
+
+#define AT_IGNOREPPC 22
+
+#define AT_SECURE 23
+
+#define AT_BASE_PLATFORM 24
+
+#define AT_RANDOM 25
+
+#define AT_HWCAP2 26
+
+#define AT_EXECFN 31
+
+#define AT_SYSINFO 32
+#define AT_SYSINFO_EHDR 33
+
+#define AT_L1I_CACHESHAPE 34
+#define AT_L1D_CACHESHAPE 35
+#define AT_L2_CACHESHAPE 36
+#define AT_L3_CACHESHAPE 37
+
+typedef struct {
+ Elf32_Word n_namesz;
+ Elf32_Word n_descsz;
+ Elf32_Word n_type;
+} Elf32_Nhdr;
+
+typedef struct {
+ Elf64_Word n_namesz;
+ Elf64_Word n_descsz;
+ Elf64_Word n_type;
+} Elf64_Nhdr;
+
+#define ELF_NOTE_SOLARIS "SUNW Solaris"
+
+#define ELF_NOTE_GNU "GNU"
+
+#define ELF_NOTE_PAGESIZE_HINT 1
+
+#define NT_GNU_ABI_TAG 1
+#define ELF_NOTE_ABI NT_GNU_ABI_TAG
+
+#define ELF_NOTE_OS_LINUX 0
+#define ELF_NOTE_OS_GNU 1
+#define ELF_NOTE_OS_SOLARIS2 2
+#define ELF_NOTE_OS_FREEBSD 3
+
+#define NT_GNU_BUILD_ID 3
+#define NT_GNU_GOLD_VERSION 4
+
+typedef struct {
+ Elf32_Xword m_value;
+ Elf32_Word m_info;
+ Elf32_Word m_poffset;
+ Elf32_Half m_repeat;
+ Elf32_Half m_stride;
+} Elf32_Move;
+
+typedef struct {
+ Elf64_Xword m_value;
+ Elf64_Xword m_info;
+ Elf64_Xword m_poffset;
+ Elf64_Half m_repeat;
+ Elf64_Half m_stride;
+} Elf64_Move;
+
+#define ELF32_M_SYM(info) ((info) >> 8)
+#define ELF32_M_SIZE(info) ((unsigned char)(info))
+#define ELF32_M_INFO(sym, size) (((sym) << 8) + (unsigned char)(size))
+
+#define ELF64_M_SYM(info) ELF32_M_SYM(info)
+#define ELF64_M_SIZE(info) ELF32_M_SIZE(info)
+#define ELF64_M_INFO(sym, size) ELF32_M_INFO(sym, size)
+
+#define EF_CPU32 0x00810000
+
+#define R_68K_NONE 0
+#define R_68K_32 1
+#define R_68K_16 2
+#define R_68K_8 3
+#define R_68K_PC32 4
+#define R_68K_PC16 5
+#define R_68K_PC8 6
+#define R_68K_GOT32 7
+#define R_68K_GOT16 8
+#define R_68K_GOT8 9
+#define R_68K_GOT32O 10
+#define R_68K_GOT16O 11
+#define R_68K_GOT8O 12
+#define R_68K_PLT32 13
+#define R_68K_PLT16 14
+#define R_68K_PLT8 15
+#define R_68K_PLT32O 16
+#define R_68K_PLT16O 17
+#define R_68K_PLT8O 18
+#define R_68K_COPY 19
+#define R_68K_GLOB_DAT 20
+#define R_68K_JMP_SLOT 21
+#define R_68K_RELATIVE 22
+#define R_68K_NUM 23
+
+#define R_386_NONE 0
+#define R_386_32 1
+#define R_386_PC32 2
+#define R_386_GOT32 3
+#define R_386_PLT32 4
+#define R_386_COPY 5
+#define R_386_GLOB_DAT 6
+#define R_386_JMP_SLOT 7
+#define R_386_RELATIVE 8
+#define R_386_GOTOFF 9
+#define R_386_GOTPC 10
+#define R_386_32PLT 11
+#define R_386_TLS_TPOFF 14
+#define R_386_TLS_IE 15
+#define R_386_TLS_GOTIE 16
+#define R_386_TLS_LE 17
+#define R_386_TLS_GD 18
+#define R_386_TLS_LDM 19
+#define R_386_16 20
+#define R_386_PC16 21
+#define R_386_8 22
+#define R_386_PC8 23
+#define R_386_TLS_GD_32 24
+#define R_386_TLS_GD_PUSH 25
+#define R_386_TLS_GD_CALL 26
+#define R_386_TLS_GD_POP 27
+#define R_386_TLS_LDM_32 28
+#define R_386_TLS_LDM_PUSH 29
+#define R_386_TLS_LDM_CALL 30
+#define R_386_TLS_LDM_POP 31
+#define R_386_TLS_LDO_32 32
+#define R_386_TLS_IE_32 33
+#define R_386_TLS_LE_32 34
+#define R_386_TLS_DTPMOD32 35
+#define R_386_TLS_DTPOFF32 36
+#define R_386_TLS_TPOFF32 37
+#define R_386_SIZE32 38
+#define R_386_TLS_GOTDESC 39
+#define R_386_TLS_DESC_CALL 40
+#define R_386_TLS_DESC 41
+#define R_386_IRELATIVE 42
+#define R_386_GOT32X 43
+#define R_386_NUM 44
+
+#define STT_SPARC_REGISTER 13
+
+#define EF_SPARCV9_MM 3
+#define EF_SPARCV9_TSO 0
+#define EF_SPARCV9_PSO 1
+#define EF_SPARCV9_RMO 2
+#define EF_SPARC_LEDATA 0x800000
+#define EF_SPARC_EXT_MASK 0xFFFF00
+#define EF_SPARC_32PLUS 0x000100
+#define EF_SPARC_SUN_US1 0x000200
+#define EF_SPARC_HAL_R1 0x000400
+#define EF_SPARC_SUN_US3 0x000800
+
+#define R_SPARC_NONE 0
+#define R_SPARC_8 1
+#define R_SPARC_16 2
+#define R_SPARC_32 3
+#define R_SPARC_DISP8 4
+#define R_SPARC_DISP16 5
+#define R_SPARC_DISP32 6
+#define R_SPARC_WDISP30 7
+#define R_SPARC_WDISP22 8
+#define R_SPARC_HI22 9
+#define R_SPARC_22 10
+#define R_SPARC_13 11
+#define R_SPARC_LO10 12
+#define R_SPARC_GOT10 13
+#define R_SPARC_GOT13 14
+#define R_SPARC_GOT22 15
+#define R_SPARC_PC10 16
+#define R_SPARC_PC22 17
+#define R_SPARC_WPLT30 18
+#define R_SPARC_COPY 19
+#define R_SPARC_GLOB_DAT 20
+#define R_SPARC_JMP_SLOT 21
+#define R_SPARC_RELATIVE 22
+#define R_SPARC_UA32 23
+
+#define R_SPARC_PLT32 24
+#define R_SPARC_HIPLT22 25
+#define R_SPARC_LOPLT10 26
+#define R_SPARC_PCPLT32 27
+#define R_SPARC_PCPLT22 28
+#define R_SPARC_PCPLT10 29
+#define R_SPARC_10 30
+#define R_SPARC_11 31
+#define R_SPARC_64 32
+#define R_SPARC_OLO10 33
+#define R_SPARC_HH22 34
+#define R_SPARC_HM10 35
+#define R_SPARC_LM22 36
+#define R_SPARC_PC_HH22 37
+#define R_SPARC_PC_HM10 38
+#define R_SPARC_PC_LM22 39
+#define R_SPARC_WDISP16 40
+#define R_SPARC_WDISP19 41
+#define R_SPARC_GLOB_JMP 42
+#define R_SPARC_7 43
+#define R_SPARC_5 44
+#define R_SPARC_6 45
+#define R_SPARC_DISP64 46
+#define R_SPARC_PLT64 47
+#define R_SPARC_HIX22 48
+#define R_SPARC_LOX10 49
+#define R_SPARC_H44 50
+#define R_SPARC_M44 51
+#define R_SPARC_L44 52
+#define R_SPARC_REGISTER 53
+#define R_SPARC_UA64 54
+#define R_SPARC_UA16 55
+#define R_SPARC_TLS_GD_HI22 56
+#define R_SPARC_TLS_GD_LO10 57
+#define R_SPARC_TLS_GD_ADD 58
+#define R_SPARC_TLS_GD_CALL 59
+#define R_SPARC_TLS_LDM_HI22 60
+#define R_SPARC_TLS_LDM_LO10 61
+#define R_SPARC_TLS_LDM_ADD 62
+#define R_SPARC_TLS_LDM_CALL 63
+#define R_SPARC_TLS_LDO_HIX22 64
+#define R_SPARC_TLS_LDO_LOX10 65
+#define R_SPARC_TLS_LDO_ADD 66
+#define R_SPARC_TLS_IE_HI22 67
+#define R_SPARC_TLS_IE_LO10 68
+#define R_SPARC_TLS_IE_LD 69
+#define R_SPARC_TLS_IE_LDX 70
+#define R_SPARC_TLS_IE_ADD 71
+#define R_SPARC_TLS_LE_HIX22 72
+#define R_SPARC_TLS_LE_LOX10 73
+#define R_SPARC_TLS_DTPMOD32 74
+#define R_SPARC_TLS_DTPMOD64 75
+#define R_SPARC_TLS_DTPOFF32 76
+#define R_SPARC_TLS_DTPOFF64 77
+#define R_SPARC_TLS_TPOFF32 78
+#define R_SPARC_TLS_TPOFF64 79
+#define R_SPARC_GOTDATA_HIX22 80
+#define R_SPARC_GOTDATA_LOX10 81
+#define R_SPARC_GOTDATA_OP_HIX22 82
+#define R_SPARC_GOTDATA_OP_LOX10 83
+#define R_SPARC_GOTDATA_OP 84
+#define R_SPARC_H34 85
+#define R_SPARC_SIZE32 86
+#define R_SPARC_SIZE64 87
+#define R_SPARC_GNU_VTINHERIT 250
+#define R_SPARC_GNU_VTENTRY 251
+#define R_SPARC_REV32 252
+
+#define R_SPARC_NUM 253
+
+#define DT_SPARC_REGISTER 0x70000001
+#define DT_SPARC_NUM 2
+
+#define EF_MIPS_NOREORDER 1
+#define EF_MIPS_PIC 2
+#define EF_MIPS_CPIC 4
+#define EF_MIPS_XGOT 8
+#define EF_MIPS_64BIT_WHIRL 16
+#define EF_MIPS_ABI2 32
+#define EF_MIPS_ABI_ON32 64
+#define EF_MIPS_FP64 512
+#define EF_MIPS_NAN2008 1024
+#define EF_MIPS_ARCH 0xf0000000
+
+#define EF_MIPS_ARCH_1 0x00000000
+#define EF_MIPS_ARCH_2 0x10000000
+#define EF_MIPS_ARCH_3 0x20000000
+#define EF_MIPS_ARCH_4 0x30000000
+#define EF_MIPS_ARCH_5 0x40000000
+#define EF_MIPS_ARCH_32 0x50000000
+#define EF_MIPS_ARCH_64 0x60000000
+#define EF_MIPS_ARCH_32R2 0x70000000
+#define EF_MIPS_ARCH_64R2 0x80000000
+
+#define E_MIPS_ARCH_1 0x00000000
+#define E_MIPS_ARCH_2 0x10000000
+#define E_MIPS_ARCH_3 0x20000000
+#define E_MIPS_ARCH_4 0x30000000
+#define E_MIPS_ARCH_5 0x40000000
+#define E_MIPS_ARCH_32 0x50000000
+#define E_MIPS_ARCH_64 0x60000000
+
+#define SHN_MIPS_ACOMMON 0xff00
+#define SHN_MIPS_TEXT 0xff01
+#define SHN_MIPS_DATA 0xff02
+#define SHN_MIPS_SCOMMON 0xff03
+#define SHN_MIPS_SUNDEFINED 0xff04
+
+#define SHT_MIPS_LIBLIST 0x70000000
+#define SHT_MIPS_MSYM 0x70000001
+#define SHT_MIPS_CONFLICT 0x70000002
+#define SHT_MIPS_GPTAB 0x70000003
+#define SHT_MIPS_UCODE 0x70000004
+#define SHT_MIPS_DEBUG 0x70000005
+#define SHT_MIPS_REGINFO 0x70000006
+#define SHT_MIPS_PACKAGE 0x70000007
+#define SHT_MIPS_PACKSYM 0x70000008
+#define SHT_MIPS_RELD 0x70000009
+#define SHT_MIPS_IFACE 0x7000000b
+#define SHT_MIPS_CONTENT 0x7000000c
+#define SHT_MIPS_OPTIONS 0x7000000d
+#define SHT_MIPS_SHDR 0x70000010
+#define SHT_MIPS_FDESC 0x70000011
+#define SHT_MIPS_EXTSYM 0x70000012
+#define SHT_MIPS_DENSE 0x70000013
+#define SHT_MIPS_PDESC 0x70000014
+#define SHT_MIPS_LOCSYM 0x70000015
+#define SHT_MIPS_AUXSYM 0x70000016
+#define SHT_MIPS_OPTSYM 0x70000017
+#define SHT_MIPS_LOCSTR 0x70000018
+#define SHT_MIPS_LINE 0x70000019
+#define SHT_MIPS_RFDESC 0x7000001a
+#define SHT_MIPS_DELTASYM 0x7000001b
+#define SHT_MIPS_DELTAINST 0x7000001c
+#define SHT_MIPS_DELTACLASS 0x7000001d
+#define SHT_MIPS_DWARF 0x7000001e
+#define SHT_MIPS_DELTADECL 0x7000001f
+#define SHT_MIPS_SYMBOL_LIB 0x70000020
+#define SHT_MIPS_EVENTS 0x70000021
+#define SHT_MIPS_TRANSLATE 0x70000022
+#define SHT_MIPS_PIXIE 0x70000023
+#define SHT_MIPS_XLATE 0x70000024
+#define SHT_MIPS_XLATE_DEBUG 0x70000025
+#define SHT_MIPS_WHIRL 0x70000026
+#define SHT_MIPS_EH_REGION 0x70000027
+#define SHT_MIPS_XLATE_OLD 0x70000028
+#define SHT_MIPS_PDR_EXCEPTION 0x70000029
+
+#define SHF_MIPS_GPREL 0x10000000
+#define SHF_MIPS_MERGE 0x20000000
+#define SHF_MIPS_ADDR 0x40000000
+#define SHF_MIPS_STRINGS 0x80000000
+#define SHF_MIPS_NOSTRIP 0x08000000
+#define SHF_MIPS_LOCAL 0x04000000
+#define SHF_MIPS_NAMES 0x02000000
+#define SHF_MIPS_NODUPE 0x01000000
+
+#define STO_MIPS_DEFAULT 0x0
+#define STO_MIPS_INTERNAL 0x1
+#define STO_MIPS_HIDDEN 0x2
+#define STO_MIPS_PROTECTED 0x3
+#define STO_MIPS_PLT 0x8
+#define STO_MIPS_SC_ALIGN_UNUSED 0xff
+
+#define STB_MIPS_SPLIT_COMMON 13
+
+typedef union {
+ struct {
+ Elf32_Word gt_current_g_value;
+ Elf32_Word gt_unused;
+ } gt_header;
+ struct {
+ Elf32_Word gt_g_value;
+ Elf32_Word gt_bytes;
+ } gt_entry;
+} Elf32_gptab;
+
+typedef struct {
+ Elf32_Word ri_gprmask;
+ Elf32_Word ri_cprmask[4];
+ Elf32_Sword ri_gp_value;
+} Elf32_RegInfo;
+
+typedef struct {
+ unsigned char kind;
+
+ unsigned char size;
+ Elf32_Section section;
+
+ Elf32_Word info;
+} Elf_Options;
+
+#define ODK_NULL 0
+#define ODK_REGINFO 1
+#define ODK_EXCEPTIONS 2
+#define ODK_PAD 3
+#define ODK_HWPATCH 4
+#define ODK_FILL 5
+#define ODK_TAGS 6
+#define ODK_HWAND 7
+#define ODK_HWOR 8
+
+#define OEX_FPU_MIN 0x1f
+#define OEX_FPU_MAX 0x1f00
+#define OEX_PAGE0 0x10000
+#define OEX_SMM 0x20000
+#define OEX_FPDBUG 0x40000
+#define OEX_PRECISEFP OEX_FPDBUG
+#define OEX_DISMISS 0x80000
+
+#define OEX_FPU_INVAL 0x10
+#define OEX_FPU_DIV0 0x08
+#define OEX_FPU_OFLO 0x04
+#define OEX_FPU_UFLO 0x02
+#define OEX_FPU_INEX 0x01
+
+#define OHW_R4KEOP 0x1
+#define OHW_R8KPFETCH 0x2
+#define OHW_R5KEOP 0x4
+#define OHW_R5KCVTL 0x8
+
+#define OPAD_PREFIX 0x1
+#define OPAD_POSTFIX 0x2
+#define OPAD_SYMBOL 0x4
+
+typedef struct {
+ Elf32_Word hwp_flags1;
+ Elf32_Word hwp_flags2;
+} Elf_Options_Hw;
+
+#define OHWA0_R4KEOP_CHECKED 0x00000001
+#define OHWA1_R4KEOP_CLEAN 0x00000002
+
+#define R_MIPS_NONE 0
+#define R_MIPS_16 1
+#define R_MIPS_32 2
+#define R_MIPS_REL32 3
+#define R_MIPS_26 4
+#define R_MIPS_HI16 5
+#define R_MIPS_LO16 6
+#define R_MIPS_GPREL16 7
+#define R_MIPS_LITERAL 8
+#define R_MIPS_GOT16 9
+#define R_MIPS_PC16 10
+#define R_MIPS_CALL16 11
+#define R_MIPS_GPREL32 12
+
+#define R_MIPS_SHIFT5 16
+#define R_MIPS_SHIFT6 17
+#define R_MIPS_64 18
+#define R_MIPS_GOT_DISP 19
+#define R_MIPS_GOT_PAGE 20
+#define R_MIPS_GOT_OFST 21
+#define R_MIPS_GOT_HI16 22
+#define R_MIPS_GOT_LO16 23
+#define R_MIPS_SUB 24
+#define R_MIPS_INSERT_A 25
+#define R_MIPS_INSERT_B 26
+#define R_MIPS_DELETE 27
+#define R_MIPS_HIGHER 28
+#define R_MIPS_HIGHEST 29
+#define R_MIPS_CALL_HI16 30
+#define R_MIPS_CALL_LO16 31
+#define R_MIPS_SCN_DISP 32
+#define R_MIPS_REL16 33
+#define R_MIPS_ADD_IMMEDIATE 34
+#define R_MIPS_PJUMP 35
+#define R_MIPS_RELGOT 36
+#define R_MIPS_JALR 37
+#define R_MIPS_TLS_DTPMOD32 38
+#define R_MIPS_TLS_DTPREL32 39
+#define R_MIPS_TLS_DTPMOD64 40
+#define R_MIPS_TLS_DTPREL64 41
+#define R_MIPS_TLS_GD 42
+#define R_MIPS_TLS_LDM 43
+#define R_MIPS_TLS_DTPREL_HI16 44
+#define R_MIPS_TLS_DTPREL_LO16 45
+#define R_MIPS_TLS_GOTTPREL 46
+#define R_MIPS_TLS_TPREL32 47
+#define R_MIPS_TLS_TPREL64 48
+#define R_MIPS_TLS_TPREL_HI16 49
+#define R_MIPS_TLS_TPREL_LO16 50
+#define R_MIPS_GLOB_DAT 51
+#define R_MIPS_COPY 126
+#define R_MIPS_JUMP_SLOT 127
+
+#define R_MIPS_NUM 128
+
+#define PT_MIPS_REGINFO 0x70000000
+#define PT_MIPS_RTPROC 0x70000001
+#define PT_MIPS_OPTIONS 0x70000002
+#define PT_MIPS_ABIFLAGS 0x70000003
+
+#define PF_MIPS_LOCAL 0x10000000
+
+#define DT_MIPS_RLD_VERSION 0x70000001
+#define DT_MIPS_TIME_STAMP 0x70000002
+#define DT_MIPS_ICHECKSUM 0x70000003
+#define DT_MIPS_IVERSION 0x70000004
+#define DT_MIPS_FLAGS 0x70000005
+#define DT_MIPS_BASE_ADDRESS 0x70000006
+#define DT_MIPS_MSYM 0x70000007
+#define DT_MIPS_CONFLICT 0x70000008
+#define DT_MIPS_LIBLIST 0x70000009
+#define DT_MIPS_LOCAL_GOTNO 0x7000000a
+#define DT_MIPS_CONFLICTNO 0x7000000b
+#define DT_MIPS_LIBLISTNO 0x70000010
+#define DT_MIPS_SYMTABNO 0x70000011
+#define DT_MIPS_UNREFEXTNO 0x70000012
+#define DT_MIPS_GOTSYM 0x70000013
+#define DT_MIPS_HIPAGENO 0x70000014
+#define DT_MIPS_RLD_MAP 0x70000016
+#define DT_MIPS_DELTA_CLASS 0x70000017
+#define DT_MIPS_DELTA_CLASS_NO 0x70000018
+
+#define DT_MIPS_DELTA_INSTANCE 0x70000019
+#define DT_MIPS_DELTA_INSTANCE_NO 0x7000001a
+
+#define DT_MIPS_DELTA_RELOC 0x7000001b
+#define DT_MIPS_DELTA_RELOC_NO 0x7000001c
+
+#define DT_MIPS_DELTA_SYM 0x7000001d
+
+#define DT_MIPS_DELTA_SYM_NO 0x7000001e
+
+#define DT_MIPS_DELTA_CLASSSYM 0x70000020
+
+#define DT_MIPS_DELTA_CLASSSYM_NO 0x70000021
+
+#define DT_MIPS_CXX_FLAGS 0x70000022
+#define DT_MIPS_PIXIE_INIT 0x70000023
+#define DT_MIPS_SYMBOL_LIB 0x70000024
+#define DT_MIPS_LOCALPAGE_GOTIDX 0x70000025
+#define DT_MIPS_LOCAL_GOTIDX 0x70000026
+#define DT_MIPS_HIDDEN_GOTIDX 0x70000027
+#define DT_MIPS_PROTECTED_GOTIDX 0x70000028
+#define DT_MIPS_OPTIONS 0x70000029
+#define DT_MIPS_INTERFACE 0x7000002a
+#define DT_MIPS_DYNSTR_ALIGN 0x7000002b
+#define DT_MIPS_INTERFACE_SIZE 0x7000002c
+#define DT_MIPS_RLD_TEXT_RESOLVE_ADDR 0x7000002d
+
+#define DT_MIPS_PERF_SUFFIX 0x7000002e
+
+#define DT_MIPS_COMPACT_SIZE 0x7000002f
+#define DT_MIPS_GP_VALUE 0x70000030
+#define DT_MIPS_AUX_DYNAMIC 0x70000031
+
+#define DT_MIPS_PLTGOT 0x70000032
+
+#define DT_MIPS_RWPLT 0x70000034
+#define DT_MIPS_RLD_MAP_REL 0x70000035
+#define DT_MIPS_NUM 0x36
+
+#define RHF_NONE 0
+#define RHF_QUICKSTART (1 << 0)
+#define RHF_NOTPOT (1 << 1)
+#define RHF_NO_LIBRARY_REPLACEMENT (1 << 2)
+#define RHF_NO_MOVE (1 << 3)
+#define RHF_SGI_ONLY (1 << 4)
+#define RHF_GUARANTEE_INIT (1 << 5)
+#define RHF_DELTA_C_PLUS_PLUS (1 << 6)
+#define RHF_GUARANTEE_START_INIT (1 << 7)
+#define RHF_PIXIE (1 << 8)
+#define RHF_DEFAULT_DELAY_LOAD (1 << 9)
+#define RHF_REQUICKSTART (1 << 10)
+#define RHF_REQUICKSTARTED (1 << 11)
+#define RHF_CORD (1 << 12)
+#define RHF_NO_UNRES_UNDEF (1 << 13)
+#define RHF_RLD_ORDER_SAFE (1 << 14)
+
+typedef struct {
+ Elf32_Word l_name;
+ Elf32_Word l_time_stamp;
+ Elf32_Word l_checksum;
+ Elf32_Word l_version;
+ Elf32_Word l_flags;
+} Elf32_Lib;
+
+typedef struct {
+ Elf64_Word l_name;
+ Elf64_Word l_time_stamp;
+ Elf64_Word l_checksum;
+ Elf64_Word l_version;
+ Elf64_Word l_flags;
+} Elf64_Lib;
+
+#define LL_NONE 0
+#define LL_EXACT_MATCH (1 << 0)
+#define LL_IGNORE_INT_VER (1 << 1)
+#define LL_REQUIRE_MINOR (1 << 2)
+#define LL_EXPORTS (1 << 3)
+#define LL_DELAY_LOAD (1 << 4)
+#define LL_DELTA (1 << 5)
+
+typedef Elf32_Addr Elf32_Conflict;
+
+typedef struct {
+ Elf32_Half version;
+ unsigned char isa_level;
+ unsigned char isa_rev;
+ unsigned char gpr_size;
+ unsigned char cpr1_size;
+ unsigned char cpr2_size;
+ unsigned char fp_abi;
+ Elf32_Word isa_ext;
+ Elf32_Word ases;
+ Elf32_Word flags1;
+ Elf32_Word flags2;
+} Elf_MIPS_ABIFlags_v0;
+
+#define MIPS_AFL_REG_NONE 0x00
+#define MIPS_AFL_REG_32 0x01
+#define MIPS_AFL_REG_64 0x02
+#define MIPS_AFL_REG_128 0x03
+
+#define MIPS_AFL_ASE_DSP 0x00000001
+#define MIPS_AFL_ASE_DSPR2 0x00000002
+#define MIPS_AFL_ASE_EVA 0x00000004
+#define MIPS_AFL_ASE_MCU 0x00000008
+#define MIPS_AFL_ASE_MDMX 0x00000010
+#define MIPS_AFL_ASE_MIPS3D 0x00000020
+#define MIPS_AFL_ASE_MT 0x00000040
+#define MIPS_AFL_ASE_SMARTMIPS 0x00000080
+#define MIPS_AFL_ASE_VIRT 0x00000100
+#define MIPS_AFL_ASE_MSA 0x00000200
+#define MIPS_AFL_ASE_MIPS16 0x00000400
+#define MIPS_AFL_ASE_MICROMIPS 0x00000800
+#define MIPS_AFL_ASE_XPA 0x00001000
+#define MIPS_AFL_ASE_MASK 0x00001fff
+
+#define MIPS_AFL_EXT_XLR 1
+#define MIPS_AFL_EXT_OCTEON2 2
+#define MIPS_AFL_EXT_OCTEONP 3
+#define MIPS_AFL_EXT_LOONGSON_3A 4
+#define MIPS_AFL_EXT_OCTEON 5
+#define MIPS_AFL_EXT_5900 6
+#define MIPS_AFL_EXT_4650 7
+#define MIPS_AFL_EXT_4010 8
+#define MIPS_AFL_EXT_4100 9
+#define MIPS_AFL_EXT_3900 10
+#define MIPS_AFL_EXT_10000 11
+#define MIPS_AFL_EXT_SB1 12
+#define MIPS_AFL_EXT_4111 13
+#define MIPS_AFL_EXT_4120 14
+#define MIPS_AFL_EXT_5400 15
+#define MIPS_AFL_EXT_5500 16
+#define MIPS_AFL_EXT_LOONGSON_2E 17
+#define MIPS_AFL_EXT_LOONGSON_2F 18
+
+#define MIPS_AFL_FLAGS1_ODDSPREG 1
+
+enum {
+ Val_GNU_MIPS_ABI_FP_ANY = 0,
+ Val_GNU_MIPS_ABI_FP_DOUBLE = 1,
+ Val_GNU_MIPS_ABI_FP_SINGLE = 2,
+ Val_GNU_MIPS_ABI_FP_SOFT = 3,
+ Val_GNU_MIPS_ABI_FP_OLD_64 = 4,
+ Val_GNU_MIPS_ABI_FP_XX = 5,
+ Val_GNU_MIPS_ABI_FP_64 = 6,
+ Val_GNU_MIPS_ABI_FP_64A = 7,
+ Val_GNU_MIPS_ABI_FP_MAX = 7
+};
+
+#define EF_PARISC_TRAPNIL 0x00010000
+#define EF_PARISC_EXT 0x00020000
+#define EF_PARISC_LSB 0x00040000
+#define EF_PARISC_WIDE 0x00080000
+#define EF_PARISC_NO_KABP 0x00100000
+
+#define EF_PARISC_LAZYSWAP 0x00400000
+#define EF_PARISC_ARCH 0x0000ffff
+
+#define EFA_PARISC_1_0 0x020b
+#define EFA_PARISC_1_1 0x0210
+#define EFA_PARISC_2_0 0x0214
+
+#define SHN_PARISC_ANSI_COMMON 0xff00
+
+#define SHN_PARISC_HUGE_COMMON 0xff01
+
+#define SHT_PARISC_EXT 0x70000000
+#define SHT_PARISC_UNWIND 0x70000001
+#define SHT_PARISC_DOC 0x70000002
+
+#define SHF_PARISC_SHORT 0x20000000
+#define SHF_PARISC_HUGE 0x40000000
+#define SHF_PARISC_SBP 0x80000000
+
+#define STT_PARISC_MILLICODE 13
+
+#define STT_HP_OPAQUE (STT_LOOS + 0x1)
+#define STT_HP_STUB (STT_LOOS + 0x2)
+
+#define R_PARISC_NONE 0
+#define R_PARISC_DIR32 1
+#define R_PARISC_DIR21L 2
+#define R_PARISC_DIR17R 3
+#define R_PARISC_DIR17F 4
+#define R_PARISC_DIR14R 6
+#define R_PARISC_PCREL32 9
+#define R_PARISC_PCREL21L 10
+#define R_PARISC_PCREL17R 11
+#define R_PARISC_PCREL17F 12
+#define R_PARISC_PCREL14R 14
+#define R_PARISC_DPREL21L 18
+#define R_PARISC_DPREL14R 22
+#define R_PARISC_GPREL21L 26
+#define R_PARISC_GPREL14R 30
+#define R_PARISC_LTOFF21L 34
+#define R_PARISC_LTOFF14R 38
+#define R_PARISC_SECREL32 41
+#define R_PARISC_SEGBASE 48
+#define R_PARISC_SEGREL32 49
+#define R_PARISC_PLTOFF21L 50
+#define R_PARISC_PLTOFF14R 54
+#define R_PARISC_LTOFF_FPTR32 57
+#define R_PARISC_LTOFF_FPTR21L 58
+#define R_PARISC_LTOFF_FPTR14R 62
+#define R_PARISC_FPTR64 64
+#define R_PARISC_PLABEL32 65
+#define R_PARISC_PLABEL21L 66
+#define R_PARISC_PLABEL14R 70
+#define R_PARISC_PCREL64 72
+#define R_PARISC_PCREL22F 74
+#define R_PARISC_PCREL14WR 75
+#define R_PARISC_PCREL14DR 76
+#define R_PARISC_PCREL16F 77
+#define R_PARISC_PCREL16WF 78
+#define R_PARISC_PCREL16DF 79
+#define R_PARISC_DIR64 80
+#define R_PARISC_DIR14WR 83
+#define R_PARISC_DIR14DR 84
+#define R_PARISC_DIR16F 85
+#define R_PARISC_DIR16WF 86
+#define R_PARISC_DIR16DF 87
+#define R_PARISC_GPREL64 88
+#define R_PARISC_GPREL14WR 91
+#define R_PARISC_GPREL14DR 92
+#define R_PARISC_GPREL16F 93
+#define R_PARISC_GPREL16WF 94
+#define R_PARISC_GPREL16DF 95
+#define R_PARISC_LTOFF64 96
+#define R_PARISC_LTOFF14WR 99
+#define R_PARISC_LTOFF14DR 100
+#define R_PARISC_LTOFF16F 101
+#define R_PARISC_LTOFF16WF 102
+#define R_PARISC_LTOFF16DF 103
+#define R_PARISC_SECREL64 104
+#define R_PARISC_SEGREL64 112
+#define R_PARISC_PLTOFF14WR 115
+#define R_PARISC_PLTOFF14DR 116
+#define R_PARISC_PLTOFF16F 117
+#define R_PARISC_PLTOFF16WF 118
+#define R_PARISC_PLTOFF16DF 119
+#define R_PARISC_LTOFF_FPTR64 120
+#define R_PARISC_LTOFF_FPTR14WR 123
+#define R_PARISC_LTOFF_FPTR14DR 124
+#define R_PARISC_LTOFF_FPTR16F 125
+#define R_PARISC_LTOFF_FPTR16WF 126
+#define R_PARISC_LTOFF_FPTR16DF 127
+#define R_PARISC_LORESERVE 128
+#define R_PARISC_COPY 128
+#define R_PARISC_IPLT 129
+#define R_PARISC_EPLT 130
+#define R_PARISC_TPREL32 153
+#define R_PARISC_TPREL21L 154
+#define R_PARISC_TPREL14R 158
+#define R_PARISC_LTOFF_TP21L 162
+#define R_PARISC_LTOFF_TP14R 166
+#define R_PARISC_LTOFF_TP14F 167
+#define R_PARISC_TPREL64 216
+#define R_PARISC_TPREL14WR 219
+#define R_PARISC_TPREL14DR 220
+#define R_PARISC_TPREL16F 221
+#define R_PARISC_TPREL16WF 222
+#define R_PARISC_TPREL16DF 223
+#define R_PARISC_LTOFF_TP64 224
+#define R_PARISC_LTOFF_TP14WR 227
+#define R_PARISC_LTOFF_TP14DR 228
+#define R_PARISC_LTOFF_TP16F 229
+#define R_PARISC_LTOFF_TP16WF 230
+#define R_PARISC_LTOFF_TP16DF 231
+#define R_PARISC_GNU_VTENTRY 232
+#define R_PARISC_GNU_VTINHERIT 233
+#define R_PARISC_TLS_GD21L 234
+#define R_PARISC_TLS_GD14R 235
+#define R_PARISC_TLS_GDCALL 236
+#define R_PARISC_TLS_LDM21L 237
+#define R_PARISC_TLS_LDM14R 238
+#define R_PARISC_TLS_LDMCALL 239
+#define R_PARISC_TLS_LDO21L 240
+#define R_PARISC_TLS_LDO14R 241
+#define R_PARISC_TLS_DTPMOD32 242
+#define R_PARISC_TLS_DTPMOD64 243
+#define R_PARISC_TLS_DTPOFF32 244
+#define R_PARISC_TLS_DTPOFF64 245
+#define R_PARISC_TLS_LE21L R_PARISC_TPREL21L
+#define R_PARISC_TLS_LE14R R_PARISC_TPREL14R
+#define R_PARISC_TLS_IE21L R_PARISC_LTOFF_TP21L
+#define R_PARISC_TLS_IE14R R_PARISC_LTOFF_TP14R
+#define R_PARISC_TLS_TPREL32 R_PARISC_TPREL32
+#define R_PARISC_TLS_TPREL64 R_PARISC_TPREL64
+#define R_PARISC_HIRESERVE 255
+
+#define PT_HP_TLS (PT_LOOS + 0x0)
+#define PT_HP_CORE_NONE (PT_LOOS + 0x1)
+#define PT_HP_CORE_VERSION (PT_LOOS + 0x2)
+#define PT_HP_CORE_KERNEL (PT_LOOS + 0x3)
+#define PT_HP_CORE_COMM (PT_LOOS + 0x4)
+#define PT_HP_CORE_PROC (PT_LOOS + 0x5)
+#define PT_HP_CORE_LOADABLE (PT_LOOS + 0x6)
+#define PT_HP_CORE_STACK (PT_LOOS + 0x7)
+#define PT_HP_CORE_SHM (PT_LOOS + 0x8)
+#define PT_HP_CORE_MMF (PT_LOOS + 0x9)
+#define PT_HP_PARALLEL (PT_LOOS + 0x10)
+#define PT_HP_FASTBIND (PT_LOOS + 0x11)
+#define PT_HP_OPT_ANNOT (PT_LOOS + 0x12)
+#define PT_HP_HSL_ANNOT (PT_LOOS + 0x13)
+#define PT_HP_STACK (PT_LOOS + 0x14)
+
+#define PT_PARISC_ARCHEXT 0x70000000
+#define PT_PARISC_UNWIND 0x70000001
+
+#define PF_PARISC_SBP 0x08000000
+
+#define PF_HP_PAGE_SIZE 0x00100000
+#define PF_HP_FAR_SHARED 0x00200000
+#define PF_HP_NEAR_SHARED 0x00400000
+#define PF_HP_CODE 0x01000000
+#define PF_HP_MODIFY 0x02000000
+#define PF_HP_LAZYSWAP 0x04000000
+#define PF_HP_SBP 0x08000000
+
+#define EF_ALPHA_32BIT 1
+#define EF_ALPHA_CANRELAX 2
+
+#define SHT_ALPHA_DEBUG 0x70000001
+#define SHT_ALPHA_REGINFO 0x70000002
+
+#define SHF_ALPHA_GPREL 0x10000000
+
+#define STO_ALPHA_NOPV 0x80
+#define STO_ALPHA_STD_GPLOAD 0x88
+
+#define R_ALPHA_NONE 0
+#define R_ALPHA_REFLONG 1
+#define R_ALPHA_REFQUAD 2
+#define R_ALPHA_GPREL32 3
+#define R_ALPHA_LITERAL 4
+#define R_ALPHA_LITUSE 5
+#define R_ALPHA_GPDISP 6
+#define R_ALPHA_BRADDR 7
+#define R_ALPHA_HINT 8
+#define R_ALPHA_SREL16 9
+#define R_ALPHA_SREL32 10
+#define R_ALPHA_SREL64 11
+#define R_ALPHA_GPRELHIGH 17
+#define R_ALPHA_GPRELLOW 18
+#define R_ALPHA_GPREL16 19
+#define R_ALPHA_COPY 24
+#define R_ALPHA_GLOB_DAT 25
+#define R_ALPHA_JMP_SLOT 26
+#define R_ALPHA_RELATIVE 27
+#define R_ALPHA_TLS_GD_HI 28
+#define R_ALPHA_TLSGD 29
+#define R_ALPHA_TLS_LDM 30
+#define R_ALPHA_DTPMOD64 31
+#define R_ALPHA_GOTDTPREL 32
+#define R_ALPHA_DTPREL64 33
+#define R_ALPHA_DTPRELHI 34
+#define R_ALPHA_DTPRELLO 35
+#define R_ALPHA_DTPREL16 36
+#define R_ALPHA_GOTTPREL 37
+#define R_ALPHA_TPREL64 38
+#define R_ALPHA_TPRELHI 39
+#define R_ALPHA_TPRELLO 40
+#define R_ALPHA_TPREL16 41
+
+#define R_ALPHA_NUM 46
+
+#define LITUSE_ALPHA_ADDR 0
+#define LITUSE_ALPHA_BASE 1
+#define LITUSE_ALPHA_BYTOFF 2
+#define LITUSE_ALPHA_JSR 3
+#define LITUSE_ALPHA_TLS_GD 4
+#define LITUSE_ALPHA_TLS_LDM 5
+
+#define DT_ALPHA_PLTRO (DT_LOPROC + 0)
+#define DT_ALPHA_NUM 1
+
+#define EF_PPC_EMB 0x80000000
+
+#define EF_PPC_RELOCATABLE 0x00010000
+#define EF_PPC_RELOCATABLE_LIB 0x00008000
+
+#define R_PPC_NONE 0
+#define R_PPC_ADDR32 1
+#define R_PPC_ADDR24 2
+#define R_PPC_ADDR16 3
+#define R_PPC_ADDR16_LO 4
+#define R_PPC_ADDR16_HI 5
+#define R_PPC_ADDR16_HA 6
+#define R_PPC_ADDR14 7
+#define R_PPC_ADDR14_BRTAKEN 8
+#define R_PPC_ADDR14_BRNTAKEN 9
+#define R_PPC_REL24 10
+#define R_PPC_REL14 11
+#define R_PPC_REL14_BRTAKEN 12
+#define R_PPC_REL14_BRNTAKEN 13
+#define R_PPC_GOT16 14
+#define R_PPC_GOT16_LO 15
+#define R_PPC_GOT16_HI 16
+#define R_PPC_GOT16_HA 17
+#define R_PPC_PLTREL24 18
+#define R_PPC_COPY 19
+#define R_PPC_GLOB_DAT 20
+#define R_PPC_JMP_SLOT 21
+#define R_PPC_RELATIVE 22
+#define R_PPC_LOCAL24PC 23
+#define R_PPC_UADDR32 24
+#define R_PPC_UADDR16 25
+#define R_PPC_REL32 26
+#define R_PPC_PLT32 27
+#define R_PPC_PLTREL32 28
+#define R_PPC_PLT16_LO 29
+#define R_PPC_PLT16_HI 30
+#define R_PPC_PLT16_HA 31
+#define R_PPC_SDAREL16 32
+#define R_PPC_SECTOFF 33
+#define R_PPC_SECTOFF_LO 34
+#define R_PPC_SECTOFF_HI 35
+#define R_PPC_SECTOFF_HA 36
+
+#define R_PPC_TLS 67
+#define R_PPC_DTPMOD32 68
+#define R_PPC_TPREL16 69
+#define R_PPC_TPREL16_LO 70
+#define R_PPC_TPREL16_HI 71
+#define R_PPC_TPREL16_HA 72
+#define R_PPC_TPREL32 73
+#define R_PPC_DTPREL16 74
+#define R_PPC_DTPREL16_LO 75
+#define R_PPC_DTPREL16_HI 76
+#define R_PPC_DTPREL16_HA 77
+#define R_PPC_DTPREL32 78
+#define R_PPC_GOT_TLSGD16 79
+#define R_PPC_GOT_TLSGD16_LO 80
+#define R_PPC_GOT_TLSGD16_HI 81
+#define R_PPC_GOT_TLSGD16_HA 82
+#define R_PPC_GOT_TLSLD16 83
+#define R_PPC_GOT_TLSLD16_LO 84
+#define R_PPC_GOT_TLSLD16_HI 85
+#define R_PPC_GOT_TLSLD16_HA 86
+#define R_PPC_GOT_TPREL16 87
+#define R_PPC_GOT_TPREL16_LO 88
+#define R_PPC_GOT_TPREL16_HI 89
+#define R_PPC_GOT_TPREL16_HA 90
+#define R_PPC_GOT_DTPREL16 91
+#define R_PPC_GOT_DTPREL16_LO 92
+#define R_PPC_GOT_DTPREL16_HI 93
+#define R_PPC_GOT_DTPREL16_HA 94
+#define R_PPC_TLSGD 95
+#define R_PPC_TLSLD 96
+
+#define R_PPC_EMB_NADDR32 101
+#define R_PPC_EMB_NADDR16 102
+#define R_PPC_EMB_NADDR16_LO 103
+#define R_PPC_EMB_NADDR16_HI 104
+#define R_PPC_EMB_NADDR16_HA 105
+#define R_PPC_EMB_SDAI16 106
+#define R_PPC_EMB_SDA2I16 107
+#define R_PPC_EMB_SDA2REL 108
+#define R_PPC_EMB_SDA21 109
+#define R_PPC_EMB_MRKREF 110
+#define R_PPC_EMB_RELSEC16 111
+#define R_PPC_EMB_RELST_LO 112
+#define R_PPC_EMB_RELST_HI 113
+#define R_PPC_EMB_RELST_HA 114
+#define R_PPC_EMB_BIT_FLD 115
+#define R_PPC_EMB_RELSDA 116
+
+#define R_PPC_DIAB_SDA21_LO 180
+#define R_PPC_DIAB_SDA21_HI 181
+#define R_PPC_DIAB_SDA21_HA 182
+#define R_PPC_DIAB_RELSDA_LO 183
+#define R_PPC_DIAB_RELSDA_HI 184
+#define R_PPC_DIAB_RELSDA_HA 185
+
+#define R_PPC_IRELATIVE 248
+
+#define R_PPC_REL16 249
+#define R_PPC_REL16_LO 250
+#define R_PPC_REL16_HI 251
+#define R_PPC_REL16_HA 252
+
+#define R_PPC_TOC16 255
+
+#define DT_PPC_GOT (DT_LOPROC + 0)
+#define DT_PPC_OPT (DT_LOPROC + 1)
+#define DT_PPC_NUM 2
+
+#define PPC_OPT_TLS 1
+
+#define R_PPC64_NONE R_PPC_NONE
+#define R_PPC64_ADDR32 R_PPC_ADDR32
+#define R_PPC64_ADDR24 R_PPC_ADDR24
+#define R_PPC64_ADDR16 R_PPC_ADDR16
+#define R_PPC64_ADDR16_LO R_PPC_ADDR16_LO
+#define R_PPC64_ADDR16_HI R_PPC_ADDR16_HI
+#define R_PPC64_ADDR16_HA R_PPC_ADDR16_HA
+#define R_PPC64_ADDR14 R_PPC_ADDR14
+#define R_PPC64_ADDR14_BRTAKEN R_PPC_ADDR14_BRTAKEN
+#define R_PPC64_ADDR14_BRNTAKEN R_PPC_ADDR14_BRNTAKEN
+#define R_PPC64_REL24 R_PPC_REL24
+#define R_PPC64_REL14 R_PPC_REL14
+#define R_PPC64_REL14_BRTAKEN R_PPC_REL14_BRTAKEN
+#define R_PPC64_REL14_BRNTAKEN R_PPC_REL14_BRNTAKEN
+#define R_PPC64_GOT16 R_PPC_GOT16
+#define R_PPC64_GOT16_LO R_PPC_GOT16_LO
+#define R_PPC64_GOT16_HI R_PPC_GOT16_HI
+#define R_PPC64_GOT16_HA R_PPC_GOT16_HA
+
+#define R_PPC64_COPY R_PPC_COPY
+#define R_PPC64_GLOB_DAT R_PPC_GLOB_DAT
+#define R_PPC64_JMP_SLOT R_PPC_JMP_SLOT
+#define R_PPC64_RELATIVE R_PPC_RELATIVE
+
+#define R_PPC64_UADDR32 R_PPC_UADDR32
+#define R_PPC64_UADDR16 R_PPC_UADDR16
+#define R_PPC64_REL32 R_PPC_REL32
+#define R_PPC64_PLT32 R_PPC_PLT32
+#define R_PPC64_PLTREL32 R_PPC_PLTREL32
+#define R_PPC64_PLT16_LO R_PPC_PLT16_LO
+#define R_PPC64_PLT16_HI R_PPC_PLT16_HI
+#define R_PPC64_PLT16_HA R_PPC_PLT16_HA
+
+#define R_PPC64_SECTOFF R_PPC_SECTOFF
+#define R_PPC64_SECTOFF_LO R_PPC_SECTOFF_LO
+#define R_PPC64_SECTOFF_HI R_PPC_SECTOFF_HI
+#define R_PPC64_SECTOFF_HA R_PPC_SECTOFF_HA
+#define R_PPC64_ADDR30 37
+#define R_PPC64_ADDR64 38
+#define R_PPC64_ADDR16_HIGHER 39
+#define R_PPC64_ADDR16_HIGHERA 40
+#define R_PPC64_ADDR16_HIGHEST 41
+#define R_PPC64_ADDR16_HIGHESTA 42
+#define R_PPC64_UADDR64 43
+#define R_PPC64_REL64 44
+#define R_PPC64_PLT64 45
+#define R_PPC64_PLTREL64 46
+#define R_PPC64_TOC16 47
+#define R_PPC64_TOC16_LO 48
+#define R_PPC64_TOC16_HI 49
+#define R_PPC64_TOC16_HA 50
+#define R_PPC64_TOC 51
+#define R_PPC64_PLTGOT16 52
+#define R_PPC64_PLTGOT16_LO 53
+#define R_PPC64_PLTGOT16_HI 54
+#define R_PPC64_PLTGOT16_HA 55
+
+#define R_PPC64_ADDR16_DS 56
+#define R_PPC64_ADDR16_LO_DS 57
+#define R_PPC64_GOT16_DS 58
+#define R_PPC64_GOT16_LO_DS 59
+#define R_PPC64_PLT16_LO_DS 60
+#define R_PPC64_SECTOFF_DS 61
+#define R_PPC64_SECTOFF_LO_DS 62
+#define R_PPC64_TOC16_DS 63
+#define R_PPC64_TOC16_LO_DS 64
+#define R_PPC64_PLTGOT16_DS 65
+#define R_PPC64_PLTGOT16_LO_DS 66
+
+#define R_PPC64_TLS 67
+#define R_PPC64_DTPMOD64 68
+#define R_PPC64_TPREL16 69
+#define R_PPC64_TPREL16_LO 70
+#define R_PPC64_TPREL16_HI 71
+#define R_PPC64_TPREL16_HA 72
+#define R_PPC64_TPREL64 73
+#define R_PPC64_DTPREL16 74
+#define R_PPC64_DTPREL16_LO 75
+#define R_PPC64_DTPREL16_HI 76
+#define R_PPC64_DTPREL16_HA 77
+#define R_PPC64_DTPREL64 78
+#define R_PPC64_GOT_TLSGD16 79
+#define R_PPC64_GOT_TLSGD16_LO 80
+#define R_PPC64_GOT_TLSGD16_HI 81
+#define R_PPC64_GOT_TLSGD16_HA 82
+#define R_PPC64_GOT_TLSLD16 83
+#define R_PPC64_GOT_TLSLD16_LO 84
+#define R_PPC64_GOT_TLSLD16_HI 85
+#define R_PPC64_GOT_TLSLD16_HA 86
+#define R_PPC64_GOT_TPREL16_DS 87
+#define R_PPC64_GOT_TPREL16_LO_DS 88
+#define R_PPC64_GOT_TPREL16_HI 89
+#define R_PPC64_GOT_TPREL16_HA 90
+#define R_PPC64_GOT_DTPREL16_DS 91
+#define R_PPC64_GOT_DTPREL16_LO_DS 92
+#define R_PPC64_GOT_DTPREL16_HI 93
+#define R_PPC64_GOT_DTPREL16_HA 94
+#define R_PPC64_TPREL16_DS 95
+#define R_PPC64_TPREL16_LO_DS 96
+#define R_PPC64_TPREL16_HIGHER 97
+#define R_PPC64_TPREL16_HIGHERA 98
+#define R_PPC64_TPREL16_HIGHEST 99
+#define R_PPC64_TPREL16_HIGHESTA 100
+#define R_PPC64_DTPREL16_DS 101
+#define R_PPC64_DTPREL16_LO_DS 102
+#define R_PPC64_DTPREL16_HIGHER 103
+#define R_PPC64_DTPREL16_HIGHERA 104
+#define R_PPC64_DTPREL16_HIGHEST 105
+#define R_PPC64_DTPREL16_HIGHESTA 106
+#define R_PPC64_TLSGD 107
+#define R_PPC64_TLSLD 108
+#define R_PPC64_TOCSAVE 109
+#define R_PPC64_ADDR16_HIGH 110
+#define R_PPC64_ADDR16_HIGHA 111
+#define R_PPC64_TPREL16_HIGH 112
+#define R_PPC64_TPREL16_HIGHA 113
+#define R_PPC64_DTPREL16_HIGH 114
+#define R_PPC64_DTPREL16_HIGHA 115
+
+#define R_PPC64_JMP_IREL 247
+#define R_PPC64_IRELATIVE 248
+#define R_PPC64_REL16 249
+#define R_PPC64_REL16_LO 250
+#define R_PPC64_REL16_HI 251
+#define R_PPC64_REL16_HA 252
+
+#define EF_PPC64_ABI 3
+
+#define DT_PPC64_GLINK (DT_LOPROC + 0)
+#define DT_PPC64_OPD (DT_LOPROC + 1)
+#define DT_PPC64_OPDSZ (DT_LOPROC + 2)
+#define DT_PPC64_OPT (DT_LOPROC + 3)
+#define DT_PPC64_NUM 4
+
+#define PPC64_OPT_TLS 1
+#define PPC64_OPT_MULTI_TOC 2
+
+#define STO_PPC64_LOCAL_BIT 5
+#define STO_PPC64_LOCAL_MASK 0xe0
+#define PPC64_LOCAL_ENTRY_OFFSET(x) (1 << (((x)&0xe0) >> 5) & 0xfc)
+
+#define EF_ARM_RELEXEC 0x01
+#define EF_ARM_HASENTRY 0x02
+#define EF_ARM_INTERWORK 0x04
+#define EF_ARM_APCS_26 0x08
+#define EF_ARM_APCS_FLOAT 0x10
+#define EF_ARM_PIC 0x20
+#define EF_ARM_ALIGN8 0x40
+#define EF_ARM_NEW_ABI 0x80
+#define EF_ARM_OLD_ABI 0x100
+#define EF_ARM_SOFT_FLOAT 0x200
+#define EF_ARM_VFP_FLOAT 0x400
+#define EF_ARM_MAVERICK_FLOAT 0x800
+
+#define EF_ARM_ABI_FLOAT_SOFT 0x200
+#define EF_ARM_ABI_FLOAT_HARD 0x400
+
+#define EF_ARM_SYMSARESORTED 0x04
+#define EF_ARM_DYNSYMSUSESEGIDX 0x08
+#define EF_ARM_MAPSYMSFIRST 0x10
+#define EF_ARM_EABIMASK 0XFF000000
+
+#define EF_ARM_BE8 0x00800000
+#define EF_ARM_LE8 0x00400000
+
+#define EF_ARM_EABI_VERSION(flags) ((flags)&EF_ARM_EABIMASK)
+#define EF_ARM_EABI_UNKNOWN 0x00000000
+#define EF_ARM_EABI_VER1 0x01000000
+#define EF_ARM_EABI_VER2 0x02000000
+#define EF_ARM_EABI_VER3 0x03000000
+#define EF_ARM_EABI_VER4 0x04000000
+#define EF_ARM_EABI_VER5 0x05000000
+
+#define STT_ARM_TFUNC STT_LOPROC
+#define STT_ARM_16BIT STT_HIPROC
+
+#define SHF_ARM_ENTRYSECT 0x10000000
+#define SHF_ARM_COMDEF 0x80000000
+
+#define PF_ARM_SB 0x10000000
+
+#define PF_ARM_PI 0x20000000
+#define PF_ARM_ABS 0x40000000
+
+#define PT_ARM_EXIDX (PT_LOPROC + 1)
+
+#define SHT_ARM_EXIDX (SHT_LOPROC + 1)
+#define SHT_ARM_PREEMPTMAP (SHT_LOPROC + 2)
+#define SHT_ARM_ATTRIBUTES (SHT_LOPROC + 3)
+
+#define R_AARCH64_NONE 0
+#define R_AARCH64_P32_ABS32 1
+#define R_AARCH64_P32_COPY 180
+#define R_AARCH64_P32_GLOB_DAT 181
+#define R_AARCH64_P32_JUMP_SLOT 182
+#define R_AARCH64_P32_RELATIVE 183
+#define R_AARCH64_P32_TLS_DTPMOD 184
+#define R_AARCH64_P32_TLS_DTPREL 185
+#define R_AARCH64_P32_TLS_TPREL 186
+#define R_AARCH64_P32_TLSDESC 187
+#define R_AARCH64_P32_IRELATIVE 188
+#define R_AARCH64_ABS64 257
+#define R_AARCH64_ABS32 258
+#define R_AARCH64_ABS16 259
+#define R_AARCH64_PREL64 260
+#define R_AARCH64_PREL32 261
+#define R_AARCH64_PREL16 262
+#define R_AARCH64_MOVW_UABS_G0 263
+#define R_AARCH64_MOVW_UABS_G0_NC 264
+#define R_AARCH64_MOVW_UABS_G1 265
+#define R_AARCH64_MOVW_UABS_G1_NC 266
+#define R_AARCH64_MOVW_UABS_G2 267
+#define R_AARCH64_MOVW_UABS_G2_NC 268
+#define R_AARCH64_MOVW_UABS_G3 269
+#define R_AARCH64_MOVW_SABS_G0 270
+#define R_AARCH64_MOVW_SABS_G1 271
+#define R_AARCH64_MOVW_SABS_G2 272
+#define R_AARCH64_LD_PREL_LO19 273
+#define R_AARCH64_ADR_PREL_LO21 274
+#define R_AARCH64_ADR_PREL_PG_HI21 275
+#define R_AARCH64_ADR_PREL_PG_HI21_NC 276
+#define R_AARCH64_ADD_ABS_LO12_NC 277
+#define R_AARCH64_LDST8_ABS_LO12_NC 278
+#define R_AARCH64_TSTBR14 279
+#define R_AARCH64_CONDBR19 280
+#define R_AARCH64_JUMP26 282
+#define R_AARCH64_CALL26 283
+#define R_AARCH64_LDST16_ABS_LO12_NC 284
+#define R_AARCH64_LDST32_ABS_LO12_NC 285
+#define R_AARCH64_LDST64_ABS_LO12_NC 286
+#define R_AARCH64_MOVW_PREL_G0 287
+#define R_AARCH64_MOVW_PREL_G0_NC 288
+#define R_AARCH64_MOVW_PREL_G1 289
+#define R_AARCH64_MOVW_PREL_G1_NC 290
+#define R_AARCH64_MOVW_PREL_G2 291
+#define R_AARCH64_MOVW_PREL_G2_NC 292
+#define R_AARCH64_MOVW_PREL_G3 293
+#define R_AARCH64_LDST128_ABS_LO12_NC 299
+#define R_AARCH64_MOVW_GOTOFF_G0 300
+#define R_AARCH64_MOVW_GOTOFF_G0_NC 301
+#define R_AARCH64_MOVW_GOTOFF_G1 302
+#define R_AARCH64_MOVW_GOTOFF_G1_NC 303
+#define R_AARCH64_MOVW_GOTOFF_G2 304
+#define R_AARCH64_MOVW_GOTOFF_G2_NC 305
+#define R_AARCH64_MOVW_GOTOFF_G3 306
+#define R_AARCH64_GOTREL64 307
+#define R_AARCH64_GOTREL32 308
+#define R_AARCH64_GOT_LD_PREL19 309
+#define R_AARCH64_LD64_GOTOFF_LO15 310
+#define R_AARCH64_ADR_GOT_PAGE 311
+#define R_AARCH64_LD64_GOT_LO12_NC 312
+#define R_AARCH64_LD64_GOTPAGE_LO15 313
+#define R_AARCH64_TLSGD_ADR_PREL21 512
+#define R_AARCH64_TLSGD_ADR_PAGE21 513
+#define R_AARCH64_TLSGD_ADD_LO12_NC 514
+#define R_AARCH64_TLSGD_MOVW_G1 515
+#define R_AARCH64_TLSGD_MOVW_G0_NC 516
+#define R_AARCH64_TLSLD_ADR_PREL21 517
+#define R_AARCH64_TLSLD_ADR_PAGE21 518
+#define R_AARCH64_TLSLD_ADD_LO12_NC 519
+#define R_AARCH64_TLSLD_MOVW_G1 520
+#define R_AARCH64_TLSLD_MOVW_G0_NC 521
+#define R_AARCH64_TLSLD_LD_PREL19 522
+#define R_AARCH64_TLSLD_MOVW_DTPREL_G2 523
+#define R_AARCH64_TLSLD_MOVW_DTPREL_G1 524
+#define R_AARCH64_TLSLD_MOVW_DTPREL_G1_NC 525
+#define R_AARCH64_TLSLD_MOVW_DTPREL_G0 526
+#define R_AARCH64_TLSLD_MOVW_DTPREL_G0_NC 527
+#define R_AARCH64_TLSLD_ADD_DTPREL_HI12 528
+#define R_AARCH64_TLSLD_ADD_DTPREL_LO12 529
+#define R_AARCH64_TLSLD_ADD_DTPREL_LO12_NC 530
+#define R_AARCH64_TLSLD_LDST8_DTPREL_LO12 531
+#define R_AARCH64_TLSLD_LDST8_DTPREL_LO12_NC 532
+#define R_AARCH64_TLSLD_LDST16_DTPREL_LO12 533
+#define R_AARCH64_TLSLD_LDST16_DTPREL_LO12_NC 534
+#define R_AARCH64_TLSLD_LDST32_DTPREL_LO12 535
+#define R_AARCH64_TLSLD_LDST32_DTPREL_LO12_NC 536
+#define R_AARCH64_TLSLD_LDST64_DTPREL_LO12 537
+#define R_AARCH64_TLSLD_LDST64_DTPREL_LO12_NC 538
+#define R_AARCH64_TLSIE_MOVW_GOTTPREL_G1 539
+#define R_AARCH64_TLSIE_MOVW_GOTTPREL_G0_NC 540
+#define R_AARCH64_TLSIE_ADR_GOTTPREL_PAGE21 541
+#define R_AARCH64_TLSIE_LD64_GOTTPREL_LO12_NC 542
+#define R_AARCH64_TLSIE_LD_GOTTPREL_PREL19 543
+#define R_AARCH64_TLSLE_MOVW_TPREL_G2 544
+#define R_AARCH64_TLSLE_MOVW_TPREL_G1 545
+#define R_AARCH64_TLSLE_MOVW_TPREL_G1_NC 546
+#define R_AARCH64_TLSLE_MOVW_TPREL_G0 547
+#define R_AARCH64_TLSLE_MOVW_TPREL_G0_NC 548
+#define R_AARCH64_TLSLE_ADD_TPREL_HI12 549
+#define R_AARCH64_TLSLE_ADD_TPREL_LO12 550
+#define R_AARCH64_TLSLE_ADD_TPREL_LO12_NC 551
+#define R_AARCH64_TLSLE_LDST8_TPREL_LO12 552
+#define R_AARCH64_TLSLE_LDST8_TPREL_LO12_NC 553
+#define R_AARCH64_TLSLE_LDST16_TPREL_LO12 554
+#define R_AARCH64_TLSLE_LDST16_TPREL_LO12_NC 555
+#define R_AARCH64_TLSLE_LDST32_TPREL_LO12 556
+#define R_AARCH64_TLSLE_LDST32_TPREL_LO12_NC 557
+#define R_AARCH64_TLSLE_LDST64_TPREL_LO12 558
+#define R_AARCH64_TLSLE_LDST64_TPREL_LO12_NC 559
+#define R_AARCH64_TLSDESC_LD_PREL19 560
+#define R_AARCH64_TLSDESC_ADR_PREL21 561
+#define R_AARCH64_TLSDESC_ADR_PAGE21 562
+#define R_AARCH64_TLSDESC_LD64_LO12 563
+#define R_AARCH64_TLSDESC_ADD_LO12 564
+#define R_AARCH64_TLSDESC_OFF_G1 565
+#define R_AARCH64_TLSDESC_OFF_G0_NC 566
+#define R_AARCH64_TLSDESC_LDR 567
+#define R_AARCH64_TLSDESC_ADD 568
+#define R_AARCH64_TLSDESC_CALL 569
+#define R_AARCH64_TLSLE_LDST128_TPREL_LO12 570
+#define R_AARCH64_TLSLE_LDST128_TPREL_LO12_NC 571
+#define R_AARCH64_TLSLD_LDST128_DTPREL_LO12 572
+#define R_AARCH64_TLSLD_LDST128_DTPREL_LO12_NC 573
+#define R_AARCH64_COPY 1024
+#define R_AARCH64_GLOB_DAT 1025
+#define R_AARCH64_JUMP_SLOT 1026
+#define R_AARCH64_RELATIVE 1027
+#define R_AARCH64_TLS_DTPMOD 1028
+#define R_AARCH64_TLS_DTPMOD64 1028
+#define R_AARCH64_TLS_DTPREL 1029
+#define R_AARCH64_TLS_DTPREL64 1029
+#define R_AARCH64_TLS_TPREL 1030
+#define R_AARCH64_TLS_TPREL64 1030
+#define R_AARCH64_TLSDESC 1031
+
+#define R_ARM_NONE 0
+#define R_ARM_PC24 1
+#define R_ARM_ABS32 2
+#define R_ARM_REL32 3
+#define R_ARM_PC13 4
+#define R_ARM_ABS16 5
+#define R_ARM_ABS12 6
+#define R_ARM_THM_ABS5 7
+#define R_ARM_ABS8 8
+#define R_ARM_SBREL32 9
+#define R_ARM_THM_PC22 10
+#define R_ARM_THM_PC8 11
+#define R_ARM_AMP_VCALL9 12
+#define R_ARM_TLS_DESC 13
+#define R_ARM_THM_SWI8 14
+#define R_ARM_XPC25 15
+#define R_ARM_THM_XPC22 16
+#define R_ARM_TLS_DTPMOD32 17
+#define R_ARM_TLS_DTPOFF32 18
+#define R_ARM_TLS_TPOFF32 19
+#define R_ARM_COPY 20
+#define R_ARM_GLOB_DAT 21
+#define R_ARM_JUMP_SLOT 22
+#define R_ARM_RELATIVE 23
+#define R_ARM_GOTOFF 24
+#define R_ARM_GOTPC 25
+#define R_ARM_GOT32 26
+#define R_ARM_PLT32 27
+#define R_ARM_CALL 28
+#define R_ARM_JUMP24 29
+#define R_ARM_THM_JUMP24 30
+#define R_ARM_BASE_ABS 31
+#define R_ARM_ALU_PCREL_7_0 32
+#define R_ARM_ALU_PCREL_15_8 33
+#define R_ARM_ALU_PCREL_23_15 34
+#define R_ARM_LDR_SBREL_11_0 35
+#define R_ARM_ALU_SBREL_19_12 36
+#define R_ARM_ALU_SBREL_27_20 37
+#define R_ARM_TARGET1 38
+#define R_ARM_SBREL31 39
+#define R_ARM_V4BX 40
+#define R_ARM_TARGET2 41
+#define R_ARM_PREL31 42
+#define R_ARM_MOVW_ABS_NC 43
+#define R_ARM_MOVT_ABS 44
+#define R_ARM_MOVW_PREL_NC 45
+#define R_ARM_MOVT_PREL 46
+#define R_ARM_THM_MOVW_ABS_NC 47
+#define R_ARM_THM_MOVT_ABS 48
+#define R_ARM_THM_MOVW_PREL_NC 49
+#define R_ARM_THM_MOVT_PREL 50
+#define R_ARM_THM_JUMP19 51
+#define R_ARM_THM_JUMP6 52
+#define R_ARM_THM_ALU_PREL_11_0 53
+#define R_ARM_THM_PC12 54
+#define R_ARM_ABS32_NOI 55
+#define R_ARM_REL32_NOI 56
+#define R_ARM_ALU_PC_G0_NC 57
+#define R_ARM_ALU_PC_G0 58
+#define R_ARM_ALU_PC_G1_NC 59
+#define R_ARM_ALU_PC_G1 60
+#define R_ARM_ALU_PC_G2 61
+#define R_ARM_LDR_PC_G1 62
+#define R_ARM_LDR_PC_G2 63
+#define R_ARM_LDRS_PC_G0 64
+#define R_ARM_LDRS_PC_G1 65
+#define R_ARM_LDRS_PC_G2 66
+#define R_ARM_LDC_PC_G0 67
+#define R_ARM_LDC_PC_G1 68
+#define R_ARM_LDC_PC_G2 69
+#define R_ARM_ALU_SB_G0_NC 70
+#define R_ARM_ALU_SB_G0 71
+#define R_ARM_ALU_SB_G1_NC 72
+#define R_ARM_ALU_SB_G1 73
+#define R_ARM_ALU_SB_G2 74
+#define R_ARM_LDR_SB_G0 75
+#define R_ARM_LDR_SB_G1 76
+#define R_ARM_LDR_SB_G2 77
+#define R_ARM_LDRS_SB_G0 78
+#define R_ARM_LDRS_SB_G1 79
+#define R_ARM_LDRS_SB_G2 80
+#define R_ARM_LDC_SB_G0 81
+#define R_ARM_LDC_SB_G1 82
+#define R_ARM_LDC_SB_G2 83
+#define R_ARM_MOVW_BREL_NC 84
+#define R_ARM_MOVT_BREL 85
+#define R_ARM_MOVW_BREL 86
+#define R_ARM_THM_MOVW_BREL_NC 87
+#define R_ARM_THM_MOVT_BREL 88
+#define R_ARM_THM_MOVW_BREL 89
+#define R_ARM_TLS_GOTDESC 90
+#define R_ARM_TLS_CALL 91
+#define R_ARM_TLS_DESCSEQ 92
+#define R_ARM_THM_TLS_CALL 93
+#define R_ARM_PLT32_ABS 94
+#define R_ARM_GOT_ABS 95
+#define R_ARM_GOT_PREL 96
+#define R_ARM_GOT_BREL12 97
+#define R_ARM_GOTOFF12 98
+#define R_ARM_GOTRELAX 99
+#define R_ARM_GNU_VTENTRY 100
+#define R_ARM_GNU_VTINHERIT 101
+#define R_ARM_THM_PC11 102
+#define R_ARM_THM_PC9 103
+#define R_ARM_TLS_GD32 104
+
+#define R_ARM_TLS_LDM32 105
+
+#define R_ARM_TLS_LDO32 106
+
+#define R_ARM_TLS_IE32 107
+
+#define R_ARM_TLS_LE32 108
+#define R_ARM_TLS_LDO12 109
+#define R_ARM_TLS_LE12 110
+#define R_ARM_TLS_IE12GP 111
+#define R_ARM_ME_TOO 128
+#define R_ARM_THM_TLS_DESCSEQ 129
+#define R_ARM_THM_TLS_DESCSEQ16 129
+#define R_ARM_THM_TLS_DESCSEQ32 130
+#define R_ARM_THM_GOT_BREL12 131
+#define R_ARM_IRELATIVE 160
+#define R_ARM_RXPC25 249
+#define R_ARM_RSBREL32 250
+#define R_ARM_THM_RPC22 251
+#define R_ARM_RREL32 252
+#define R_ARM_RABS22 253
+#define R_ARM_RPC24 254
+#define R_ARM_RBASE 255
+
+#define R_ARM_NUM 256
+
+#define EF_IA_64_MASKOS 0x0000000f
+#define EF_IA_64_ABI64 0x00000010
+#define EF_IA_64_ARCH 0xff000000
+
+#define PT_IA_64_ARCHEXT (PT_LOPROC + 0)
+#define PT_IA_64_UNWIND (PT_LOPROC + 1)
+#define PT_IA_64_HP_OPT_ANOT (PT_LOOS + 0x12)
+#define PT_IA_64_HP_HSL_ANOT (PT_LOOS + 0x13)
+#define PT_IA_64_HP_STACK (PT_LOOS + 0x14)
+
+#define PF_IA_64_NORECOV 0x80000000
+
+#define SHT_IA_64_EXT (SHT_LOPROC + 0)
+#define SHT_IA_64_UNWIND (SHT_LOPROC + 1)
+
+#define SHF_IA_64_SHORT 0x10000000
+#define SHF_IA_64_NORECOV 0x20000000
+
+#define DT_IA_64_PLT_RESERVE (DT_LOPROC + 0)
+#define DT_IA_64_NUM 1
+
+#define R_IA64_NONE 0x00
+#define R_IA64_IMM14 0x21
+#define R_IA64_IMM22 0x22
+#define R_IA64_IMM64 0x23
+#define R_IA64_DIR32MSB 0x24
+#define R_IA64_DIR32LSB 0x25
+#define R_IA64_DIR64MSB 0x26
+#define R_IA64_DIR64LSB 0x27
+#define R_IA64_GPREL22 0x2a
+#define R_IA64_GPREL64I 0x2b
+#define R_IA64_GPREL32MSB 0x2c
+#define R_IA64_GPREL32LSB 0x2d
+#define R_IA64_GPREL64MSB 0x2e
+#define R_IA64_GPREL64LSB 0x2f
+#define R_IA64_LTOFF22 0x32
+#define R_IA64_LTOFF64I 0x33
+#define R_IA64_PLTOFF22 0x3a
+#define R_IA64_PLTOFF64I 0x3b
+#define R_IA64_PLTOFF64MSB 0x3e
+#define R_IA64_PLTOFF64LSB 0x3f
+#define R_IA64_FPTR64I 0x43
+#define R_IA64_FPTR32MSB 0x44
+#define R_IA64_FPTR32LSB 0x45
+#define R_IA64_FPTR64MSB 0x46
+#define R_IA64_FPTR64LSB 0x47
+#define R_IA64_PCREL60B 0x48
+#define R_IA64_PCREL21B 0x49
+#define R_IA64_PCREL21M 0x4a
+#define R_IA64_PCREL21F 0x4b
+#define R_IA64_PCREL32MSB 0x4c
+#define R_IA64_PCREL32LSB 0x4d
+#define R_IA64_PCREL64MSB 0x4e
+#define R_IA64_PCREL64LSB 0x4f
+#define R_IA64_LTOFF_FPTR22 0x52
+#define R_IA64_LTOFF_FPTR64I 0x53
+#define R_IA64_LTOFF_FPTR32MSB 0x54
+#define R_IA64_LTOFF_FPTR32LSB 0x55
+#define R_IA64_LTOFF_FPTR64MSB 0x56
+#define R_IA64_LTOFF_FPTR64LSB 0x57
+#define R_IA64_SEGREL32MSB 0x5c
+#define R_IA64_SEGREL32LSB 0x5d
+#define R_IA64_SEGREL64MSB 0x5e
+#define R_IA64_SEGREL64LSB 0x5f
+#define R_IA64_SECREL32MSB 0x64
+#define R_IA64_SECREL32LSB 0x65
+#define R_IA64_SECREL64MSB 0x66
+#define R_IA64_SECREL64LSB 0x67
+#define R_IA64_REL32MSB 0x6c
+#define R_IA64_REL32LSB 0x6d
+#define R_IA64_REL64MSB 0x6e
+#define R_IA64_REL64LSB 0x6f
+#define R_IA64_LTV32MSB 0x74
+#define R_IA64_LTV32LSB 0x75
+#define R_IA64_LTV64MSB 0x76
+#define R_IA64_LTV64LSB 0x77
+#define R_IA64_PCREL21BI 0x79
+#define R_IA64_PCREL22 0x7a
+#define R_IA64_PCREL64I 0x7b
+#define R_IA64_IPLTMSB 0x80
+#define R_IA64_IPLTLSB 0x81
+#define R_IA64_COPY 0x84
+#define R_IA64_SUB 0x85
+#define R_IA64_LTOFF22X 0x86
+#define R_IA64_LDXMOV 0x87
+#define R_IA64_TPREL14 0x91
+#define R_IA64_TPREL22 0x92
+#define R_IA64_TPREL64I 0x93
+#define R_IA64_TPREL64MSB 0x96
+#define R_IA64_TPREL64LSB 0x97
+#define R_IA64_LTOFF_TPREL22 0x9a
+#define R_IA64_DTPMOD64MSB 0xa6
+#define R_IA64_DTPMOD64LSB 0xa7
+#define R_IA64_LTOFF_DTPMOD22 0xaa
+#define R_IA64_DTPREL14 0xb1
+#define R_IA64_DTPREL22 0xb2
+#define R_IA64_DTPREL64I 0xb3
+#define R_IA64_DTPREL32MSB 0xb4
+#define R_IA64_DTPREL32LSB 0xb5
+#define R_IA64_DTPREL64MSB 0xb6
+#define R_IA64_DTPREL64LSB 0xb7
+#define R_IA64_LTOFF_DTPREL22 0xba
+
+#define EF_SH_MACH_MASK 0x1f
+#define EF_SH_UNKNOWN 0x0
+#define EF_SH1 0x1
+#define EF_SH2 0x2
+#define EF_SH3 0x3
+#define EF_SH_DSP 0x4
+#define EF_SH3_DSP 0x5
+#define EF_SH4AL_DSP 0x6
+#define EF_SH3E 0x8
+#define EF_SH4 0x9
+#define EF_SH2E 0xb
+#define EF_SH4A 0xc
+#define EF_SH2A 0xd
+#define EF_SH4_NOFPU 0x10
+#define EF_SH4A_NOFPU 0x11
+#define EF_SH4_NOMMU_NOFPU 0x12
+#define EF_SH2A_NOFPU 0x13
+#define EF_SH3_NOMMU 0x14
+#define EF_SH2A_SH4_NOFPU 0x15
+#define EF_SH2A_SH3_NOFPU 0x16
+#define EF_SH2A_SH4 0x17
+#define EF_SH2A_SH3E 0x18
+
+#define R_SH_NONE 0
+#define R_SH_DIR32 1
+#define R_SH_REL32 2
+#define R_SH_DIR8WPN 3
+#define R_SH_IND12W 4
+#define R_SH_DIR8WPL 5
+#define R_SH_DIR8WPZ 6
+#define R_SH_DIR8BP 7
+#define R_SH_DIR8W 8
+#define R_SH_DIR8L 9
+#define R_SH_SWITCH16 25
+#define R_SH_SWITCH32 26
+#define R_SH_USES 27
+#define R_SH_COUNT 28
+#define R_SH_ALIGN 29
+#define R_SH_CODE 30
+#define R_SH_DATA 31
+#define R_SH_LABEL 32
+#define R_SH_SWITCH8 33
+#define R_SH_GNU_VTINHERIT 34
+#define R_SH_GNU_VTENTRY 35
+#define R_SH_TLS_GD_32 144
+#define R_SH_TLS_LD_32 145
+#define R_SH_TLS_LDO_32 146
+#define R_SH_TLS_IE_32 147
+#define R_SH_TLS_LE_32 148
+#define R_SH_TLS_DTPMOD32 149
+#define R_SH_TLS_DTPOFF32 150
+#define R_SH_TLS_TPOFF32 151
+#define R_SH_GOT32 160
+#define R_SH_PLT32 161
+#define R_SH_COPY 162
+#define R_SH_GLOB_DAT 163
+#define R_SH_JMP_SLOT 164
+#define R_SH_RELATIVE 165
+#define R_SH_GOTOFF 166
+#define R_SH_GOTPC 167
+#define R_SH_GOT20 201
+#define R_SH_GOTOFF20 202
+#define R_SH_GOTFUNCDESC 203
+#define R_SH_GOTFUNCDEST20 204
+#define R_SH_GOTOFFFUNCDESC 205
+#define R_SH_GOTOFFFUNCDEST20 206
+#define R_SH_FUNCDESC 207
+#define R_SH_FUNCDESC_VALUE 208
+
+#define R_SH_NUM 256
+
+#define R_390_NONE 0
+#define R_390_8 1
+#define R_390_12 2
+#define R_390_16 3
+#define R_390_32 4
+#define R_390_PC32 5
+#define R_390_GOT12 6
+#define R_390_GOT32 7
+#define R_390_PLT32 8
+#define R_390_COPY 9
+#define R_390_GLOB_DAT 10
+#define R_390_JMP_SLOT 11
+#define R_390_RELATIVE 12
+#define R_390_GOTOFF32 13
+#define R_390_GOTPC 14
+#define R_390_GOT16 15
+#define R_390_PC16 16
+#define R_390_PC16DBL 17
+#define R_390_PLT16DBL 18
+#define R_390_PC32DBL 19
+#define R_390_PLT32DBL 20
+#define R_390_GOTPCDBL 21
+#define R_390_64 22
+#define R_390_PC64 23
+#define R_390_GOT64 24
+#define R_390_PLT64 25
+#define R_390_GOTENT 26
+#define R_390_GOTOFF16 27
+#define R_390_GOTOFF64 28
+#define R_390_GOTPLT12 29
+#define R_390_GOTPLT16 30
+#define R_390_GOTPLT32 31
+#define R_390_GOTPLT64 32
+#define R_390_GOTPLTENT 33
+#define R_390_PLTOFF16 34
+#define R_390_PLTOFF32 35
+#define R_390_PLTOFF64 36
+#define R_390_TLS_LOAD 37
+#define R_390_TLS_GDCALL 38
+
+#define R_390_TLS_LDCALL 39
+
+#define R_390_TLS_GD32 40
+
+#define R_390_TLS_GD64 41
+
+#define R_390_TLS_GOTIE12 42
+
+#define R_390_TLS_GOTIE32 43
+
+#define R_390_TLS_GOTIE64 44
+
+#define R_390_TLS_LDM32 45
+
+#define R_390_TLS_LDM64 46
+
+#define R_390_TLS_IE32 47
+
+#define R_390_TLS_IE64 48
+
+#define R_390_TLS_IEENT 49
+
+#define R_390_TLS_LE32 50
+
+#define R_390_TLS_LE64 51
+
+#define R_390_TLS_LDO32 52
+
+#define R_390_TLS_LDO64 53
+
+#define R_390_TLS_DTPMOD 54
+#define R_390_TLS_DTPOFF 55
+#define R_390_TLS_TPOFF 56
+
+#define R_390_20 57
+#define R_390_GOT20 58
+#define R_390_GOTPLT20 59
+#define R_390_TLS_GOTIE20 60
+
+#define R_390_NUM 61
+
+#define R_CRIS_NONE 0
+#define R_CRIS_8 1
+#define R_CRIS_16 2
+#define R_CRIS_32 3
+#define R_CRIS_8_PCREL 4
+#define R_CRIS_16_PCREL 5
+#define R_CRIS_32_PCREL 6
+#define R_CRIS_GNU_VTINHERIT 7
+#define R_CRIS_GNU_VTENTRY 8
+#define R_CRIS_COPY 9
+#define R_CRIS_GLOB_DAT 10
+#define R_CRIS_JUMP_SLOT 11
+#define R_CRIS_RELATIVE 12
+#define R_CRIS_16_GOT 13
+#define R_CRIS_32_GOT 14
+#define R_CRIS_16_GOTPLT 15
+#define R_CRIS_32_GOTPLT 16
+#define R_CRIS_32_GOTREL 17
+#define R_CRIS_32_PLT_GOTREL 18
+#define R_CRIS_32_PLT_PCREL 19
+
+#define R_CRIS_NUM 20
+
+#define R_X86_64_NONE 0
+#define R_X86_64_64 1
+#define R_X86_64_PC32 2
+#define R_X86_64_GOT32 3
+#define R_X86_64_PLT32 4
+#define R_X86_64_COPY 5
+#define R_X86_64_GLOB_DAT 6
+#define R_X86_64_JUMP_SLOT 7
+#define R_X86_64_RELATIVE 8
+#define R_X86_64_GOTPCREL 9
+
+#define R_X86_64_32 10
+#define R_X86_64_32S 11
+#define R_X86_64_16 12
+#define R_X86_64_PC16 13
+#define R_X86_64_8 14
+#define R_X86_64_PC8 15
+#define R_X86_64_DTPMOD64 16
+#define R_X86_64_DTPOFF64 17
+#define R_X86_64_TPOFF64 18
+#define R_X86_64_TLSGD 19
+
+#define R_X86_64_TLSLD 20
+
+#define R_X86_64_DTPOFF32 21
+#define R_X86_64_GOTTPOFF 22
+
+#define R_X86_64_TPOFF32 23
+#define R_X86_64_PC64 24
+#define R_X86_64_GOTOFF64 25
+#define R_X86_64_GOTPC32 26
+#define R_X86_64_GOT64 27
+#define R_X86_64_GOTPCREL64 28
+#define R_X86_64_GOTPC64 29
+#define R_X86_64_GOTPLT64 30
+#define R_X86_64_PLTOFF64 31
+#define R_X86_64_SIZE32 32
+#define R_X86_64_SIZE64 33
+
+#define R_X86_64_GOTPC32_TLSDESC 34
+#define R_X86_64_TLSDESC_CALL 35
+
+#define R_X86_64_TLSDESC 36
+#define R_X86_64_IRELATIVE 37
+#define R_X86_64_RELATIVE64 38
+#define R_X86_64_GOTPCRELX 41
+#define R_X86_64_REX_GOTPCRELX 42
+#define R_X86_64_NUM 43
+
+#define R_MN10300_NONE 0
+#define R_MN10300_32 1
+#define R_MN10300_16 2
+#define R_MN10300_8 3
+#define R_MN10300_PCREL32 4
+#define R_MN10300_PCREL16 5
+#define R_MN10300_PCREL8 6
+#define R_MN10300_GNU_VTINHERIT 7
+#define R_MN10300_GNU_VTENTRY 8
+#define R_MN10300_24 9
+#define R_MN10300_GOTPC32 10
+#define R_MN10300_GOTPC16 11
+#define R_MN10300_GOTOFF32 12
+#define R_MN10300_GOTOFF24 13
+#define R_MN10300_GOTOFF16 14
+#define R_MN10300_PLT32 15
+#define R_MN10300_PLT16 16
+#define R_MN10300_GOT32 17
+#define R_MN10300_GOT24 18
+#define R_MN10300_GOT16 19
+#define R_MN10300_COPY 20
+#define R_MN10300_GLOB_DAT 21
+#define R_MN10300_JMP_SLOT 22
+#define R_MN10300_RELATIVE 23
+
+#define R_MN10300_NUM 24
+
+#define R_M32R_NONE 0
+#define R_M32R_16 1
+#define R_M32R_32 2
+#define R_M32R_24 3
+#define R_M32R_10_PCREL 4
+#define R_M32R_18_PCREL 5
+#define R_M32R_26_PCREL 6
+#define R_M32R_HI16_ULO 7
+#define R_M32R_HI16_SLO 8
+#define R_M32R_LO16 9
+#define R_M32R_SDA16 10
+#define R_M32R_GNU_VTINHERIT 11
+#define R_M32R_GNU_VTENTRY 12
+
+#define R_M32R_16_RELA 33
+#define R_M32R_32_RELA 34
+#define R_M32R_24_RELA 35
+#define R_M32R_10_PCREL_RELA 36
+#define R_M32R_18_PCREL_RELA 37
+#define R_M32R_26_PCREL_RELA 38
+#define R_M32R_HI16_ULO_RELA 39
+#define R_M32R_HI16_SLO_RELA 40
+#define R_M32R_LO16_RELA 41
+#define R_M32R_SDA16_RELA 42
+#define R_M32R_RELA_GNU_VTINHERIT 43
+#define R_M32R_RELA_GNU_VTENTRY 44
+#define R_M32R_REL32 45
+
+#define R_M32R_GOT24 48
+#define R_M32R_26_PLTREL 49
+#define R_M32R_COPY 50
+#define R_M32R_GLOB_DAT 51
+#define R_M32R_JMP_SLOT 52
+#define R_M32R_RELATIVE 53
+#define R_M32R_GOTOFF 54
+#define R_M32R_GOTPC24 55
+#define R_M32R_GOT16_HI_ULO 56
+
+#define R_M32R_GOT16_HI_SLO 57
+
+#define R_M32R_GOT16_LO 58
+#define R_M32R_GOTPC_HI_ULO 59
+
+#define R_M32R_GOTPC_HI_SLO 60
+
+#define R_M32R_GOTPC_LO 61
+
+#define R_M32R_GOTOFF_HI_ULO 62
+
+#define R_M32R_GOTOFF_HI_SLO 63
+
+#define R_M32R_GOTOFF_LO 64
+#define R_M32R_NUM 256
+
+#define R_MICROBLAZE_NONE 0
+#define R_MICROBLAZE_32 1
+#define R_MICROBLAZE_32_PCREL 2
+#define R_MICROBLAZE_64_PCREL 3
+#define R_MICROBLAZE_32_PCREL_LO 4
+#define R_MICROBLAZE_64 5
+#define R_MICROBLAZE_32_LO 6
+#define R_MICROBLAZE_SRO32 7
+#define R_MICROBLAZE_SRW32 8
+#define R_MICROBLAZE_64_NONE 9
+#define R_MICROBLAZE_32_SYM_OP_SYM 10
+#define R_MICROBLAZE_GNU_VTINHERIT 11
+#define R_MICROBLAZE_GNU_VTENTRY 12
+#define R_MICROBLAZE_GOTPC_64 13
+#define R_MICROBLAZE_GOT_64 14
+#define R_MICROBLAZE_PLT_64 15
+#define R_MICROBLAZE_REL 16
+#define R_MICROBLAZE_JUMP_SLOT 17
+#define R_MICROBLAZE_GLOB_DAT 18
+#define R_MICROBLAZE_GOTOFF_64 19
+#define R_MICROBLAZE_GOTOFF_32 20
+#define R_MICROBLAZE_COPY 21
+#define R_MICROBLAZE_TLS 22
+#define R_MICROBLAZE_TLSGD 23
+#define R_MICROBLAZE_TLSLD 24
+#define R_MICROBLAZE_TLSDTPMOD32 25
+#define R_MICROBLAZE_TLSDTPREL32 26
+#define R_MICROBLAZE_TLSDTPREL64 27
+#define R_MICROBLAZE_TLSGOTTPREL32 28
+#define R_MICROBLAZE_TLSTPREL32 29
+
+#define DT_NIOS2_GP 0x70000002
+
+#define R_NIOS2_NONE 0
+#define R_NIOS2_S16 1
+#define R_NIOS2_U16 2
+#define R_NIOS2_PCREL16 3
+#define R_NIOS2_CALL26 4
+#define R_NIOS2_IMM5 5
+#define R_NIOS2_CACHE_OPX 6
+#define R_NIOS2_IMM6 7
+#define R_NIOS2_IMM8 8
+#define R_NIOS2_HI16 9
+#define R_NIOS2_LO16 10
+#define R_NIOS2_HIADJ16 11
+#define R_NIOS2_BFD_RELOC_32 12
+#define R_NIOS2_BFD_RELOC_16 13
+#define R_NIOS2_BFD_RELOC_8 14
+#define R_NIOS2_GPREL 15
+#define R_NIOS2_GNU_VTINHERIT 16
+#define R_NIOS2_GNU_VTENTRY 17
+#define R_NIOS2_UJMP 18
+#define R_NIOS2_CJMP 19
+#define R_NIOS2_CALLR 20
+#define R_NIOS2_ALIGN 21
+#define R_NIOS2_GOT16 22
+#define R_NIOS2_CALL16 23
+#define R_NIOS2_GOTOFF_LO 24
+#define R_NIOS2_GOTOFF_HA 25
+#define R_NIOS2_PCREL_LO 26
+#define R_NIOS2_PCREL_HA 27
+#define R_NIOS2_TLS_GD16 28
+#define R_NIOS2_TLS_LDM16 29
+#define R_NIOS2_TLS_LDO16 30
+#define R_NIOS2_TLS_IE16 31
+#define R_NIOS2_TLS_LE16 32
+#define R_NIOS2_TLS_DTPMOD 33
+#define R_NIOS2_TLS_DTPREL 34
+#define R_NIOS2_TLS_TPREL 35
+#define R_NIOS2_COPY 36
+#define R_NIOS2_GLOB_DAT 37
+#define R_NIOS2_JUMP_SLOT 38
+#define R_NIOS2_RELATIVE 39
+#define R_NIOS2_GOTOFF 40
+#define R_NIOS2_CALL26_NOAT 41
+#define R_NIOS2_GOT_LO 42
+#define R_NIOS2_GOT_HA 43
+#define R_NIOS2_CALL_LO 44
+#define R_NIOS2_CALL_HA 45
+
+#define R_OR1K_NONE 0
+#define R_OR1K_32 1
+#define R_OR1K_16 2
+#define R_OR1K_8 3
+#define R_OR1K_LO_16_IN_INSN 4
+#define R_OR1K_HI_16_IN_INSN 5
+#define R_OR1K_INSN_REL_26 6
+#define R_OR1K_GNU_VTENTRY 7
+#define R_OR1K_GNU_VTINHERIT 8
+#define R_OR1K_32_PCREL 9
+#define R_OR1K_16_PCREL 10
+#define R_OR1K_8_PCREL 11
+#define R_OR1K_GOTPC_HI16 12
+#define R_OR1K_GOTPC_LO16 13
+#define R_OR1K_GOT16 14
+#define R_OR1K_PLT26 15
+#define R_OR1K_GOTOFF_HI16 16
+#define R_OR1K_GOTOFF_LO16 17
+#define R_OR1K_COPY 18
+#define R_OR1K_GLOB_DAT 19
+#define R_OR1K_JMP_SLOT 20
+#define R_OR1K_RELATIVE 21
+#define R_OR1K_TLS_GD_HI16 22
+#define R_OR1K_TLS_GD_LO16 23
+#define R_OR1K_TLS_LDM_HI16 24
+#define R_OR1K_TLS_LDM_LO16 25
+#define R_OR1K_TLS_LDO_HI16 26
+#define R_OR1K_TLS_LDO_LO16 27
+#define R_OR1K_TLS_IE_HI16 28
+#define R_OR1K_TLS_IE_LO16 29
+#define R_OR1K_TLS_LE_HI16 30
+#define R_OR1K_TLS_LE_LO16 31
+#define R_OR1K_TLS_TPOFF 32
+#define R_OR1K_TLS_DTPOFF 33
+#define R_OR1K_TLS_DTPMOD 34
+
+#define R_BPF_NONE 0
+#define R_BPF_MAP_FD 1
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif
diff --git a/tools/src/gbafix/gbafix.c b/tools/src/gbafix/gbafix.c
new file mode 100644
index 00000000..db301c78
--- /dev/null
+++ b/tools/src/gbafix/gbafix.c
@@ -0,0 +1,333 @@
+/*
+ "$Id: gbafix.c,v 1.2 2008-07-30 17:12:51 wntrmute Exp $"
+
+ DevkitPro GBA ROM fix utility
+
+ This library is free software; you can redistribute it and/or
+ modify it under the terms of the GNU Library General Public
+ License as published by the Free Software Foundation; either
+ version 2 of the License, or (at your option) any later version.
+
+ This library 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
+ Library General Public License for more details.
+
+ You should have received a copy of the GNU Library General Public
+ License along with this library; if not, write to the Free Software
+ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ USA.
+
+ Please report all bugs and problems through the bug tracker at
+ "http://sourceforge.net/tracker/?group_id=114505&atid=668551".
+
+ "$Header: /lvm/shared/ds/ds/cvs/devkitpro-cvsbackup/tools/gba/gbatools/gbafix.c,v 1.2 2008-07-30 17:12:51 wntrmute
+ Exp $"
+
+*/
+//---------------------------------------------------------------------------------
+// gbafix.c
+//---------------------------------------------------------------------------------
+/*
+ Gameboy Advance ROM fixer (by Dark Fader / BlackThunder / WinterMute / Diegoisawesome)
+ Validates header of GBA roms.
+
+ History
+ -------
+ v1.07 - added support for ELF input, (PikalaxALT)
+ v1.06 - added output silencing, (Diegoisawesome)
+ v1.05 - added debug offset argument, (Diegoisawesome)
+ v1.04 - converted to plain C, (WinterMute)
+ v1.03 - header.fixed, header.device_type
+ v1.02 - redefined the options (rgbfix style), checksum=0
+ v1.01 - fix in parameters
+ v1.00 - logo, complement
+*/
+
+#pragma pack(1)
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <stdint.h>
+#include "elf.h"
+
+#define VER "1.07"
+#define ARGV argv[arg]
+#define VALUE (ARGV + 2)
+#define NUMBER strtoul(VALUE, NULL, 0)
+
+typedef struct {
+ uint32_t start_code; // B instruction
+ uint8_t logo[0xA0 - 0x04]; // logo data
+ uint8_t title[0xC]; // game title name
+ uint32_t game_code; //
+ uint16_t maker_code; //
+ uint8_t fixed; // 0x96
+ uint8_t unit_code; // 0x00
+ uint8_t device_type; // 0x00
+ uint8_t unused[7]; //
+ uint8_t game_version; // 0x00
+ uint8_t complement; // 800000A0..800000BC
+ uint16_t checksum; // 0x0000
+} Header;
+
+Header header;
+
+unsigned short checksum_without_header = 0;
+
+const Header good_header = {
+ // start_code
+ 0xEA00002E,
+ // logo
+ { 0x24, 0xFF, 0xAE, 0x51, 0x69, 0x9A, 0xA2, 0x21, 0x3D, 0x84, 0x82, 0x0A, 0x84, 0xE4, 0x09, 0xAD, 0x11, 0x24,
+ 0x8B, 0x98, 0xC0, 0x81, 0x7F, 0x21, 0xA3, 0x52, 0xBE, 0x19, 0x93, 0x09, 0xCE, 0x20, 0x10, 0x46, 0x4A, 0x4A,
+ 0xF8, 0x27, 0x31, 0xEC, 0x58, 0xC7, 0xE8, 0x33, 0x82, 0xE3, 0xCE, 0xBF, 0x85, 0xF4, 0xDF, 0x94, 0xCE, 0x4B,
+ 0x09, 0xC1, 0x94, 0x56, 0x8A, 0xC0, 0x13, 0x72, 0xA7, 0xFC, 0x9F, 0x84, 0x4D, 0x73, 0xA3, 0xCA, 0x9A, 0x61,
+ 0x58, 0x97, 0xA3, 0x27, 0xFC, 0x03, 0x98, 0x76, 0x23, 0x1D, 0xC7, 0x61, 0x03, 0x04, 0xAE, 0x56, 0xBF, 0x38,
+ 0x84, 0x00, 0x40, 0xA7, 0x0E, 0xFD, 0xFF, 0x52, 0xFE, 0x03, 0x6F, 0x95, 0x30, 0xF1, 0x97, 0xFB, 0xC0, 0x85,
+ 0x60, 0xD6, 0x80, 0x25, 0xA9, 0x63, 0xBE, 0x03, 0x01, 0x4E, 0x38, 0xE2, 0xF9, 0xA2, 0x34, 0xFF, 0xBB, 0x3E,
+ 0x03, 0x44, 0x78, 0x00, 0x90, 0xCB, 0x88, 0x11, 0x3A, 0x94, 0x65, 0xC0, 0x7C, 0x63, 0x87, 0xF0, 0x3C, 0xAF,
+ 0xD6, 0x25, 0xE4, 0x8B, 0x38, 0x0A, 0xAC, 0x72, 0x21, 0xD4, 0xF8, 0x07 },
+ // title
+ { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 },
+ // game code
+ 0x00000000,
+ // maker code
+ 0x3130,
+ // fixed
+ 0x96,
+ // unit_code
+ 0x00,
+ // device type
+ 0x00,
+ // unused
+ { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 },
+ // game version
+ 0x00,
+ // complement
+ 0x00,
+ // checksum
+ 0x0000
+};
+
+//---------------------------------------------------------------------------------
+char HeaderComplement()
+/*---------------------------------------------------------------------------------
+ Calculate Header complement check
+---------------------------------------------------------------------------------*/
+{
+ int n;
+ char c = 0;
+ char* p = (char*)&header + 0xA0;
+ for (n = 0; n < 0xBD - 0xA0; n++) {
+ c += *p++;
+ }
+ return -(0x19 + c);
+}
+
+//---------------------------------------------------------------------------------
+int main(int argc, char* argv[])
+//---------------------------------------------------------------------------------
+{
+ int arg;
+ char* argfile = 0;
+ FILE* infile;
+ int silent = 0;
+ int schedule_pad = 0;
+
+ int size, bit;
+
+ // show syntax
+ if (argc <= 1) {
+ printf("GBA ROM fixer v" VER " by Dark Fader / BlackThunder / WinterMute / Diegoisawesome \n");
+ printf("Syntax: gbafix <rom.gba> [-p] [-t[title]] [-c<game_code>] [-m<maker_code>] [-r<version>] [-d<debug>] "
+ "[--silent]\n");
+ printf("\n");
+ printf("parameters:\n");
+ printf(" -p Pad to next exact power of 2. No minimum size!\n");
+ printf(" -t[<title>] Patch title. Stripped filename if none given.\n");
+ printf(" -c<game_code> Patch game code (four characters)\n");
+ printf(" -m<maker_code> Patch maker code (two characters)\n");
+ printf(" -r<version> Patch game version (number)\n");
+ printf(" -d<debug> Enable debugging handler and set debug entry point (0 or 1)\n");
+ printf(" --silent Silence non-error output\n");
+ return -1;
+ }
+
+ // get filename
+ for (arg = 1; arg < argc; arg++) {
+ if (ARGV[0] != '-') {
+ argfile = ARGV;
+ }
+ if (strncmp("--silent", &ARGV[0], 7) == 0) {
+ silent = 1;
+ }
+ }
+
+ // check filename
+ if (!argfile) {
+ fprintf(stderr, "Filename needed!\n");
+ return -1;
+ }
+
+ uint32_t sh_offset = 0;
+
+ // read file
+ infile = fopen(argfile, "r+b");
+ if (!infile) {
+ fprintf(stderr, "Error opening input file!\n");
+ return -1;
+ }
+ fseek(infile, sh_offset, SEEK_SET);
+ fread(&header, sizeof(header), 1, infile);
+
+ // elf check
+ Elf32_Shdr secHeader;
+ if (memcmp(&header, ELFMAG, 4) == 0) {
+ Elf32_Ehdr* elfHeader = (Elf32_Ehdr*)&header;
+ fseek(infile, elfHeader->e_shoff, SEEK_SET);
+ int i;
+ for (i = 0; i < elfHeader->e_shnum; i++) {
+ fread(&secHeader, sizeof(Elf32_Shdr), 1, infile);
+ if (secHeader.sh_type == SHT_PROGBITS && secHeader.sh_addr == elfHeader->e_entry)
+ break;
+ }
+ if (i == elfHeader->e_shnum) {
+ fprintf(stderr, "Error finding entry point!\n");
+ return 1;
+ }
+ fseek(infile, secHeader.sh_offset, SEEK_SET);
+ sh_offset = secHeader.sh_offset;
+ fread(&header, sizeof(header), 1, infile);
+ }
+
+ // fix some data
+ memcpy(header.logo, good_header.logo, sizeof(header.logo));
+ memcpy(&header.fixed, &good_header.fixed, sizeof(header.fixed));
+ memcpy(&header.device_type, &good_header.device_type, sizeof(header.device_type));
+
+ // parse command line
+ for (arg = 1; arg < argc; arg++) {
+ if (ARGV[0] == '-') {
+ switch (ARGV[1]) {
+ case 'p': // pad
+ {
+ schedule_pad = 1;
+ break;
+ }
+
+ case 't': // title
+ {
+ char title[256];
+ memset(title, 0, sizeof(title));
+ if (VALUE[0]) {
+ strncpy(title, VALUE, sizeof(header.title));
+ } else {
+ // use filename
+ char s[256], *begin = s, *t;
+ strcpy(s, argfile);
+ t = strrchr(s, '\\');
+ if (t)
+ begin = t + 1;
+ t = strrchr(s, '/');
+ if (t)
+ begin = t + 1;
+ t = strrchr(s, '.');
+ if (t)
+ *t = 0;
+ strncpy(title, begin, sizeof(header.title));
+ if (!silent)
+ printf("%s\n", begin);
+ }
+ memcpy(header.title, title, sizeof(header.title)); // copy
+ break;
+ }
+
+ case 'c': // game code
+ {
+ // if (!VALUE[0]) { fprintf(stderr, "Need value for %s\n", ARGV); break; }
+ // header.game_code = NUMBER;
+ header.game_code = VALUE[0] | VALUE[1] << 8 | VALUE[2] << 16 | VALUE[3] << 24;
+ break;
+ }
+
+ case 'm': // maker code
+ {
+ // if (!VALUE[0]) { fprintf(stderr, "Need value for %s\n", ARGV); break; }
+ // header.maker_code = (unsigned short)NUMBER;
+ header.maker_code = VALUE[0] | VALUE[1] << 8;
+ break;
+ }
+
+ case 'v': // ignored, compatability with other gbafix
+ {
+ break;
+ }
+
+ case 'r': // version
+ {
+ if (!VALUE[0]) {
+ fprintf(stderr, "Need value for %s\n", ARGV);
+ break;
+ }
+ header.game_version = (unsigned char)NUMBER;
+ break;
+ }
+
+ case 'd': // debug
+ {
+ if (!VALUE[0]) {
+ fprintf(stderr, "Need value for %s\n", ARGV);
+ break;
+ }
+ header.logo[0x9C - 0x04] = 0xA5; // debug enable
+ header.device_type = (unsigned char)((NUMBER & 1) << 7); // debug handler entry point
+ break;
+ }
+ case '-': // long arguments
+ {
+ if (strncmp("silent", &ARGV[2], 6) == 0) {
+ continue;
+ }
+ break;
+ }
+ default: {
+ printf("Invalid option: %s\n", ARGV);
+ }
+ }
+ }
+ }
+
+ // update complement check & total checksum
+ header.complement = 0;
+ header.checksum = 0; // must be 0
+ header.complement = HeaderComplement();
+ // header.checksum = checksum_without_header + HeaderChecksum();
+
+ if (schedule_pad) {
+ if (sh_offset != 0) {
+ fprintf(stderr, "Warning: Cannot safely pad an ELF\n");
+ } else {
+ fseek(infile, 0, SEEK_END);
+ size = ftell(infile);
+ for (bit = 31; bit >= 0; bit--)
+ if (size & (1 << bit))
+ break;
+ if (size != (1 << bit)) {
+ int todo = (1 << (bit + 1)) - size;
+ while (todo--)
+ fputc(0xFF, infile);
+ }
+ }
+ }
+
+ fseek(infile, sh_offset, SEEK_SET);
+ fwrite(&header, sizeof(header), 1, infile);
+ fclose(infile);
+
+ if (!silent)
+ printf("ROM fixed!\n");
+
+ return 0;
+}
diff --git a/tools/src/gbagfx/CMakeLists.txt b/tools/src/gbagfx/CMakeLists.txt
new file mode 100644
index 00000000..85085b95
--- /dev/null
+++ b/tools/src/gbagfx/CMakeLists.txt
@@ -0,0 +1,12 @@
+file(GLOB_RECURSE sources *.c)
+
+set(CMAKE_FIND_FRAMEWORK LAST)
+
+find_package(PNG REQUIRED)
+
+# TODO Makefile had a debug build with -DDEBUG
+add_executable(gbagfx ${sources})
+target_include_directories(gbagfx PRIVATE .)
+target_link_libraries(gbagfx PRIVATE PNG::PNG)
+
+install(TARGETS gbagfx RUNTIME DESTINATION bin)
diff --git a/tools/src/gbagfx/LICENSE b/tools/src/gbagfx/LICENSE
new file mode 100755
index 00000000..b66bf81c
--- /dev/null
+++ b/tools/src/gbagfx/LICENSE
@@ -0,0 +1,19 @@
+Copyright (c) 2015 YamaArashi
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in
+all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE.
diff --git a/tools/src/gbagfx/convert_png.c b/tools/src/gbagfx/convert_png.c
new file mode 100644
index 00000000..61760836
--- /dev/null
+++ b/tools/src/gbagfx/convert_png.c
@@ -0,0 +1,243 @@
+// Copyright (c) 2015 YamaArashi
+
+#include <stdio.h>
+#include <setjmp.h>
+#include <png.h>
+#include "global.h"
+#include "convert_png.h"
+#include "gfx.h"
+
+static FILE* PngReadOpen(char* path, png_structp* pngStruct, png_infop* pngInfo) {
+ FILE* fp = fopen(path, "rb");
+
+ if (fp == NULL)
+ FATAL_ERROR("Failed to open \"%s\" for reading.\n", path);
+
+ unsigned char sig[8];
+
+ if (fread(sig, 8, 1, fp) != 1)
+ FATAL_ERROR("Failed to read PNG signature from \"%s\".\n", path);
+
+ if (png_sig_cmp(sig, 0, 8))
+ FATAL_ERROR("\"%s\" does not have a valid PNG signature.\n", path);
+
+ png_structp png_ptr = png_create_read_struct(PNG_LIBPNG_VER_STRING, NULL, NULL, NULL);
+
+ if (!png_ptr)
+ FATAL_ERROR("Failed to create PNG read struct.\n");
+
+ png_infop info_ptr = png_create_info_struct(png_ptr);
+
+ if (!info_ptr)
+ FATAL_ERROR("Failed to create PNG info struct.\n");
+
+ if (setjmp(png_jmpbuf(png_ptr)))
+ FATAL_ERROR("Failed to init I/O for reading \"%s\".\n", path);
+
+ png_init_io(png_ptr, fp);
+ png_set_sig_bytes(png_ptr, 8);
+ png_read_info(png_ptr, info_ptr);
+
+ *pngStruct = png_ptr;
+ *pngInfo = info_ptr;
+
+ return fp;
+}
+
+static unsigned char* ConvertBitDepth(unsigned char* src, int srcBitDepth, int destBitDepth, int numPixels) {
+ // Round the number of bits up to the next 8 and divide by 8 to get the number of bytes.
+ int srcSize = ((numPixels * srcBitDepth + 7) & ~7) / 8;
+ int destSize = ((numPixels * destBitDepth + 7) & ~7) / 8;
+ unsigned char* output = calloc(destSize, 1);
+ unsigned char* dest = output;
+ int i;
+ int j;
+ int destBit = 8 - destBitDepth;
+
+ for (i = 0; i < srcSize; i++) {
+ unsigned char srcByte = src[i];
+
+ for (j = 8 - srcBitDepth; j >= 0; j -= srcBitDepth) {
+ unsigned char pixel = (srcByte >> j) % (1 << srcBitDepth);
+
+ if (pixel >= (1 << destBitDepth))
+ FATAL_ERROR("Image exceeds the maximum color value for a %ibpp image.\n", destBitDepth);
+ *dest |= pixel << destBit;
+ destBit -= destBitDepth;
+ if (destBit < 0) {
+ dest++;
+ destBit = 8 - destBitDepth;
+ }
+ }
+ }
+
+ return output;
+}
+
+void ReadPng(char* path, struct Image* image) {
+ png_structp png_ptr;
+ png_infop info_ptr;
+
+ FILE* fp = PngReadOpen(path, &png_ptr, &info_ptr);
+
+ int bit_depth = png_get_bit_depth(png_ptr, info_ptr);
+
+ int color_type = png_get_color_type(png_ptr, info_ptr);
+
+ if (color_type != PNG_COLOR_TYPE_GRAY && color_type != PNG_COLOR_TYPE_PALETTE)
+ FATAL_ERROR("\"%s\" has an unsupported color type.\n", path);
+
+ // Check if the image has a palette so that we can tell if the colors need to be inverted later.
+ // Don't read the palette because it's not needed for now.
+ image->hasPalette = (color_type == PNG_COLOR_TYPE_PALETTE);
+
+ image->width = png_get_image_width(png_ptr, info_ptr);
+ image->height = png_get_image_height(png_ptr, info_ptr);
+
+ int rowbytes = png_get_rowbytes(png_ptr, info_ptr);
+
+ image->pixels = malloc(image->height * rowbytes);
+
+ if (image->pixels == NULL)
+ FATAL_ERROR("Failed to allocate pixel buffer.\n");
+
+ png_bytepp row_pointers = malloc(image->height * sizeof(png_bytep));
+
+ if (row_pointers == NULL)
+ FATAL_ERROR("Failed to allocate row pointers.\n");
+
+ for (int i = 0; i < image->height; i++)
+ row_pointers[i] = (png_bytep)(image->pixels + (i * rowbytes));
+
+ if (setjmp(png_jmpbuf(png_ptr)))
+ FATAL_ERROR("Error reading from \"%s\".\n", path);
+
+ png_read_image(png_ptr, row_pointers);
+
+ png_destroy_read_struct(&png_ptr, &info_ptr, NULL);
+
+ free(row_pointers);
+ fclose(fp);
+
+ if (bit_depth != image->bitDepth) {
+ unsigned char* src = image->pixels;
+
+ if (bit_depth != 1 && bit_depth != 2 && bit_depth != 4 && bit_depth != 8)
+ FATAL_ERROR("Bit depth of image must be 1, 2, 4, or 8.\n");
+ image->pixels = ConvertBitDepth(image->pixels, bit_depth, image->bitDepth, image->width * image->height);
+ free(src);
+ image->bitDepth = bit_depth;
+ }
+}
+
+void ReadPngPalette(char* path, struct Palette* palette) {
+ png_structp png_ptr;
+ png_infop info_ptr;
+ png_colorp colors;
+ int numColors;
+
+ FILE* fp = PngReadOpen(path, &png_ptr, &info_ptr);
+
+ if (png_get_color_type(png_ptr, info_ptr) != PNG_COLOR_TYPE_PALETTE)
+ FATAL_ERROR("The image \"%s\" does not contain a palette.\n", path);
+
+ if (png_get_PLTE(png_ptr, info_ptr, &colors, &numColors) != PNG_INFO_PLTE)
+ FATAL_ERROR("Failed to retrieve palette from \"%s\".\n", path);
+
+ if (numColors > 256)
+ FATAL_ERROR("Images with more than 256 colors are not supported.\n");
+
+ palette->numColors = numColors;
+ for (int i = 0; i < numColors; i++) {
+ palette->colors[i].red = colors[i].red;
+ palette->colors[i].green = colors[i].green;
+ palette->colors[i].blue = colors[i].blue;
+ }
+
+ png_destroy_read_struct(&png_ptr, &info_ptr, NULL);
+
+ fclose(fp);
+}
+
+void SetPngPalette(png_structp png_ptr, png_infop info_ptr, struct Palette* palette) {
+ png_colorp colors = malloc(palette->numColors * sizeof(png_color));
+
+ if (colors == NULL)
+ FATAL_ERROR("Failed to allocate PNG palette.\n");
+
+ for (int i = 0; i < palette->numColors; i++) {
+ colors[i].red = palette->colors[i].red;
+ colors[i].green = palette->colors[i].green;
+ colors[i].blue = palette->colors[i].blue;
+ }
+
+ png_set_PLTE(png_ptr, info_ptr, colors, palette->numColors);
+
+ free(colors);
+}
+
+void WritePng(char* path, struct Image* image) {
+ FILE* fp = fopen(path, "wb");
+
+ if (fp == NULL)
+ FATAL_ERROR("Failed to open \"%s\" for writing.\n", path);
+
+ png_structp png_ptr = png_create_write_struct(PNG_LIBPNG_VER_STRING, NULL, NULL, NULL);
+
+ if (!png_ptr)
+ FATAL_ERROR("Failed to create PNG write struct.\n");
+
+ png_infop info_ptr = png_create_info_struct(png_ptr);
+
+ if (!info_ptr)
+ FATAL_ERROR("Failed to create PNG info struct.\n");
+
+ if (setjmp(png_jmpbuf(png_ptr)))
+ FATAL_ERROR("Failed to init I/O for writing \"%s\".\n", path);
+
+ png_init_io(png_ptr, fp);
+
+ if (setjmp(png_jmpbuf(png_ptr)))
+ FATAL_ERROR("Error writing header for \"%s\".\n", path);
+
+ int color_type = image->hasPalette ? PNG_COLOR_TYPE_PALETTE : PNG_COLOR_TYPE_GRAY;
+
+ png_set_IHDR(png_ptr, info_ptr, image->width, image->height, image->bitDepth, color_type, PNG_INTERLACE_NONE,
+ PNG_COMPRESSION_TYPE_BASE, PNG_FILTER_TYPE_BASE);
+
+ if (image->hasPalette) {
+ SetPngPalette(png_ptr, info_ptr, &image->palette);
+
+ if (image->hasTransparency) {
+ png_byte trans = 0;
+ png_set_tRNS(png_ptr, info_ptr, &trans, 1, 0);
+ }
+ }
+
+ png_write_info(png_ptr, info_ptr);
+
+ png_bytepp row_pointers = malloc(image->height * sizeof(png_bytep));
+
+ if (row_pointers == NULL)
+ FATAL_ERROR("Failed to allocate row pointers.\n");
+
+ int rowbytes = png_get_rowbytes(png_ptr, info_ptr);
+
+ for (int i = 0; i < image->height; i++)
+ row_pointers[i] = (png_bytep)(image->pixels + (i * rowbytes));
+
+ if (setjmp(png_jmpbuf(png_ptr)))
+ FATAL_ERROR("Error writing \"%s\".\n", path);
+
+ png_write_image(png_ptr, row_pointers);
+
+ if (setjmp(png_jmpbuf(png_ptr)))
+ FATAL_ERROR("Error ending write of \"%s\".\n", path);
+
+ png_write_end(png_ptr, NULL);
+
+ fclose(fp);
+
+ png_destroy_write_struct(&png_ptr, &info_ptr);
+ free(row_pointers);
+}
diff --git a/tools/src/gbagfx/convert_png.h b/tools/src/gbagfx/convert_png.h
new file mode 100644
index 00000000..d45531ed
--- /dev/null
+++ b/tools/src/gbagfx/convert_png.h
@@ -0,0 +1,12 @@
+// Copyright (c) 2015 YamaArashi
+
+#ifndef CONVERT_PNG_H
+#define CONVERT_PNG_H
+
+#include "gfx.h"
+
+void ReadPng(char* path, struct Image* image);
+void WritePng(char* path, struct Image* image);
+void ReadPngPalette(char* path, struct Palette* palette);
+
+#endif // CONVERT_PNG_H
diff --git a/tools/src/gbagfx/font.c b/tools/src/gbagfx/font.c
new file mode 100644
index 00000000..366b78b3
--- /dev/null
+++ b/tools/src/gbagfx/font.c
@@ -0,0 +1,315 @@
+// Copyright (c) 2015 YamaArashi
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdint.h>
+#include <stdbool.h>
+#include "global.h"
+#include "font.h"
+#include "gfx.h"
+#include "util.h"
+
+unsigned char gFontPalette[][3] = {
+ { 0x90, 0xC8, 0xFF }, // bg (saturated blue that contrasts well with the shadow color)
+ { 0x38, 0x38, 0x38 }, // fg (dark grey)
+ { 0xD8, 0xD8, 0xD8 }, // shadow (light grey)
+ { 0xFF, 0xFF, 0xFF } // box (white)
+};
+
+static void ConvertFromLatinFont(unsigned char* src, unsigned char* dest, unsigned int numRows) {
+ unsigned int srcPixelsOffset = 0;
+
+ for (unsigned int row = 0; row < numRows; row++) {
+ for (unsigned int column = 0; column < 16; column++) {
+ for (unsigned int glyphTile = 0; glyphTile < 4; glyphTile++) {
+ unsigned int pixelsX = (column * 16) + ((glyphTile & 1) * 8);
+
+ for (unsigned int i = 0; i < 8; i++) {
+ unsigned int pixelsY = (row * 16) + ((glyphTile >> 1) * 8) + i;
+ unsigned int destPixelsOffset = (pixelsY * 64) + (pixelsX / 4);
+
+ dest[destPixelsOffset] = src[srcPixelsOffset + 1];
+ dest[destPixelsOffset + 1] = src[srcPixelsOffset];
+
+ srcPixelsOffset += 2;
+ }
+ }
+ }
+ }
+}
+
+static void ConvertToLatinFont(unsigned char* src, unsigned char* dest, unsigned int numRows) {
+ unsigned int destPixelsOffset = 0;
+
+ for (unsigned int row = 0; row < numRows; row++) {
+ for (unsigned int column = 0; column < 16; column++) {
+ for (unsigned int glyphTile = 0; glyphTile < 4; glyphTile++) {
+ unsigned int pixelsX = (column * 16) + ((glyphTile & 1) * 8);
+
+ for (unsigned int i = 0; i < 8; i++) {
+ unsigned int pixelsY = (row * 16) + ((glyphTile >> 1) * 8) + i;
+ unsigned int srcPixelsOffset = (pixelsY * 64) + (pixelsX / 4);
+
+ dest[destPixelsOffset] = src[srcPixelsOffset + 1];
+ dest[destPixelsOffset + 1] = src[srcPixelsOffset];
+
+ destPixelsOffset += 2;
+ }
+ }
+ }
+ }
+}
+
+static void ConvertFromHalfwidthJapaneseFont(unsigned char* src, unsigned char* dest, unsigned int numRows) {
+ for (unsigned int row = 0; row < numRows; row++) {
+ for (unsigned int column = 0; column < 16; column++) {
+ unsigned int glyphIndex = (row * 16) + column;
+
+ for (unsigned int glyphTile = 0; glyphTile < 2; glyphTile++) {
+ unsigned int pixelsX = column * 8;
+ unsigned int srcPixelsOffset = 512 * (glyphIndex >> 4) + 16 * (glyphIndex & 0xF) + 256 * glyphTile;
+
+ for (unsigned int i = 0; i < 8; i++) {
+ unsigned int pixelsY = (row * 16) + (glyphTile * 8) + i;
+ unsigned int destPixelsOffset = (pixelsY * 32) + (pixelsX / 4);
+
+ dest[destPixelsOffset] = src[srcPixelsOffset + 1];
+ dest[destPixelsOffset + 1] = src[srcPixelsOffset];
+
+ srcPixelsOffset += 2;
+ }
+ }
+ }
+ }
+}
+
+static void ConvertToHalfwidthJapaneseFont(unsigned char* src, unsigned char* dest, unsigned int numRows) {
+ for (unsigned int row = 0; row < numRows; row++) {
+ for (unsigned int column = 0; column < 16; column++) {
+ unsigned int glyphIndex = (row * 16) + column;
+
+ for (unsigned int glyphTile = 0; glyphTile < 2; glyphTile++) {
+ unsigned int pixelsX = column * 8;
+ unsigned int destPixelsOffset = 512 * (glyphIndex >> 4) + 16 * (glyphIndex & 0xF) + 256 * glyphTile;
+
+ for (unsigned int i = 0; i < 8; i++) {
+ unsigned int pixelsY = (row * 16) + (glyphTile * 8) + i;
+ unsigned int srcPixelsOffset = (pixelsY * 32) + (pixelsX / 4);
+
+ dest[destPixelsOffset] = src[srcPixelsOffset + 1];
+ dest[destPixelsOffset + 1] = src[srcPixelsOffset];
+
+ destPixelsOffset += 2;
+ }
+ }
+ }
+ }
+}
+
+static void ConvertFromFullwidthJapaneseFont(unsigned char* src, unsigned char* dest, unsigned int numRows) {
+ for (unsigned int row = 0; row < numRows; row++) {
+ for (unsigned int column = 0; column < 16; column++) {
+ unsigned int glyphIndex = (row * 16) + column;
+
+ for (unsigned int glyphTile = 0; glyphTile < 4; glyphTile++) {
+ unsigned int pixelsX = (column * 16) + ((glyphTile & 1) * 8);
+ unsigned int srcPixelsOffset =
+ 512 * (glyphIndex >> 3) + 32 * (glyphIndex & 7) + 256 * (glyphTile >> 1) + 16 * (glyphTile & 1);
+
+ for (unsigned int i = 0; i < 8; i++) {
+ unsigned int pixelsY = (row * 16) + ((glyphTile >> 1) * 8) + i;
+ unsigned int destPixelsOffset = (pixelsY * 64) + (pixelsX / 4);
+
+ dest[destPixelsOffset] = src[srcPixelsOffset + 1];
+ dest[destPixelsOffset + 1] = src[srcPixelsOffset];
+
+ srcPixelsOffset += 2;
+ }
+ }
+ }
+ }
+}
+
+static void ConvertToFullwidthJapaneseFont(unsigned char* src, unsigned char* dest, unsigned int numRows) {
+ for (unsigned int row = 0; row < numRows; row++) {
+ for (unsigned int column = 0; column < 16; column++) {
+ unsigned int glyphIndex = (row * 16) + column;
+
+ for (unsigned int glyphTile = 0; glyphTile < 4; glyphTile++) {
+ unsigned int pixelsX = (column * 16) + ((glyphTile & 1) * 8);
+ unsigned int destPixelsOffset =
+ 512 * (glyphIndex >> 3) + 32 * (glyphIndex & 7) + 256 * (glyphTile >> 1) + 16 * (glyphTile & 1);
+
+ for (unsigned int i = 0; i < 8; i++) {
+ unsigned int pixelsY = (row * 16) + ((glyphTile >> 1) * 8) + i;
+ unsigned int srcPixelsOffset = (pixelsY * 64) + (pixelsX / 4);
+
+ dest[destPixelsOffset] = src[srcPixelsOffset + 1];
+ dest[destPixelsOffset + 1] = src[srcPixelsOffset];
+
+ destPixelsOffset += 2;
+ }
+ }
+ }
+ }
+}
+
+static void SetFontPalette(struct Image* image) {
+ image->hasPalette = true;
+
+ image->palette.numColors = 4;
+
+ for (int i = 0; i < image->palette.numColors; i++) {
+ image->palette.colors[i].red = gFontPalette[i][0];
+ image->palette.colors[i].green = gFontPalette[i][1];
+ image->palette.colors[i].blue = gFontPalette[i][2];
+ }
+
+ image->hasTransparency = false;
+}
+
+void ReadLatinFont(char* path, struct Image* image) {
+ int fileSize;
+ unsigned char* buffer = ReadWholeFile(path, &fileSize);
+
+ int numGlyphs = fileSize / 64;
+
+ if (numGlyphs % 16 != 0)
+ FATAL_ERROR("The number of glyphs (%d) is not a multiple of 16.\n", numGlyphs);
+
+ int numRows = numGlyphs / 16;
+
+ image->width = 256;
+ image->height = numRows * 16;
+ image->bitDepth = 2;
+ image->pixels = malloc(fileSize);
+
+ if (image->pixels == NULL)
+ FATAL_ERROR("Failed to allocate memory for font.\n");
+
+ ConvertFromLatinFont(buffer, image->pixels, numRows);
+
+ free(buffer);
+
+ SetFontPalette(image);
+}
+
+void WriteLatinFont(char* path, struct Image* image) {
+ if (image->width != 256)
+ FATAL_ERROR("The width of the font image (%d) is not 256.\n", image->width);
+
+ if (image->height % 16 != 0)
+ FATAL_ERROR("The height of the font image (%d) is not a multiple of 16.\n", image->height);
+
+ int numRows = image->height / 16;
+ int bufferSize = numRows * 16 * 64;
+ unsigned char* buffer = malloc(bufferSize);
+
+ if (buffer == NULL)
+ FATAL_ERROR("Failed to allocate memory for font.\n");
+
+ ConvertToLatinFont(image->pixels, buffer, numRows);
+
+ WriteWholeFile(path, buffer, bufferSize);
+
+ free(buffer);
+}
+
+void ReadHalfwidthJapaneseFont(char* path, struct Image* image) {
+ int fileSize;
+ unsigned char* buffer = ReadWholeFile(path, &fileSize);
+
+ int glyphSize = 32;
+
+ if (fileSize % glyphSize != 0)
+ FATAL_ERROR("The file size (%d) is not a multiple of %d.\n", fileSize, glyphSize);
+
+ int numGlyphs = fileSize / glyphSize;
+
+ if (numGlyphs % 16 != 0)
+ FATAL_ERROR("The number of glyphs (%d) is not a multiple of 16.\n", numGlyphs);
+
+ int numRows = numGlyphs / 16;
+
+ image->width = 128;
+ image->height = numRows * 16;
+ image->bitDepth = 2;
+ image->pixels = malloc(fileSize);
+
+ if (image->pixels == NULL)
+ FATAL_ERROR("Failed to allocate memory for font.\n");
+
+ ConvertFromHalfwidthJapaneseFont(buffer, image->pixels, numRows);
+
+ free(buffer);
+
+ SetFontPalette(image);
+}
+
+void WriteHalfwidthJapaneseFont(char* path, struct Image* image) {
+ if (image->width != 128)
+ FATAL_ERROR("The width of the font image (%d) is not 128.\n", image->width);
+
+ if (image->height % 16 != 0)
+ FATAL_ERROR("The height of the font image (%d) is not a multiple of 16.\n", image->height);
+
+ int numRows = image->height / 16;
+ int bufferSize = numRows * 16 * 32;
+ unsigned char* buffer = malloc(bufferSize);
+
+ if (buffer == NULL)
+ FATAL_ERROR("Failed to allocate memory for font.\n");
+
+ ConvertToHalfwidthJapaneseFont(image->pixels, buffer, numRows);
+
+ WriteWholeFile(path, buffer, bufferSize);
+
+ free(buffer);
+}
+
+void ReadFullwidthJapaneseFont(char* path, struct Image* image) {
+ int fileSize;
+ unsigned char* buffer = ReadWholeFile(path, &fileSize);
+
+ int numGlyphs = fileSize / 64;
+
+ if (numGlyphs % 16 != 0)
+ FATAL_ERROR("The number of glyphs (%d) is not a multiple of 16.\n", numGlyphs);
+
+ int numRows = numGlyphs / 16;
+
+ image->width = 256;
+ image->height = numRows * 16;
+ image->bitDepth = 2;
+ image->pixels = malloc(fileSize);
+
+ if (image->pixels == NULL)
+ FATAL_ERROR("Failed to allocate memory for font.\n");
+
+ ConvertFromFullwidthJapaneseFont(buffer, image->pixels, numRows);
+
+ free(buffer);
+
+ SetFontPalette(image);
+}
+
+void WriteFullwidthJapaneseFont(char* path, struct Image* image) {
+ if (image->width != 256)
+ FATAL_ERROR("The width of the font image (%d) is not 256.\n", image->width);
+
+ if (image->height % 16 != 0)
+ FATAL_ERROR("The height of the font image (%d) is not a multiple of 16.\n", image->height);
+
+ int numRows = image->height / 16;
+ int bufferSize = numRows * 16 * 64;
+ unsigned char* buffer = malloc(bufferSize);
+
+ if (buffer == NULL)
+ FATAL_ERROR("Failed to allocate memory for font.\n");
+
+ ConvertToFullwidthJapaneseFont(image->pixels, buffer, numRows);
+
+ WriteWholeFile(path, buffer, bufferSize);
+
+ free(buffer);
+}
diff --git a/tools/src/gbagfx/font.h b/tools/src/gbagfx/font.h
new file mode 100644
index 00000000..9ff03792
--- /dev/null
+++ b/tools/src/gbagfx/font.h
@@ -0,0 +1,16 @@
+// Copyright (c) 2015 YamaArashi
+
+#ifndef FONT_H
+#define FONT_H
+
+#include <stdbool.h>
+#include "gfx.h"
+
+void ReadLatinFont(char* path, struct Image* image);
+void WriteLatinFont(char* path, struct Image* image);
+void ReadHalfwidthJapaneseFont(char* path, struct Image* image);
+void WriteHalfwidthJapaneseFont(char* path, struct Image* image);
+void ReadFullwidthJapaneseFont(char* path, struct Image* image);
+void WriteFullwidthJapaneseFont(char* path, struct Image* image);
+
+#endif // FONT_H
diff --git a/tools/src/gbagfx/gfx.c b/tools/src/gbagfx/gfx.c
new file mode 100644
index 00000000..f16dd8ac
--- /dev/null
+++ b/tools/src/gbagfx/gfx.c
@@ -0,0 +1,358 @@
+// Copyright (c) 2015 YamaArashi
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdint.h>
+#include <stdbool.h>
+#include "global.h"
+#include "gfx.h"
+#include "util.h"
+
+#define GET_GBA_PAL_RED(x) (((x) >> 0) & 0x1F)
+#define GET_GBA_PAL_GREEN(x) (((x) >> 5) & 0x1F)
+#define GET_GBA_PAL_BLUE(x) (((x) >> 10) & 0x1F)
+
+#define SET_GBA_PAL(r, g, b) (((b) << 10) | ((g) << 5) | (r))
+
+#define UPCONVERT_BIT_DEPTH(x) (((x)*255) / 31)
+
+#define DOWNCONVERT_BIT_DEPTH(x) ((x) / 8)
+
+static void AdvanceMetatilePosition(int* subTileX, int* subTileY, int* metatileX, int* metatileY, int metatilesWide,
+ int metatileWidth, int metatileHeight) {
+ (*subTileX)++;
+ if (*subTileX == metatileWidth) {
+ *subTileX = 0;
+ (*subTileY)++;
+ if (*subTileY == metatileHeight) {
+ *subTileY = 0;
+ (*metatileX)++;
+ if (*metatileX == metatilesWide) {
+ *metatileX = 0;
+ (*metatileY)++;
+ }
+ }
+ }
+}
+
+static void ConvertFromTiles1Bpp(unsigned char* src, unsigned char* dest, int numTiles, int metatilesWide,
+ int metatileWidth, int metatileHeight, bool invertColors) {
+ int subTileX = 0;
+ int subTileY = 0;
+ int metatileX = 0;
+ int metatileY = 0;
+ int pitch = metatilesWide * metatileWidth;
+
+ for (int i = 0; i < numTiles; i++) {
+ for (int j = 0; j < 8; j++) {
+ int destY = (metatileY * metatileHeight + subTileY) * 8 + j;
+ int destX = metatileX * metatileWidth + subTileX;
+ unsigned char srcPixelOctet = *src++;
+ unsigned char* destPixelOctet = &dest[destY * pitch + destX];
+
+ for (int k = 0; k < 8; k++) {
+ *destPixelOctet <<= 1;
+ *destPixelOctet |= (srcPixelOctet & 1) ^ invertColors;
+ srcPixelOctet >>= 1;
+ }
+ }
+
+ AdvanceMetatilePosition(&subTileX, &subTileY, &metatileX, &metatileY, metatilesWide, metatileWidth,
+ metatileHeight);
+ }
+}
+
+static void ConvertFromTiles4Bpp(unsigned char* src, unsigned char* dest, int numTiles, int metatilesWide,
+ int metatileWidth, int metatileHeight, bool invertColors) {
+ int subTileX = 0;
+ int subTileY = 0;
+ int metatileX = 0;
+ int metatileY = 0;
+ int pitch = (metatilesWide * metatileWidth) * 4;
+
+ for (int i = 0; i < numTiles; i++) {
+ for (int j = 0; j < 8; j++) {
+ int destY = (metatileY * metatileHeight + subTileY) * 8 + j;
+
+ for (int k = 0; k < 4; k++) {
+ int destX = (metatileX * metatileWidth + subTileX) * 4 + k;
+ unsigned char srcPixelPair = *src++;
+ unsigned char leftPixel = srcPixelPair & 0xF;
+ unsigned char rightPixel = srcPixelPair >> 4;
+
+ if (invertColors) {
+ leftPixel = 15 - leftPixel;
+ rightPixel = 15 - rightPixel;
+ }
+
+ dest[destY * pitch + destX] = (leftPixel << 4) | rightPixel;
+ }
+ }
+
+ AdvanceMetatilePosition(&subTileX, &subTileY, &metatileX, &metatileY, metatilesWide, metatileWidth,
+ metatileHeight);
+ }
+}
+
+static void ConvertFromTiles8Bpp(unsigned char* src, unsigned char* dest, int numTiles, int metatilesWide,
+ int metatileWidth, int metatileHeight, bool invertColors) {
+ int subTileX = 0;
+ int subTileY = 0;
+ int metatileX = 0;
+ int metatileY = 0;
+ int pitch = (metatilesWide * metatileWidth) * 8;
+
+ for (int i = 0; i < numTiles; i++) {
+ for (int j = 0; j < 8; j++) {
+ int destY = (metatileY * metatileHeight + subTileY) * 8 + j;
+
+ for (int k = 0; k < 8; k++) {
+ int destX = (metatileX * metatileWidth + subTileX) * 8 + k;
+ unsigned char srcPixel = *src++;
+
+ if (invertColors)
+ srcPixel = 255 - srcPixel;
+
+ dest[destY * pitch + destX] = srcPixel;
+ }
+ }
+
+ AdvanceMetatilePosition(&subTileX, &subTileY, &metatileX, &metatileY, metatilesWide, metatileWidth,
+ metatileHeight);
+ }
+}
+
+static void ConvertToTiles1Bpp(unsigned char* src, unsigned char* dest, int numTiles, int metatilesWide,
+ int metatileWidth, int metatileHeight, bool invertColors) {
+ int subTileX = 0;
+ int subTileY = 0;
+ int metatileX = 0;
+ int metatileY = 0;
+ int pitch = metatilesWide * metatileWidth;
+
+ for (int i = 0; i < numTiles; i++) {
+ for (int j = 0; j < 8; j++) {
+ int srcY = (metatileY * metatileHeight + subTileY) * 8 + j;
+ int srcX = metatileX * metatileWidth + subTileX;
+ unsigned char srcPixelOctet = src[srcY * pitch + srcX];
+ unsigned char* destPixelOctet = dest++;
+
+ for (int k = 0; k < 8; k++) {
+ *destPixelOctet <<= 1;
+ *destPixelOctet |= (srcPixelOctet & 1) ^ invertColors;
+ srcPixelOctet >>= 1;
+ }
+ }
+
+ AdvanceMetatilePosition(&subTileX, &subTileY, &metatileX, &metatileY, metatilesWide, metatileWidth,
+ metatileHeight);
+ }
+}
+
+static void ConvertToTiles4Bpp(unsigned char* src, unsigned char* dest, int numTiles, int metatilesWide,
+ int metatileWidth, int metatileHeight, bool invertColors) {
+ int subTileX = 0;
+ int subTileY = 0;
+ int metatileX = 0;
+ int metatileY = 0;
+ int pitch = (metatilesWide * metatileWidth) * 4;
+
+ for (int i = 0; i < numTiles; i++) {
+ for (int j = 0; j < 8; j++) {
+ int srcY = (metatileY * metatileHeight + subTileY) * 8 + j;
+
+ for (int k = 0; k < 4; k++) {
+ int srcX = (metatileX * metatileWidth + subTileX) * 4 + k;
+ unsigned char srcPixelPair = src[srcY * pitch + srcX];
+ unsigned char leftPixel = srcPixelPair >> 4;
+ unsigned char rightPixel = srcPixelPair & 0xF;
+
+ if (invertColors) {
+ leftPixel = 15 - leftPixel;
+ rightPixel = 15 - rightPixel;
+ }
+
+ *dest++ = (rightPixel << 4) | leftPixel;
+ }
+ }
+
+ AdvanceMetatilePosition(&subTileX, &subTileY, &metatileX, &metatileY, metatilesWide, metatileWidth,
+ metatileHeight);
+ }
+}
+
+static void ConvertToTiles8Bpp(unsigned char* src, unsigned char* dest, int numTiles, int metatilesWide,
+ int metatileWidth, int metatileHeight, bool invertColors) {
+ int subTileX = 0;
+ int subTileY = 0;
+ int metatileX = 0;
+ int metatileY = 0;
+ int pitch = (metatilesWide * metatileWidth) * 8;
+
+ for (int i = 0; i < numTiles; i++) {
+ for (int j = 0; j < 8; j++) {
+ int srcY = (metatileY * metatileHeight + subTileY) * 8 + j;
+
+ for (int k = 0; k < 8; k++) {
+ int srcX = (metatileX * metatileWidth + subTileX) * 8 + k;
+ unsigned char srcPixel = src[srcY * pitch + srcX];
+
+ if (invertColors)
+ srcPixel = 255 - srcPixel;
+
+ *dest++ = srcPixel;
+ }
+ }
+
+ AdvanceMetatilePosition(&subTileX, &subTileY, &metatileX, &metatileY, metatilesWide, metatileWidth,
+ metatileHeight);
+ }
+}
+
+void ReadImage(char* path, int tilesWidth, int bitDepth, int metatileWidth, int metatileHeight, struct Image* image,
+ bool invertColors) {
+ int tileSize = bitDepth * 8;
+
+ int fileSize;
+ unsigned char* buffer = ReadWholeFile(path, &fileSize);
+
+ int numTiles = fileSize / tileSize;
+
+ int tilesHeight = (numTiles + tilesWidth - 1) / tilesWidth;
+
+ if (tilesWidth % metatileWidth != 0)
+ FATAL_ERROR("The width in tiles (%d) isn't a multiple of the specified metatile width (%d)", tilesWidth,
+ metatileWidth);
+
+ if (tilesHeight % metatileHeight != 0)
+ FATAL_ERROR("The height in tiles (%d) isn't a multiple of the specified metatile height (%d)", tilesHeight,
+ metatileHeight);
+
+ image->width = tilesWidth * 8;
+ image->height = tilesHeight * 8;
+ image->bitDepth = bitDepth;
+ image->pixels = calloc(tilesWidth * tilesHeight, tileSize);
+
+ if (image->pixels == NULL)
+ FATAL_ERROR("Failed to allocate memory for pixels.\n");
+
+ int metatilesWide = tilesWidth / metatileWidth;
+
+ switch (bitDepth) {
+ case 1:
+ ConvertFromTiles1Bpp(buffer, image->pixels, numTiles, metatilesWide, metatileWidth, metatileHeight,
+ invertColors);
+ break;
+ case 4:
+ ConvertFromTiles4Bpp(buffer, image->pixels, numTiles, metatilesWide, metatileWidth, metatileHeight,
+ invertColors);
+ break;
+ case 8:
+ ConvertFromTiles8Bpp(buffer, image->pixels, numTiles, metatilesWide, metatileWidth, metatileHeight,
+ invertColors);
+ break;
+ }
+
+ free(buffer);
+}
+
+void WriteImage(char* path, int numTiles, int bitDepth, int metatileWidth, int metatileHeight, struct Image* image,
+ bool invertColors) {
+ int tileSize = bitDepth * 8;
+
+ if (image->width % 8 != 0)
+ FATAL_ERROR("The width in pixels (%d) isn't a multiple of 8.\n", image->width);
+
+ if (image->height % 8 != 0)
+ FATAL_ERROR("The height in pixels (%d) isn't a multiple of 8.\n", image->height);
+
+ int tilesWidth = image->width / 8;
+ int tilesHeight = image->height / 8;
+
+ if (tilesWidth % metatileWidth != 0)
+ FATAL_ERROR("The width in tiles (%d) isn't a multiple of the specified metatile width (%d)", tilesWidth,
+ metatileWidth);
+
+ if (tilesHeight % metatileHeight != 0)
+ FATAL_ERROR("The height in tiles (%d) isn't a multiple of the specified metatile height (%d)", tilesHeight,
+ metatileHeight);
+
+ int maxNumTiles = tilesWidth * tilesHeight;
+
+ if (numTiles == 0)
+ numTiles = maxNumTiles;
+ else if (numTiles > maxNumTiles)
+ FATAL_ERROR("The specified number of tiles (%d) is greater than the maximum possible value (%d).\n", numTiles,
+ maxNumTiles);
+
+ int bufferSize = numTiles * tileSize;
+ unsigned char* buffer = malloc(bufferSize);
+
+ if (buffer == NULL)
+ FATAL_ERROR("Failed to allocate memory for pixels.\n");
+
+ int metatilesWide = tilesWidth / metatileWidth;
+
+ switch (bitDepth) {
+ case 1:
+ ConvertToTiles1Bpp(image->pixels, buffer, numTiles, metatilesWide, metatileWidth, metatileHeight,
+ invertColors);
+ break;
+ case 4:
+ ConvertToTiles4Bpp(image->pixels, buffer, numTiles, metatilesWide, metatileWidth, metatileHeight,
+ invertColors);
+ break;
+ case 8:
+ ConvertToTiles8Bpp(image->pixels, buffer, numTiles, metatilesWide, metatileWidth, metatileHeight,
+ invertColors);
+ break;
+ }
+
+ WriteWholeFile(path, buffer, bufferSize);
+
+ free(buffer);
+}
+
+void FreeImage(struct Image* image) {
+ free(image->pixels);
+ image->pixels = NULL;
+}
+
+void ReadGbaPalette(char* path, struct Palette* palette) {
+ int fileSize;
+ unsigned char* data = ReadWholeFile(path, &fileSize);
+
+ if (fileSize % 2 != 0)
+ FATAL_ERROR("The file size (%d) is not a multiple of 2.\n", fileSize);
+
+ palette->numColors = fileSize / 2;
+
+ for (int i = 0; i < palette->numColors; i++) {
+ uint16_t paletteEntry = (data[i * 2 + 1] << 8) | data[i * 2];
+ palette->colors[i].red = UPCONVERT_BIT_DEPTH(GET_GBA_PAL_RED(paletteEntry));
+ palette->colors[i].green = UPCONVERT_BIT_DEPTH(GET_GBA_PAL_GREEN(paletteEntry));
+ palette->colors[i].blue = UPCONVERT_BIT_DEPTH(GET_GBA_PAL_BLUE(paletteEntry));
+ }
+
+ free(data);
+}
+
+void WriteGbaPalette(char* path, struct Palette* palette) {
+ FILE* fp = fopen(path, "wb");
+
+ if (fp == NULL)
+ FATAL_ERROR("Failed to open \"%s\" for writing.\n", path);
+
+ for (int i = 0; i < palette->numColors; i++) {
+ unsigned char red = DOWNCONVERT_BIT_DEPTH(palette->colors[i].red);
+ unsigned char green = DOWNCONVERT_BIT_DEPTH(palette->colors[i].green);
+ unsigned char blue = DOWNCONVERT_BIT_DEPTH(palette->colors[i].blue);
+
+ uint16_t paletteEntry = SET_GBA_PAL(red, green, blue);
+
+ fputc(paletteEntry & 0xFF, fp);
+ fputc(paletteEntry >> 8, fp);
+ }
+
+ fclose(fp);
+}
diff --git a/tools/src/gbagfx/gfx.h b/tools/src/gbagfx/gfx.h
new file mode 100644
index 00000000..6b3aff3a
--- /dev/null
+++ b/tools/src/gbagfx/gfx.h
@@ -0,0 +1,38 @@
+// Copyright (c) 2015 YamaArashi
+
+#ifndef GFX_H
+#define GFX_H
+
+#include <stdint.h>
+#include <stdbool.h>
+
+struct Color {
+ unsigned char red;
+ unsigned char green;
+ unsigned char blue;
+};
+
+struct Palette {
+ struct Color colors[256];
+ int numColors;
+};
+
+struct Image {
+ int width;
+ int height;
+ int bitDepth;
+ unsigned char* pixels;
+ bool hasPalette;
+ struct Palette palette;
+ bool hasTransparency;
+};
+
+void ReadImage(char* path, int tilesWidth, int bitDepth, int metatileWidth, int metatileHeight, struct Image* image,
+ bool invertColors);
+void WriteImage(char* path, int numTiles, int bitDepth, int metatileWidth, int metatileHeight, struct Image* image,
+ bool invertColors);
+void FreeImage(struct Image* image);
+void ReadGbaPalette(char* path, struct Palette* palette);
+void WriteGbaPalette(char* path, struct Palette* palette);
+
+#endif // GFX_H
diff --git a/tools/src/gbagfx/global.h b/tools/src/gbagfx/global.h
new file mode 100644
index 00000000..b6fea5a0
--- /dev/null
+++ b/tools/src/gbagfx/global.h
@@ -0,0 +1,31 @@
+// Copyright (c) 2015 YamaArashi
+
+#ifndef GLOBAL_H
+#define GLOBAL_H
+
+#include <stdio.h>
+#include <stdlib.h>
+
+#ifdef _MSC_VER
+
+#define FATAL_ERROR(format, ...) \
+ do { \
+ fprintf(stderr, format, __VA_ARGS__); \
+ exit(1); \
+ } while (0)
+
+#define UNUSED
+
+#else
+
+#define FATAL_ERROR(format, ...) \
+ do { \
+ fprintf(stderr, format, ##__VA_ARGS__); \
+ exit(1); \
+ } while (0)
+
+#define UNUSED __attribute__((__unused__))
+
+#endif // _MSC_VER
+
+#endif // GLOBAL_H
diff --git a/tools/src/gbagfx/huff.c b/tools/src/gbagfx/huff.c
new file mode 100644
index 00000000..f52a7d2d
--- /dev/null
+++ b/tools/src/gbagfx/huff.c
@@ -0,0 +1,398 @@
+#include <stdbool.h>
+#include <string.h>
+#include <assert.h>
+#include <stdio.h>
+#include <stdint.h>
+#include "global.h"
+#include "huff.h"
+
+static int cmp_tree(const void* a0, const void* b0) {
+ return ((struct HuffData*)a0)->value - ((struct HuffData*)b0)->value;
+}
+
+typedef int (*cmpfun)(const void*, const void*);
+
+int msort_r(void* data, size_t count, size_t size, cmpfun cmp, void* buffer) {
+ /*
+ * Out-of-place mergesort (stable sort)
+ * Returns 1 on success, 0 on failure
+ */
+ void* leftPtr;
+ void* rightPtr;
+ void* leftEnd;
+ void* rightEnd;
+ int i;
+
+ switch (count) {
+ case 0:
+ // Should never be here
+ return 0;
+
+ case 1:
+ // Nothing to do here
+ break;
+
+ case 2:
+ // Swap the two entries if the right one compares higher.
+ if (cmp(data, data + size) > 0) {
+ memcpy(buffer, data, size);
+ memcpy(data, data + size, size);
+ memcpy(data + size, buffer, size);
+ }
+ break;
+ default:
+ // Merge sort out-of-place.
+ leftPtr = data;
+ leftEnd = rightPtr = data + count / 2 * size;
+ rightEnd = data + count * size;
+
+ // Sort the left half
+ if (!msort_r(leftPtr, count / 2, size, cmp, buffer))
+ return 0;
+
+ // Sort the right half
+ if (!msort_r(rightPtr, count / 2 + (count & 1), size, cmp, buffer))
+ return 0;
+
+ // Merge the sorted halves out of place
+ i = 0;
+ do {
+ if (cmp(leftPtr, rightPtr) <= 0) {
+ memcpy(buffer + i * size, leftPtr, size);
+ leftPtr += size;
+ } else {
+ memcpy(buffer + i * size, rightPtr, size);
+ rightPtr += size;
+ }
+
+ } while (++i < count && leftPtr < leftEnd && rightPtr < rightEnd);
+
+ // Copy the remainder
+ if (i < count) {
+ if (leftPtr < leftEnd) {
+ memcpy(buffer + i * size, leftPtr, leftEnd - leftPtr);
+ } else {
+ memcpy(buffer + i * size, rightPtr, rightEnd - rightPtr);
+ }
+ }
+
+ // Copy the merged data back
+ memcpy(data, buffer, count * size);
+ break;
+ }
+
+ return 1;
+}
+
+int msort(void* data, size_t count, size_t size, cmpfun cmp) {
+ void* buffer = malloc(count * size);
+ if (buffer == NULL)
+ return 0;
+ int result = msort_r(data, count, size, cmp, buffer);
+ free(buffer);
+ return result;
+}
+
+static void write_tree(unsigned char* dest, HuffNode_t* tree, int nitems, struct BitEncoding* encoding) {
+ /*
+ * The example used to guide this function encodes the tree in a
+ * breadth-first manner. We attempt to emulate that here.
+ */
+
+ int i, j, k;
+
+ // There are (2 * nitems - 1) nodes in the binary tree. Allocate that.
+ HuffNode_t* traversal = calloc(2 * nitems - 1, sizeof(HuffNode_t));
+ if (traversal == NULL)
+ FATAL_ERROR("Fatal error while compressing Huff file.\n");
+
+ // The first node is the root of the tree.
+ traversal[0] = *tree;
+ i = 1;
+
+ // Copy the tree into a breadth-first ordering using brute force.
+ for (int depth = 1; i < 2 * nitems - 1; depth++) {
+ // Consider every possible path up to the current depth.
+ for (j = 0; i < 2 * nitems - 1 && j < 1 << depth; j++) {
+ // The index of the path is used to encode the path itself.
+ // Start from the most significant relevant bit and work our way down.
+ // Keep track of the current and previous nodes.
+ HuffNode_t* currNode = traversal;
+ HuffNode_t* parent = NULL;
+ for (k = 0; k < depth; k++) {
+ if (currNode->header.isLeaf)
+ break;
+ parent = currNode;
+ if ((j >> (depth - k - 1)) & 1)
+ currNode = currNode->branch.right;
+ else
+ currNode = currNode->branch.left;
+ }
+ // Check that the length of the current path equals the current depth.
+ if (k == depth) {
+ // Make sure we can encode the current branch.
+ // Bail here if we cannot.
+ // This is only applicable for 8-bit encodings.
+ if (traversal + i - parent > 128)
+ FATAL_ERROR("Fatal error while compressing Huff file: unable to encode binary tree.\n");
+ // Copy the current node, and update its parent.
+ traversal[i] = *currNode;
+ if (parent != NULL) {
+ if ((j & 1) == 1)
+ parent->branch.right = traversal + i;
+ else
+ parent->branch.left = traversal + i;
+ }
+ // Encode the path through the tree in the lookup table
+ if (traversal[i].header.isLeaf) {
+ encoding[traversal[i].leaf.key].nbits = depth;
+ encoding[traversal[i].leaf.key].bitstring = j;
+ }
+ i++;
+ }
+ }
+ }
+
+ // Encode the size of the tree.
+ // This is used by the decompressor to skip the tree.
+ dest[4] = nitems - 1;
+
+ // Encode each node in the tree.
+ for (i = 0; i < 2 * nitems - 1; i++) {
+ HuffNode_t* currNode = traversal + i;
+ if (currNode->header.isLeaf) {
+ dest[5 + i] = traversal[i].leaf.key;
+ } else {
+ dest[5 + i] = (((currNode->branch.right - traversal - i) / 2) - 1);
+ if (currNode->branch.left->header.isLeaf)
+ dest[5 + i] |= 0x80;
+ if (currNode->branch.right->header.isLeaf)
+ dest[5 + i] |= 0x40;
+ }
+ }
+
+ free(traversal);
+}
+
+static inline void write_32_le(unsigned char* dest, int* destPos, uint32_t* buff, int* buffPos) {
+ dest[*destPos] = *buff;
+ dest[*destPos + 1] = *buff >> 8;
+ dest[*destPos + 2] = *buff >> 16;
+ dest[*destPos + 3] = *buff >> 24;
+ *destPos += 4;
+ *buff = 0;
+ *buffPos = 0;
+}
+
+static inline void read_32_le(unsigned char* src, int* srcPos, uint32_t* buff) {
+ uint32_t tmp = src[*srcPos];
+ tmp |= src[*srcPos + 1] << 8;
+ tmp |= src[*srcPos + 2] << 16;
+ tmp |= src[*srcPos + 3] << 24;
+ *srcPos += 4;
+ *buff = tmp;
+}
+
+static void write_bits(unsigned char* dest, int* destPos, struct BitEncoding* encoding, int value, uint32_t* buff,
+ int* buffBits) {
+ int nbits = encoding[value].nbits;
+ uint32_t bitstring = encoding[value].bitstring;
+
+ if (*buffBits + nbits >= 32) {
+ int diff = *buffBits + nbits - 32;
+ *buff <<= nbits - diff;
+ *buff |= bitstring >> diff;
+ bitstring &= ~(1 << diff);
+ nbits = diff;
+ write_32_le(dest, destPos, buff, buffBits);
+ }
+ if (nbits != 0) {
+ *buff <<= nbits;
+ *buff |= bitstring;
+ *buffBits += nbits;
+ }
+}
+
+/*
+=======================================
+MAIN COMPRESSION/DECOMPRESSION ROUTINES
+=======================================
+ */
+
+unsigned char* HuffCompress(unsigned char* src, int srcSize, int* compressedSize_p, int bitDepth) {
+ if (srcSize <= 0)
+ goto fail;
+
+ int worstCaseDestSize = 4 + (2 << bitDepth) + srcSize * 3;
+
+ unsigned char* dest = malloc(worstCaseDestSize);
+ if (dest == NULL)
+ goto fail;
+
+ int nitems = 1 << bitDepth;
+
+ HuffNode_t* freqs = calloc(nitems, sizeof(HuffNode_t));
+ if (freqs == NULL)
+ goto fail;
+
+ struct BitEncoding* encoding = calloc(nitems, sizeof(struct BitEncoding));
+ if (encoding == NULL)
+ goto fail;
+
+ // Set up the frequencies table. This will inform the tree.
+ for (int i = 0; i < nitems; i++) {
+ freqs[i].header.isLeaf = 1;
+ freqs[i].header.value = 0;
+ freqs[i].leaf.key = i;
+ }
+
+ // Count each nybble or byte.
+ for (int i = 0; i < srcSize; i++) {
+ if (bitDepth == 8) {
+ freqs[src[i]].header.value++;
+ } else {
+ freqs[src[i] >> 4].header.value++;
+ freqs[src[i] & 0xF].header.value++;
+ }
+ }
+
+#ifdef DEBUG
+ for (int i = 0; i < nitems; i++) {
+ fprintf(stderr, "%d: %d\n", i, freqs[i].header.value);
+ }
+#endif // DEBUG
+
+ // Sort the frequency table.
+ if (!msort(freqs, nitems, sizeof(HuffNode_t), cmp_tree))
+ goto fail;
+
+ // Prune zero-frequency values.
+ for (int i = 0; i < nitems; i++) {
+ if (freqs[i].header.value != 0) {
+ if (i > 0) {
+ for (int j = i; j < nitems; j++) {
+ freqs[j - i] = freqs[j];
+ }
+ nitems -= i;
+ }
+ break;
+ }
+ // This should never happen:
+ if (i == nitems - 1)
+ goto fail;
+ }
+
+ HuffNode_t* tree = calloc(nitems * 2 - 1, sizeof(HuffNode_t));
+ if (tree == NULL)
+ goto fail;
+
+ // Iteratively collapse the two least frequent nodes.
+ HuffNode_t* endptr = freqs + nitems - 2;
+
+ for (int i = 0; i < nitems - 1; i++) {
+ HuffNode_t* left = freqs;
+ HuffNode_t* right = freqs + 1;
+ tree[i * 2] = *right;
+ tree[i * 2 + 1] = *left;
+ for (int j = 0; j < nitems - i - 2; j++)
+ freqs[j] = freqs[j + 2];
+ endptr->header.isLeaf = 0;
+ endptr->header.value = tree[i * 2].header.value + tree[i * 2 + 1].header.value;
+ endptr->branch.left = tree + i * 2;
+ endptr->branch.right = tree + i * 2 + 1;
+ endptr--;
+ if (i < nitems - 2 && !msort(freqs, nitems - i - 1, sizeof(HuffNode_t), cmp_tree))
+ goto fail;
+ }
+
+ // Write the tree breadth-first, and create the path lookup table.
+ write_tree(dest, freqs, nitems, encoding);
+
+ free(tree);
+ free(freqs);
+
+ // Encode the data itself.
+ int destPos = 4 + nitems * 2;
+ uint32_t destBuf = 0;
+ uint32_t srcBuf = 0;
+ int destBitPos = 0;
+
+ for (int srcPos = 0; srcPos < srcSize;) {
+ read_32_le(src, &srcPos, &srcBuf);
+ for (int i = 0; i < 32 / bitDepth; i++) {
+ write_bits(dest, &destPos, encoding, srcBuf & (0xFF >> (8 - bitDepth)), &destBuf, &destBitPos);
+ srcBuf >>= bitDepth;
+ }
+ }
+
+ if (destBitPos != 0) {
+ write_32_le(dest, &destPos, &destBuf, &destBitPos);
+ }
+
+ free(encoding);
+
+ // Write the header.
+ dest[0] = bitDepth | 0x20;
+ dest[1] = srcSize;
+ dest[2] = srcSize >> 8;
+ dest[3] = srcSize >> 16;
+ *compressedSize_p = (destPos + 3) & ~3;
+ return dest;
+
+fail:
+ FATAL_ERROR("Fatal error while compressing Huff file.\n");
+}
+
+unsigned char* HuffDecompress(unsigned char* src, int srcSize, int* uncompressedSize_p) {
+ if (srcSize < 4)
+ goto fail;
+
+ int bitDepth = *src & 15;
+ if (bitDepth != 4 && bitDepth != 8)
+ goto fail;
+
+ int destSize = (src[3] << 16) | (src[2] << 8) | src[1];
+
+ unsigned char* dest = malloc(destSize);
+
+ if (dest == NULL)
+ goto fail;
+
+ int treePos = 5;
+ int treeSize = (src[4] + 1) * 2;
+ int srcPos = 4 + treeSize;
+ int destPos = 0;
+ int curValPos = 0;
+ uint32_t destTmp = 0;
+ uint32_t window;
+
+ for (;;) {
+ if (srcPos >= srcSize)
+ goto fail;
+ read_32_le(src, &srcPos, &window);
+ for (int i = 0; i < 32; i++) {
+ int curBit = (window >> 31) & 1;
+ unsigned char treeView = src[treePos];
+ bool isLeaf = ((treeView << curBit) & 0x80) != 0;
+ treePos &= ~1; // align
+ treePos += ((treeView & 0x3F) + 1) * 2 + curBit;
+ if (isLeaf) {
+ destTmp >>= bitDepth;
+ destTmp |= (src[treePos] << (32 - bitDepth));
+ curValPos++;
+ if (curValPos == 32 / bitDepth) {
+ write_32_le(dest, &destPos, &destTmp, &curValPos);
+ if (destPos == destSize) {
+ *uncompressedSize_p = destSize;
+ return dest;
+ }
+ }
+ treePos = 5;
+ }
+ window <<= 1;
+ }
+ }
+
+fail:
+ FATAL_ERROR("Fatal error while decompressing Huff file.\n");
+}
diff --git a/tools/src/gbagfx/huff.h b/tools/src/gbagfx/huff.h
new file mode 100644
index 00000000..354cf7de
--- /dev/null
+++ b/tools/src/gbagfx/huff.h
@@ -0,0 +1,38 @@
+#ifndef HUFF_H
+#define HUFF_H
+
+union HuffNode;
+
+struct HuffData {
+ unsigned value : 31;
+ unsigned isLeaf : 1;
+};
+
+struct HuffLeaf {
+ struct HuffData header;
+ unsigned char key;
+};
+
+struct HuffBranch {
+ struct HuffData header;
+ union HuffNode* left;
+ union HuffNode* right;
+};
+
+union HuffNode {
+ struct HuffData header;
+ struct HuffLeaf leaf;
+ struct HuffBranch branch;
+};
+
+typedef union HuffNode HuffNode_t;
+
+struct BitEncoding {
+ unsigned long long nbits : 6;
+ unsigned long long bitstring : 58;
+};
+
+unsigned char* HuffCompress(unsigned char* buffer, int srcSize, int* compressedSize_p, int bitDepth);
+unsigned char* HuffDecompress(unsigned char* buffer, int srcSize, int* uncompressedSize_p);
+
+#endif // HUFF_H
diff --git a/tools/src/gbagfx/jasc_pal.c b/tools/src/gbagfx/jasc_pal.c
new file mode 100644
index 00000000..afaf8248
--- /dev/null
+++ b/tools/src/gbagfx/jasc_pal.c
@@ -0,0 +1,165 @@
+// Copyright (c) 2015 YamaArashi
+
+#include <stdio.h>
+#include <string.h>
+#include "global.h"
+#include "gfx.h"
+#include "util.h"
+
+// Read/write Paint Shop Pro palette files.
+
+// Format of a Paint Shop Pro palette file, line by line:
+// "JASC-PAL\r\n" (signature)
+// "0100\r\n" (version; seems to always be "0100")
+// "<NUMBER_OF_COLORS>\r\n" (number of colors in decimal)
+//
+// <NUMBER_OF_COLORS> times:
+// "<RED> <GREEN> <BLUE>\r\n" (color entry)
+//
+// Each color component is a decimal number from 0 to 255.
+// Examples:
+// Black - "0 0 0\r\n"
+// Blue - "0 0 255\r\n"
+// Brown - "150 75 0\r\n"
+
+#define MAX_LINE_LENGTH 11
+
+void ReadJascPaletteLine(FILE* fp, char* line) {
+ int c;
+ int length = 0;
+
+ for (;;) {
+ c = fgetc(fp);
+
+ if (c == '\r') {
+ c = fgetc(fp);
+
+ if (c != '\n')
+ FATAL_ERROR("CR line endings aren't supported.\n");
+
+ line[length] = 0;
+
+ return;
+ }
+
+ if (c == '\n')
+ FATAL_ERROR("LF line endings aren't supported.\n");
+
+ if (c == EOF)
+ FATAL_ERROR("Unexpected EOF. No CRLF at end of file.\n");
+
+ if (c == 0)
+ FATAL_ERROR("NUL character in file.\n");
+
+ if (length == MAX_LINE_LENGTH) {
+ line[length] = 0;
+ FATAL_ERROR("The line \"%s\" is too long.\n", line);
+ }
+
+ line[length++] = c;
+ }
+}
+
+void ReadJascPalette(char* path, struct Palette* palette) {
+ char line[MAX_LINE_LENGTH + 1];
+
+ FILE* fp = fopen(path, "rb");
+
+ if (fp == NULL)
+ FATAL_ERROR("Failed to open JASC-PAL file \"%s\" for reading.\n", path);
+
+ ReadJascPaletteLine(fp, line);
+
+ if (strcmp(line, "JASC-PAL") != 0)
+ FATAL_ERROR("Invalid JASC-PAL signature.\n");
+
+ ReadJascPaletteLine(fp, line);
+
+ if (strcmp(line, "0100") != 0)
+ FATAL_ERROR("Unsuported JASC-PAL version.\n");
+
+ ReadJascPaletteLine(fp, line);
+
+ if (!ParseNumber(line, NULL, 10, &palette->numColors))
+ FATAL_ERROR("Failed to parse number of colors.\n");
+
+ if (palette->numColors < 1 || palette->numColors > 256)
+ FATAL_ERROR("%d is an invalid number of colors. The number of colors must be in the range [1, 256].\n",
+ palette->numColors);
+
+ for (int i = 0; i < palette->numColors; i++) {
+ ReadJascPaletteLine(fp, line);
+
+ char* s = line;
+ char* end;
+
+ int red;
+ int green;
+ int blue;
+
+ if (!ParseNumber(s, &end, 10, &red))
+ FATAL_ERROR("Failed to parse red color component.\n");
+
+ s = end;
+
+ if (*s != ' ')
+ FATAL_ERROR("Expected a space after red color component.\n");
+
+ s++;
+
+ if (*s < '0' || *s > '9')
+ FATAL_ERROR("Expected only a space between red and green color components.\n");
+
+ if (!ParseNumber(s, &end, 10, &green))
+ FATAL_ERROR("Failed to parse green color component.\n");
+
+ s = end;
+
+ if (*s != ' ')
+ FATAL_ERROR("Expected a space after green color component.\n");
+
+ s++;
+
+ if (*s < '0' || *s > '9')
+ FATAL_ERROR("Expected only a space between green and blue color components.\n");
+
+ if (!ParseNumber(s, &end, 10, &blue))
+ FATAL_ERROR("Failed to parse blue color component.\n");
+
+ if (*end != 0)
+ FATAL_ERROR("Garbage after blue color component.\n");
+
+ if (red < 0 || red > 255)
+ FATAL_ERROR("Red color component (%d) is outside the range [0, 255].\n", red);
+
+ if (green < 0 || green > 255)
+ FATAL_ERROR("Green color component (%d) is outside the range [0, 255].\n", green);
+
+ if (blue < 0 || blue > 255)
+ FATAL_ERROR("Blue color component (%d) is outside the range [0, 255].\n", blue);
+
+ palette->colors[i].red = red;
+ palette->colors[i].green = green;
+ palette->colors[i].blue = blue;
+ }
+
+ if (fgetc(fp) != EOF)
+ FATAL_ERROR("Garbage after color data.\n");
+
+ fclose(fp);
+}
+
+void WriteJascPalette(char* path, struct Palette* palette) {
+ FILE* fp = fopen(path, "wb");
+
+ fputs("JASC-PAL\r\n", fp);
+ fputs("0100\r\n", fp);
+ fprintf(fp, "%d\r\n", palette->numColors);
+
+ for (int i = 0; i < palette->numColors; i++) {
+ struct Color* color = &palette->colors[i];
+ fprintf(fp, "%d %d %d\r\n", color->red, color->green, color->blue);
+ }
+
+ fclose(fp);
+}
diff --git a/tools/src/gbagfx/jasc_pal.h b/tools/src/gbagfx/jasc_pal.h
new file mode 100644
index 00000000..9a2eea80
--- /dev/null
+++ b/tools/src/gbagfx/jasc_pal.h
@@ -0,0 +1,9 @@
+// Copyright (c) 2015 YamaArashi
+
+#ifndef JASC_PAL_H
+#define JASC_PAL_H
+
+void ReadJascPalette(char* path, struct Palette* palette);
+void WriteJascPalette(char* path, struct Palette* palette);
+
+#endif // JASC_PAL_H
diff --git a/tools/src/gbagfx/link.pal b/tools/src/gbagfx/link.pal
new file mode 100644
index 00000000..f7ee0b1d
--- /dev/null
+++ b/tools/src/gbagfx/link.pal
@@ -0,0 +1,19 @@
+JASC-PAL
+0100
+16
+65 180 139
+222 41 41
+123 16 41
+16 123 41
+65 164 41
+16 255 8
+49 82 255
+189 255 164
+222 172 82
+255 255 0
+115 65 32
+164 123 65
+213 180 139
+255 230 189
+255 255 255
+0 0 0
diff --git a/tools/src/gbagfx/lz.c b/tools/src/gbagfx/lz.c
new file mode 100644
index 00000000..97168917
--- /dev/null
+++ b/tools/src/gbagfx/lz.c
@@ -0,0 +1,150 @@
+// Copyright (c) 2015 YamaArashi
+
+#include <stdlib.h>
+#include <stdbool.h>
+#include "global.h"
+#include "lz.h"
+
+unsigned char* LZDecompress(unsigned char* src, int srcSize, int* uncompressedSize) {
+ if (srcSize < 4)
+ goto fail;
+
+ int destSize = (src[3] << 16) | (src[2] << 8) | src[1];
+
+ unsigned char* dest = malloc(destSize);
+
+ if (dest == NULL)
+ goto fail;
+
+ int srcPos = 4;
+ int destPos = 0;
+
+ for (;;) {
+ if (srcPos >= srcSize)
+ goto fail;
+
+ unsigned char flags = src[srcPos++];
+
+ for (int i = 0; i < 8; i++) {
+ if (flags & 0x80) {
+ if (srcPos + 1 >= srcSize)
+ goto fail;
+
+ int blockSize = (src[srcPos] >> 4) + 3;
+ int blockDistance = (((src[srcPos] & 0xF) << 8) | src[srcPos + 1]) + 1;
+
+ srcPos += 2;
+
+ int blockPos = destPos - blockDistance;
+
+ // Some Ruby/Sapphire tilesets overflow.
+ if (destPos + blockSize > destSize) {
+ blockSize = destSize - destPos;
+ fprintf(stderr, "Destination buffer overflow.\n");
+ }
+
+ if (blockPos < 0)
+ goto fail;
+
+ for (int j = 0; j < blockSize; j++)
+ dest[destPos++] = dest[blockPos + j];
+ } else {
+ if (srcPos >= srcSize || destPos >= destSize)
+ goto fail;
+
+ dest[destPos++] = src[srcPos++];
+ }
+
+ if (destPos == destSize) {
+ *uncompressedSize = destSize;
+ return dest;
+ }
+
+ flags <<= 1;
+ }
+ }
+
+fail:
+ FATAL_ERROR("Fatal error while decompressing LZ file.\n");
+}
+
+unsigned char* LZCompress(unsigned char* src, int srcSize, int* compressedSize, const int minDistance) {
+ if (srcSize <= 0)
+ goto fail;
+
+ int worstCaseDestSize = 4 + srcSize + ((srcSize + 7) / 8);
+
+ // Round up to the next multiple of four.
+ worstCaseDestSize = (worstCaseDestSize + 3) & ~3;
+
+ unsigned char* dest = malloc(worstCaseDestSize);
+
+ if (dest == NULL)
+ goto fail;
+
+ // header
+ dest[0] = 0x10; // LZ compression type
+ dest[1] = (unsigned char)srcSize;
+ dest[2] = (unsigned char)(srcSize >> 8);
+ dest[3] = (unsigned char)(srcSize >> 16);
+
+ int srcPos = 0;
+ int destPos = 4;
+
+ for (;;) {
+ unsigned char* flags = &dest[destPos++];
+ *flags = 0;
+
+ for (int i = 0; i < 8; i++) {
+ int bestBlockDistance = 0;
+ int bestBlockSize = 0;
+ int blockDistance = minDistance;
+
+ while (blockDistance <= srcPos && blockDistance <= 0x1000) {
+ int blockStart = srcPos - blockDistance;
+ int blockSize = 0;
+
+ while (blockSize < 18 && srcPos + blockSize < srcSize &&
+ src[blockStart + blockSize] == src[srcPos + blockSize])
+ blockSize++;
+
+ if (blockSize > bestBlockSize) {
+ bestBlockDistance = blockDistance;
+ bestBlockSize = blockSize;
+
+ if (blockSize == 18)
+ break;
+ }
+
+ blockDistance++;
+ }
+
+ if (bestBlockSize >= 3) {
+ *flags |= (0x80 >> i);
+ srcPos += bestBlockSize;
+ bestBlockSize -= 3;
+ bestBlockDistance--;
+ dest[destPos++] = (bestBlockSize << 4) | ((unsigned int)bestBlockDistance >> 8);
+ dest[destPos++] = (unsigned char)bestBlockDistance;
+ } else {
+ dest[destPos++] = src[srcPos++];
+ }
+
+ if (srcPos == srcSize) {
+ // Pad to multiple of 4 bytes.
+ int remainder = destPos % 4;
+
+ if (remainder != 0) {
+ for (int i = 0; i < 4 - remainder; i++)
+ dest[destPos++] = 0;
+ }
+
+ *compressedSize = destPos;
+ return dest;
+ }
+ }
+ }
+
+fail:
+ FATAL_ERROR("Fatal error while compressing LZ file.\n");
+}
diff --git a/tools/src/gbagfx/lz.h b/tools/src/gbagfx/lz.h
new file mode 100644
index 00000000..989aa239
--- /dev/null
+++ b/tools/src/gbagfx/lz.h
@@ -0,0 +1,9 @@
+// Copyright (c) 2015 YamaArashi
+
+#ifndef LZ_H
+#define LZ_H
+
+unsigned char* LZDecompress(unsigned char* src, int srcSize, int* uncompressedSize);
+unsigned char* LZCompress(unsigned char* src, int srcSize, int* compressedSize, const int minDistance);
+
+#endif // LZ_H
diff --git a/tools/src/gbagfx/main.c b/tools/src/gbagfx/main.c
new file mode 100644
index 00000000..d353e2b4
--- /dev/null
+++ b/tools/src/gbagfx/main.c
@@ -0,0 +1,478 @@
+// Copyright (c) 2015 YamaArashi
+
+#include <stdio.h>
+#include <string.h>
+#include <stdbool.h>
+#include "global.h"
+#include "util.h"
+#include "options.h"
+#include "gfx.h"
+#include "convert_png.h"
+#include "jasc_pal.h"
+#include "lz.h"
+#include "rl.h"
+#include "font.h"
+#include "huff.h"
+
+struct CommandHandler {
+ const char* inputFileExtension;
+ const char* outputFileExtension;
+ void (*function)(char* inputPath, char* outputPath, int argc, char** argv);
+};
+
+void ConvertGbaToPng(char* inputPath, char* outputPath, struct GbaToPngOptions* options) {
+ struct Image image;
+
+ if (options->paletteFilePath != NULL) {
+ ReadGbaPalette(options->paletteFilePath, &image.palette);
+ image.hasPalette = true;
+ } else {
+ image.hasPalette = false;
+ }
+
+ ReadImage(inputPath, options->width, options->bitDepth, options->metatileWidth, options->metatileHeight, &image,
+ !image.hasPalette);
+
+ image.hasTransparency = options->hasTransparency;
+
+ WritePng(outputPath, &image);
+
+ FreeImage(&image);
+}
+
+void ConvertPngToGba(char* inputPath, char* outputPath, struct PngToGbaOptions* options) {
+ struct Image image;
+
+ image.bitDepth = options->bitDepth;
+
+ ReadPng(inputPath, &image);
+
+ WriteImage(outputPath, options->numTiles, options->bitDepth, options->metatileWidth, options->metatileHeight,
+ &image, !image.hasPalette);
+
+ FreeImage(&image);
+}
+
+void HandleGbaToPngCommand(char* inputPath, char* outputPath, int argc, char** argv) {
+ char* inputFileExtension = GetFileExtension(inputPath);
+ struct GbaToPngOptions options;
+ options.paletteFilePath = NULL;
+ options.bitDepth = inputFileExtension[0] - '0';
+ options.hasTransparency = false;
+ options.width = 1;
+ options.metatileWidth = 1;
+ options.metatileHeight = 1;
+
+ for (int i = 3; i < argc; i++) {
+ char* option = argv[i];
+
+ if (strcmp(option, "-palette") == 0) {
+ if (i + 1 >= argc)
+ FATAL_ERROR("No palette file path following \"-palette\".\n");
+
+ i++;
+
+ options.paletteFilePath = argv[i];
+ } else if (strcmp(option, "-object") == 0) {
+ options.hasTransparency = true;
+ } else if (strcmp(option, "-width") == 0) {
+ if (i + 1 >= argc)
+ FATAL_ERROR("No width following \"-width\".\n");
+
+ i++;
+
+ if (!ParseNumber(argv[i], NULL, 10, &options.width))
+ FATAL_ERROR("Failed to parse width.\n");
+
+ if (options.width < 1)
+ FATAL_ERROR("Width must be positive.\n");
+ } else if (strcmp(option, "-mwidth") == 0) {
+ if (i + 1 >= argc)
+ FATAL_ERROR("No metatile width value following \"-mwidth\".\n");
+
+ i++;
+
+ if (!ParseNumber(argv[i], NULL, 10, &options.metatileWidth))
+ FATAL_ERROR("Failed to parse metatile width.\n");
+
+ if (options.metatileWidth < 1)
+ FATAL_ERROR("metatile width must be positive.\n");
+ } else if (strcmp(option, "-mheight") == 0) {
+ if (i + 1 >= argc)
+ FATAL_ERROR("No metatile height value following \"-mheight\".\n");
+
+ i++;
+
+ if (!ParseNumber(argv[i], NULL, 10, &options.metatileHeight))
+ FATAL_ERROR("Failed to parse metatile height.\n");
+
+ if (options.metatileHeight < 1)
+ FATAL_ERROR("metatile height must be positive.\n");
+ } else {
+ FATAL_ERROR("Unrecognized option \"%s\".\n", option);
+ }
+ }
+
+ if (options.metatileWidth > options.width)
+ options.width = options.metatileWidth;
+
+ ConvertGbaToPng(inputPath, outputPath, &options);
+}
+
+void HandlePngToGbaCommand(char* inputPath, char* outputPath, int argc, char** argv) {
+ char* outputFileExtension = GetFileExtension(outputPath);
+ int bitDepth = outputFileExtension[0] - '0';
+ struct PngToGbaOptions options;
+ options.numTiles = 0;
+ options.bitDepth = bitDepth;
+ options.metatileWidth = 1;
+ options.metatileHeight = 1;
+
+ for (int i = 3; i < argc; i++) {
+ char* option = argv[i];
+
+ if (strcmp(option, "-num_tiles") == 0) {
+ if (i + 1 >= argc)
+ FATAL_ERROR("No number of tiles following \"-num_tiles\".\n");
+
+ i++;
+
+ if (!ParseNumber(argv[i], NULL, 10, &options.numTiles))
+ FATAL_ERROR("Failed to parse number of tiles.\n");
+
+ if (options.numTiles < 1)
+ FATAL_ERROR("Number of tiles must be positive.\n");
+ } else if (strcmp(option, "-mwidth") == 0) {
+ if (i + 1 >= argc)
+ FATAL_ERROR("No metatile width value following \"-mwidth\".\n");
+
+ i++;
+
+ if (!ParseNumber(argv[i], NULL, 10, &options.metatileWidth))
+ FATAL_ERROR("Failed to parse metatile width.\n");
+
+ if (options.metatileWidth < 1)
+ FATAL_ERROR("metatile width must be positive.\n");
+ } else if (strcmp(option, "-mheight") == 0) {
+ if (i + 1 >= argc)
+ FATAL_ERROR("No metatile height value following \"-mheight\".\n");
+
+ i++;
+
+ if (!ParseNumber(argv[i], NULL, 10, &options.metatileHeight))
+ FATAL_ERROR("Failed to parse metatile height.\n");
+
+ if (options.metatileHeight < 1)
+ FATAL_ERROR("metatile height must be positive.\n");
+ } else {
+ FATAL_ERROR("Unrecognized option \"%s\".\n", option);
+ }
+ }
+
+ ConvertPngToGba(inputPath, outputPath, &options);
+}
+
+void HandlePngToGbaPaletteCommand(char* inputPath, char* outputPath, int argc UNUSED, char** argv UNUSED) {
+ struct Palette palette;
+
+ ReadPngPalette(inputPath, &palette);
+ WriteGbaPalette(outputPath, &palette);
+}
+
+void HandleGbaToJascPaletteCommand(char* inputPath, char* outputPath, int argc UNUSED, char** argv UNUSED) {
+ struct Palette palette;
+
+ ReadGbaPalette(inputPath, &palette);
+ WriteJascPalette(outputPath, &palette);
+}
+
+void HandleJascToGbaPaletteCommand(char* inputPath, char* outputPath, int argc, char** argv) {
+ int numColors = 0;
+
+ for (int i = 3; i < argc; i++) {
+ char* option = argv[i];
+
+ if (strcmp(option, "-num_colors") == 0) {
+ if (i + 1 >= argc)
+ FATAL_ERROR("No number of colors following \"-num_colors\".\n");
+
+ i++;
+
+ if (!ParseNumber(argv[i], NULL, 10, &numColors))
+ FATAL_ERROR("Failed to parse number of colors.\n");
+
+ if (numColors < 1)
+ FATAL_ERROR("Number of colors must be positive.\n");
+ } else {
+ FATAL_ERROR("Unrecognized option \"%s\".\n", option);
+ }
+ }
+
+ struct Palette palette;
+
+ ReadJascPalette(inputPath, &palette);
+
+ if (numColors != 0)
+ palette.numColors = numColors;
+
+ WriteGbaPalette(outputPath, &palette);
+}
+
+void HandleLatinFontToPngCommand(char* inputPath, char* outputPath, int argc UNUSED, char** argv UNUSED) {
+ struct Image image;
+
+ ReadLatinFont(inputPath, &image);
+ WritePng(outputPath, &image);
+
+ FreeImage(&image);
+}
+
+void HandlePngToLatinFontCommand(char* inputPath, char* outputPath, int argc UNUSED, char** argv UNUSED) {
+ struct Image image;
+
+ image.bitDepth = 2;
+
+ ReadPng(inputPath, &image);
+ WriteLatinFont(outputPath, &image);
+
+ FreeImage(&image);
+}
+
+void HandleHalfwidthJapaneseFontToPngCommand(char* inputPath, char* outputPath, int argc UNUSED, char** argv UNUSED) {
+ struct Image image;
+
+ ReadHalfwidthJapaneseFont(inputPath, &image);
+ WritePng(outputPath, &image);
+
+ FreeImage(&image);
+}
+
+void HandlePngToHalfwidthJapaneseFontCommand(char* inputPath, char* outputPath, int argc UNUSED, char** argv UNUSED) {
+ struct Image image;
+
+ image.bitDepth = 2;
+
+ ReadPng(inputPath, &image);
+ WriteHalfwidthJapaneseFont(outputPath, &image);
+
+ FreeImage(&image);
+}
+
+void HandleFullwidthJapaneseFontToPngCommand(char* inputPath, char* outputPath, int argc UNUSED, char** argv UNUSED) {
+ struct Image image;
+
+ ReadFullwidthJapaneseFont(inputPath, &image);
+ WritePng(outputPath, &image);
+
+ FreeImage(&image);
+}
+
+void HandlePngToFullwidthJapaneseFontCommand(char* inputPath, char* outputPath, int argc UNUSED, char** argv UNUSED) {
+ struct Image image;
+
+ image.bitDepth = 2;
+
+ ReadPng(inputPath, &image);
+ WriteFullwidthJapaneseFont(outputPath, &image);
+
+ FreeImage(&image);
+}
+
+void HandleLZCompressCommand(char* inputPath, char* outputPath, int argc, char** argv) {
+ int overflowSize = 0;
+ int minDistance = 2; // default, for compatibility with LZ77UnCompVram()
+
+ for (int i = 3; i < argc; i++) {
+ char* option = argv[i];
+
+ if (strcmp(option, "-overflow") == 0) {
+ if (i + 1 >= argc)
+ FATAL_ERROR("No size following \"-overflow\".\n");
+
+ i++;
+
+ if (!ParseNumber(argv[i], NULL, 10, &overflowSize))
+ FATAL_ERROR("Failed to parse overflow size.\n");
+
+ if (overflowSize < 1)
+ FATAL_ERROR("Overflow size must be positive.\n");
+ } else if (strcmp(option, "-search") == 0) {
+ if (i + 1 >= argc)
+ FATAL_ERROR("No size following \"-overflow\".\n");
+
+ i++;
+
+ if (!ParseNumber(argv[i], NULL, 10, &minDistance))
+ FATAL_ERROR("Failed to parse LZ min search distance.\n");
+
+ if (minDistance < 1)
+ FATAL_ERROR("LZ min search distance must be positive.\n");
+ } else {
+ FATAL_ERROR("Unrecognized option \"%s\".\n", option);
+ }
+ }
+
+ // The overflow option allows a quirk in some of Ruby/Sapphire's tilesets
+ // to be reproduced. It works by appending a number of zeros to the data
+ // before compressing it and then amending the LZ header's size field to
+ // reflect the expected size. This will cause an overflow when decompressing
+ // the data.
+
+ int fileSize;
+ unsigned char* buffer = ReadWholeFileZeroPadded(inputPath, &fileSize, overflowSize);
+
+ int compressedSize;
+ unsigned char* compressedData = LZCompress(buffer, fileSize + overflowSize, &compressedSize, minDistance);
+
+ compressedData[1] = (unsigned char)fileSize;
+ compressedData[2] = (unsigned char)(fileSize >> 8);
+ compressedData[3] = (unsigned char)(fileSize >> 16);
+
+ free(buffer);
+
+ WriteWholeFile(outputPath, compressedData, compressedSize);
+
+ free(compressedData);
+}
+
+void HandleLZDecompressCommand(char* inputPath, char* outputPath, int argc UNUSED, char** argv UNUSED) {
+ int fileSize;
+ unsigned char* buffer = ReadWholeFile(inputPath, &fileSize);
+
+ int uncompressedSize;
+ unsigned char* uncompressedData = LZDecompress(buffer, fileSize, &uncompressedSize);
+
+ free(buffer);
+
+ WriteWholeFile(outputPath, uncompressedData, uncompressedSize);
+
+ free(uncompressedData);
+}
+
+void HandleRLCompressCommand(char* inputPath, char* outputPath, int argc UNUSED, char** argv UNUSED) {
+ int fileSize;
+ unsigned char* buffer = ReadWholeFile(inputPath, &fileSize);
+
+ int compressedSize;
+ unsigned char* compressedData = RLCompress(buffer, fileSize, &compressedSize);
+
+ free(buffer);
+
+ WriteWholeFile(outputPath, compressedData, compressedSize);
+
+ free(compressedData);
+}
+
+void HandleRLDecompressCommand(char* inputPath, char* outputPath, int argc UNUSED, char** argv UNUSED) {
+ int fileSize;
+ unsigned char* buffer = ReadWholeFile(inputPath, &fileSize);
+
+ int uncompressedSize;
+ unsigned char* uncompressedData = RLDecompress(buffer, fileSize, &uncompressedSize);
+
+ free(buffer);
+
+ WriteWholeFile(outputPath, uncompressedData, uncompressedSize);
+
+ free(uncompressedData);
+}
+
+void HandleHuffCompressCommand(char* inputPath, char* outputPath, int argc, char** argv) {
+ int fileSize;
+ int bitDepth = 4;
+
+ for (int i = 3; i < argc; i++) {
+ char* option = argv[i];
+
+ if (strcmp(option, "-depth") == 0) {
+ if (i + 1 >= argc)
+ FATAL_ERROR("No size following \"-depth\".\n");
+
+ i++;
+
+ if (!ParseNumber(argv[i], NULL, 10, &bitDepth))
+ FATAL_ERROR("Failed to parse bit depth.\n");
+
+ if (bitDepth != 4 && bitDepth != 8)
+ FATAL_ERROR("GBA only supports bit depth of 4 or 8.\n");
+ } else {
+ FATAL_ERROR("Unrecognized option \"%s\".\n", option);
+ }
+ }
+
+ unsigned char* buffer = ReadWholeFile(inputPath, &fileSize);
+
+ int compressedSize;
+ unsigned char* compressedData = HuffCompress(buffer, fileSize, &compressedSize, bitDepth);
+
+ free(buffer);
+
+ WriteWholeFile(outputPath, compressedData, compressedSize);
+
+ free(compressedData);
+}
+
+void HandleHuffDecompressCommand(char* inputPath, char* outputPath, int argc UNUSED, char** argv UNUSED) {
+ int fileSize;
+ unsigned char* buffer = ReadWholeFile(inputPath, &fileSize);
+
+ int uncompressedSize;
+ unsigned char* uncompressedData = HuffDecompress(buffer, fileSize, &uncompressedSize);
+
+ free(buffer);
+
+ WriteWholeFile(outputPath, uncompressedData, uncompressedSize);
+
+ free(uncompressedData);
+}
+
+int main(int argc, char** argv) {
+ if (argc < 3)
+ FATAL_ERROR("Usage: gbagfx INPUT_PATH OUTPUT_PATH [options...]\n");
+
+ struct CommandHandler handlers[] = { { "1bpp", "png", HandleGbaToPngCommand },
+ { "4bpp", "png", HandleGbaToPngCommand },
+ { "8bpp", "png", HandleGbaToPngCommand },
+ { "png", "1bpp", HandlePngToGbaCommand },
+ { "png", "4bpp", HandlePngToGbaCommand },
+ { "png", "8bpp", HandlePngToGbaCommand },
+ { "png", "gbapal", HandlePngToGbaPaletteCommand },
+ { "gbapal", "pal", HandleGbaToJascPaletteCommand },
+ { "pal", "gbapal", HandleJascToGbaPaletteCommand },
+ { "latfont", "png", HandleLatinFontToPngCommand },
+ { "png", "latfont", HandlePngToLatinFontCommand },
+ { "hwjpnfont", "png", HandleHalfwidthJapaneseFontToPngCommand },
+ { "png", "hwjpnfont", HandlePngToHalfwidthJapaneseFontCommand },
+ { "fwjpnfont", "png", HandleFullwidthJapaneseFontToPngCommand },
+ { "png", "fwjpnfont", HandlePngToFullwidthJapaneseFontCommand },
+ { NULL, "huff", HandleHuffCompressCommand },
+ { NULL, "lz", HandleLZCompressCommand },
+ { "huff", NULL, HandleHuffDecompressCommand },
+ { "lz", NULL, HandleLZDecompressCommand },
+ { NULL, "rl", HandleRLCompressCommand },
+ { "rl", NULL, HandleRLDecompressCommand },
+ { NULL, NULL, NULL } };
+
+ char* inputPath = argv[1];
+ char* outputPath = argv[2];
+ char* inputFileExtension = GetFileExtension(inputPath);
+ char* outputFileExtension = GetFileExtension(outputPath);
+
+ if (inputFileExtension == NULL)
+ FATAL_ERROR("Input file \"%s\" has no extension.\n", inputPath);
+
+ if (outputFileExtension == NULL)
+ FATAL_ERROR("Output file \"%s\" has no extension.\n", outputPath);
+
+ for (int i = 0; handlers[i].function != NULL; i++) {
+ if ((handlers[i].inputFileExtension == NULL ||
+ strcmp(handlers[i].inputFileExtension, inputFileExtension) == 0) &&
+ (handlers[i].outputFileExtension == NULL ||
+ strcmp(handlers[i].outputFileExtension, outputFileExtension) == 0)) {
+ handlers[i].function(inputPath, outputPath, argc, argv);
+ return 0;
+ }
+ }
+
+ FATAL_ERROR("Don't know how to convert \"%s\" to \"%s\".\n", inputPath, outputPath);
+}
diff --git a/tools/src/gbagfx/options.h b/tools/src/gbagfx/options.h
new file mode 100644
index 00000000..29536621
--- /dev/null
+++ b/tools/src/gbagfx/options.h
@@ -0,0 +1,24 @@
+// Copyright (c) 2018 huderlem
+
+#ifndef OPTIONS_H
+#define OPTIONS_H
+
+#include <stdbool.h>
+
+struct GbaToPngOptions {
+ char* paletteFilePath;
+ int bitDepth;
+ bool hasTransparency;
+ int width;
+ int metatileWidth;
+ int metatileHeight;
+};
+
+struct PngToGbaOptions {
+ int numTiles;
+ int bitDepth;
+ int metatileWidth;
+ int metatileHeight;
+};
+
+#endif // OPTIONS_H
diff --git a/tools/src/gbagfx/rl.c b/tools/src/gbagfx/rl.c
new file mode 100644
index 00000000..697b59c2
--- /dev/null
+++ b/tools/src/gbagfx/rl.c
@@ -0,0 +1,134 @@
+// Copyright (c) 2016 YamaArashi
+
+#include <stdlib.h>
+#include <stdbool.h>
+#include "global.h"
+#include "rl.h"
+
+unsigned char* RLDecompress(unsigned char* src, int srcSize, int* uncompressedSize) {
+ if (srcSize < 4)
+ goto fail;
+
+ int destSize = (src[3] << 16) | (src[2] << 8) | src[1];
+
+ unsigned char* dest = malloc(destSize);
+
+ if (dest == NULL)
+ goto fail;
+
+ int srcPos = 4;
+ int destPos = 0;
+
+ for (;;) {
+ if (srcPos >= srcSize)
+ goto fail;
+
+ unsigned char flags = src[srcPos++];
+ bool compressed = ((flags & 0x80) != 0);
+
+ if (compressed) {
+ int length = (flags & 0x7F) + 3;
+ unsigned char data = src[srcPos++];
+
+ if (destPos + length > destSize)
+ goto fail;
+
+ for (int i = 0; i < length; i++)
+ dest[destPos++] = data;
+ } else {
+ int length = (flags & 0x7F) + 1;
+
+ if (destPos + length > destSize)
+ goto fail;
+
+ for (int i = 0; i < length; i++)
+ dest[destPos++] = src[srcPos++];
+ }
+
+ if (destPos == destSize) {
+ *uncompressedSize = destSize;
+ return dest;
+ }
+ }
+
+fail:
+ FATAL_ERROR("Fatal error while decompressing RL file.\n");
+}
+
+unsigned char* RLCompress(unsigned char* src, int srcSize, int* compressedSize) {
+ if (srcSize <= 0)
+ goto fail;
+
+ int worstCaseDestSize = 4 + srcSize * 2;
+
+ // Round up to the next multiple of four.
+ worstCaseDestSize = (worstCaseDestSize + 3) & ~3;
+
+ unsigned char* dest = malloc(worstCaseDestSize);
+
+ if (dest == NULL)
+ goto fail;
+
+ // header
+ dest[0] = 0x30; // RL compression type
+ dest[1] = (unsigned char)srcSize;
+ dest[2] = (unsigned char)(srcSize >> 8);
+ dest[3] = (unsigned char)(srcSize >> 16);
+
+ int srcPos = 0;
+ int destPos = 4;
+
+ for (;;) {
+ bool compress = false;
+ int uncompressedStart = srcPos;
+ int uncompressedLength = 0;
+
+ while (srcPos < srcSize && uncompressedLength < (0x7F + 1)) {
+ compress = (srcPos + 2 < srcSize && src[srcPos] == src[srcPos + 1] && src[srcPos] == src[srcPos + 2]);
+
+ if (compress)
+ break;
+
+ srcPos++;
+ uncompressedLength++;
+ }
+
+ if (uncompressedLength > 0) {
+ dest[destPos++] = uncompressedLength - 1;
+
+ for (int i = 0; i < uncompressedLength; i++)
+ dest[destPos++] = src[uncompressedStart + i];
+ }
+
+ if (compress) {
+ unsigned char data = src[srcPos];
+ int compressedLength = 0;
+
+ while (compressedLength < (0x7F + 3) && srcPos + compressedLength < srcSize &&
+ src[srcPos + compressedLength] == data) {
+ compressedLength++;
+ }
+
+ dest[destPos++] = 0x80 | (compressedLength - 3);
+ dest[destPos++] = data;
+
+ srcPos += compressedLength;
+ }
+
+ if (srcPos == srcSize) {
+ // Pad to multiple of 4 bytes.
+ int remainder = destPos % 4;
+
+ if (remainder != 0) {
+ for (int i = 0; i < 4 - remainder; i++)
+ dest[destPos++] = 0;
+ }
+
+ *compressedSize = destPos;
+ return dest;
+ }
+ }
+
+fail:
+ FATAL_ERROR("Fatal error while compressing RL file.\n");
+}
diff --git a/tools/src/gbagfx/rl.h b/tools/src/gbagfx/rl.h
new file mode 100644
index 00000000..4a366c39
--- /dev/null
+++ b/tools/src/gbagfx/rl.h
@@ -0,0 +1,9 @@
+// Copyright (c) 2016 YamaArashi
+
+#ifndef RL_H
+#define RL_H
+
+unsigned char* RLDecompress(unsigned char* src, int srcSize, int* uncompressedSize);
+unsigned char* RLCompress(unsigned char* src, int srcSize, int* compressedSize);
+
+#endif // RL_H
diff --git a/tools/src/gbagfx/util.c b/tools/src/gbagfx/util.c
new file mode 100644
index 00000000..c2230fb2
--- /dev/null
+++ b/tools/src/gbagfx/util.c
@@ -0,0 +1,119 @@
+// Copyright (c) 2015 YamaArashi
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <stdbool.h>
+#include <errno.h>
+#include <limits.h>
+#include "global.h"
+#include "util.h"
+
+bool ParseNumber(char* s, char** end, int radix, int* intValue) {
+ char* localEnd;
+
+ if (end == NULL)
+ end = &localEnd;
+
+ errno = 0;
+
+ const long longValue = strtol(s, end, radix);
+
+ if (*end == s)
+ return false; // not a number
+
+ if ((longValue == LONG_MIN || longValue == LONG_MAX) && errno == ERANGE)
+ return false;
+
+ if (longValue > INT_MAX)
+ return false;
+
+ if (longValue < INT_MIN)
+ return false;
+
+ *intValue = (int)longValue;
+
+ return true;
+}
+
+char* GetFileExtension(char* path) {
+ char* extension = path;
+
+ while (*extension != 0)
+ extension++;
+
+ while (extension > path && *extension != '.')
+ extension--;
+
+ if (extension == path)
+ return NULL;
+
+ extension++;
+
+ if (*extension == 0)
+ return NULL;
+
+ return extension;
+}
+
+unsigned char* ReadWholeFile(char* path, int* size) {
+ FILE* fp = fopen(path, "rb");
+
+ if (fp == NULL)
+ FATAL_ERROR("Failed to open \"%s\" for reading.\n", path);
+
+ fseek(fp, 0, SEEK_END);
+
+ *size = ftell(fp);
+
+ unsigned char* buffer = malloc(*size);
+
+ if (buffer == NULL)
+ FATAL_ERROR("Failed to allocate memory for reading \"%s\".\n", path);
+
+ rewind(fp);
+
+ if (fread(buffer, *size, 1, fp) != 1)
+ FATAL_ERROR("Failed to read \"%s\".\n", path);
+
+ fclose(fp);
+
+ return buffer;
+}
+
+unsigned char* ReadWholeFileZeroPadded(char* path, int* size, int padAmount) {
+ FILE* fp = fopen(path, "rb");
+
+ if (fp == NULL)
+ FATAL_ERROR("Failed to open \"%s\" for reading.\n", path);
+
+ fseek(fp, 0, SEEK_END);
+
+ *size = ftell(fp);
+
+ unsigned char* buffer = calloc(*size + padAmount, 1);
+
+ if (buffer == NULL)
+ FATAL_ERROR("Failed to allocate memory for reading \"%s\".\n", path);
+
+ rewind(fp);
+
+ if (fread(buffer, *size, 1, fp) != 1)
+ FATAL_ERROR("Failed to read \"%s\".\n", path);
+
+ fclose(fp);
+
+ return buffer;
+}
+
+void WriteWholeFile(char* path, void* buffer, int bufferSize) {
+ FILE* fp = fopen(path, "wb");
+
+ if (fp == NULL)
+ FATAL_ERROR("Failed to open \"%s\" for writing.\n", path);
+
+ if (fwrite(buffer, bufferSize, 1, fp) != 1)
+ FATAL_ERROR("Failed to write to \"%s\".\n", path);
+
+ fclose(fp);
+}
diff --git a/tools/src/gbagfx/util.h b/tools/src/gbagfx/util.h
new file mode 100644
index 00000000..62b6d8d7
--- /dev/null
+++ b/tools/src/gbagfx/util.h
@@ -0,0 +1,14 @@
+// Copyright (c) 2015 YamaArashi
+
+#ifndef UTIL_H
+#define UTIL_H
+
+#include <stdbool.h>
+
+bool ParseNumber(char* s, char** end, int radix, int* intValue);
+char* GetFileExtension(char* path);
+unsigned char* ReadWholeFile(char* path, int* size);
+unsigned char* ReadWholeFileZeroPadded(char* path, int* size, int padAmount);
+void WriteWholeFile(char* path, void* buffer, int bufferSize);
+
+#endif // UTIL_H
diff --git a/tools/src/mid2agb/CMakeLists.txt b/tools/src/mid2agb/CMakeLists.txt
new file mode 100644
index 00000000..47643d39
--- /dev/null
+++ b/tools/src/mid2agb/CMakeLists.txt
@@ -0,0 +1,6 @@
+file(GLOB_RECURSE sources *.cpp)
+
+add_executable(mid2agb ${sources})
+target_include_directories(mid2agb PRIVATE .)
+
+install(TARGETS mid2agb RUNTIME DESTINATION bin)
diff --git a/tools/src/mid2agb/LICENSE b/tools/src/mid2agb/LICENSE
new file mode 100755
index 00000000..534d1534
--- /dev/null
+++ b/tools/src/mid2agb/LICENSE
@@ -0,0 +1,19 @@
+Copyright (c) 2016 YamaArashi
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in
+all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE.
diff --git a/tools/src/mid2agb/agb.cpp b/tools/src/mid2agb/agb.cpp
new file mode 100644
index 00000000..361f67f0
--- /dev/null
+++ b/tools/src/mid2agb/agb.cpp
@@ -0,0 +1,510 @@
+// Copyright(c) 2016 YamaArashi
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+
+#include <cstdio>
+#include <cstdarg>
+#include <cstring>
+#include <vector>
+#include "agb.h"
+#include "main.h"
+#include "midi.h"
+#include "tables.h"
+
+int g_agbTrack;
+
+static std::string s_lastOpName;
+static int s_blockNum;
+static bool s_keepLastOpName;
+static int s_lastNote;
+static int s_lastVelocity;
+static bool s_noteChanged;
+static bool s_velocityChanged;
+static bool s_inPattern;
+static int s_extendedCommand;
+static int s_memaccOp;
+static int s_memaccParam1;
+static int s_memaccParam2;
+
+void PrintAgbHeader() {
+ std::fprintf(g_outputFile, "\t.include \"sound/MPlayDef.s\"\n\n");
+ std::fprintf(g_outputFile, "\t.equ\t%s_grp, voicegroup%03u\n", g_asmLabel.c_str(), g_voiceGroup);
+ std::fprintf(g_outputFile, "\t.equ\t%s_pri, %u\n", g_asmLabel.c_str(), g_priority);
+
+ if (g_reverb >= 0)
+ std::fprintf(g_outputFile, "\t.equ\t%s_rev, reverb_set+%u\n", g_asmLabel.c_str(), g_reverb);
+ else
+ std::fprintf(g_outputFile, "\t.equ\t%s_rev, 0\n", g_asmLabel.c_str());
+
+ std::fprintf(g_outputFile, "\t.equ\t%s_mvl, %u\n", g_asmLabel.c_str(), g_masterVolume);
+ std::fprintf(g_outputFile, "\t.equ\t%s_key, %u\n", g_asmLabel.c_str(), 0);
+ std::fprintf(g_outputFile, "\t.equ\t%s_tbs, %u\n", g_asmLabel.c_str(), g_clocksPerBeat);
+ std::fprintf(g_outputFile, "\t.equ\t%s_exg, %u\n", g_asmLabel.c_str(), g_exactGateTime);
+ std::fprintf(g_outputFile, "\t.equ\t%s_cmp, %u\n", g_asmLabel.c_str(), g_compressionEnabled);
+
+ std::fprintf(g_outputFile, "\n\t.section .rodata\n");
+ std::fprintf(g_outputFile, "\t.global\t%s\n", g_asmLabel.c_str());
+
+ std::fprintf(g_outputFile, "\t.align\t2\n");
+}
+
+void ResetTrackVars() {
+ s_lastVelocity = -1;
+ s_lastNote = -1;
+ s_velocityChanged = false;
+ s_noteChanged = false;
+ s_keepLastOpName = false;
+ s_lastOpName = "";
+ s_inPattern = false;
+}
+
+void PrintWait(int wait) {
+ if (wait > 0) {
+ std::fprintf(g_outputFile, "\t.byte\tW%02d\n", wait);
+ s_velocityChanged = true;
+ s_noteChanged = true;
+ s_keepLastOpName = true;
+ }
+}
+
+void PrintOp(int wait, std::string name, const char* format, ...) {
+ std::va_list args;
+ va_start(args, format);
+ std::fprintf(g_outputFile, "\t.byte\t\t");
+
+ if (format != nullptr) {
+ if (!g_compressionEnabled || s_lastOpName != name) {
+ std::fprintf(g_outputFile, "%s, ", name.c_str());
+ s_lastOpName = name;
+ } else {
+ std::fprintf(g_outputFile, " ");
+ }
+ std::vfprintf(g_outputFile, format, args);
+ } else {
+ std::fputs(name.c_str(), g_outputFile);
+ s_lastOpName = name;
+ }
+
+ std::fprintf(g_outputFile, "\n");
+
+ va_end(args);
+
+ PrintWait(wait);
+}
+
+void PrintByte(const char* format, ...) {
+ std::va_list args;
+ va_start(args, format);
+ std::fprintf(g_outputFile, "\t.byte\t");
+ std::vfprintf(g_outputFile, format, args);
+ std::fprintf(g_outputFile, "\n");
+ s_velocityChanged = true;
+ s_noteChanged = true;
+ s_keepLastOpName = true;
+ va_end(args);
+}
+
+void PrintWord(const char* format, ...) {
+ std::va_list args;
+ va_start(args, format);
+ std::fprintf(g_outputFile, "\t .word\t");
+ std::vfprintf(g_outputFile, format, args);
+ std::fprintf(g_outputFile, "\n");
+ va_end(args);
+}
+
+void PrintNote(const Event& event) {
+ int note = event.note;
+ int velocity = g_noteVelocityLUT[event.param1];
+ int duration = -1;
+
+ if (event.param2 != -1)
+ duration = g_noteDurationLUT[event.param2];
+
+ int gateTimeParam = 0;
+
+ if (g_exactGateTime && duration != -1)
+ gateTimeParam = event.param2 - duration;
+
+ char gtpBuf[16];
+
+ if (gateTimeParam > 0)
+ std::snprintf(gtpBuf, sizeof(gtpBuf), ", gtp%u", gateTimeParam);
+ else
+ gtpBuf[0] = 0;
+
+ char opName[16];
+
+ if (duration == -1)
+ std::strcpy(opName, "TIE ");
+ else
+ std::snprintf(opName, sizeof(opName), "N%02u ", duration);
+
+ bool noteChanged = true;
+ bool velocityChanged = true;
+
+ if (g_compressionEnabled) {
+ noteChanged = (note != s_lastNote);
+ velocityChanged = (velocity != s_lastVelocity);
+ }
+
+ if (s_keepLastOpName)
+ s_keepLastOpName = false;
+ else
+ s_lastOpName = "";
+
+ if (noteChanged || velocityChanged || (gateTimeParam > 0)) {
+ s_lastNote = note;
+
+ char noteBuf[16];
+
+ if (note >= 24)
+ std::snprintf(noteBuf, sizeof(noteBuf), g_noteTable[note % 12], note / 12 - 2);
+ else
+ std::snprintf(noteBuf, sizeof(noteBuf), g_minusNoteTable[note % 12], note / -12 + 2);
+
+ char velocityBuf[16];
+
+ if (velocityChanged || (gateTimeParam > 0)) {
+ s_lastVelocity = velocity;
+ std::snprintf(velocityBuf, sizeof(velocityBuf), ", v%03u", velocity);
+ } else {
+ velocityBuf[0] = 0;
+ }
+
+ PrintOp(event.time, opName, "%s%s%s", noteBuf, velocityBuf, gtpBuf);
+ } else {
+ PrintOp(event.time, opName, 0);
+ }
+
+ s_noteChanged = noteChanged;
+ s_velocityChanged = velocityChanged;
+}
+
+void PrintEndOfTieOp(const Event& event) {
+ int note = event.note;
+ bool noteChanged = (note != s_lastNote);
+
+ if (!noteChanged || !s_noteChanged)
+ s_lastOpName = "";
+
+ if (!noteChanged && g_compressionEnabled) {
+ PrintOp(event.time, "EOT ", nullptr);
+ } else {
+ s_lastNote = note;
+ if (note >= 24)
+ PrintOp(event.time, "EOT ", g_noteTable[note % 12], note / 12 - 2);
+ else
+ PrintOp(event.time, "EOT ", g_minusNoteTable[note % 12], note / -12 + 2);
+ }
+
+ s_noteChanged = noteChanged;
+}
+
+void PrintSeqLoopLabel(const Event& event) {
+ s_blockNum = event.param1 + 1;
+ std::fprintf(g_outputFile, "%s_%u_B%u::\n", g_asmLabel.c_str(), g_agbTrack, s_blockNum);
+ PrintWait(event.time);
+ ResetTrackVars();
+}
+
+void PrintMemAcc(const Event& event) {
+ switch (s_memaccOp) {
+ case 0x00:
+ PrintByte("MEMACC, mem_set, 0x%02X, %u", s_memaccParam1, event.param2);
+ break;
+ case 0x01:
+ PrintByte("MEMACC, mem_add, 0x%02X, %u", s_memaccParam1, event.param2);
+ break;
+ case 0x02:
+ PrintByte("MEMACC, mem_sub, 0x%02X, %u", s_memaccParam1, event.param2);
+ break;
+ case 0x03:
+ PrintByte("MEMACC, mem_mem_set, 0x%02X, 0x%02X", s_memaccParam1, event.param2);
+ break;
+ case 0x04:
+ PrintByte("MEMACC, mem_mem_add, 0x%02X, 0x%02X", s_memaccParam1, event.param2);
+ break;
+ case 0x05:
+ PrintByte("MEMACC, mem_mem_sub, 0x%02X, 0x%02X", s_memaccParam1, event.param2);
+ break;
+ // TODO: everything else
+ case 0x06:
+ break;
+ case 0x07:
+ break;
+ case 0x08:
+ break;
+ case 0x09:
+ break;
+ case 0x0A:
+ break;
+ case 0x0B:
+ break;
+ case 0x0C:
+ break;
+ case 0x0D:
+ break;
+ case 0x0E:
+ break;
+ case 0x0F:
+ break;
+ case 0x10:
+ break;
+ case 0x11:
+ break;
+ case 0x46:
+ break;
+ case 0x47:
+ break;
+ case 0x48:
+ break;
+ case 0x49:
+ break;
+ case 0x4A:
+ break;
+ case 0x4B:
+ break;
+ case 0x4C:
+ break;
+ case 0x4D:
+ break;
+ case 0x4E:
+ break;
+ case 0x4F:
+ break;
+ case 0x50:
+ break;
+ case 0x51:
+ break;
+ default:
+ break;
+ }
+
+ PrintWait(event.time);
+}
+
+void PrintExtendedOp(const Event& event) {
+ // TODO: support for other extended commands
+
+ switch (s_extendedCommand) {
+ case 0x08:
+ PrintOp(event.time, "XCMD ", "xIECV , %u", event.param2);
+ break;
+ case 0x09:
+ PrintOp(event.time, "XCMD ", "xIECL , %u", event.param2);
+ break;
+ default:
+ PrintWait(event.time);
+ break;
+ }
+}
+
+void PrintControllerOp(const Event& event) {
+ switch (event.param1) {
+ case 0x01:
+ PrintOp(event.time, "MOD ", "%u", event.param2);
+ break;
+ case 0x07:
+ PrintOp(event.time, "VOL ", "%u*%s_mvl/mxv", event.param2, g_asmLabel.c_str());
+ break;
+ case 0x0A:
+ PrintOp(event.time, "PAN ", "c_v%+d", event.param2 - 64);
+ break;
+ case 0x0C:
+ case 0x10:
+ PrintMemAcc(event);
+ break;
+ case 0x0D:
+ s_memaccOp = event.param2;
+ PrintWait(event.time);
+ break;
+ case 0x0E:
+ s_memaccParam1 = event.param2;
+ PrintWait(event.time);
+ break;
+ case 0x0F:
+ s_memaccParam2 = event.param2;
+ PrintWait(event.time);
+ break;
+ case 0x11:
+ std::fprintf(g_outputFile, "%s_%u_L%u::\n", g_asmLabel.c_str(), g_agbTrack, event.param2);
+ PrintWait(event.time);
+ ResetTrackVars();
+ break;
+ case 0x14:
+ PrintOp(event.time, "BENDR ", "%u", event.param2);
+ break;
+ case 0x15:
+ PrintOp(event.time, "LFOS ", "%u", event.param2);
+ break;
+ case 0x16:
+ PrintOp(event.time, "MODT ", "%u", event.param2);
+ break;
+ case 0x18:
+ PrintOp(event.time, "TUNE ", "c_v%+d", event.param2 - 64);
+ break;
+ case 0x1A:
+ PrintOp(event.time, "LFODL ", "%u", event.param2);
+ break;
+ case 0x1D:
+ case 0x1F:
+ PrintExtendedOp(event);
+ break;
+ case 0x1E:
+ s_extendedCommand = event.param2;
+ // TODO: loop op
+ break;
+ case 0x21:
+ case 0x27:
+ PrintByte("PRIO , %u", event.param2);
+ PrintWait(event.time);
+ break;
+ default:
+ PrintWait(event.time);
+ break;
+ }
+}
+
+void PrintAgbTrack(std::vector<Event>& events) {
+ std::fprintf(g_outputFile, "\n@**************** Track %u (Midi-Chn.%u) ****************@\n\n", g_agbTrack,
+ g_midiChan + 1);
+ std::fprintf(g_outputFile, "%s_%u::\n", g_asmLabel.c_str(), g_agbTrack);
+
+ int wholeNoteCount = 0;
+ int loopEndBlockNum = 0;
+
+ ResetTrackVars();
+
+ bool foundVolBeforeNote = false;
+
+ for (const Event& event : events) {
+ if (event.type == EventType::Note)
+ break;
+
+ if (event.type == EventType::Controller && event.param1 == 0x07) {
+ foundVolBeforeNote = true;
+ break;
+ }
+ }
+
+ if (!foundVolBeforeNote)
+ PrintByte("\tVOL , 127*%s_mvl/mxv", g_asmLabel.c_str());
+
+ PrintWait(g_initialWait);
+ PrintByte("KEYSH , %s_key%+d", g_asmLabel.c_str(), 0);
+
+ for (unsigned i = 0; events[i].type != EventType::EndOfTrack; i++) {
+ const Event& event = events[i];
+
+ if (IsPatternBoundary(event.type)) {
+ if (s_inPattern)
+ PrintByte("PEND");
+ s_inPattern = false;
+ }
+
+ if (event.type == EventType::WholeNoteMark || event.type == EventType::Pattern)
+ std::fprintf(g_outputFile, "@ %03d ----------------------------------------\n", wholeNoteCount++);
+
+ switch (event.type) {
+ case EventType::Note:
+ PrintNote(event);
+ break;
+ case EventType::EndOfTie:
+ PrintEndOfTieOp(event);
+ break;
+ case EventType::Label:
+ PrintSeqLoopLabel(event);
+ break;
+ case EventType::LoopEnd:
+ PrintByte("GOTO");
+ PrintWord("%s_%u_B%u", g_asmLabel.c_str(), g_agbTrack, loopEndBlockNum);
+ // PrintSeqLoopLabel(event); // Breaks same note in EOT bgmCrenelStorm 0xDD4356
+ PrintWait(event.time); // instead just print the wait
+ break;
+ case EventType::LoopEndBegin:
+ PrintByte("GOTO");
+ PrintWord("%s_%u_B%u", g_asmLabel.c_str(), g_agbTrack, loopEndBlockNum);
+ PrintSeqLoopLabel(event);
+ loopEndBlockNum = s_blockNum;
+ break;
+ case EventType::LoopBegin:
+ PrintSeqLoopLabel(event);
+ loopEndBlockNum = s_blockNum;
+ break;
+ case EventType::WholeNoteMark:
+ if (event.param2 & 0x80000000) {
+ std::fprintf(g_outputFile, "%s_%u_%03lu::\n", g_asmLabel.c_str(), g_agbTrack,
+ (unsigned long)(event.param2 & 0x7FFFFFFF));
+ ResetTrackVars();
+ s_inPattern = true;
+ }
+ PrintWait(event.time);
+ break;
+ case EventType::Pattern:
+ PrintByte("PATT");
+ PrintWord("%s_%u_%03lu", g_asmLabel.c_str(), g_agbTrack, event.param2);
+
+ while (!IsPatternBoundary(events[i + 1].type))
+ i++;
+
+ ResetTrackVars();
+ break;
+ case EventType::Tempo:
+ PrintByte("TEMPO , %u*%s_tbs/2", 60000000 / event.param2, g_asmLabel.c_str());
+ PrintWait(event.time);
+ break;
+ case EventType::InstrumentChange:
+ PrintOp(event.time, "VOICE ", "%u", event.param1);
+ break;
+ case EventType::PitchBend:
+ PrintOp(event.time, "BEND ", "c_v%+d", event.param2 - 64);
+ break;
+ case EventType::Controller:
+ PrintControllerOp(event);
+ break;
+ default:
+ PrintWait(event.time);
+ break;
+ }
+ }
+
+ PrintByte("FINE");
+}
+
+void PrintAgbFooter() {
+ int trackCount = g_agbTrack - 1;
+
+ std::fprintf(g_outputFile, "\n@******************************************************@\n");
+ std::fprintf(g_outputFile, "\t.align\t2\n");
+ std::fprintf(g_outputFile, "\n%s:\n", g_asmLabel.c_str());
+ std::fprintf(g_outputFile, "\t.byte\t%u\t@ NumTrks\n", trackCount);
+ std::fprintf(g_outputFile, "\t.byte\t%u\t@ NumBlks\n", 0);
+ std::fprintf(g_outputFile, "\t.byte\t%s_pri\t@ Priority\n", g_asmLabel.c_str());
+ std::fprintf(g_outputFile, "\t.byte\t%s_rev\t@ Reverb.\n", g_asmLabel.c_str());
+ std::fprintf(g_outputFile, "\n");
+ std::fprintf(g_outputFile, "\t.word\t%s_grp\n", g_asmLabel.c_str());
+ std::fprintf(g_outputFile, "\n");
+
+ // track pointers
+ for (int i = 1; i <= trackCount; i++)
+ std::fprintf(g_outputFile, "\t.word\t%s_%u\n", g_asmLabel.c_str(), i);
+
+ // std::fprintf(g_outputFile, "\n\t.end\n");
+}
diff --git a/tools/src/mid2agb/agb.h b/tools/src/mid2agb/agb.h
new file mode 100755
index 00000000..7eab3c14
--- /dev/null
+++ b/tools/src/mid2agb/agb.h
@@ -0,0 +1,33 @@
+// Copyright(c) 2016 YamaArashi
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+
+#ifndef AGB_H
+#define AGB_H
+
+#include <vector>
+#include "midi.h"
+
+void PrintAgbHeader();
+void PrintAgbTrack(std::vector<Event>& events);
+void PrintAgbFooter();
+
+extern int g_agbTrack;
+
+#endif // AGB_H
diff --git a/tools/src/mid2agb/error.cpp b/tools/src/mid2agb/error.cpp
new file mode 100644
index 00000000..d824ae86
--- /dev/null
+++ b/tools/src/mid2agb/error.cpp
@@ -0,0 +1,35 @@
+// Copyright(c) 2016 YamaArashi
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+
+#include <cstdio>
+#include <cstdlib>
+#include <cstdarg>
+
+// Reports an error diagnostic and terminates the program.
+[[noreturn]] void RaiseError(const char* format, ...) {
+ const int bufferSize = 1024;
+ char buffer[bufferSize];
+ std::va_list args;
+ va_start(args, format);
+ std::vsnprintf(buffer, bufferSize, format, args);
+ std::fprintf(stderr, "error: %s\n", buffer);
+ va_end(args);
+ std::exit(1);
+}
diff --git a/tools/src/mid2agb/error.h b/tools/src/mid2agb/error.h
new file mode 100755
index 00000000..da4f0114
--- /dev/null
+++ b/tools/src/mid2agb/error.h
@@ -0,0 +1,26 @@
+// Copyright(c) 2016 YamaArashi
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+
+#ifndef ERROR_H
+#define ERROR_H
+
+[[noreturn]] void RaiseError(const char* format, ...);
+
+#endif // ERROR_H
diff --git a/tools/src/mid2agb/main.cpp b/tools/src/mid2agb/main.cpp
new file mode 100644
index 00000000..6436b5a1
--- /dev/null
+++ b/tools/src/mid2agb/main.cpp
@@ -0,0 +1,215 @@
+// Copyright(c) 2016 YamaArashi
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+
+#include <cstdio>
+#include <cstdlib>
+#include <cstring>
+#include <cctype>
+#include <cassert>
+#include <string>
+#include <set>
+#include "main.h"
+#include "error.h"
+#include "midi.h"
+#include "agb.h"
+
+FILE* g_inputFile = nullptr;
+FILE* g_outputFile = nullptr;
+
+std::string g_asmLabel;
+int g_masterVolume = 127;
+int g_voiceGroup = 0;
+int g_priority = 0;
+int g_reverb = -1;
+int g_clocksPerBeat = 1;
+bool g_exactGateTime = false;
+bool g_compressionEnabled = true;
+
+[[noreturn]] static void PrintUsage() {
+ std::printf("Usage: MID2AGB name [options]\n"
+ "\n"
+ " input_file filename(.mid) of MIDI file\n"
+ " output_file filename(.s) for AGB file (default:input_file)\n"
+ "\n"
+ "options -L??? label for assembler (default:output_file)\n"
+ " -V??? master volume (default:127)\n"
+ " -G??? voice group number (default:0)\n"
+ " -P??? priority (default:0)\n"
+ " -R??? reverb (default:off)\n"
+ " -X 48 clocks/beat (default:24 clocks/beat)\n"
+ " -E exact gate-time\n"
+ " -N no compression\n");
+ std::exit(1);
+}
+
+static std::string StripExtension(std::string s) {
+ std::size_t pos = s.find_last_of('.');
+
+ if (pos > 0 && pos != std::string::npos) {
+ s = s.substr(0, pos);
+ }
+
+ return s;
+}
+
+static std::string GetExtension(std::string s) {
+ std::size_t pos = s.find_last_of('.');
+
+ if (pos > 0 && pos != std::string::npos) {
+ return s.substr(pos + 1);
+ }
+
+ return "";
+}
+
+static std::string BaseName(std::string s) {
+ std::size_t posAfterSlash = s.find_last_of("/\\");
+
+ if (posAfterSlash == std::string::npos)
+ posAfterSlash = 0;
+ else
+ posAfterSlash++;
+
+ std::size_t dotPos = s.find_first_of('.', posAfterSlash);
+ if (dotPos > posAfterSlash && dotPos != std::string::npos)
+ s = s.substr(posAfterSlash, dotPos - posAfterSlash);
+
+ return s;
+}
+
+static const char* GetArgument(int argc, char** argv, int& index) {
+ assert(index >= 0 && index < argc);
+
+ const char* option = argv[index];
+
+ assert(option != nullptr);
+ assert(option[0] == '-');
+
+ // If there is text following the letter, return that.
+ if (std::strlen(option) >= 3)
+ return option + 2;
+
+ // Otherwise, try to get the next arg.
+ if (index + 1 < argc) {
+ index++;
+ return argv[index];
+ } else {
+ return nullptr;
+ }
+}
+
+int main(int argc, char** argv) {
+ std::string inputFilename;
+ std::string outputFilename;
+
+ for (int i = 1; i < argc; i++) {
+ const char* option = argv[i];
+
+ if (option[0] == '-' && option[1] != '\0') {
+ const char* arg;
+
+ switch (std::toupper(option[1])) {
+ case 'E':
+ g_exactGateTime = true;
+ break;
+ case 'G':
+ arg = GetArgument(argc, argv, i);
+ if (arg == nullptr)
+ PrintUsage();
+ g_voiceGroup = std::stoi(arg);
+ break;
+ case 'L':
+ arg = GetArgument(argc, argv, i);
+ if (arg == nullptr)
+ PrintUsage();
+ g_asmLabel = arg;
+ break;
+ case 'N':
+ g_compressionEnabled = false;
+ break;
+ case 'P':
+ arg = GetArgument(argc, argv, i);
+ if (arg == nullptr)
+ PrintUsage();
+ g_priority = std::stoi(arg);
+ break;
+ case 'R':
+ arg = GetArgument(argc, argv, i);
+ if (arg == nullptr)
+ PrintUsage();
+ g_reverb = std::stoi(arg);
+ break;
+ case 'V':
+ arg = GetArgument(argc, argv, i);
+ if (arg == nullptr)
+ PrintUsage();
+ g_masterVolume = std::stoi(arg);
+ break;
+ case 'X':
+ g_clocksPerBeat = 2;
+ break;
+ default:
+ PrintUsage();
+ }
+ } else {
+ if (inputFilename.empty())
+ inputFilename = argv[i];
+ else if (outputFilename.empty())
+ outputFilename = argv[i];
+ else
+ PrintUsage();
+ }
+ }
+
+ if (inputFilename.empty())
+ PrintUsage();
+
+ if (GetExtension(inputFilename) != "mid")
+ RaiseError("input filename extension is not \"mid\"");
+
+ if (outputFilename.empty())
+ outputFilename = StripExtension(inputFilename) + ".s";
+
+ if (GetExtension(outputFilename) != "s")
+ RaiseError("output filename extension is not \"s\"");
+
+ if (g_asmLabel.empty())
+ g_asmLabel = BaseName(outputFilename);
+
+ g_inputFile = std::fopen(inputFilename.c_str(), "rb");
+
+ if (g_inputFile == nullptr)
+ RaiseError("failed to open \"%s\" for reading", inputFilename.c_str());
+
+ g_outputFile = std::fopen(outputFilename.c_str(), "w");
+
+ if (g_outputFile == nullptr)
+ RaiseError("failed to open \"%s\" for writing", outputFilename.c_str());
+
+ ReadMidiFileHeader();
+ PrintAgbHeader();
+ ReadMidiTracks();
+ PrintAgbFooter();
+
+ std::fclose(g_inputFile);
+ std::fclose(g_outputFile);
+
+ return 0;
+}
diff --git a/tools/src/mid2agb/main.h b/tools/src/mid2agb/main.h
new file mode 100755
index 00000000..6e71e73f
--- /dev/null
+++ b/tools/src/mid2agb/main.h
@@ -0,0 +1,39 @@
+// Copyright(c) 2016 YamaArashi
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+
+#ifndef MAIN_H
+#define MAIN_H
+
+#include <cstdio>
+#include <string>
+
+extern FILE* g_inputFile;
+extern FILE* g_outputFile;
+
+extern std::string g_asmLabel;
+extern int g_masterVolume;
+extern int g_voiceGroup;
+extern int g_priority;
+extern int g_reverb;
+extern int g_clocksPerBeat;
+extern bool g_exactGateTime;
+extern bool g_compressionEnabled;
+
+#endif // MAIN_H
diff --git a/tools/src/mid2agb/midi.cpp b/tools/src/mid2agb/midi.cpp
new file mode 100644
index 00000000..a082fecf
--- /dev/null
+++ b/tools/src/mid2agb/midi.cpp
@@ -0,0 +1,880 @@
+// Copyright(c) 2016 YamaArashi
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+
+#include <cstdio>
+#include <cassert>
+#include <string>
+#include <vector>
+#include <algorithm>
+#include <memory>
+#include "midi.h"
+#include "main.h"
+#include "error.h"
+#include "agb.h"
+#include "tables.h"
+
+enum class MidiEventCategory {
+ Control,
+ SysEx,
+ Meta,
+ Invalid,
+};
+
+MidiFormat g_midiFormat;
+std::int_fast32_t g_midiTrackCount;
+std::int16_t g_midiTimeDiv;
+
+int g_midiChan;
+std::int32_t g_initialWait;
+
+static long s_trackDataStart;
+static std::vector<Event> s_seqEvents;
+static std::vector<Event> s_trackEvents;
+static std::int32_t s_absoluteTime;
+static int s_blockCount = 0;
+static int s_minNote;
+static int s_maxNote;
+static int s_runningStatus;
+
+void Seek(long offset) {
+ if (std::fseek(g_inputFile, offset, SEEK_SET) != 0)
+ RaiseError("failed to seek to %l", offset);
+}
+
+void Skip(long offset) {
+ if (std::fseek(g_inputFile, offset, SEEK_CUR) != 0)
+ RaiseError("failed to skip %l bytes", offset);
+}
+
+std::string ReadSignature() {
+ char signature[4];
+
+ if (std::fread(signature, 4, 1, g_inputFile) != 1)
+ RaiseError("failed to read signature");
+
+ return std::string(signature, 4);
+}
+
+std::uint32_t ReadInt8() {
+ int c = std::fgetc(g_inputFile);
+
+ if (c < 0)
+ RaiseError("unexpected EOF");
+
+ return c;
+}
+
+std::uint32_t ReadInt16() {
+ std::uint32_t val = 0;
+ val |= ReadInt8() << 8;
+ val |= ReadInt8();
+ return val;
+}
+
+std::uint32_t ReadInt24() {
+ std::uint32_t val = 0;
+ val |= ReadInt8() << 16;
+ val |= ReadInt8() << 8;
+ val |= ReadInt8();
+ return val;
+}
+
+std::uint32_t ReadInt32() {
+ std::uint32_t val = 0;
+ val |= ReadInt8() << 24;
+ val |= ReadInt8() << 16;
+ val |= ReadInt8() << 8;
+ val |= ReadInt8();
+ return val;
+}
+
+std::uint32_t ReadVLQ() {
+ std::uint32_t val = 0;
+ std::uint32_t c;
+
+ do {
+ c = ReadInt8();
+ val <<= 7;
+ val |= (c & 0x7F);
+ } while (c & 0x80);
+
+ return val;
+}
+
+void ReadMidiFileHeader() {
+ Seek(0);
+
+ if (ReadSignature() != "MThd")
+ RaiseError("MIDI file header signature didn't match \"MThd\"");
+
+ std::uint32_t headerLength = ReadInt32();
+
+ if (headerLength != 6)
+ RaiseError("MIDI file header length isn't 6");
+
+ std::uint16_t midiFormat = ReadInt16();
+
+ if (midiFormat >= 2)
+ RaiseError("unsupported MIDI format (%u)", midiFormat);
+
+ g_midiFormat = (MidiFormat)midiFormat;
+ g_midiTrackCount = ReadInt16();
+ g_midiTimeDiv = ReadInt16();
+
+ if (g_midiTimeDiv < 0)
+ RaiseError("unsupported MIDI time division (%d)", g_midiTimeDiv);
+}
+
+long ReadMidiTrackHeader(long offset) {
+ Seek(offset);
+
+ if (ReadSignature() != "MTrk")
+ RaiseError("MIDI track header signature didn't match \"MTrk\"");
+
+ long size = ReadInt32();
+
+ s_trackDataStart = std::ftell(g_inputFile);
+
+ return size + 8;
+}
+
+void StartTrack() {
+ Seek(s_trackDataStart);
+ s_absoluteTime = 0;
+ s_runningStatus = 0;
+}
+
+void SkipEventData() {
+ Skip(ReadVLQ());
+}
+
+void DetermineEventCategory(MidiEventCategory& category, int& typeChan, int& size) {
+ typeChan = ReadInt8();
+
+ if (typeChan < 0x80) {
+ // If data byte was found, use the running status.
+ ungetc(typeChan, g_inputFile);
+ typeChan = s_runningStatus;
+ }
+
+ if (typeChan == 0xFF) {
+ category = MidiEventCategory::Meta;
+ size = 0;
+ s_runningStatus = 0;
+ } else if (typeChan >= 0xF0) {
+ category = MidiEventCategory::SysEx;
+ size = 0;
+ s_runningStatus = 0;
+ } else if (typeChan >= 0x80) {
+ category = MidiEventCategory::Control;
+
+ switch (typeChan >> 4) {
+ case 0xC:
+ case 0xD:
+ size = 1;
+ break;
+ default:
+ size = 2;
+ break;
+ }
+ s_runningStatus = typeChan;
+ } else {
+ category = MidiEventCategory::Invalid;
+ }
+}
+
+void MakeBlockEvent(Event& event, EventType type) {
+ event.type = type;
+ event.param1 = s_blockCount++;
+ event.param2 = 0;
+}
+
+std::string ReadEventText() {
+ char buffer[2];
+ std::uint32_t length = ReadVLQ();
+
+ if (length <= 2) {
+ if (fread(buffer, length, 1, g_inputFile) != 1)
+ RaiseError("failed to read event text");
+ } else {
+ Skip(length);
+ length = 0;
+ }
+
+ return std::string(buffer, length);
+}
+
+bool ReadSeqEvent(Event& event) {
+ s_absoluteTime += ReadVLQ();
+ event.time = s_absoluteTime;
+
+ MidiEventCategory category;
+ int typeChan;
+ int size;
+
+ DetermineEventCategory(category, typeChan, size);
+
+ if (category == MidiEventCategory::Control) {
+ Skip(size);
+ return false;
+ }
+
+ if (category == MidiEventCategory::SysEx) {
+ SkipEventData();
+ return false;
+ }
+
+ if (category == MidiEventCategory::Invalid)
+ RaiseError("invalid event");
+
+ // meta event
+ int metaEventType = ReadInt8();
+
+ if (metaEventType >= 1 && metaEventType <= 7) {
+ // text event
+ std::string text = ReadEventText();
+
+ if (text == "[")
+ MakeBlockEvent(event, EventType::LoopBegin);
+ else if (text == "][")
+ MakeBlockEvent(event, EventType::LoopEndBegin);
+ else if (text == "]")
+ MakeBlockEvent(event, EventType::LoopEnd);
+ else if (text == ":")
+ MakeBlockEvent(event, EventType::Label);
+ else
+ return false;
+ } else {
+ switch (metaEventType) {
+ case 0x2F: // end of track
+ SkipEventData();
+ event.type = EventType::EndOfTrack;
+ event.param1 = 0;
+ event.param2 = 0;
+ break;
+ case 0x51: // tempo
+ if (ReadVLQ() != 3)
+ RaiseError("invalid tempo size");
+
+ event.type = EventType::Tempo;
+ event.param1 = 0;
+ event.param2 = ReadInt24();
+ break;
+ case 0x58: // time signature
+ {
+ if (ReadVLQ() != 4)
+ RaiseError("invalid time signature size");
+
+ int numerator = ReadInt8();
+ int denominatorExponent = ReadInt8();
+
+ if (denominatorExponent >= 16)
+ RaiseError("invalid time signature denominator");
+
+ Skip(2); // ignore other values
+
+ int clockTicks = 96 * numerator * g_clocksPerBeat;
+ int denominator = 1 << denominatorExponent;
+ int timeSig = clockTicks / denominator;
+
+ if (timeSig <= 0 || timeSig >= 0x10000)
+ RaiseError("invalid time signature");
+
+ event.type = EventType::TimeSignature;
+ event.param1 = 0;
+ event.param2 = timeSig;
+ break;
+ }
+ default:
+ SkipEventData();
+ return false;
+ }
+ }
+
+ return true;
+}
+
+void ReadSeqEvents() {
+ StartTrack();
+
+ for (;;) {
+ Event event = {};
+
+ if (ReadSeqEvent(event)) {
+ s_seqEvents.push_back(event);
+
+ if (event.type == EventType::EndOfTrack)
+ return;
+ }
+ }
+}
+
+bool CheckNoteEnd(Event& event) {
+ event.param2 += ReadVLQ();
+
+ MidiEventCategory category;
+ int typeChan;
+ int size;
+
+ DetermineEventCategory(category, typeChan, size);
+
+ if (category == MidiEventCategory::Control) {
+ int chan = typeChan & 0xF;
+
+ if (chan != g_midiChan) {
+ Skip(size);
+ return false;
+ }
+
+ switch (typeChan & 0xF0) {
+ case 0x80: // note off
+ {
+ int note = ReadInt8();
+ ReadInt8(); // ignore velocity
+ if (note == event.note)
+ return true;
+ break;
+ }
+ case 0x90: // note on
+ {
+ int note = ReadInt8();
+ int velocity = ReadInt8();
+ if (velocity == 0 && note == event.note)
+ return true;
+ break;
+ }
+ default:
+ Skip(size);
+ break;
+ }
+
+ return false;
+ }
+
+ if (category == MidiEventCategory::SysEx) {
+ SkipEventData();
+ return false;
+ }
+
+ if (category == MidiEventCategory::Meta) {
+ int metaEventType = ReadInt8();
+ SkipEventData();
+
+ if (metaEventType == 0x2F)
+ RaiseError("note doesn't end");
+
+ return false;
+ }
+
+ RaiseError("invalid event");
+}
+
+void FindNoteEnd(Event& event) {
+ // Save the current file position and running status
+ // which get modified by CheckNoteEnd.
+ long startPos = ftell(g_inputFile);
+ int savedRunningStatus = s_runningStatus;
+
+ event.param2 = 0;
+
+ while (!CheckNoteEnd(event))
+ ;
+
+ Seek(startPos);
+ s_runningStatus = savedRunningStatus;
+}
+
+bool ReadTrackEvent(Event& event) {
+ s_absoluteTime += ReadVLQ();
+ event.time = s_absoluteTime;
+
+ MidiEventCategory category;
+ int typeChan;
+ int size;
+
+ DetermineEventCategory(category, typeChan, size);
+
+ if (category == MidiEventCategory::Control) {
+ int chan = typeChan & 0xF;
+
+ if (chan != g_midiChan) {
+ Skip(size);
+ return false;
+ }
+
+ switch (typeChan & 0xF0) {
+ case 0x90: // note on
+ {
+ int note = ReadInt8();
+ int velocity = ReadInt8();
+
+ if (velocity != 0) {
+ event.type = EventType::Note;
+ event.note = note;
+ event.param1 = velocity;
+ FindNoteEnd(event);
+ if (event.param2 > 0) {
+ if (note < s_minNote)
+ s_minNote = note;
+ if (note > s_maxNote)
+ s_maxNote = note;
+ }
+ }
+ break;
+ }
+ case 0xB0: // controller event
+ event.type = EventType::Controller;
+ event.param1 = ReadInt8(); // controller index
+ event.param2 = ReadInt8(); // value
+ break;
+ case 0xC0: // instrument change
+ event.type = EventType::InstrumentChange;
+ event.param1 = ReadInt8(); // instrument
+ event.param2 = 0;
+ break;
+ case 0xE0: // pitch bend
+ event.type = EventType::PitchBend;
+ event.param1 = ReadInt8();
+ event.param2 = ReadInt8();
+ break;
+ default:
+ Skip(size);
+ return false;
+ }
+
+ return true;
+ }
+
+ if (category == MidiEventCategory::SysEx) {
+ SkipEventData();
+ return false;
+ }
+
+ if (category == MidiEventCategory::Meta) {
+ int metaEventType = ReadInt8();
+
+ if (metaEventType >= 1 && metaEventType <= 7) {
+ // text event
+ std::string text = ReadEventText();
+
+ if (text == "[")
+ MakeBlockEvent(event, EventType::LoopBegin);
+ else if (text == "][")
+ MakeBlockEvent(event, EventType::LoopEndBegin);
+ else if (text == "]")
+ MakeBlockEvent(event, EventType::LoopEnd);
+ else if (text == ":")
+ MakeBlockEvent(event, EventType::Label);
+ else
+ return false;
+ return true;
+ } else if (metaEventType == 0x2F) {
+ SkipEventData();
+ event.type = EventType::EndOfTrack;
+ event.param1 = 0;
+ event.param2 = 0;
+ return true;
+ } else {
+ SkipEventData();
+ }
+
+ return false;
+ }
+
+ RaiseError("invalid event");
+}
+
+void ReadTrackEvents() {
+ StartTrack();
+
+ s_trackEvents.clear();
+
+ s_minNote = 0xFF;
+ s_maxNote = 0;
+
+ for (;;) {
+ Event event = {};
+
+ if (ReadTrackEvent(event)) {
+ s_trackEvents.push_back(event);
+
+ if (event.type == EventType::EndOfTrack)
+ return;
+ }
+ }
+}
+
+bool EventCompare(const Event& event1, const Event& event2) {
+ if (event1.time < event2.time)
+ return true;
+
+ if (event1.time > event2.time)
+ return false;
+
+ unsigned event1Type = (unsigned)event1.type;
+ unsigned event2Type = (unsigned)event2.type;
+
+ if (event1.type == EventType::Note)
+ event1Type += event1.note;
+
+ if (event2.type == EventType::Note)
+ event2Type += event2.note;
+
+ if (event1Type < event2Type)
+ return true;
+
+ if (event1Type > event2Type)
+ return false;
+
+ if (event1.type == EventType::EndOfTie) {
+ if (event1.note < event2.note)
+ return true;
+
+ if (event1.note > event2.note)
+ return false;
+ }
+
+ return false;
+}
+
+std::unique_ptr<std::vector<Event>> MergeEvents() {
+ std::unique_ptr<std::vector<Event>> events(new std::vector<Event>());
+
+ unsigned trackEventPos = 0;
+ unsigned seqEventPos = 0;
+
+ while (s_trackEvents[trackEventPos].type != EventType::EndOfTrack &&
+ s_seqEvents[seqEventPos].type != EventType::EndOfTrack) {
+ if (EventCompare(s_trackEvents[trackEventPos], s_seqEvents[seqEventPos]))
+ events->push_back(s_trackEvents[trackEventPos++]);
+ else
+ events->push_back(s_seqEvents[seqEventPos++]);
+ }
+
+ while (s_trackEvents[trackEventPos].type != EventType::EndOfTrack)
+ events->push_back(s_trackEvents[trackEventPos++]);
+
+ while (s_seqEvents[seqEventPos].type != EventType::EndOfTrack)
+ events->push_back(s_seqEvents[seqEventPos++]);
+
+ // Push the EndOfTrack event with the larger time.
+ if (EventCompare(s_trackEvents[trackEventPos], s_seqEvents[seqEventPos]))
+ events->push_back(s_seqEvents[seqEventPos]);
+ else
+ events->push_back(s_trackEvents[trackEventPos]);
+
+ return events;
+}
+
+void ConvertTimes(std::vector<Event>& events) {
+ for (Event& event : events) {
+ event.time = (24 * g_clocksPerBeat * event.time) / g_midiTimeDiv;
+
+ if (event.type == EventType::Note) {
+ event.param1 = g_noteVelocityLUT[event.param1];
+
+ std::uint32_t duration = (24 * g_clocksPerBeat * event.param2) / g_midiTimeDiv;
+
+ if (duration == 0)
+ duration = 1;
+
+ if (!g_exactGateTime && duration < 96)
+ duration = g_noteDurationLUT[duration];
+
+ event.param2 = duration;
+ }
+ }
+}
+
+void insertAtCorrectTimeFromEnd(const std::unique_ptr<std::vector<Event>>& events, const Event& event) {
+ for (auto it = events->rbegin(); it != events->rend(); it++) {
+ if (it->time <= event.time) {
+ events->insert(it.base(), event);
+ return;
+ }
+ }
+ events->insert(events->begin(), event);
+}
+
+std::unique_ptr<std::vector<Event>> InsertTimingEvents(std::vector<Event>& inEvents) {
+ std::unique_ptr<std::vector<Event>> outEvents(new std::vector<Event>());
+
+ Event timingEvent = {};
+ timingEvent.time = 0;
+ timingEvent.type = EventType::TimeSignature;
+ timingEvent.param2 = 96 * g_clocksPerBeat;
+
+ for (const Event& event : inEvents) {
+ while (EventCompare(timingEvent, event)) {
+ outEvents->push_back(timingEvent);
+ timingEvent.time += timingEvent.param2;
+ }
+
+ if (event.type == EventType::TimeSignature) {
+ if (g_agbTrack == 1 && event.param2 != timingEvent.param2) {
+ Event originalTimingEvent = event;
+ originalTimingEvent.type = EventType::OriginalTimeSignature;
+ outEvents->push_back(originalTimingEvent);
+ }
+ timingEvent.param2 = event.param2;
+ timingEvent.time = event.time + timingEvent.param2;
+ }
+
+ outEvents->push_back(event);
+ }
+
+ return outEvents;
+}
+
+std::unique_ptr<std::vector<Event>> SplitTime(std::vector<Event>& inEvents) {
+ std::unique_ptr<std::vector<Event>> outEvents(new std::vector<Event>());
+
+ std::int32_t time = 0;
+
+ for (const Event& event : inEvents) {
+ std::int32_t diff = event.time - time;
+
+ if (diff > 96) {
+ int wholeNoteCount = (diff - 1) / 96;
+ diff -= 96 * wholeNoteCount;
+
+ for (int i = 0; i < wholeNoteCount; i++) {
+ time += 96;
+ Event timeSplitEvent = {};
+ timeSplitEvent.time = time;
+ timeSplitEvent.type = EventType::TimeSplit;
+ outEvents->push_back(timeSplitEvent);
+ }
+ }
+
+ std::int32_t lutValue = g_noteDurationLUT[diff];
+
+ if (lutValue != diff) {
+ Event timeSplitEvent = {};
+ timeSplitEvent.time = time + lutValue;
+ timeSplitEvent.type = EventType::TimeSplit;
+ outEvents->push_back(timeSplitEvent);
+ }
+
+ time = event.time;
+
+ outEvents->push_back(event);
+ }
+
+ return outEvents;
+}
+
+std::unique_ptr<std::vector<Event>> CreateTies(std::vector<Event>& inEvents) {
+ std::unique_ptr<std::vector<Event>> outEvents(new std::vector<Event>());
+
+ for (const Event& event : inEvents) {
+ if (event.type == EventType::Note && event.param2 > 96) {
+ Event tieEvent = event;
+ tieEvent.param2 = -1;
+ insertAtCorrectTimeFromEnd(outEvents, tieEvent);
+
+ Event eotEvent = {};
+ eotEvent.time = event.time + event.param2;
+ eotEvent.type = EventType::EndOfTie;
+ eotEvent.note = event.note;
+ // directly insert at the correct position, so it does not need to be sorted later.
+ // TODO rather keep eotEvent in queue until it's time is reached?
+ insertAtCorrectTimeFromEnd(outEvents, eotEvent);
+ } else {
+ insertAtCorrectTimeFromEnd(outEvents, event);
+ }
+ }
+
+ return outEvents;
+}
+
+void CalculateWaits(std::vector<Event>& events) {
+ g_initialWait = events[0].time;
+ int wholeNoteCount = 0;
+
+ for (unsigned i = 0; i < events.size() && events[i].type != EventType::EndOfTrack; i++) {
+ events[i].time = events[i + 1].time - events[i].time;
+
+ if (events[i].type == EventType::TimeSignature) {
+ events[i].type = EventType::WholeNoteMark;
+ events[i].param2 = wholeNoteCount++;
+ }
+ }
+}
+
+int CalculateCompressionScore(std::vector<Event>& events, int index) {
+ int score = 0;
+ std::uint8_t lastParam1 = events[index].param1;
+ std::uint8_t lastVelocity = 0x80u;
+ EventType lastType = events[index].type;
+ std::int32_t lastDuration = 0x80000000;
+ std::uint8_t lastNote = 0x40u;
+
+ if (events[index].time > 0)
+ score++;
+
+ for (int i = index + 1; !IsPatternBoundary(events[i].type); i++) {
+ if (events[i].type == EventType::Note) {
+ int val = 0;
+
+ if (events[i].note != lastNote) {
+ val++;
+ lastNote = events[i].note;
+ }
+
+ if (events[i].param1 != lastVelocity) {
+ val++;
+ lastVelocity = events[i].param1;
+ }
+
+ std::int32_t duration = events[i].param2;
+
+ if (g_noteDurationLUT[duration] != lastDuration) {
+ val++;
+ lastDuration = g_noteDurationLUT[duration];
+ }
+
+ if (duration != lastDuration)
+ val++;
+
+ if (val == 0)
+ val = 1;
+
+ score += val;
+ } else {
+ lastDuration = 0x80000000;
+
+ if (events[i].type == lastType) {
+ if ((lastType != EventType::Controller && (int)lastType != 0x25 && lastType != EventType::EndOfTie) ||
+ events[i].param1 == lastParam1) {
+ score++;
+ } else {
+ score += 2;
+ }
+ } else {
+ score += 2;
+ }
+ }
+
+ lastParam1 = events[i].param1;
+ lastType = events[i].type;
+
+ if (events[i].time)
+ score++;
+ }
+
+ return score;
+}
+
+bool IsCompressionMatch(std::vector<Event>& events, int index1, int index2) {
+ if (events[index1].type != events[index2].type || events[index1].note != events[index2].note ||
+ events[index1].param1 != events[index2].param1 || events[index1].time != events[index2].time)
+ return false;
+
+ index1++;
+ index2++;
+
+ do {
+ if (events[index1] != events[index2])
+ return false;
+
+ index1++;
+ index2++;
+ } while (!IsPatternBoundary(events[index1].type));
+
+ return IsPatternBoundary(events[index2].type);
+}
+
+void CompressWholeNote(std::vector<Event>& events, int index) {
+ for (int j = index + 1; events[j].type != EventType::EndOfTrack; j++) {
+ while (events[j].type != EventType::WholeNoteMark) {
+ j++;
+
+ if (events[j].type == EventType::EndOfTrack)
+ return;
+ }
+
+ if (IsCompressionMatch(events, index, j)) {
+ events[j].type = EventType::Pattern;
+ events[j].param2 = events[index].param2 & 0x7FFFFFFF;
+ events[index].param2 |= 0x80000000;
+ }
+ }
+}
+
+void Compress(std::vector<Event>& events) {
+ for (int i = 0; events[i].type != EventType::EndOfTrack; i++) {
+ while (events[i].type != EventType::WholeNoteMark) {
+ i++;
+
+ if (events[i].type == EventType::EndOfTrack)
+ return;
+ }
+
+ if (CalculateCompressionScore(events, i) >= 6) {
+ CompressWholeNote(events, i);
+ }
+ }
+}
+
+void ReadMidiTracks() {
+ long trackHeaderStart = 14;
+
+ ReadMidiTrackHeader(trackHeaderStart);
+ ReadSeqEvents();
+
+ g_agbTrack = 1;
+
+ for (int midiTrack = 0; midiTrack < g_midiTrackCount; midiTrack++) {
+ trackHeaderStart += ReadMidiTrackHeader(trackHeaderStart);
+
+ for (g_midiChan = 0; g_midiChan < 16; g_midiChan++) {
+ ReadTrackEvents();
+
+ if (s_minNote != 0xFF) {
+#ifdef DEBUG
+ printf("Track%d = Midi-Ch.%d\n", g_agbTrack, g_midiChan + 1);
+#endif
+
+ std::unique_ptr<std::vector<Event>> events(MergeEvents());
+
+ // We don't need TEMPO in anything but track 1.
+ if (g_agbTrack == 1) {
+ auto it = std::remove_if(s_seqEvents.begin(), s_seqEvents.end(),
+ [](const Event& event) { return event.type == EventType::Tempo; });
+ s_seqEvents.erase(it, s_seqEvents.end());
+ }
+
+ ConvertTimes(*events);
+ events = InsertTimingEvents(*events);
+ events = CreateTies(*events);
+ events = SplitTime(*events);
+ CalculateWaits(*events);
+
+ if (g_compressionEnabled)
+ Compress(*events);
+
+ PrintAgbTrack(*events);
+
+ g_agbTrack++;
+ }
+ }
+ }
+}
diff --git a/tools/src/mid2agb/midi.h b/tools/src/mid2agb/midi.h
new file mode 100644
index 00000000..5cfd1458
--- /dev/null
+++ b/tools/src/mid2agb/midi.h
@@ -0,0 +1,75 @@
+// Copyright(c) 2016 YamaArashi
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+
+#ifndef MIDI_H
+#define MIDI_H
+
+#include <cstdint>
+
+enum class MidiFormat { SingleTrack, MultiTrack };
+
+enum class EventType {
+ EndOfTie = 0x01,
+ Label = 0x11,
+ LoopEnd = 0x38, // To place it last if at the same time as other meta events, but before notes on the same frame
+ LoopEndBegin = 0x13,
+ LoopBegin = 0x24, // To place it last if at the same time as other meta events, but before notes on the same frame
+ OriginalTimeSignature = 0x15,
+ WholeNoteMark = 0x16,
+ Pattern = 0x17,
+ TimeSignature = 0x18,
+ Tempo = 0x19,
+ InstrumentChange = 0x21,
+ Controller = 0x22,
+ PitchBend = 0x23,
+ KeyShift = 0x31,
+ Note = 0x40,
+ TimeSplit = 0xFE,
+ EndOfTrack = 0xFF,
+};
+
+struct Event {
+ std::int32_t time;
+ EventType type;
+ std::uint8_t note;
+ std::uint8_t param1;
+ std::int32_t param2;
+
+ bool operator==(const Event& other) {
+ return (time == other.time && type == other.type && note == other.note && param1 == other.param1 &&
+ param2 == other.param2);
+ }
+
+ bool operator!=(const Event& other) {
+ return !(*this == other);
+ }
+};
+
+void ReadMidiFileHeader();
+void ReadMidiTracks();
+
+extern int g_midiChan;
+extern std::int32_t g_initialWait;
+
+inline bool IsPatternBoundary(EventType type) {
+ return type == EventType::EndOfTrack || (int)type <= 0x17;
+}
+
+#endif // MIDI_H
diff --git a/tools/src/mid2agb/tables.cpp b/tools/src/mid2agb/tables.cpp
new file mode 100644
index 00000000..f30f37c8
--- /dev/null
+++ b/tools/src/mid2agb/tables.cpp
@@ -0,0 +1,262 @@
+// Copyright(c) 2016 YamaArashi
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+
+#include "tables.h"
+
+const int g_noteDurationLUT[] = {
+ 0, // 0
+ 1, // 1
+ 2, // 2
+ 3, // 3
+ 4, // 4
+ 5, // 5
+ 6, // 6
+ 7, // 7
+ 8, // 8
+ 9, // 9
+ 10, // 10
+ 11, // 11
+ 12, // 12
+ 13, // 13
+ 14, // 14
+ 15, // 15
+ 16, // 16
+ 17, // 17
+ 18, // 18
+ 19, // 19
+ 20, // 20
+ 21, // 21
+ 22, // 22
+ 23, // 23
+ 24, // 24
+ 24, // 25
+ 24, // 26
+ 24, // 27
+ 28, // 28
+ 28, // 29
+ 30, // 30
+ 30, // 31
+ 32, // 32
+ 32, // 33
+ 32, // 34
+ 32, // 35
+ 36, // 36
+ 36, // 37
+ 36, // 38
+ 36, // 39
+ 40, // 40
+ 40, // 41
+ 42, // 42
+ 42, // 43
+ 44, // 44
+ 44, // 45
+ 44, // 46
+ 44, // 47
+ 48, // 48
+ 48, // 49
+ 48, // 50
+ 48, // 51
+ 52, // 52
+ 52, // 53
+ 54, // 54
+ 54, // 55
+ 56, // 56
+ 56, // 57
+ 56, // 58
+ 56, // 59
+ 60, // 60
+ 60, // 61
+ 60, // 62
+ 60, // 63
+ 64, // 64
+ 64, // 65
+ 66, // 66
+ 66, // 67
+ 68, // 68
+ 68, // 69
+ 68, // 70
+ 68, // 71
+ 72, // 72
+ 72, // 73
+ 72, // 74
+ 72, // 75
+ 76, // 76
+ 76, // 77
+ 78, // 78
+ 78, // 79
+ 80, // 80
+ 80, // 81
+ 80, // 82
+ 80, // 83
+ 84, // 84
+ 84, // 85
+ 84, // 86
+ 84, // 87
+ 88, // 88
+ 88, // 89
+ 90, // 90
+ 90, // 91
+ 92, // 92
+ 92, // 93
+ 92, // 94
+ 92, // 95
+ 96, // 96
+};
+
+const int g_noteVelocityLUT[] = {
+ 0, // 0
+ 4, // 1
+ 4, // 2
+ 4, // 3
+ 4, // 4
+ 8, // 5
+ 8, // 6
+ 8, // 7
+ 8, // 8
+ 12, // 9
+ 12, // 10
+ 12, // 11
+ 12, // 12
+ 16, // 13
+ 16, // 14
+ 16, // 15
+ 16, // 16
+ 20, // 17
+ 20, // 18
+ 20, // 19
+ 20, // 20
+ 24, // 21
+ 24, // 22
+ 24, // 23
+ 24, // 24
+ 28, // 25
+ 28, // 26
+ 28, // 27
+ 28, // 28
+ 32, // 29
+ 32, // 30
+ 32, // 31
+ 32, // 32
+ 36, // 33
+ 36, // 34
+ 36, // 35
+ 36, // 36
+ 40, // 37
+ 40, // 38
+ 40, // 39
+ 40, // 40
+ 44, // 41
+ 44, // 42
+ 44, // 43
+ 44, // 44
+ 48, // 45
+ 48, // 46
+ 48, // 47
+ 48, // 48
+ 52, // 49
+ 52, // 50
+ 52, // 51
+ 52, // 52
+ 56, // 53
+ 56, // 54
+ 56, // 55
+ 56, // 56
+ 60, // 57
+ 60, // 58
+ 60, // 59
+ 60, // 60
+ 64, // 61
+ 64, // 62
+ 64, // 63
+ 64, // 64
+ 68, // 65
+ 68, // 66
+ 68, // 67
+ 68, // 68
+ 72, // 69
+ 72, // 70
+ 72, // 71
+ 72, // 72
+ 76, // 73
+ 76, // 74
+ 76, // 75
+ 76, // 76
+ 80, // 77
+ 80, // 78
+ 80, // 79
+ 80, // 80
+ 84, // 81
+ 84, // 82
+ 84, // 83
+ 84, // 84
+ 88, // 85
+ 88, // 86
+ 88, // 87
+ 88, // 88
+ 92, // 89
+ 92, // 90
+ 92, // 91
+ 92, // 92
+ 96, // 93
+ 96, // 94
+ 96, // 95
+ 96, // 96
+ 100, // 97
+ 100, // 98
+ 100, // 99
+ 100, // 100
+ 104, // 101
+ 104, // 102
+ 104, // 103
+ 104, // 104
+ 108, // 105
+ 108, // 106
+ 108, // 107
+ 108, // 108
+ 112, // 109
+ 112, // 110
+ 112, // 111
+ 112, // 112
+ 116, // 113
+ 116, // 114
+ 116, // 115
+ 116, // 116
+ 120, // 117
+ 120, // 118
+ 120, // 119
+ 120, // 120
+ 124, // 121
+ 124, // 122
+ 124, // 123
+ 124, // 124
+ 127, // 125
+ 127, // 126
+ 127, // 127
+};
+
+const char* g_noteTable[] = {
+ "Cn%01u ", "Cs%01u ", "Dn%01u ", "Ds%01u ", "En%01u ", "Fn%01u ",
+ "Fs%01u ", "Gn%01u ", "Gs%01u ", "An%01u ", "As%01u ", "Bn%01u ",
+};
+
+const char* g_minusNoteTable[] = {
+ "CnM%01u", "CsM%01u", "DnM%01u", "DsM%01u", "EnM%01u", "FnM%01u",
+ "FsM%01u", "GnM%01u", "GsM%01u", "AnM%01u", "AsM%01u", "BnM%01u",
+};
diff --git a/tools/src/mid2agb/tables.h b/tools/src/mid2agb/tables.h
new file mode 100755
index 00000000..da3c9072
--- /dev/null
+++ b/tools/src/mid2agb/tables.h
@@ -0,0 +1,29 @@
+// Copyright(c) 2016 YamaArashi
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+
+#ifndef TABLES_H
+#define TABLES_H
+
+extern const int g_noteDurationLUT[];
+extern const int g_noteVelocityLUT[];
+extern const char* g_noteTable[];
+extern const char* g_minusNoteTable[];
+
+#endif // TABLES_H
diff --git a/tools/src/preproc/CMakeLists.txt b/tools/src/preproc/CMakeLists.txt
new file mode 100644
index 00000000..f2674fae
--- /dev/null
+++ b/tools/src/preproc/CMakeLists.txt
@@ -0,0 +1,6 @@
+file(GLOB_RECURSE sources *.cpp)
+
+add_executable(preproc ${sources})
+target_include_directories(preproc PRIVATE .)
+
+install(TARGETS preproc RUNTIME DESTINATION bin)
diff --git a/tools/src/preproc/LICENSE b/tools/src/preproc/LICENSE
new file mode 100755
index 00000000..534d1534
--- /dev/null
+++ b/tools/src/preproc/LICENSE
@@ -0,0 +1,19 @@
+Copyright (c) 2016 YamaArashi
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in
+all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE.
diff --git a/tools/src/preproc/asm_file.cpp b/tools/src/preproc/asm_file.cpp
new file mode 100644
index 00000000..30d6758f
--- /dev/null
+++ b/tools/src/preproc/asm_file.cpp
@@ -0,0 +1,439 @@
+// Copyright(c) 2016 YamaArashi
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+
+#include <cstdio>
+#include <cstdarg>
+#include <stdexcept>
+#include "preproc.h"
+#include "asm_file.h"
+#include "char_util.h"
+#include "utf8.h"
+#include "string_parser.h"
+
+AsmFile::AsmFile(std::string filename) : m_filename(filename) {
+ FILE* fp = std::fopen(filename.c_str(), "rb");
+
+ if (fp == NULL) {
+ // The include might be an asset.
+ fp = std::fopen(("build/" + g_buildName + "/assets/" + filename).c_str(), "rb");
+
+ if (fp == NULL)
+ FATAL_ERROR("Failed to open \"%s\" for reading.\n", filename.c_str());
+ }
+
+ std::fseek(fp, 0, SEEK_END);
+
+ m_size = std::ftell(fp);
+
+ if (m_size < 0)
+ FATAL_ERROR("File size of \"%s\" is less than zero.\n", filename.c_str());
+
+ m_buffer = new char[m_size + 1];
+
+ std::rewind(fp);
+
+ if (std::fread(m_buffer, m_size, 1, fp) != 1)
+ FATAL_ERROR("Failed to read \"%s\".\n", filename.c_str());
+
+ m_buffer[m_size] = 0;
+
+ std::fclose(fp);
+
+ m_pos = 0;
+ m_lineNum = 1;
+ m_lineStart = 0;
+
+ RemoveComments();
+}
+
+AsmFile::AsmFile(AsmFile&& other) : m_filename(std::move(other.m_filename)) {
+ m_buffer = other.m_buffer;
+ m_pos = other.m_pos;
+ m_size = other.m_size;
+ m_lineNum = other.m_lineNum;
+ m_lineStart = other.m_lineStart;
+
+ other.m_buffer = nullptr;
+}
+
+AsmFile::~AsmFile() {
+ delete[] m_buffer;
+}
+
+// Removes comments to simplify further processing.
+// It stops upon encountering a null character,
+// which may or may not be the end of file marker.
+// If it's not, the error will be caught later.
+void AsmFile::RemoveComments() {
+ long pos = 0;
+ char stringChar = 0;
+
+ for (;;) {
+ if (m_buffer[pos] == 0)
+ return;
+
+ if (stringChar != 0) {
+ if (m_buffer[pos] == '\\' && m_buffer[pos + 1] == stringChar) {
+ pos += 2;
+ } else {
+ if (m_buffer[pos] == stringChar)
+ stringChar = 0;
+ pos++;
+ }
+ } else if (m_buffer[pos] == '@' && (pos == 0 || m_buffer[pos - 1] != '\\')) {
+ while (m_buffer[pos] != '\n' && m_buffer[pos] != 0)
+ m_buffer[pos++] = ' ';
+ } else if (m_buffer[pos] == '/' && m_buffer[pos + 1] == '*') {
+ m_buffer[pos++] = ' ';
+ m_buffer[pos++] = ' ';
+
+ for (;;) {
+ if (m_buffer[pos] == 0)
+ return;
+
+ if (m_buffer[pos] == '*' && m_buffer[pos + 1] == '/') {
+ m_buffer[pos++] = ' ';
+ m_buffer[pos++] = ' ';
+ break;
+ } else {
+ if (m_buffer[pos] != '\n')
+ m_buffer[pos] = ' ';
+ pos++;
+ }
+ }
+ } else {
+ if (m_buffer[pos] == '"' || m_buffer[pos] == '\'')
+ stringChar = m_buffer[pos];
+ pos++;
+ }
+ }
+}
+
+// Checks if we're at a particular directive and if so, consumes it.
+// Returns whether the directive was found.
+bool AsmFile::CheckForDirective(std::string name) {
+ long i;
+ long length = static_cast<long>(name.length());
+
+ for (i = 0; i < length && m_pos + i < m_size; i++)
+ if (name[i] != m_buffer[m_pos + i])
+ return false;
+
+ if (i < length)
+ return false;
+
+ m_pos += length;
+
+ return true;
+}
+
+// Checks if we're at a known directive and if so, consumes it.
+// Returns which directive was found.
+Directive AsmFile::GetDirective() {
+ SkipWhitespace();
+
+ if (CheckForDirective(".include"))
+ return Directive::Include;
+ else if (CheckForDirective(".string"))
+ return Directive::String;
+ else if (CheckForDirective(".braille"))
+ return Directive::Braille;
+ else
+ return Directive::Unknown;
+}
+
+// Checks if we're at label that ends with '::'.
+// Returns the name if so and an empty string if not.
+std::string AsmFile::GetGlobalLabel() {
+ long start = m_pos;
+ long pos = m_pos;
+
+ if (IsIdentifierStartingChar(m_buffer[pos])) {
+ pos++;
+
+ while (IsIdentifierChar(m_buffer[pos]))
+ pos++;
+ }
+
+ if (m_buffer[pos] == ':' && m_buffer[pos + 1] == ':') {
+ m_pos = pos + 2;
+ ExpectEmptyRestOfLine();
+ return std::string(&m_buffer[start], pos - start);
+ }
+
+ return std::string();
+}
+
+// Skips tabs and spaces.
+void AsmFile::SkipWhitespace() {
+ while (m_buffer[m_pos] == '\t' || m_buffer[m_pos] == ' ')
+ m_pos++;
+}
+
+// Reads include path.
+std::string AsmFile::ReadPath() {
+ SkipWhitespace();
+
+ if (m_buffer[m_pos] != '"')
+ RaiseError("expected file path");
+
+ m_pos++;
+
+ int length = 0;
+ long startPos = m_pos;
+
+ while (m_buffer[m_pos] != '"') {
+ unsigned char c = m_buffer[m_pos++];
+
+ if (c == 0) {
+ if (m_pos >= m_size)
+ RaiseError("unexpected EOF in include string");
+ else
+ RaiseError("unexpected null character in include string");
+ }
+
+ if (!IsAsciiPrintable(c))
+ RaiseError("unexpected character '\\x%02X' in include string", c);
+
+ // Don't bother allowing any escape sequences.
+ if (c == '\\') {
+ c = m_buffer[m_pos];
+ RaiseError("unexpected escape '\\%c' in include string", c);
+ }
+
+ length++;
+
+ if (length > kMaxPath)
+ RaiseError("path is too long");
+ }
+
+ m_pos++; // Go past the right quote.
+
+ ExpectEmptyRestOfLine();
+
+ return std::string(&m_buffer[startPos], length);
+}
+
+// Reads a charmap string.
+int AsmFile::ReadString(unsigned char* s) {
+ SkipWhitespace();
+
+ int length;
+ StringParser stringParser(m_buffer, m_size);
+
+ try {
+ m_pos += stringParser.ParseString(m_pos, s, length);
+ } catch (std::runtime_error& e) { RaiseError(e.what()); }
+
+ SkipWhitespace();
+
+ if (ConsumeComma()) {
+ SkipWhitespace();
+ int padLength = ReadPadLength();
+
+ while (length < padLength) {
+ s[length++] = 0;
+ }
+ }
+
+ ExpectEmptyRestOfLine();
+
+ return length;
+}
+
+int AsmFile::ReadBraille(unsigned char* s) {
+ static std::map<char, unsigned char> encoding = {
+ { 'A', 0x01 }, { 'B', 0x05 }, { 'C', 0x03 }, { 'D', 0x0B }, { 'E', 0x09 }, { 'F', 0x07 },
+ { 'G', 0x0F }, { 'H', 0x0D }, { 'I', 0x06 }, { 'J', 0x0E }, { 'K', 0x11 }, { 'L', 0x15 },
+ { 'M', 0x13 }, { 'N', 0x1B }, { 'O', 0x19 }, { 'P', 0x17 }, { 'Q', 0x1F }, { 'R', 0x1D },
+ { 'S', 0x16 }, { 'T', 0x1E }, { 'U', 0x31 }, { 'V', 0x35 }, { 'W', 0x2E }, { 'X', 0x33 },
+ { 'Y', 0x3B }, { 'Z', 0x39 }, { ' ', 0x00 }, { ',', 0x04 }, { '.', 0x2C }, { '$', 0xFF },
+ };
+
+ SkipWhitespace();
+
+ int length = 0;
+
+ if (m_buffer[m_pos] != '"')
+ RaiseError("expected braille string literal");
+
+ m_pos++;
+
+ while (m_buffer[m_pos] != '"') {
+ if (length == kMaxStringLength)
+ RaiseError("mapped string longer than %d bytes", kMaxStringLength);
+
+ if (m_buffer[m_pos] == '\\' && m_buffer[m_pos + 1] == 'n') {
+ s[length++] = 0xFE;
+ m_pos += 2;
+ } else {
+ char c = m_buffer[m_pos];
+
+ if (encoding.count(c) == 0) {
+ if (IsAsciiPrintable(c))
+ RaiseError("character '%c' not valid in braille string", m_buffer[m_pos]);
+ else
+ RaiseError("character '\\x%02X' not valid in braille string", m_buffer[m_pos]);
+ }
+
+ s[length++] = encoding[c];
+ m_pos++;
+ }
+ }
+
+ m_pos++; // Go past the right quote.
+
+ ExpectEmptyRestOfLine();
+
+ return length;
+}
+
+// If we're at a comma, consumes it.
+// Returns whether a comma was found.
+bool AsmFile::ConsumeComma() {
+ if (m_buffer[m_pos] == ',') {
+ m_pos++;
+ return true;
+ }
+
+ return false;
+}
+
+// Converts digit character to numerical value.
+static int ConvertDigit(char c, int radix) {
+ int digit;
+
+ if (c >= '0' && c <= '9')
+ digit = c - '0';
+ else if (c >= 'A' && c <= 'F')
+ digit = 10 + c - 'A';
+ else if (c >= 'a' && c <= 'f')
+ digit = 10 + c - 'a';
+ else
+ return -1;
+
+ return (digit < radix) ? digit : -1;
+}
+
+// Reads an integer. If the integer is greater than maxValue, it returns -1.
+int AsmFile::ReadPadLength() {
+ if (!IsAsciiDigit(m_buffer[m_pos]))
+ RaiseError("expected integer");
+
+ int radix = 10;
+
+ if (m_buffer[m_pos] == '0' && m_buffer[m_pos + 1] == 'x') {
+ radix = 16;
+ m_pos += 2;
+ }
+
+ unsigned n = 0;
+ int digit;
+
+ while ((digit = ConvertDigit(m_buffer[m_pos], radix)) != -1) {
+ n = n * radix + digit;
+
+ if (n > kMaxStringLength)
+ RaiseError("pad length greater than maximum length (%d)", kMaxStringLength);
+
+ m_pos++;
+ }
+
+ return n;
+}
+
+// Outputs the current line and moves to the next one.
+void AsmFile::OutputLine() {
+ while (m_buffer[m_pos] != '\n' && m_buffer[m_pos] != 0)
+ m_pos++;
+
+ if (m_buffer[m_pos] == 0) {
+ if (m_pos >= m_size) {
+ RaiseWarning("file doesn't end with newline");
+ puts(&m_buffer[m_lineStart]);
+ } else {
+ RaiseError("unexpected null character");
+ }
+ } else {
+ m_buffer[m_pos] = 0;
+ puts(&m_buffer[m_lineStart]);
+ m_buffer[m_pos] = '\n';
+ m_pos++;
+ m_lineStart = m_pos;
+ m_lineNum++;
+ }
+}
+
+// Asserts that the rest of the line is empty and moves to the next one.
+void AsmFile::ExpectEmptyRestOfLine() {
+ SkipWhitespace();
+
+ if (m_buffer[m_pos] == 0) {
+ if (m_pos >= m_size)
+ RaiseWarning("file doesn't end with newline");
+ else
+ RaiseError("unexpected null character");
+ } else if (m_buffer[m_pos] == '\n') {
+ m_pos++;
+ m_lineStart = m_pos;
+ m_lineNum++;
+ } else if (m_buffer[m_pos] == '\r') {
+ RaiseError("only Unix-style LF newlines are supported");
+ } else {
+ RaiseError("junk at end of line");
+ }
+}
+
+// Checks if we're at the end of the file.
+bool AsmFile::IsAtEnd() {
+ return (m_pos >= m_size);
+}
+
+// Output the current location to set gas's logical file and line numbers.
+void AsmFile::OutputLocation() {
+ std::printf("# %ld \"%s\"\n", m_lineNum, m_filename.c_str());
+}
+
+// Reports a diagnostic message.
+void AsmFile::ReportDiagnostic(const char* type, const char* format, std::va_list args) {
+ const int bufferSize = 1024;
+ char buffer[bufferSize];
+ std::vsnprintf(buffer, bufferSize, format, args);
+ std::fprintf(stderr, "%s:%ld: %s: %s\n", m_filename.c_str(), m_lineNum, type, buffer);
+}
+
+#define DO_REPORT(type) \
+ do { \
+ std::va_list args; \
+ va_start(args, format); \
+ ReportDiagnostic(type, format, args); \
+ va_end(args); \
+ } while (0)
+
+// Reports an error diagnostic and terminates the program.
+void AsmFile::RaiseError(const char* format, ...) {
+ DO_REPORT("error");
+ std::exit(1);
+}
+
+// Reports a warning diagnostic.
+void AsmFile::RaiseWarning(const char* format, ...) {
+ DO_REPORT("warning");
+}
diff --git a/tools/src/preproc/asm_file.h b/tools/src/preproc/asm_file.h
new file mode 100644
index 00000000..dc8838fe
--- /dev/null
+++ b/tools/src/preproc/asm_file.h
@@ -0,0 +1,65 @@
+// Copyright(c) 2016 YamaArashi
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+
+#ifndef ASM_FILE_H
+#define ASM_FILE_H
+
+#include <cstdarg>
+#include <cstdint>
+#include <string>
+#include "preproc.h"
+
+enum class Directive { Include, String, Braille, Unknown };
+
+class AsmFile {
+ public:
+ AsmFile(std::string filename);
+ AsmFile(AsmFile&& other);
+ AsmFile(const AsmFile&) = delete;
+ ~AsmFile();
+ Directive GetDirective();
+ std::string GetGlobalLabel();
+ std::string ReadPath();
+ int ReadString(unsigned char* s);
+ int ReadBraille(unsigned char* s);
+ bool IsAtEnd();
+ void OutputLine();
+ void OutputLocation();
+
+ private:
+ char* m_buffer;
+ long m_pos;
+ long m_size;
+ long m_lineNum;
+ long m_lineStart;
+ std::string m_filename;
+
+ bool ConsumeComma();
+ int ReadPadLength();
+ void RemoveComments();
+ bool CheckForDirective(std::string name);
+ void SkipWhitespace();
+ void ExpectEmptyRestOfLine();
+ void ReportDiagnostic(const char* type, const char* format, std::va_list args);
+ void RaiseError(const char* format, ...);
+ void RaiseWarning(const char* format, ...);
+};
+
+#endif // ASM_FILE_H
diff --git a/tools/src/preproc/c_file.cpp b/tools/src/preproc/c_file.cpp
new file mode 100644
index 00000000..5ef403b3
--- /dev/null
+++ b/tools/src/preproc/c_file.cpp
@@ -0,0 +1,377 @@
+// Copyright(c) 2016 YamaArashi
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+
+#include <cstdio>
+#include <cstdarg>
+#include <string>
+#include <memory>
+#include <stdexcept>
+#include "preproc.h"
+#include "c_file.h"
+#include "char_util.h"
+#include "utf8.h"
+#include "string_parser.h"
+
+CFile::CFile(std::string filename) : m_filename(filename) {
+ FILE* fp = std::fopen(filename.c_str(), "rb");
+
+ if (fp == NULL)
+ FATAL_ERROR("Failed to open \"%s\" for reading.\n", filename.c_str());
+
+ std::fseek(fp, 0, SEEK_END);
+
+ m_size = std::ftell(fp);
+
+ if (m_size < 0)
+ FATAL_ERROR("File size of \"%s\" is less than zero.\n", filename.c_str());
+
+ m_buffer = new char[m_size + 1];
+
+ std::rewind(fp);
+
+ if (std::fread(m_buffer, m_size, 1, fp) != 1)
+ FATAL_ERROR("Failed to read \"%s\".\n", filename.c_str());
+
+ m_buffer[m_size] = 0;
+
+ std::fclose(fp);
+
+ m_pos = 0;
+ m_lineNum = 1;
+}
+
+CFile::CFile(CFile&& other) : m_filename(std::move(other.m_filename)) {
+ m_buffer = other.m_buffer;
+ m_pos = other.m_pos;
+ m_size = other.m_size;
+ m_lineNum = other.m_lineNum;
+
+ other.m_buffer = nullptr;
+}
+
+CFile::~CFile() {
+ delete[] m_buffer;
+}
+
+void CFile::Preproc() {
+ char stringChar = 0;
+
+ while (m_pos < m_size) {
+ if (stringChar) {
+ if (m_buffer[m_pos] == stringChar) {
+ std::putchar(stringChar);
+ m_pos++;
+ stringChar = 0;
+ } else if (m_buffer[m_pos] == '\\' && m_buffer[m_pos + 1] == stringChar) {
+ std::putchar('\\');
+ std::putchar(stringChar);
+ m_pos += 2;
+ } else {
+ if (m_buffer[m_pos] == '\n')
+ m_lineNum++;
+ std::putchar(m_buffer[m_pos]);
+ m_pos++;
+ }
+ } else {
+ TryConvertString();
+ TryConvertIncbin();
+
+ if (m_pos >= m_size)
+ break;
+
+ char c = m_buffer[m_pos++];
+
+ std::putchar(c);
+
+ if (c == '\n')
+ m_lineNum++;
+ else if (c == '"')
+ stringChar = '"';
+ else if (c == '\'')
+ stringChar = '\'';
+ }
+ }
+}
+
+bool CFile::ConsumeHorizontalWhitespace() {
+ if (m_buffer[m_pos] == '\t' || m_buffer[m_pos] == ' ') {
+ m_pos++;
+ return true;
+ }
+
+ return false;
+}
+
+bool CFile::ConsumeNewline() {
+ if (m_buffer[m_pos] == '\r' && m_buffer[m_pos + 1] == '\n') {
+ m_pos += 2;
+ m_lineNum++;
+ std::putchar('\n');
+ return true;
+ }
+
+ if (m_buffer[m_pos] == '\n') {
+ m_pos++;
+ m_lineNum++;
+ std::putchar('\n');
+ return true;
+ }
+
+ return false;
+}
+
+void CFile::SkipWhitespace() {
+ while (ConsumeHorizontalWhitespace() || ConsumeNewline())
+ ;
+}
+
+void CFile::TryConvertString() {
+ long oldPos = m_pos;
+ long oldLineNum = m_lineNum;
+ bool noTerminator = false;
+
+ if (m_buffer[m_pos] != '_' || (m_pos > 0 && IsIdentifierChar(m_buffer[m_pos - 1])))
+ return;
+
+ m_pos++;
+
+ if (m_buffer[m_pos] == '_') {
+ noTerminator = true;
+ m_pos++;
+ }
+
+ SkipWhitespace();
+
+ if (m_buffer[m_pos] != '(') {
+ m_pos = oldPos;
+ m_lineNum = oldLineNum;
+ return;
+ }
+
+ m_pos++;
+
+ SkipWhitespace();
+
+ std::printf("{ ");
+
+ while (1) {
+ SkipWhitespace();
+
+ if (m_buffer[m_pos] == '"') {
+ unsigned char s[kMaxStringLength];
+ int length;
+ StringParser stringParser(m_buffer, m_size);
+
+ try {
+ m_pos += stringParser.ParseString(m_pos, s, length);
+ } catch (std::runtime_error& e) { RaiseError(e.what()); }
+
+ for (int i = 0; i < length; i++)
+ printf("0x%02X, ", s[i]);
+ } else if (m_buffer[m_pos] == ')') {
+ m_pos++;
+ break;
+ } else {
+ if (m_pos >= m_size)
+ RaiseError("unexpected EOF");
+ if (IsAsciiPrintable(m_buffer[m_pos]))
+ RaiseError("unexpected character '%c'", m_buffer[m_pos]);
+ else
+ RaiseError("unexpected character '\\x%02X'", m_buffer[m_pos]);
+ }
+ }
+
+ if (noTerminator)
+ std::printf(" }");
+ else
+ std::printf("0xFF }");
+}
+
+bool CFile::CheckIdentifier(const std::string& ident) {
+ unsigned int i;
+
+ for (i = 0; i < ident.length() && m_pos + i < (unsigned)m_size; i++)
+ if (ident[i] != m_buffer[m_pos + i])
+ return false;
+
+ return (i == ident.length());
+}
+
+std::unique_ptr<unsigned char[]> CFile::ReadWholeFile(const std::string& path, int& size) {
+ FILE* fp = std::fopen(path.c_str(), "rb");
+
+ if (fp == nullptr)
+ RaiseError("Failed to open \"%s\" for reading.\n", path.c_str());
+
+ std::fseek(fp, 0, SEEK_END);
+
+ size = std::ftell(fp);
+
+ std::unique_ptr<unsigned char[]> buffer = std::unique_ptr<unsigned char[]>(new unsigned char[size]);
+
+ std::rewind(fp);
+
+ if (std::fread(buffer.get(), size, 1, fp) != 1)
+ RaiseError("Failed to read \"%s\".\n", path.c_str());
+
+ std::fclose(fp);
+
+ return buffer;
+}
+
+int ExtractData(const std::unique_ptr<unsigned char[]>& buffer, int offset, int size) {
+ switch (size) {
+ case 1:
+ return buffer[offset];
+ case 2:
+ return (buffer[offset + 1] << 8) | buffer[offset];
+ case 4:
+ return (buffer[offset + 3] << 24) | (buffer[offset + 2] << 16) | (buffer[offset + 1] << 8) | buffer[offset];
+ default:
+ FATAL_ERROR("Invalid size passed to ExtractData.\n");
+ }
+}
+
+void CFile::TryConvertIncbin() {
+ std::string idents[6] = { "INCBIN_S8", "INCBIN_U8", "INCBIN_S16", "INCBIN_U16", "INCBIN_S32", "INCBIN_U32" };
+ int incbinType = -1;
+
+ for (int i = 0; i < 6; i++) {
+ if (CheckIdentifier(idents[i])) {
+ incbinType = i;
+ break;
+ }
+ }
+
+ if (incbinType == -1)
+ return;
+
+ int size = 1 << (incbinType / 2);
+ bool isSigned = ((incbinType % 2) == 0);
+
+ long oldPos = m_pos;
+ long oldLineNum = m_lineNum;
+
+ m_pos += idents[incbinType].length();
+
+ SkipWhitespace();
+
+ if (m_buffer[m_pos] != '(') {
+ m_pos = oldPos;
+ m_lineNum = oldLineNum;
+ return;
+ }
+
+ m_pos++;
+
+ std::printf("{");
+
+ while (true) {
+ SkipWhitespace();
+
+ if (m_buffer[m_pos] != '"')
+ RaiseError("expected double quote");
+
+ m_pos++;
+
+ int startPos = m_pos;
+
+ while (m_buffer[m_pos] != '"') {
+ if (m_buffer[m_pos] == 0) {
+ if (m_pos >= m_size)
+ RaiseError("unexpected EOF in path string");
+ else
+ RaiseError("unexpected null character in path string");
+ }
+
+ if (m_buffer[m_pos] == '\r' || m_buffer[m_pos] == '\n')
+ RaiseError("unexpected end of line character in path string");
+
+ if (m_buffer[m_pos] == '\\')
+ RaiseError("unexpected escape in path string");
+
+ m_pos++;
+ }
+
+ std::string path(&m_buffer[startPos], m_pos - startPos);
+
+ m_pos++;
+
+ int fileSize;
+ std::unique_ptr<unsigned char[]> buffer = ReadWholeFile(path, fileSize);
+
+ if ((fileSize % size) != 0)
+ RaiseError("Size %d doesn't evenly divide file size %d.\n", size, fileSize);
+
+ int count = fileSize / size;
+ int offset = 0;
+
+ for (int i = 0; i < count; i++) {
+ int data = ExtractData(buffer, offset, size);
+ offset += size;
+
+ if (isSigned)
+ std::printf("%d,", data);
+ else
+ std::printf("%uu,", data);
+ }
+
+ SkipWhitespace();
+
+ if (m_buffer[m_pos] != ',')
+ break;
+
+ m_pos++;
+ }
+
+ if (m_buffer[m_pos] != ')')
+ RaiseError("expected ')'");
+
+ m_pos++;
+
+ std::printf("}");
+}
+
+// Reports a diagnostic message.
+void CFile::ReportDiagnostic(const char* type, const char* format, std::va_list args) {
+ const int bufferSize = 1024;
+ char buffer[bufferSize];
+ std::vsnprintf(buffer, bufferSize, format, args);
+ std::fprintf(stderr, "%s:%ld: %s: %s\n", m_filename.c_str(), m_lineNum, type, buffer);
+}
+
+#define DO_REPORT(type) \
+ do { \
+ std::va_list args; \
+ va_start(args, format); \
+ ReportDiagnostic(type, format, args); \
+ va_end(args); \
+ } while (0)
+
+// Reports an error diagnostic and terminates the program.
+void CFile::RaiseError(const char* format, ...) {
+ DO_REPORT("error");
+ std::exit(1);
+}
+
+// Reports a warning diagnostic.
+void CFile::RaiseWarning(const char* format, ...) {
+ DO_REPORT("warning");
+}
diff --git a/tools/src/preproc/c_file.h b/tools/src/preproc/c_file.h
new file mode 100644
index 00000000..2d565179
--- /dev/null
+++ b/tools/src/preproc/c_file.h
@@ -0,0 +1,57 @@
+// Copyright(c) 2016 YamaArashi
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+
+#ifndef C_FILE_H
+#define C_FILE_H
+
+#include <cstdarg>
+#include <cstdint>
+#include <string>
+#include <memory>
+#include "preproc.h"
+
+class CFile {
+ public:
+ CFile(std::string filename);
+ CFile(CFile&& other);
+ CFile(const CFile&) = delete;
+ ~CFile();
+ void Preproc();
+
+ private:
+ char* m_buffer;
+ long m_pos;
+ long m_size;
+ long m_lineNum;
+ std::string m_filename;
+
+ bool ConsumeHorizontalWhitespace();
+ bool ConsumeNewline();
+ void SkipWhitespace();
+ void TryConvertString();
+ std::unique_ptr<unsigned char[]> ReadWholeFile(const std::string& path, int& size);
+ bool CheckIdentifier(const std::string& ident);
+ void TryConvertIncbin();
+ void ReportDiagnostic(const char* type, const char* format, std::va_list args);
+ void RaiseError(const char* format, ...);
+ void RaiseWarning(const char* format, ...);
+};
+
+#endif // C_FILE_H
diff --git a/tools/src/preproc/char_util.h b/tools/src/preproc/char_util.h
new file mode 100644
index 00000000..23b7db79
--- /dev/null
+++ b/tools/src/preproc/char_util.h
@@ -0,0 +1,61 @@
+// Copyright(c) 2016 YamaArashi
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+
+#ifndef CHAR_UTIL_H
+#define CHAR_UTIL_H
+
+#include <cstdint>
+#include <cassert>
+
+inline bool IsAscii(unsigned char c) {
+ return (c < 128);
+}
+
+inline bool IsAsciiAlpha(unsigned char c) {
+ return ((c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z'));
+}
+
+inline bool IsAsciiDigit(unsigned char c) {
+ return (c >= '0' && c <= '9');
+}
+
+inline bool IsAsciiHexDigit(unsigned char c) {
+ return ((c >= '0' && c <= '9') || (c >= 'a' && c <= 'f') || (c >= 'A' && c <= 'F'));
+}
+
+inline bool IsAsciiAlphanum(unsigned char c) {
+ return (IsAsciiAlpha(c) || IsAsciiDigit(c));
+}
+
+inline bool IsAsciiPrintable(unsigned char c) {
+ return (c >= ' ' && c <= '~');
+}
+
+// Returns whether the character can start a C identifier or the identifier of a "{FOO}" constant in strings.
+inline bool IsIdentifierStartingChar(unsigned char c) {
+ return IsAsciiAlpha(c) || c == '_';
+}
+
+// Returns whether the character can be used in a C identifier or the identifier of a "{FOO}" constant in strings.
+inline bool IsIdentifierChar(unsigned char c) {
+ return IsAsciiAlphanum(c) || c == '_';
+}
+
+#endif // CHAR_UTIL_H
diff --git a/tools/src/preproc/charmap.cpp b/tools/src/preproc/charmap.cpp
new file mode 100644
index 00000000..d86eb755
--- /dev/null
+++ b/tools/src/preproc/charmap.cpp
@@ -0,0 +1,356 @@
+// Copyright(c) 2016 YamaArashi
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+
+#include <cstdio>
+#include <cstdint>
+#include <cstdarg>
+#include "preproc.h"
+#include "charmap.h"
+#include "char_util.h"
+#include "utf8.h"
+
+enum LhsType { Char, Escape, Constant, None };
+
+struct Lhs {
+ LhsType type;
+ std::string name;
+ std::int32_t code;
+};
+
+class CharmapReader {
+ public:
+ CharmapReader(std::string filename);
+ CharmapReader(const CharmapReader&) = delete;
+ ~CharmapReader();
+ Lhs ReadLhs();
+ void ExpectEqualsSign();
+ std::string ReadSequence();
+ void ExpectEmptyRestOfLine();
+ void RaiseError(const char* format, ...);
+
+ private:
+ char* m_buffer;
+ long m_pos;
+ long m_size;
+ long m_lineNum;
+ std::string m_filename;
+
+ void RemoveComments();
+ std::string ReadConstant();
+ void SkipWhitespace();
+};
+
+CharmapReader::CharmapReader(std::string filename) : m_filename(filename) {
+ if (filename == "") {
+ m_pos = 0;
+ m_size = 0;
+ m_buffer = new char[1]{};
+ return;
+ }
+
+ FILE* fp = std::fopen(filename.c_str(), "rb");
+
+ if (fp == NULL)
+ FATAL_ERROR("Failed to open \"%s\" for reading.\n", filename.c_str());
+
+ std::fseek(fp, 0, SEEK_END);
+
+ m_size = std::ftell(fp);
+
+ if (m_size < 0)
+ FATAL_ERROR("File size of \"%s\" is less than zero.\n", filename.c_str());
+
+ m_buffer = new char[m_size + 1];
+
+ std::rewind(fp);
+
+ if (std::fread(m_buffer, m_size, 1, fp) != 1)
+ FATAL_ERROR("Failed to read \"%s\".\n", filename.c_str());
+
+ m_buffer[m_size] = 0;
+
+ std::fclose(fp);
+
+ m_pos = 0;
+ m_lineNum = 1;
+
+ RemoveComments();
+}
+
+CharmapReader::~CharmapReader() {
+ delete[] m_buffer;
+}
+
+Lhs CharmapReader::ReadLhs() {
+ Lhs lhs;
+
+ for (;;) {
+ SkipWhitespace();
+
+ if (m_buffer[m_pos] == '\n') {
+ m_pos++;
+ m_lineNum++;
+ } else {
+ break;
+ }
+ }
+
+ if (m_buffer[m_pos] == '\'') {
+ m_pos++;
+
+ bool isEscape = (m_buffer[m_pos] == '\\');
+
+ if (isEscape) {
+ m_pos++;
+ }
+
+ unsigned char c = m_buffer[m_pos];
+
+ if (c == 0) {
+ if (m_pos >= m_size)
+ RaiseError("unexpected EOF in UTF-8 character literal");
+ else
+ RaiseError("unexpected null character in UTF-8 character literal");
+ }
+
+ if (IsAscii(c) && !IsAsciiPrintable(c))
+ RaiseError("unexpected character U+%X in UTF-8 character literal", c);
+
+ UnicodeChar unicodeChar = DecodeUtf8(&m_buffer[m_pos]);
+ std::int32_t code = unicodeChar.code;
+
+ if (code == -1)
+ RaiseError("invalid encoding in UTF-8 character literal");
+
+ m_pos += unicodeChar.encodingLength;
+
+ if (m_buffer[m_pos] != '\'')
+ RaiseError("unterminated character literal");
+
+ m_pos++;
+
+ lhs.code = code;
+
+ if (isEscape) {
+ if (code >= 128)
+ RaiseError("escapes using non-ASCII characters are invalid");
+
+ switch (code) {
+ case '\'':
+ lhs.type = LhsType::Char;
+ break;
+ case '\\':
+ lhs.type = LhsType::Char;
+ case '"':
+ RaiseError("cannot escape double quote");
+ break;
+ default:
+ lhs.type = LhsType::Escape;
+ }
+ } else {
+ if (code == '\'')
+ RaiseError("empty character literal");
+
+ lhs.type = LhsType::Char;
+ }
+ } else if (IsIdentifierStartingChar(m_buffer[m_pos])) {
+ lhs.type = LhsType::Constant;
+ lhs.name = ReadConstant();
+ } else if (m_buffer[m_pos] == '\r') {
+ RaiseError("only Unix-style LF newlines are supported");
+ } else if (m_buffer[m_pos] == 0) {
+ if (m_pos < m_size)
+ RaiseError("unexpected null character");
+ lhs.type = LhsType::None;
+ } else {
+ RaiseError("junk at start of line");
+ }
+
+ return lhs;
+}
+
+void CharmapReader::ExpectEqualsSign() {
+ SkipWhitespace();
+
+ if (m_buffer[m_pos] != '=')
+ RaiseError("expected equals sign");
+
+ m_pos++;
+}
+
+static unsigned int ConvertHexDigit(char c) {
+ unsigned int digit = 0;
+
+ if (c >= '0' && c <= '9')
+ digit = c - '0';
+ else if (c >= 'A' && c <= 'F')
+ digit = 10 + c - 'A';
+ else if (c >= 'a' && c <= 'f')
+ digit = 10 + c - 'a';
+
+ return digit;
+}
+
+std::string CharmapReader::ReadSequence() {
+ SkipWhitespace();
+
+ long startPos = m_pos;
+
+ unsigned int length = 0;
+
+ while (IsAsciiHexDigit(m_buffer[m_pos]) && IsAsciiHexDigit(m_buffer[m_pos + 1])) {
+ m_pos += 2;
+ length++;
+
+ if (length > kMaxCharmapSequenceLength)
+ RaiseError("byte sequence too long (max is %lu bytes)", kMaxCharmapSequenceLength);
+
+ SkipWhitespace();
+ }
+
+ if (IsAsciiHexDigit(m_buffer[m_pos]))
+ RaiseError("each byte must have 2 hex digits");
+
+ if (length == 0)
+ RaiseError("expected byte sequence");
+
+ std::string sequence;
+ sequence.reserve(length);
+
+ m_pos = startPos;
+
+ for (unsigned int i = 0; i < length; i++) {
+ unsigned int digit1 = ConvertHexDigit(m_buffer[m_pos]);
+ unsigned int digit2 = ConvertHexDigit(m_buffer[m_pos + 1]);
+ unsigned char byte = digit1 * 16 + digit2;
+ sequence += byte;
+
+ m_pos += 2;
+ SkipWhitespace();
+ }
+
+ return sequence;
+}
+
+void CharmapReader::ExpectEmptyRestOfLine() {
+ SkipWhitespace();
+
+ if (m_buffer[m_pos] == 0) {
+ if (m_pos < m_size)
+ RaiseError("unexpected null character");
+ } else if (m_buffer[m_pos] == '\n') {
+ m_pos++;
+ m_lineNum++;
+ } else if (m_buffer[m_pos] == '\r') {
+ RaiseError("only Unix-style LF newlines are supported");
+ } else {
+ RaiseError("junk at end of line");
+ }
+}
+
+void CharmapReader::RaiseError(const char* format, ...) {
+ const int bufferSize = 1024;
+ char buffer[bufferSize];
+
+ std::va_list args;
+ va_start(args, format);
+ std::vsnprintf(buffer, bufferSize, format, args);
+ va_end(args);
+
+ std::fprintf(stderr, "%s:%ld: error: %s\n", m_filename.c_str(), m_lineNum, buffer);
+
+ std::exit(1);
+}
+
+void CharmapReader::RemoveComments() {
+ long pos = 0;
+ bool inString = false;
+
+ for (;;) {
+ if (m_buffer[pos] == 0)
+ return;
+
+ if (inString) {
+ if (m_buffer[pos] == '\\' && m_buffer[pos + 1] == '\'') {
+ pos += 2;
+ } else {
+ if (m_buffer[pos] == '\'')
+ inString = false;
+ pos++;
+ }
+ } else if (m_buffer[pos] == '@') {
+ while (m_buffer[pos] != '\n' && m_buffer[pos] != 0)
+ m_buffer[pos++] = ' ';
+ } else {
+ if (m_buffer[pos] == '\'')
+ inString = true;
+ pos++;
+ }
+ }
+}
+
+std::string CharmapReader::ReadConstant() {
+ long startPos = m_pos;
+
+ while (IsIdentifierChar(m_buffer[m_pos]))
+ m_pos++;
+
+ return std::string(&m_buffer[startPos], m_pos - startPos);
+}
+
+void CharmapReader::SkipWhitespace() {
+ while (m_buffer[m_pos] == '\t' || m_buffer[m_pos] == ' ')
+ m_pos++;
+}
+
+Charmap::Charmap(std::string filename) {
+ CharmapReader reader(filename);
+
+ for (;;) {
+ Lhs lhs = reader.ReadLhs();
+
+ if (lhs.type == LhsType::None)
+ return;
+
+ reader.ExpectEqualsSign();
+
+ std::string sequence = reader.ReadSequence();
+
+ switch (lhs.type) {
+ case LhsType::Char:
+ if (m_chars.find(lhs.code) != m_chars.end())
+ reader.RaiseError("redefining char");
+ m_chars[lhs.code] = sequence;
+ break;
+ case LhsType::Escape:
+ if (m_escapes[lhs.code].length() != 0)
+ reader.RaiseError("redefining escape");
+ m_escapes[lhs.code] = sequence;
+ break;
+ case LhsType::Constant:
+ if (m_constants.find(lhs.name) != m_constants.end())
+ reader.RaiseError("redefining constant");
+ m_constants[lhs.name] = sequence;
+ break;
+ }
+
+ reader.ExpectEmptyRestOfLine();
+ }
+}
diff --git a/tools/src/preproc/charmap.h b/tools/src/preproc/charmap.h
new file mode 100644
index 00000000..f4eaca84
--- /dev/null
+++ b/tools/src/preproc/charmap.h
@@ -0,0 +1,61 @@
+// Copyright(c) 2016 YamaArashi
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+
+#ifndef CHARMAP_H
+#define CHARMAP_H
+
+#include <cstdint>
+#include <string>
+#include <map>
+#include <vector>
+
+class Charmap {
+ public:
+ Charmap(std::string filename);
+
+ std::string Char(std::int32_t code) {
+ auto it = m_chars.find(code);
+
+ if (it == m_chars.end())
+ return std::string();
+
+ return it->second;
+ }
+
+ std::string Escape(unsigned char code) {
+ return m_escapes[code];
+ }
+
+ std::string Constant(std::string identifier) {
+ auto it = m_constants.find(identifier);
+
+ if (it == m_constants.end())
+ return std::string();
+
+ return it->second;
+ }
+
+ private:
+ std::map<std::int32_t, std::string> m_chars;
+ std::string m_escapes[128];
+ std::map<std::string, std::string> m_constants;
+};
+
+#endif // CHARMAP_H
diff --git a/tools/src/preproc/preproc.cpp b/tools/src/preproc/preproc.cpp
new file mode 100644
index 00000000..2aada83d
--- /dev/null
+++ b/tools/src/preproc/preproc.cpp
@@ -0,0 +1,141 @@
+// Copyright(c) 2016 YamaArashi
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+
+#include <string>
+#include <stack>
+#include "preproc.h"
+#include "asm_file.h"
+#include "c_file.h"
+#include "charmap.h"
+
+Charmap* g_charmap;
+std::string g_buildName;
+
+void PrintAsmBytes(unsigned char* s, int length) {
+ if (length > 0) {
+ std::printf("\t.byte ");
+ for (int i = 0; i < length; i++) {
+ std::printf("0x%02X", s[i]);
+
+ if (i < length - 1)
+ std::printf(", ");
+ }
+ std::putchar('\n');
+ }
+}
+
+void PreprocAsmFile(std::string filename) {
+ std::stack<AsmFile> stack;
+
+ stack.push(AsmFile(filename));
+
+ for (;;) {
+ while (stack.top().IsAtEnd()) {
+ stack.pop();
+
+ if (stack.empty())
+ return;
+ else
+ stack.top().OutputLocation();
+ }
+
+ Directive directive = stack.top().GetDirective();
+
+ switch (directive) {
+ case Directive::Include:
+ stack.push(AsmFile(stack.top().ReadPath()));
+ stack.top().OutputLocation();
+ break;
+ case Directive::String: {
+ unsigned char s[kMaxStringLength];
+ int length = stack.top().ReadString(s);
+ PrintAsmBytes(s, length);
+ break;
+ }
+ case Directive::Braille: {
+ unsigned char s[kMaxStringLength];
+ int length = stack.top().ReadBraille(s);
+ PrintAsmBytes(s, length);
+ break;
+ }
+ case Directive::Unknown: {
+ std::string globalLabel = stack.top().GetGlobalLabel();
+
+ if (globalLabel.length() != 0) {
+ const char* s = globalLabel.c_str();
+ std::printf("%s: ; .global %s\n", s, s);
+ } else {
+ stack.top().OutputLine();
+ }
+
+ break;
+ }
+ }
+ }
+}
+
+void PreprocCFile(std::string filename) {
+ CFile cFile(filename);
+ cFile.Preproc();
+}
+
+char* GetFileExtension(char* filename) {
+ char* extension = filename;
+
+ while (*extension != 0)
+ extension++;
+
+ while (extension > filename && *extension != '.')
+ extension--;
+
+ if (extension == filename)
+ return nullptr;
+
+ extension++;
+
+ if (*extension == 0)
+ return nullptr;
+
+ return extension;
+}
+
+int main(int argc, char** argv) {
+ if (argc != 4 && argc != 3) {
+ std::fprintf(stderr, "Usage: %s BUILD_NAME SRC_FILE CHARMAP_FILE", argv[0]);
+ return 1;
+ }
+
+ g_buildName = std::string(argv[1]);
+ g_charmap = new Charmap(argc == 4 ? argv[3] : "");
+
+ char* extension = GetFileExtension(argv[2]);
+
+ if (!extension)
+ FATAL_ERROR("\"%s\" has no file extension.\n", argv[2]);
+
+ if ((extension[0] == 's') && extension[1] == 0)
+ PreprocAsmFile(argv[2]);
+ else if ((extension[0] == 'c' || extension[0] == 'i') && extension[1] == 0)
+ PreprocCFile(argv[2]);
+ else
+ FATAL_ERROR("\"%s\" has an unknown file extension of \"%s\".\n", argv[2], extension);
+
+ return 0;
+}
diff --git a/tools/src/preproc/preproc.h b/tools/src/preproc/preproc.h
new file mode 100644
index 00000000..f896b6ae
--- /dev/null
+++ b/tools/src/preproc/preproc.h
@@ -0,0 +1,53 @@
+// Copyright(c) 2016 YamaArashi
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+
+#ifndef PREPROC_H
+#define PREPROC_H
+
+#include <cstdio>
+#include <cstdlib>
+#include "charmap.h"
+
+#ifdef _MSC_VER
+
+#define FATAL_ERROR(format, ...) \
+ do { \
+ std::fprintf(stderr, format, __VA_ARGS__); \
+ std::exit(1); \
+ } while (0)
+
+#else
+
+#define FATAL_ERROR(format, ...) \
+ do { \
+ std::fprintf(stderr, format, ##__VA_ARGS__); \
+ std::exit(1); \
+ } while (0)
+
+#endif // _MSC_VER
+
+const int kMaxPath = 256;
+const int kMaxStringLength = 1024;
+const unsigned long kMaxCharmapSequenceLength = 16;
+
+extern Charmap* g_charmap;
+extern std::string g_buildName;
+
+#endif // PREPROC_H
diff --git a/tools/src/preproc/string_parser.cpp b/tools/src/preproc/string_parser.cpp
new file mode 100644
index 00000000..7c3cc1c7
--- /dev/null
+++ b/tools/src/preproc/string_parser.cpp
@@ -0,0 +1,314 @@
+// Copyright(c) 2016 YamaArashi
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+
+#include <cstdio>
+#include <cstdarg>
+#include <stdexcept>
+#include "preproc.h"
+#include "string_parser.h"
+#include "char_util.h"
+#include "utf8.h"
+
+// Reads a charmap char or escape sequence.
+std::string StringParser::ReadCharOrEscape() {
+ std::string sequence;
+
+ bool isEscape = (m_buffer[m_pos] == '\\');
+
+ if (isEscape) {
+ m_pos++;
+
+ if (m_buffer[m_pos] == '"') {
+ sequence = g_charmap->Char('"');
+
+ if (sequence.length() == 0)
+ RaiseError("no mapping exists for double quote");
+
+ return sequence;
+ } else if (m_buffer[m_pos] == '\\') {
+ sequence = g_charmap->Char('\\');
+
+ if (sequence.length() == 0)
+ RaiseError("no mapping exists for backslash");
+
+ return sequence;
+ }
+ }
+
+ unsigned char c = m_buffer[m_pos];
+
+ if (c == 0) {
+ if (m_pos >= m_size)
+ RaiseError("unexpected EOF in UTF-8 string");
+ else
+ RaiseError("unexpected null character in UTF-8 string");
+ }
+
+ if (IsAscii(c) && !IsAsciiPrintable(c))
+ RaiseError("unexpected character U+%X in UTF-8 string", c);
+
+ UnicodeChar unicodeChar = DecodeUtf8(&m_buffer[m_pos]);
+ m_pos += unicodeChar.encodingLength;
+ std::int32_t code = unicodeChar.code;
+
+ if (code == -1)
+ RaiseError("invalid encoding in UTF-8 string");
+
+ if (isEscape && code >= 128)
+ RaiseError("escapes using non-ASCII characters are invalid");
+
+ sequence = isEscape ? g_charmap->Escape(code) : g_charmap->Char(code);
+
+ if (sequence.length() == 0) {
+ if (isEscape)
+ RaiseError("unknown escape '\\%c'", code);
+ else
+ RaiseError("unknown character U+%X", code);
+ }
+
+ return sequence;
+}
+
+// Reads a charmap constant, i.e. "{FOO}".
+std::string StringParser::ReadBracketedConstants() {
+ std::string totalSequence;
+
+ m_pos++; // Assume we're on the left curly bracket.
+
+ while (m_buffer[m_pos] != '}') {
+ SkipWhitespace();
+
+ if (IsIdentifierStartingChar(m_buffer[m_pos])) {
+ long startPos = m_pos;
+
+ m_pos++;
+
+ while (IsIdentifierChar(m_buffer[m_pos]))
+ m_pos++;
+
+ std::string sequence = g_charmap->Constant(std::string(&m_buffer[startPos], m_pos - startPos));
+
+ if (sequence.length() == 0) {
+ m_buffer[m_pos] = 0;
+ RaiseError("unknown constant '%s'", &m_buffer[startPos]);
+ }
+
+ totalSequence += sequence;
+ } else if (IsAsciiDigit(m_buffer[m_pos])) {
+ Integer integer = ReadInteger();
+
+ switch (integer.size) {
+ case 1:
+ totalSequence += (unsigned char)integer.value;
+ break;
+ case 2:
+ totalSequence += (unsigned char)integer.value;
+ totalSequence += (unsigned char)(integer.value >> 8);
+ break;
+ case 4:
+ totalSequence += (unsigned char)integer.value;
+ totalSequence += (unsigned char)(integer.value >> 8);
+ totalSequence += (unsigned char)(integer.value >> 16);
+ totalSequence += (unsigned char)(integer.value >> 24);
+ break;
+ }
+ } else if (m_buffer[m_pos] == 0) {
+ if (m_pos >= m_size)
+ RaiseError("unexpected EOF after left curly bracket");
+ else
+ RaiseError("unexpected null character within curly brackets");
+ } else {
+ if (IsAsciiPrintable(m_buffer[m_pos]))
+ RaiseError("unexpected character '%c' within curly brackets", m_buffer[m_pos]);
+ else
+ RaiseError("unexpected character '\\x%02X' within curly brackets", m_buffer[m_pos]);
+ }
+ }
+
+ m_pos++; // Go past the right curly bracket.
+
+ return totalSequence;
+}
+
+// Reads a charmap string.
+int StringParser::ParseString(long srcPos, unsigned char* dest, int& destLength) {
+ m_pos = srcPos;
+
+ if (m_buffer[m_pos] != '"')
+ RaiseError("expected UTF-8 string literal");
+
+ long start = m_pos;
+
+ m_pos++;
+
+ destLength = 0;
+
+ while (m_buffer[m_pos] != '"') {
+ std::string sequence = (m_buffer[m_pos] == '{') ? ReadBracketedConstants() : ReadCharOrEscape();
+
+ for (const char& c : sequence) {
+ if (destLength == kMaxStringLength)
+ RaiseError("mapped string longer than %d bytes", kMaxStringLength);
+
+ dest[destLength++] = c;
+ }
+ }
+
+ m_pos++; // Go past the right quote.
+
+ return m_pos - start;
+}
+
+void StringParser::RaiseError(const char* format, ...) {
+ const int bufferSize = 1024;
+ char buffer[bufferSize];
+
+ std::va_list args;
+ va_start(args, format);
+ std::vsnprintf(buffer, bufferSize, format, args);
+ va_end(args);
+
+ throw std::runtime_error(buffer);
+}
+
+// Converts digit character to numerical value.
+static int ConvertDigit(char c, int radix) {
+ int digit;
+
+ if (c >= '0' && c <= '9')
+ digit = c - '0';
+ else if (c >= 'A' && c <= 'F')
+ digit = 10 + c - 'A';
+ else if (c >= 'a' && c <= 'f')
+ digit = 10 + c - 'a';
+ else
+ return -1;
+
+ return (digit < radix) ? digit : -1;
+}
+
+void StringParser::SkipRestOfInteger(int radix) {
+ while (ConvertDigit(m_buffer[m_pos], radix) != -1)
+ m_pos++;
+}
+
+StringParser::Integer StringParser::ReadDecimal() {
+ const int radix = 10;
+ std::uint64_t n = 0;
+ int digit;
+ std::uint64_t max = UINT32_MAX;
+ long startPos = m_pos;
+
+ while ((digit = ConvertDigit(m_buffer[m_pos], radix)) != -1) {
+ n = n * radix + digit;
+
+ if (n >= max) {
+ SkipRestOfInteger(radix);
+
+ std::string intLiteral(m_buffer + startPos, m_pos - startPos);
+ RaiseError("integer literal \"%s\" is too large", intLiteral.c_str());
+ }
+
+ m_pos++;
+ }
+
+ int size;
+
+ if (m_buffer[m_pos] == 'H') {
+ if (n >= 0x10000) {
+ RaiseError("%lu is too large to be a halfword", (unsigned long)n);
+ }
+
+ size = 2;
+ m_pos++;
+ } else if (m_buffer[m_pos] == 'W') {
+ size = 4;
+ m_pos++;
+ } else {
+ if (n >= 0x10000)
+ size = 4;
+ else if (n >= 0x100)
+ size = 2;
+ else
+ size = 1;
+ }
+
+ return { static_cast<std::uint32_t>(n), size };
+}
+
+StringParser::Integer StringParser::ReadHex() {
+ const int radix = 16;
+ std::uint64_t n = 0;
+ int digit;
+ std::uint64_t max = UINT32_MAX;
+ long startPos = m_pos;
+
+ while ((digit = ConvertDigit(m_buffer[m_pos], radix)) != -1) {
+ n = n * radix + digit;
+
+ if (n >= max) {
+ SkipRestOfInteger(radix);
+
+ std::string intLiteral(m_buffer + startPos, m_pos - startPos);
+ RaiseError("integer literal \"%s\" is too large", intLiteral.c_str());
+ }
+
+ m_pos++;
+ }
+
+ int length = m_pos - startPos;
+ int size = 0;
+
+ switch (length) {
+ case 2:
+ size = 1;
+ break;
+ case 4:
+ size = 2;
+ break;
+ case 8:
+ size = 4;
+ break;
+ default: {
+ std::string intLiteral(m_buffer + startPos, m_pos - startPos);
+ RaiseError("hex integer literal \"0x%s\" doesn't have length of 2, 4, or 8 digits", intLiteral.c_str());
+ }
+ }
+
+ return { static_cast<std::uint32_t>(n), size };
+}
+
+StringParser::Integer StringParser::ReadInteger() {
+ if (!IsAsciiDigit(m_buffer[m_pos]))
+ RaiseError("expected integer");
+
+ if (m_buffer[m_pos] == '0' && m_buffer[m_pos + 1] == 'x') {
+ m_pos += 2;
+ return ReadHex();
+ }
+
+ return ReadDecimal();
+}
+
+// Skips tabs and spaces.
+void StringParser::SkipWhitespace() {
+ while (m_buffer[m_pos] == '\t' || m_buffer[m_pos] == ' ')
+ m_pos++;
+}
diff --git a/tools/src/preproc/string_parser.h b/tools/src/preproc/string_parser.h
new file mode 100644
index 00000000..2e212640
--- /dev/null
+++ b/tools/src/preproc/string_parser.h
@@ -0,0 +1,54 @@
+// Copyright(c) 2016 YamaArashi
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+
+#ifndef STRING_PARSER_H
+#define STRING_PARSER_H
+
+#include <cstdint>
+#include <string>
+#include "preproc.h"
+
+class StringParser {
+ public:
+ StringParser(char* buffer, long size) : m_buffer(buffer), m_size(size), m_pos(0) {
+ }
+ int ParseString(long srcPos, unsigned char* dest, int& destLength);
+
+ private:
+ struct Integer {
+ std::uint32_t value;
+ int size;
+ };
+
+ char* m_buffer;
+ long m_size;
+ long m_pos;
+
+ Integer ReadInteger();
+ Integer ReadDecimal();
+ Integer ReadHex();
+ std::string ReadCharOrEscape();
+ std::string ReadBracketedConstants();
+ void SkipWhitespace();
+ void SkipRestOfInteger(int radix);
+ void RaiseError(const char* format, ...);
+};
+
+#endif // STRING_PARSER_H
diff --git a/tools/src/preproc/utf8.cpp b/tools/src/preproc/utf8.cpp
new file mode 100644
index 00000000..abeccfe9
--- /dev/null
+++ b/tools/src/preproc/utf8.cpp
@@ -0,0 +1,94 @@
+// Copyright (c) 2008-2009 Bjoern Hoehrmann <bjoern@hoehrmann.de>
+// See http://bjoern.hoehrmann.de/utf-8/decoder/dfa/ for details.
+//
+// Copyright(c) 2016 YamaArashi
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+
+#include <cstdint>
+#include "utf8.h"
+
+static const unsigned char s_byteTypeTable[] = {
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 00..1f
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 20..3f
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 40..5f
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 60..7f
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+ 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, // 80..9f
+ 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7,
+ 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, // a0..bf
+ 8, 8, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
+ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, // c0..df
+ 0xa, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x4, 0x3, 0x3, // e0..ef
+ 0xb, 0x6, 0x6, 0x6, 0x5, 0x8, 0x8, 0x8, 0x8, 0x8, 0x8, 0x8, 0x8, 0x8, 0x8, 0x8, // f0..ff
+};
+
+const unsigned char s0 = 0 * 12;
+const unsigned char s1 = 1 * 12;
+const unsigned char s2 = 2 * 12;
+const unsigned char s3 = 3 * 12;
+const unsigned char s4 = 4 * 12;
+const unsigned char s5 = 5 * 12;
+const unsigned char s6 = 6 * 12;
+const unsigned char s7 = 7 * 12;
+const unsigned char s8 = 8 * 12;
+
+static const unsigned char s_transitionTable[] = {
+ s0, s1, s2, s3, s5, s8, s7, s1, s1, s1, s4, s6, // s0
+ s1, s1, s1, s1, s1, s1, s1, s1, s1, s1, s1, s1, // s1
+ s1, s0, s1, s1, s1, s1, s1, s0, s1, s0, s1, s1, // s2
+ s1, s2, s1, s1, s1, s1, s1, s2, s1, s2, s1, s1, // s3
+ s1, s1, s1, s1, s1, s1, s1, s2, s1, s1, s1, s1, // s4
+ s1, s2, s1, s1, s1, s1, s1, s1, s1, s2, s1, s1, // s5
+ s1, s1, s1, s1, s1, s1, s1, s3, s1, s3, s1, s1, // s6
+ s1, s3, s1, s1, s1, s1, s1, s3, s1, s3, s1, s1, // s7
+ s1, s3, s1, s1, s1, s1, s1, s1, s1, s1, s1, s1, // s8
+};
+
+// Decodes UTF-8 encoded Unicode code point at "s".
+UnicodeChar DecodeUtf8(const char* s) {
+ UnicodeChar unicodeChar;
+ int state = s0;
+ auto start = s;
+
+ do {
+ unsigned char byte = *s++;
+ int type = s_byteTypeTable[byte];
+
+ if (state == s0)
+ unicodeChar.code = (0xFF >> type) & byte;
+ else
+ unicodeChar.code = (unicodeChar.code << 6) | (byte & 0x3F);
+
+ state = s_transitionTable[state + type];
+
+ if (state == s1) {
+ unicodeChar.code = -1;
+ return unicodeChar;
+ }
+ } while (state != s0);
+
+ unicodeChar.encodingLength = s - start;
+
+ return unicodeChar;
+}
diff --git a/tools/src/preproc/utf8.h b/tools/src/preproc/utf8.h
new file mode 100644
index 00000000..28d03d30
--- /dev/null
+++ b/tools/src/preproc/utf8.h
@@ -0,0 +1,33 @@
+// Copyright(c) 2016 YamaArashi
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+
+#ifndef UTF8_H
+#define UTF8_H
+
+#include <cstdint>
+
+struct UnicodeChar {
+ std::int32_t code;
+ int encodingLength;
+};
+
+UnicodeChar DecodeUtf8(const char* s);
+
+#endif // UTF8_H
diff --git a/tools/src/scaninc/CMakeLists.txt b/tools/src/scaninc/CMakeLists.txt
new file mode 100644
index 00000000..8f8bbc3e
--- /dev/null
+++ b/tools/src/scaninc/CMakeLists.txt
@@ -0,0 +1,7 @@
+file(GLOB_RECURSE sources *.cpp)
+
+add_executable(scaninc ${sources})
+target_include_directories(scaninc PRIVATE .)
+target_link_libraries(scaninc PRIVATE project_settings fmt::fmt)
+
+install(TARGETS scaninc RUNTIME DESTINATION bin)
diff --git a/tools/src/scaninc/LICENSE b/tools/src/scaninc/LICENSE
new file mode 100755
index 00000000..b66bf81c
--- /dev/null
+++ b/tools/src/scaninc/LICENSE
@@ -0,0 +1,19 @@
+Copyright (c) 2015 YamaArashi
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in
+all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE.
diff --git a/tools/src/scaninc/asm_file.cpp b/tools/src/scaninc/asm_file.cpp
new file mode 100644
index 00000000..f53777bc
--- /dev/null
+++ b/tools/src/scaninc/asm_file.cpp
@@ -0,0 +1,165 @@
+// Copyright(c) 2015-2017 YamaArashi
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+
+#include <cstdio>
+#include <string>
+#include "scaninc.h"
+#include "asm_file.h"
+
+AsmFile::AsmFile(std::string path) {
+ m_path = path;
+
+ FILE* fp = std::fopen(path.c_str(), "rb");
+
+ if (fp == NULL)
+ fatal_error("Failed to open \"%s\" for reading.\n", path.c_str());
+
+ std::fseek(fp, 0, SEEK_END);
+
+ m_size = static_cast<size_t>(std::ftell(fp));
+
+ m_buffer = new char[m_size];
+
+ std::rewind(fp);
+
+ if (std::fread(m_buffer, m_size, 1, fp) != 1)
+ fatal_error("Failed to read \"%s\".\n", path.c_str());
+
+ std::fclose(fp);
+
+ m_pos = 0;
+ m_lineNum = 1;
+}
+
+AsmFile::~AsmFile() {
+ delete[] m_buffer;
+}
+
+IncDirectiveType AsmFile::ReadUntilIncDirective(std::string& path) {
+ // At the beginning of each loop iteration, the current file position
+ // should be at the start of a line or at the end of the file.
+ for (;;) {
+ SkipTabsAndSpaces();
+
+ IncDirectiveType incDirectiveType = IncDirectiveType::None;
+
+ if (char c = static_cast<char>(PeekChar()); c == '.' || c == '#') {
+ m_pos++;
+
+ if (MatchIncDirective("incbin", path))
+ incDirectiveType = IncDirectiveType::Incbin;
+ else if (MatchIncDirective("include", path))
+ incDirectiveType = IncDirectiveType::Include;
+ }
+
+ for (;;) {
+ int c = GetChar();
+
+ if (c == -1)
+ return incDirectiveType;
+
+ if (c == ';') {
+ SkipEndOfLineComment();
+ break;
+ } else if (c == '/' && PeekChar() == '*') {
+ m_pos++;
+ SkipMultiLineComment();
+ } else if (c == '"') {
+ SkipString();
+ } else if (c == '\n') {
+ break;
+ }
+ }
+
+ if (incDirectiveType != IncDirectiveType::None)
+ return incDirectiveType;
+ }
+}
+
+std::string AsmFile::ReadPath() {
+ size_t length = 0;
+ size_t startPos = m_pos;
+
+ for (;;) {
+ int c = GetChar();
+
+ if (c == '"')
+ break;
+
+ if (c == -1)
+ fatal_error(INPUT_ERROR_MESSAGE("unexpected EOF in include string\n"));
+
+ if (c == 0)
+ fatal_error(INPUT_ERROR_MESSAGE("unexpected NUL character in include string\n"));
+
+ if (c == '\n')
+ fatal_error(INPUT_ERROR_MESSAGE("unexpected end of line character in include string\n"));
+
+ // Don't bother allowing any escape sequences.
+ if (c == '\\')
+ fatal_error(INPUT_ERROR_MESSAGE("unexpected escape in include string\n"));
+
+ length++;
+
+ if (length > SCANINC_MAX_PATH)
+ fatal_error(INPUT_ERROR_MESSAGE("path is too long"));
+ }
+
+ return std::string(m_buffer + startPos, length);
+}
+
+void AsmFile::SkipEndOfLineComment() {
+ int c;
+
+ do {
+ c = GetChar();
+ } while (c != -1 && c != '\n');
+}
+
+void AsmFile::SkipMultiLineComment() {
+ for (;;) {
+ int c = GetChar();
+
+ if (c == '*') {
+ if (PeekChar() == '/') {
+ m_pos++;
+ return;
+ }
+ } else if (c == -1) {
+ return;
+ }
+ }
+}
+
+void AsmFile::SkipString() {
+ for (;;) {
+ int c = GetChar();
+
+ if (c == '"')
+ break;
+
+ if (c == -1)
+ fatal_error(INPUT_ERROR_MESSAGE("unexpected EOF in string\n"));
+
+ if (c == '\\') {
+ c = GetChar();
+ }
+ }
+}
diff --git a/tools/src/scaninc/asm_file.h b/tools/src/scaninc/asm_file.h
new file mode 100644
index 00000000..79a87a63
--- /dev/null
+++ b/tools/src/scaninc/asm_file.h
@@ -0,0 +1,105 @@
+// Copyright(c) 2015-2017 YamaArashi
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+
+#ifndef ASM_FILE_H
+#define ASM_FILE_H
+
+#include <string>
+#include "scaninc.h"
+
+enum class IncDirectiveType { None, Include, Incbin };
+
+class AsmFile {
+ public:
+ AsmFile(std::string path);
+ ~AsmFile();
+ IncDirectiveType ReadUntilIncDirective(std::string& path);
+
+ private:
+ char* m_buffer;
+ size_t m_pos;
+ size_t m_size;
+ int m_lineNum;
+ std::string m_path;
+
+ int GetChar() {
+ if (m_pos >= m_size)
+ return -1;
+
+ int c = m_buffer[m_pos++];
+
+ if (c == '\r') {
+ if (m_pos < m_size && m_buffer[m_pos++] == '\n') {
+ m_lineNum++;
+ return '\n';
+ } else {
+ fatal_error(INPUT_ERROR_MESSAGE("CR line endings are not supported\n"));
+ }
+ }
+
+ if (c == '\n')
+ m_lineNum++;
+
+ return c;
+ }
+
+ // No newline translation because it's not needed for any use of this function.
+ int PeekChar() {
+ if (m_pos >= m_size)
+ return -1;
+
+ return m_buffer[m_pos];
+ }
+
+ void SkipTabsAndSpaces() {
+ while (m_pos < m_size && (m_buffer[m_pos] == '\t' || m_buffer[m_pos] == ' '))
+ m_pos++;
+ }
+
+ bool MatchIncDirective(std::string directiveName, std::string& path) {
+ size_t length = directiveName.length();
+ size_t i;
+
+ for (i = 0; i < length && m_pos + i < m_size; i++)
+ if (directiveName[i] != m_buffer[m_pos + i])
+ return false;
+
+ if (i < length)
+ return false;
+
+ m_pos += length;
+
+ SkipTabsAndSpaces();
+
+ if (GetChar() != '"')
+ fatal_error(INPUT_ERROR_MESSAGE("no path after \".%s\" directive\n"), directiveName.c_str());
+
+ path = ReadPath();
+
+ return true;
+ }
+
+ std::string ReadPath();
+ void SkipEndOfLineComment();
+ void SkipMultiLineComment();
+ void SkipString();
+};
+
+#endif // ASM_FILE_H
diff --git a/tools/src/scaninc/c_file.cpp b/tools/src/scaninc/c_file.cpp
new file mode 100644
index 00000000..a8ff921f
--- /dev/null
+++ b/tools/src/scaninc/c_file.cpp
@@ -0,0 +1,264 @@
+// Copyright(c) 2017 YamaArashi
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+
+#include "c_file.h"
+
+CFile::CFile(std::string path) {
+ m_path = path;
+
+ FILE* fp = std::fopen(path.c_str(), "rb");
+
+ if (fp == NULL)
+ fatal_error("Failed to open \"%s\" for reading.\n", path.c_str());
+
+ std::fseek(fp, 0, SEEK_END);
+
+ m_size = static_cast<size_t>(std::ftell(fp));
+
+ m_buffer = new char[m_size + 1];
+ m_buffer[m_size] = 0;
+
+ std::rewind(fp);
+
+ if (std::fread(m_buffer, m_size, 1, fp) != 1)
+ fatal_error("Failed to read \"%s\".\n", path.c_str());
+
+ std::fclose(fp);
+
+ m_pos = 0;
+ m_lineNum = 1;
+}
+
+CFile::~CFile() {
+ delete[] m_buffer;
+}
+
+void CFile::FindIncbins() {
+ char stringChar = 0;
+
+ while (m_pos < m_size) {
+ if (stringChar) {
+ if (m_buffer[m_pos] == stringChar) {
+ m_pos++;
+ stringChar = 0;
+ } else if (m_buffer[m_pos] == '\\' && m_buffer[m_pos + 1] == stringChar) {
+ m_pos += 2;
+ } else {
+ if (m_buffer[m_pos] == '\n')
+ m_lineNum++;
+ m_pos++;
+ }
+ } else {
+ SkipWhitespace();
+ CheckInclude();
+ CheckIncbin();
+
+ if (m_pos >= m_size)
+ break;
+
+ char c = m_buffer[m_pos++];
+
+ if (c == '\n')
+ m_lineNum++;
+ else if (c == '"')
+ stringChar = '"';
+ else if (c == '\'')
+ stringChar = '\'';
+ else if (c == 0)
+ fatal_error(INPUT_ERROR_MESSAGE("unexpected null character"));
+ }
+ }
+}
+
+bool CFile::ConsumeHorizontalWhitespace() {
+ if (m_buffer[m_pos] == '\t' || m_buffer[m_pos] == ' ') {
+ m_pos++;
+ return true;
+ }
+
+ return false;
+}
+
+bool CFile::ConsumeNewline() {
+ if (m_buffer[m_pos] == '\n') {
+ m_pos++;
+ m_lineNum++;
+ return true;
+ }
+
+ if (m_buffer[m_pos] == '\r' && m_buffer[m_pos + 1] == '\n') {
+ m_pos += 2;
+ m_lineNum++;
+ return true;
+ }
+
+ return false;
+}
+
+bool CFile::ConsumeComment() {
+ if (m_buffer[m_pos] == '/' && m_buffer[m_pos + 1] == '*') {
+ m_pos += 2;
+ while (m_buffer[m_pos] != '*' || m_buffer[m_pos + 1] != '/') {
+ if (m_buffer[m_pos] == 0)
+ return false;
+ if (!ConsumeNewline())
+ m_pos++;
+ }
+ m_pos += 2;
+ return true;
+ } else if (m_buffer[m_pos] == '/' && m_buffer[m_pos + 1] == '/') {
+ m_pos += 2;
+ while (!ConsumeNewline()) {
+ if (m_buffer[m_pos] == 0)
+ return false;
+ m_pos++;
+ }
+ return true;
+ }
+
+ return false;
+}
+
+void CFile::SkipWhitespace() {
+ while (ConsumeHorizontalWhitespace() || ConsumeNewline() || ConsumeComment())
+ ;
+}
+
+bool CFile::CheckIdentifier(const std::string& ident) {
+ unsigned int i;
+
+ for (i = 0; i < ident.length() && m_pos + i < m_size; i++)
+ if (ident[i] != m_buffer[m_pos + i])
+ return false;
+
+ return (i == ident.length());
+}
+
+void CFile::CheckInclude() {
+ if (m_buffer[m_pos] != '#')
+ return;
+
+ std::string ident = "#include";
+
+ if (!CheckIdentifier(ident)) {
+ return;
+ }
+
+ m_pos += ident.length();
+
+ ConsumeHorizontalWhitespace();
+
+ std::string path = ReadPath();
+
+ if (!path.empty()) {
+ m_includes.emplace(path);
+ }
+}
+
+void CFile::CheckIncbin() {
+ // Optimization: assume most lines are not incbins
+ if (!(m_buffer[m_pos + 0] == 'I' && m_buffer[m_pos + 1] == 'N' && m_buffer[m_pos + 2] == 'C' &&
+ m_buffer[m_pos + 3] == 'B' && m_buffer[m_pos + 4] == 'I' && m_buffer[m_pos + 5] == 'N' &&
+ m_buffer[m_pos + 6] == '_')) {
+ return;
+ }
+
+ std::string idents[6] = { "INCBIN_S8", "INCBIN_U8", "INCBIN_S16", "INCBIN_U16", "INCBIN_S32", "INCBIN_U32" };
+ int incbinType = -1;
+
+ for (int i = 0; i < 6; i++) {
+ if (CheckIdentifier(idents[i])) {
+ incbinType = i;
+ break;
+ }
+ }
+
+ if (incbinType == -1)
+ return;
+
+ auto oldPos = m_pos;
+ auto oldLineNum = m_lineNum;
+
+ m_pos += idents[incbinType].length();
+
+ SkipWhitespace();
+
+ if (m_buffer[m_pos] != '(') {
+ m_pos = oldPos;
+ m_lineNum = oldLineNum;
+ return;
+ }
+
+ m_pos++;
+
+ while (true) {
+ SkipWhitespace();
+
+ std::string path = ReadPath();
+
+ SkipWhitespace();
+
+ m_incbins.emplace(path);
+
+ if (m_buffer[m_pos] != ',')
+ break;
+
+ m_pos++;
+ }
+
+ if (m_buffer[m_pos] != ')')
+ fatal_error(INPUT_ERROR_MESSAGE("expected ')'"));
+
+ m_pos++;
+}
+
+std::string CFile::ReadPath() {
+ if (m_buffer[m_pos] != '"') {
+ if (m_buffer[m_pos] == '<') {
+ return std::string();
+ }
+ fatal_error(INPUT_ERROR_MESSAGE("expected '\"' or '<'"));
+ }
+
+ m_pos++;
+
+ auto startPos = m_pos;
+
+ while (m_buffer[m_pos] != '"') {
+ if (m_buffer[m_pos] == 0) {
+ if (m_pos >= m_size)
+ fatal_error(INPUT_ERROR_MESSAGE("unexpected EOF in path string"));
+ else
+ fatal_error(INPUT_ERROR_MESSAGE("unexpected null character in path string"));
+ }
+
+ if (m_buffer[m_pos] == '\r' || m_buffer[m_pos] == '\n')
+ fatal_error(INPUT_ERROR_MESSAGE("unexpected end of line character in path string"));
+
+ if (m_buffer[m_pos] == '\\')
+ fatal_error(INPUT_ERROR_MESSAGE("unexpected escape in path string"));
+
+ m_pos++;
+ }
+
+ m_pos++;
+
+ return std::string(m_buffer + startPos, m_pos - 1 - startPos);
+}
diff --git a/tools/src/scaninc/c_file.h b/tools/src/scaninc/c_file.h
new file mode 100644
index 00000000..f09b7e3e
--- /dev/null
+++ b/tools/src/scaninc/c_file.h
@@ -0,0 +1,60 @@
+// Copyright(c) 2017 YamaArashi
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+
+#ifndef C_FILE_H
+#define C_FILE_H
+
+#include <string>
+#include <set>
+#include <memory>
+#include "scaninc.h"
+
+class CFile {
+ public:
+ CFile(std::string path);
+ ~CFile();
+ void FindIncbins();
+ const std::set<std::string>& GetIncbins() {
+ return m_incbins;
+ }
+ const std::set<std::string>& GetIncludes() {
+ return m_includes;
+ }
+
+ private:
+ char* m_buffer;
+ size_t m_pos;
+ size_t m_size;
+ int m_lineNum;
+ std::string m_path;
+ std::set<std::string> m_incbins;
+ std::set<std::string> m_includes;
+
+ bool ConsumeHorizontalWhitespace();
+ bool ConsumeNewline();
+ bool ConsumeComment();
+ void SkipWhitespace();
+ bool CheckIdentifier(const std::string& ident);
+ void CheckInclude();
+ void CheckIncbin();
+ std::string ReadPath();
+};
+
+#endif // C_FILE_H
diff --git a/tools/src/scaninc/scaninc.cpp b/tools/src/scaninc/scaninc.cpp
new file mode 100644
index 00000000..d1487422
--- /dev/null
+++ b/tools/src/scaninc/scaninc.cpp
@@ -0,0 +1,113 @@
+// Copyright(c) 2015-2017 YamaArashi
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+
+#include <cstdio>
+#include <cstdlib>
+#include <list>
+#include <queue>
+#include <set>
+#include <string>
+#include "scaninc.h"
+#include "source_file.h"
+
+bool CanOpenFile(std::string path) {
+ FILE* fp = std::fopen(path.c_str(), "rb");
+
+ if (fp == NULL)
+ return false;
+
+ std::fclose(fp);
+ return true;
+}
+
+const char* const USAGE = "Usage: scaninc [-I INCLUDE_PATH] FILE_PATH\n";
+
+int main(int argc, char** argv) {
+ std::queue<std::string> filesToProcess;
+ std::set<std::string> dependencies;
+
+ std::vector<std::string> includeDirs;
+
+ argc--;
+ argv++;
+
+ while (argc > 1) {
+ std::string arg(argv[0]);
+ if (arg.substr(0, 2) == "-I") {
+ std::string includeDir = arg.substr(2);
+ if (includeDir.empty()) {
+ argc--;
+ argv++;
+ includeDir = std::string(argv[0]);
+ }
+ if (!includeDir.empty() && includeDir.back() != '/') {
+ includeDir += '/';
+ }
+ includeDirs.push_back(includeDir);
+ } else {
+ fatal_error(USAGE);
+ }
+ argc--;
+ argv++;
+ }
+
+ if (argc != 1) {
+ fatal_error(USAGE);
+ }
+
+ std::string initialPath(argv[0]);
+
+ filesToProcess.push(initialPath);
+
+ while (!filesToProcess.empty()) {
+ std::string filePath = filesToProcess.front();
+ SourceFile file(filePath);
+ filesToProcess.pop();
+
+ includeDirs.push_back(file.GetSrcDir());
+ for (auto incbin : file.GetIncbins()) {
+ // Search for the incbin in the include directories as well.
+ for (auto includeDir : includeDirs) {
+ std::string path(includeDir + incbin);
+ if (CanOpenFile(path)) {
+ dependencies.insert(path);
+ break;
+ }
+ }
+ }
+ for (const auto &include : file.GetIncludes()) {
+ for (const auto &includeDir : includeDirs) {
+ std::string path(includeDir + include);
+ if (CanOpenFile(path)) {
+ bool inserted = dependencies.insert(path).second;
+ if (inserted) {
+ filesToProcess.push(path);
+ }
+ break;
+ }
+ }
+ }
+ includeDirs.pop_back();
+ }
+
+ for (const std::string& path : dependencies) {
+ std::printf("%s\n", path.c_str());
+ }
+}
diff --git a/tools/src/scaninc/scaninc.h b/tools/src/scaninc/scaninc.h
new file mode 100644
index 00000000..61f76137
--- /dev/null
+++ b/tools/src/scaninc/scaninc.h
@@ -0,0 +1,38 @@
+// Copyright(c) 2015-2017 YamaArashi
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+
+#ifndef SCANINC_H
+#define SCANINC_H
+
+#include <cstdio>
+#include <cstdlib>
+#include <fmt/format.h>
+
+#define INPUT_ERROR_MESSAGE(format) "{}:{} " format, m_path.c_str(), m_lineNum
+
+template<typename...T>
+inline void fatal_error(std::string_view format, T...args){
+ fmt::print(stderr, format, args...);
+ exit(EXIT_FAILURE);
+}
+
+#define SCANINC_MAX_PATH 255
+
+#endif // SCANINC_H
diff --git a/tools/src/scaninc/source_file.cpp b/tools/src/scaninc/source_file.cpp
new file mode 100644
index 00000000..0cefc9f1
--- /dev/null
+++ b/tools/src/scaninc/source_file.cpp
@@ -0,0 +1,108 @@
+// Copyright(c) 2019 Phlosioneer
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+
+#include <new>
+#include "source_file.h"
+
+SourceFileType GetFileType(std::string& path) {
+ std::size_t pos = path.find_last_of('.');
+
+ if (pos == std::string::npos)
+ fatal_error("no file extension in path \"%s\"\n", path.c_str());
+
+ std::string extension = path.substr(pos + 1);
+
+ if (extension == "c")
+ return SourceFileType::Cpp;
+ else if (extension == "s")
+ return SourceFileType::Asm;
+ else if (extension == "h")
+ return SourceFileType::Header;
+ else if (extension == "inc")
+ return SourceFileType::Inc;
+ else
+ fatal_error("Unrecognized extension \"%s\"\n", extension.c_str());
+
+ // Unreachable
+ return SourceFileType::Cpp;
+}
+
+std::string GetDir(std::string& path) {
+ std::size_t slash = path.rfind('/');
+
+ if (slash != std::string::npos)
+ return path.substr(0, slash + 1);
+ else
+ return std::string("");
+}
+
+SourceFile::SourceFile(std::string path) {
+ m_file_type = GetFileType(path);
+
+ m_src_dir = GetDir(path);
+
+ if (m_file_type == SourceFileType::Cpp || m_file_type == SourceFileType::Header) {
+ new (&m_source_file.c_file) CFile(path);
+ m_source_file.c_file.FindIncbins();
+ } else {
+ AsmFile file(path);
+ std::set<std::string> incbins;
+ std::set<std::string> includes;
+
+ IncDirectiveType incDirectiveType;
+ std::string outputPath;
+
+ while ((incDirectiveType = file.ReadUntilIncDirective(outputPath)) != IncDirectiveType::None) {
+ if (incDirectiveType == IncDirectiveType::Include)
+ includes.insert(outputPath);
+ else
+ incbins.insert(outputPath);
+ }
+
+ new (&m_source_file.asm_wrapper) SourceFile::InnerUnion::AsmWrapper{ incbins, includes };
+ }
+}
+
+SourceFile::~SourceFile() {
+ if (m_file_type == SourceFileType::Cpp || m_file_type == SourceFileType::Header) {
+ m_source_file.c_file.~CFile();
+ } else {
+ m_source_file.asm_wrapper.asm_incbins.~set();
+ m_source_file.asm_wrapper.asm_includes.~set();
+ }
+}
+
+const std::set<std::string>& SourceFile::GetIncbins() {
+ if (m_file_type == SourceFileType::Cpp || m_file_type == SourceFileType::Header)
+ return m_source_file.c_file.GetIncbins();
+ else
+ return m_source_file.asm_wrapper.asm_incbins;
+}
+
+const std::set<std::string>& SourceFile::GetIncludes() {
+ if (m_file_type == SourceFileType::Cpp || m_file_type == SourceFileType::Header)
+ return m_source_file.c_file.GetIncludes();
+ else
+ return m_source_file.asm_wrapper.asm_includes;
+}
+
+std::string& SourceFile::GetSrcDir() {
+ return m_src_dir;
+}
diff --git a/tools/src/scaninc/source_file.h b/tools/src/scaninc/source_file.h
new file mode 100644
index 00000000..76a09d8c
--- /dev/null
+++ b/tools/src/scaninc/source_file.h
@@ -0,0 +1,62 @@
+// Copyright(c) 2019 Phlosioneer
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+
+#ifndef SOURCE_FILE_H
+#define SOURCE_FILE_H
+
+#include <string>
+#include "scaninc.h"
+#include "asm_file.h"
+#include "c_file.h"
+
+enum class SourceFileType { Cpp, Header, Asm, Inc };
+
+SourceFileType GetFileType(std::string& path);
+
+class SourceFile {
+ public:
+ SourceFile(std::string path);
+ ~SourceFile();
+ SourceFile(SourceFile const&) = delete;
+ SourceFile(SourceFile&&) = delete;
+ SourceFile& operator=(SourceFile const&) = delete;
+ SourceFile& operator=(SourceFile&&) = delete;
+ bool HasIncbins();
+ const std::set<std::string>& GetIncbins();
+ const std::set<std::string>& GetIncludes();
+ std::string& GetSrcDir();
+
+ private:
+ union InnerUnion {
+ CFile c_file;
+ struct AsmWrapper {
+ std::set<std::string> asm_incbins;
+ std::set<std::string> asm_includes;
+ } asm_wrapper;
+
+ // Construction and destruction handled by SourceFile.
+ InnerUnion(){};
+ ~InnerUnion(){};
+ } m_source_file;
+ SourceFileType m_file_type;
+ std::string m_src_dir;
+};
+
+#endif // SOURCE_FILE_H
diff --git a/tools/src/tmc_strings/CMakeLists.txt b/tools/src/tmc_strings/CMakeLists.txt
new file mode 100644
index 00000000..04c782ed
--- /dev/null
+++ b/tools/src/tmc_strings/CMakeLists.txt
@@ -0,0 +1,6 @@
+file(GLOB_RECURSE sources *.cpp)
+
+add_executable(tmc_strings ${sources})
+target_link_libraries(tmc_strings PRIVATE fmt::fmt nlohmann_json::nlohmann_json)
+
+install(TARGETS tmc_strings RUNTIME DESTINATION bin)
diff --git a/tools/src/tmc_strings/main.cpp b/tools/src/tmc_strings/main.cpp
new file mode 100644
index 00000000..7f5bbf22
--- /dev/null
+++ b/tools/src/tmc_strings/main.cpp
@@ -0,0 +1,614 @@
+#include <fmt/format.h>
+#include <fstream>
+#include <nlohmann/json.hpp>
+
+using namespace std::string_literals;
+using json = nlohmann::json;
+using u8 = std::uint8_t;
+using u16 = std::uint16_t;
+using u32 = std::uint32_t;
+
+const u32 RomStartAddress = 0x8000000;
+
+namespace {
+
+struct LanguageTable {
+ const char* name;
+ u32 address;
+};
+
+enum Color {
+ Color_White,
+ Color_Red,
+ Color_Green,
+ Color_Blue,
+ Color_Yellow,
+
+ Color_Count,
+};
+
+constexpr const char* const ColorStrings[] = {
+ "White", "Red", "Green", "Blue", "Yellow",
+};
+
+enum Input {
+ Input_A,
+ Input_B,
+ Input_Left,
+ Input_Right,
+ Input_DUp,
+ Input_DDown,
+ Input_DLeft,
+ Input_DRight,
+ Input_Dpad,
+ Input_Select,
+ Input_Start,
+
+ Input_Count,
+};
+
+constexpr const char* const InputStrings[] = {
+ "A", "B", "Left", "Right", "DUp", "DDown", "DLeft", "DRight", "Dpad", "Select", "Start",
+};
+
+const std::map<u8, std::string> CharConvertArray = {
+ { 0x0a, "\n" }, { 0x0d, "\r" }, { 0x20, " " }, { 0x21, "!" }, { 0x22, "\"" }, { 0x23, "#" }, { 0x24, "$" },
+ { 0x25, "%" }, { 0x26, "&" }, { 0x27, "\'" }, { 0x28, "(" }, { 0x29, ")" }, { 0x2a, "*" }, { 0x2b, "+" },
+ { 0x2c, "," }, { 0x2d, "-" }, { 0x2e, "." }, { 0x2f, "/" }, { 0x30, "0" }, { 0x31, "1" }, { 0x32, "2" },
+ { 0x33, "3" }, { 0x34, "4" }, { 0x35, "5" }, { 0x36, "6" }, { 0x37, "7" }, { 0x38, "8" }, { 0x39, "9" },
+ { 0x3a, ":" }, { 0x3b, ";" }, { 0x3c, "<" }, { 0x3d, "=" }, { 0x3e, ">" }, { 0x3f, "?" }, { 0x40, "@" },
+ { 0x41, "A" }, { 0x42, "B" }, { 0x43, "C" }, { 0x44, "D" }, { 0x45, "E" }, { 0x46, "F" }, { 0x47, "G" },
+ { 0x48, "H" }, { 0x49, "I" }, { 0x4a, "J" }, { 0x4b, "K" }, { 0x4c, "L" }, { 0x4d, "M" }, { 0x4e, "N" },
+ { 0x4f, "O" }, { 0x50, "P" }, { 0x51, "Q" }, { 0x52, "R" }, { 0x53, "S" }, { 0x54, "T" }, { 0x55, "U" },
+ { 0x56, "V" }, { 0x57, "W" }, { 0x58, "X" }, { 0x59, "Y" }, { 0x5a, "Z" }, { 0x5b, "[" }, { 0x5c, "\'" },
+ { 0x5d, "]" }, { 0x5e, "^" }, { 0x5f, "_" }, { 0x60, "`" }, { 0x61, "a" }, { 0x62, "b" }, { 0x63, "c" },
+ { 0x64, "d" }, { 0x65, "e" }, { 0x66, "f" }, { 0x67, "g" }, { 0x68, "h" }, { 0x69, "i" }, { 0x6a, "j" },
+ { 0x6b, "k" }, { 0x6c, "l" }, { 0x6d, "m" }, { 0x6e, "n" }, { 0x6f, "o" }, { 0x70, "p" }, { 0x71, "q" },
+ { 0x72, "r" }, { 0x73, "s" }, { 0x74, "t" }, { 0x75, "u" }, { 0x76, "v" }, { 0x77, "w" }, { 0x78, "x" },
+ { 0x79, "y" }, { 0x7a, "z" }, { 0x82, "," }, { 0x84, "„" }, { 0x85, "⋯" }, { 0x8A, "Š" }, { 0x8B, "‹" },
+ { 0x8C, "Œ" }, { 0x8E, "Ž" }, { 0x91, "‘" }, { 0x92, "’" }, { 0x93, "“" }, { 0x94, "”" }, { 0x95, "·" },
+ { 0x99, "™" }, { 0x9A, "š" }, { 0x9B, "›" }, { 0x9C, "œ" }, { 0x9E, "ž" }, { 0x9F, "Ÿ" }, { 0xA1, "¡" },
+ { 0xA3, "♪" }, { 0xAA, "ª" }, { 0xAB, "«" }, { 0xB0, "º" }, { 0xB4, "'" }, { 0xB7, "´" }, { 0xBA, "º" },
+ { 0xBB, "»" }, { 0xBF, "¿" }, { 0xC0, "À" }, { 0xC1, "Á" }, { 0xC2, "Â" }, { 0xC3, "Ã" }, { 0xC4, "Ä" },
+ { 0xC5, "Å" }, { 0xC6, "Æ" }, { 0xC7, "Ç" }, { 0xC8, "È" }, { 0xC9, "É" }, { 0xCA, "Ê" }, { 0xCB, "Ë" },
+ { 0xCC, "Ì" }, { 0xCD, "Í" }, { 0xCE, "Î" }, { 0xCF, "Ï" }, { 0xD0, "Đ" }, { 0xD1, "Ñ" }, { 0xD2, "Ò" },
+ { 0xD3, "Ó" }, { 0xD4, "Ô" }, { 0xD5, "Õ" }, { 0xD6, "Ö" }, { 0xD7, "×" }, { 0xD8, "Ø" }, { 0xD9, "Ù" },
+ { 0xDA, "Ú" }, { 0xDB, "Û" }, { 0xDC, "Ü" }, { 0xDD, "Ý" }, { 0xDE, "Þ" }, { 0xDF, "β" }, { 0xE0, "à" },
+ { 0xE1, "á" }, { 0xE2, "â" }, { 0xE3, "ã" }, { 0xE4, "ä" }, { 0xE5, "å" }, { 0xE6, "æ" }, { 0xE7, "ç" },
+ { 0xE8, "è" }, { 0xE9, "é" }, { 0xEA, "ê" }, { 0xEB, "ë" }, { 0xEC, "ì" }, { 0xED, "í" }, { 0xEE, "î" },
+ { 0xEF, "ï" }, { 0xF0, "ð" }, { 0xF1, "ñ" }, { 0xF2, "ò" }, { 0xF3, "ó" }, { 0xF4, "ô" }, { 0xF5, "õ" },
+ { 0xF6, "ö" }, { 0xF7, "÷" }, { 0xF8, "ø" }, { 0xF9, "ù" }, { 0xFA, "ú" }, { 0xFB, "û" }, { 0xFC, "ü" },
+ { 0xFD, "ý" }, { 0xFE, "þ" }, { 0xFF, "ÿ" },
+};
+
+using ConvertFunction = std::string (*const)(const char*&);
+
+std::string Unk1Handler(const char*& ptr) {
+ u8 a = *ptr++;
+ return fmt::format("{{01:{:02X}}}", a);
+}
+
+std::string ColorHandler(const char*& ptr) {
+ u8 color = *ptr++;
+ if (color >= Color_Count)
+ throw std::runtime_error(ptr);
+ return fmt::format("{{Color:{}}}", ColorStrings[color]);
+}
+
+std::string SoundHandler(const char*& ptr) {
+ u8 a = *ptr++;
+ u8 b = *ptr++;
+ return fmt::format("{{Sound:{:02X}:{:02X}}}", a, b);
+}
+
+std::string Unk4Handler(const char*& ptr) {
+ u8 a = *ptr++;
+ if (a == 0x10) {
+ u8 b = *ptr++;
+ return fmt::format("{{04:{:02X}:{:02X}}}", a, b);
+ } else {
+ return fmt::format("{{04:{:02X}}}", a);
+ }
+}
+
+std::string ChoiceHandler(const char*& ptr) {
+ u8 category = *ptr++;
+ if (category == 0xff) {
+ return "{Choice:FF}";
+ } else {
+ u8 action = *ptr++;
+ return fmt::format("{{Choice:{:02X}:{:02X}}}", category, action);
+ }
+}
+
+std::string VariableHandler(const char*& ptr) {
+ u8 idx = *ptr++;
+ if (idx == 0) {
+ return "{Player}";
+ } else {
+ return fmt::format("{{Var:{:X}}}", idx);
+ }
+}
+
+std::string Unk7Handler(const char*& ptr) {
+ u8 a = *ptr++;
+ u8 b = *ptr++;
+ return fmt::format("{{07:{:02X}:{:02X}}}", a, b);
+}
+
+std::string Unk8Handler(const char*& ptr) {
+ u8 a = *ptr++;
+ if (a != 0xff)
+ throw std::runtime_error("unmatched unk8: "s + std::to_string(a));
+ return "{08:FF}";
+}
+
+std::string Unk9Handler(const char*& ptr) {
+ u8 a = *ptr++;
+ if (a != 0x78 && a != 0x00)
+ throw std::runtime_error("unmatched unk8: "s + std::to_string(a));
+ return fmt::format("{{09:{:02X}}}", a);
+}
+
+std::string InputHandler(const char*& ptr) {
+ u8 key = *ptr++;
+ if (key > 8)
+ throw std::runtime_error("unmatched key: "s + std::to_string(key));
+ return fmt::format("{{Key:{}}}", InputStrings[key]);
+}
+
+std::string SymbolHandler(const char*& ptr) {
+ u8 a = *ptr++;
+ return fmt::format("{{Symbol:{:02X}}}", a);
+}
+
+const std::map<u8, ConvertFunction> FuncConvertArray = {
+ { 0x01, Unk1Handler }, { 0x02, ColorHandler }, { 0x03, SoundHandler }, { 0x04, Unk4Handler },
+ { 0x05, ChoiceHandler }, { 0x06, VariableHandler }, { 0x07, Unk7Handler }, { 0x08, Unk8Handler },
+ { 0x09, Unk9Handler }, { 0x0c, InputHandler }, { 0x0f, SymbolHandler },
+};
+
+std::string ParseTMCString(const char* ptr) {
+ std::string ret;
+
+ while (*ptr) {
+ u8 c = *ptr++;
+
+ /* Convert character. */
+ {
+ const auto it = CharConvertArray.find(c);
+
+ if (it != std::end(CharConvertArray)) {
+ ret += it->second;
+ continue;
+ }
+ }
+
+ /* Convert function. */
+ {
+ const auto it = FuncConvertArray.find(c);
+
+ if (it != std::end(FuncConvertArray)) {
+ ret += it->second(ptr);
+ continue;
+ }
+ }
+
+ throw std::runtime_error(fmt::format("Unknown characters: {}", ptr));
+ }
+
+ return ret;
+}
+
+using RevertFunction = void (*)(const char*&, char*&);
+
+void ColorRevert(const char*& src, char*& dst) {
+ *dst++ = 0x02;
+ src++; // ':'
+ for (u32 i = 0; i < Color_Count; i++) {
+ const char* const color = ColorStrings[i];
+ const std::size_t color_len = std::strlen(color);
+ if (std::strncmp(src, color, color_len) == 0) {
+ *dst++ = i;
+ src += color_len;
+ return;
+ }
+ }
+
+ throw std::runtime_error(fmt::format("Color not found: {}", src));
+}
+
+void SoundRevert(const char*& src, char*& dst) {
+ *dst++ = 0x03;
+ *dst++ = std::strtoul(++src, nullptr, 0x10);
+ src += 2;
+ *dst++ = std::strtoul(++src, nullptr, 0x10);
+ src += 2;
+}
+
+void ChoiceRevert(const char*& src, char*& dst) {
+ *dst++ = 0x05;
+ u8 choice = std::strtoul(++src, nullptr, 0x10);
+
+ *dst++ = choice;
+ src += 2;
+
+ if (choice == 0xff)
+ return;
+
+ *dst++ = std::strtoul(++src, nullptr, 0x10);
+ src += 2;
+}
+
+void PlayerRevert(const char*& src, char*& dst) {
+ *dst++ = 0x06;
+ *dst++ = 0x00;
+
+ (void)src;
+}
+
+void VariableRevert(const char*& src, char*& dst) {
+ *dst++ = 0x06;
+ src++; // ':'
+
+ *dst++ = std::strtoul(src, nullptr, 0x10);
+ src++;
+}
+
+void KeyRevert(const char*& src, char*& dst) {
+ *dst++ = 0x0c;
+ src++; // ':'
+ for (u32 i = 0; i < Input_Count; i++) {
+ const char* const input = InputStrings[i];
+ const std::size_t input_len = std::strlen(input);
+ if (std::strncmp(src, input, input_len) == 0) {
+ *dst++ = i;
+ src += input_len;
+ return;
+ }
+ }
+
+ throw std::runtime_error(fmt::format("Input not found: {}", src));
+}
+
+void SymbolRevert(const char*& src, char*& dst) {
+ *dst++ = 0x0f;
+ src++; // ':'
+
+ *dst++ = std::strtoul(src, nullptr, 0x10);
+ src += 2;
+}
+
+const std::pair<std::string, RevertFunction> FuncRevertArray[] = {
+ { "Color", ColorRevert }, { "Sound", SoundRevert }, { "Choice", ChoiceRevert }, { "Player", PlayerRevert },
+ { "Var", VariableRevert }, { "Key", KeyRevert }, { "Symbol", SymbolRevert },
+};
+
+void WriteTMCString(char*& dst, const std::string& src) {
+ const char* ptr = src.data();
+
+ while (*ptr) {
+ /* Parse special */
+ {
+ if (*ptr == '{') {
+ ptr++;
+ const auto it =
+ std::find_if(std::begin(FuncRevertArray), std::end(FuncRevertArray), [&](const auto& data) {
+ return std::strncmp(ptr, data.first.c_str(), data.first.size()) == 0;
+ });
+
+ if (it != std::end(FuncRevertArray)) {
+ ptr += it->first.size();
+ it->second(ptr, dst);
+ } else {
+ do {
+ *dst++ = std::strtoul(ptr, nullptr, 0x10);
+ ptr += 2;
+ } while (*ptr == ':' && (ptr++, true));
+ }
+
+ if (*ptr != '}')
+ throw std::runtime_error(fmt::format("unmatched characters: \"{}\"\n", ptr));
+
+ ptr++;
+ continue;
+ }
+ }
+
+ /* Convert character. */
+ {
+ const auto it =
+ std::find_if(std::begin(CharConvertArray), std::end(CharConvertArray), [ptr](const auto& data) {
+ return std::strncmp(ptr, data.second.c_str(), data.second.length()) == 0;
+ });
+
+ if (it != std::end(CharConvertArray)) {
+ ptr += it->second.size();
+ *dst++ = it->first;
+ continue;
+ }
+ }
+
+ throw std::runtime_error(fmt::format("unmatched characters: \"{}\"\n", ptr));
+ }
+ *dst++ = '\0';
+}
+
+} // namespace
+
+void ExtractStringTable(std::string& rom_path, const std::vector<LanguageTable>& tables) {
+ const std::vector<char> rom = [&]() {
+ std::vector<char> rom;
+
+ /* Open ROM. */
+ std::ifstream rom_file(rom_path, std::ios::in);
+
+ if (!rom_file.good()) {
+ fmt::print(stderr, "Couldn't open ROM {}\n", rom_path);
+ exit(EXIT_FAILURE);
+ }
+
+ /* Get ROM size. */
+ rom_file.seekg(0, std::ios::end);
+ const std::size_t rom_size = rom_file.tellg();
+
+ /* Read ROM to buffer. */
+ rom.resize(rom_size);
+ rom_file.seekg(0, std::ios::beg);
+ rom_file.read(&rom[0], rom_size);
+
+ return rom;
+ }();
+
+ auto ReadAbsolute = [&](u32 address) -> u32 { return *(u32*)&rom[address - RomStartAddress]; };
+
+ for (auto& language : tables) {
+ /* Get category table start. */
+ const std::size_t table_start = language.address;
+
+ /* Get category count. */
+ const std::size_t category_count = ReadAbsolute(table_start) / sizeof(u32);
+
+ /* Read category offsets. */
+ std::vector<u32> category_table(category_count);
+ std::memcpy(&category_table[0], &rom[table_start - RomStartAddress], category_count * sizeof(u32));
+
+ json j = json::array();
+
+ for (auto& category_offset : category_table) {
+ /* Get string table start. */
+ const std::size_t category_start = table_start + category_offset;
+
+ /* Get string count. */
+ const std::size_t string_count = ReadAbsolute(category_start) / sizeof(u32);
+
+ /* Read string offsets. */
+ std::vector<u32> string_table(string_count);
+ std::memcpy(&string_table[0], &rom[category_start - RomStartAddress], string_count * sizeof(u32));
+
+ auto& category = j.emplace_back();
+
+ for (std::size_t l = 0; l < string_count; l++) {
+ /* Get string start. */
+ const std::size_t string_start = category_start + string_table[l];
+
+ /* Parse TMC. */
+ category[l] = ParseTMCString(&rom[string_start - RomStartAddress]);
+ }
+ }
+
+ const std::string out_path = language.name + ".json"s;
+ std::ofstream ofs(out_path);
+ ofs << j.dump(4);
+ }
+}
+
+void PackStringTable(const std::string& src_path, const std::string& dst_path, const std::size_t out_size) {
+ const json j = [&]() -> json {
+ std::ifstream ifs(src_path);
+
+ if (!ifs.good()) {
+ fmt::print(stderr, "Couldn't open JSON {}\n", src_path);
+ exit(EXIT_FAILURE);
+ }
+
+ return json::parse(ifs);
+ }();
+
+ std::vector<char> buffer(0x100000);
+ std::uintptr_t root_start = (std::uintptr_t)buffer.data();
+
+ char* root_ptr = buffer.data();
+ char* table = buffer.data() + j.size() * sizeof(u32);
+
+ for (auto& category : j) {
+ char* table_ptr = table;
+ char* str_start = table_ptr + category.size() * sizeof(u32);
+ char* str_ptr = str_start;
+ for (auto& str_j : category) {
+ /* Write string offset to table. */
+ *(u32*)table_ptr = (std::uintptr_t)str_ptr - (std::uintptr_t)table;
+ table_ptr += sizeof(u32);
+
+ auto str = str_j.get<std::string>();
+
+ /* Copy string to table. */
+ WriteTMCString(str_ptr, str);
+ }
+
+ /* Align string table size by 16 bytes. */
+ while (static_cast<std::uintptr_t>(str_ptr - str_start) & 0xf) {
+ *str_ptr = 0xff;
+ str_ptr++;
+ }
+
+ /* Write category offset to root table. */
+ *(u32*)root_ptr = (std::uintptr_t)table - root_start;
+ root_ptr += sizeof(u32);
+
+ table = str_ptr;
+ }
+
+ /* Align table end to 0x10 bytes. */
+ while ((std::uintptr_t)table & 0xf) {
+ *table++ = 0xff;
+ }
+
+ std::size_t table_size = (uintptr_t)table - root_start;
+
+ if (out_size) {
+ /* Abort if string table is too big. */
+ if (table_size > out_size) {
+ fmt::print(stderr, "Table is too big. Should be {} but was {}", out_size, table_size);
+ exit(EXIT_FAILURE);
+ }
+
+ /* Pad table if it's too small. */
+ while (table_size < out_size) {
+ *table++ = 0xff;
+ table_size++;
+ }
+ }
+
+ std::ofstream ofs(dst_path);
+ ofs.write(buffer.data(), table_size);
+}
+
+// clang-format off
+const std::vector<LanguageTable> LanguageTableUS = {
+ {"USA", 0x89B1D90},
+};
+
+const std::vector<LanguageTable> LanguageTableEU = {
+ {"English", 0x89AEB60},
+ {"French", 0x89F7420},
+ {"German", 0x8A3EEB0},
+ {"Spanish", 0x8A81E70},
+ {"Italian", 0x8AC37A0},
+};
+// clang-format on
+
+#include <getopt.h>
+
+const char* progname;
+
+void usage() {
+ fmt::print(stderr,
+ "tmc_strings (c) Luis Scheurenbrand\n"
+ "Built: " __TIME__ " " __DATE__ "\n"
+ "\n"
+ "Usage: {} [options...]\n"
+ "Options:\n"
+ " -x, --extract Extract string table from ROM and store it in json format. (Default)\n"
+ " -p, --pack Pack a string table from json format.\n"
+ " --region Specify ROM region. [USA, EU]\n"
+ " --source Specify source (-x: ROM, -p: JSON)\n"
+ " --dest Specify string table destination.\n"
+ " --size Specify string table size.\n",
+ progname);
+}
+
+// clang-format off
+constexpr const struct option long_options[] = {
+ {"extract", no_argument, nullptr, 'x'},
+ {"pack", no_argument, nullptr, 'p'},
+ {"region", required_argument, nullptr, 0},
+ {"source", required_argument, nullptr, 1},
+ {"dest", required_argument, nullptr, 2},
+ {"size", required_argument, nullptr, 3},
+ {},
+};
+// clang-format on
+
+int main(int argc, char** argv) {
+ std::string src_path;
+ std::string dst_path;
+ std::size_t max_size = 0;
+
+ progname = (argc < 1) ? "tmc_strings" : argv[0];
+
+ enum {
+ Mode_Extract,
+ Mode_Pack,
+ } mode = Mode_Extract;
+
+ enum {
+ Region_USA,
+ Region_JAPAN,
+ Region_EU,
+ } region = Region_USA;
+
+ while (true) {
+ int opt_index;
+ int c = getopt_long(argc, argv, "xp", long_options, &opt_index);
+ if (c == -1)
+ break;
+
+ switch (c) {
+ case 'x':
+ mode = Mode_Extract;
+ break;
+ case 'p':
+ mode = Mode_Pack;
+ break;
+ case 0:
+ if (strcmp(optarg, "USA") == 0) {
+ region = Region_USA;
+ } else if (strcmp(optarg, "EU") == 0) {
+ region = Region_EU;
+ } else if (strcmp(optarg, "JAPAN") == 0) {
+ region = Region_JAPAN;
+ fmt::print("Region unsupported\n\n");
+ usage();
+ exit(EXIT_FAILURE);
+ }
+ break;
+ case 1:
+ src_path = optarg;
+ break;
+ case 2:
+ dst_path = optarg;
+ break;
+ case 3:
+ max_size = std::strtoul(optarg, nullptr, 0);
+ break;
+ default:
+ usage();
+ exit(EXIT_FAILURE);
+ }
+ }
+
+ switch (mode) {
+ case Mode_Extract:
+ if (src_path == "") {
+ fmt::print("ROM source path not supplied.\n\n");
+ usage();
+ exit(EXIT_FAILURE);
+ }
+ ExtractStringTable(src_path, [region]() {
+ switch (region) {
+ case Region_USA:
+ return LanguageTableUS;
+ case Region_EU:
+ return LanguageTableEU;
+ default:
+ fmt::print("Region unsupported\n\n");
+ usage();
+ exit(EXIT_FAILURE);
+ }
+ }());
+ break;
+ case Mode_Pack:
+ if (src_path == "" || dst_path == "") {
+ fmt::print("Source or destination path not supplied\n\n");
+ usage();
+ exit(EXIT_FAILURE);
+ }
+ if (max_size == 0) {
+ fmt::print("Max size not supplied. Assuming shiftable.\n");
+ }
+ PackStringTable(src_path, dst_path, max_size);
+ }
+
+ return EXIT_SUCCESS;
+}
diff --git a/tools/src/util/CMakeLists.txt b/tools/src/util/CMakeLists.txt
new file mode 100644
index 00000000..eb28af93
--- /dev/null
+++ b/tools/src/util/CMakeLists.txt
@@ -0,0 +1,5 @@
+add_library(util INTERFACE)
+target_include_directories(util INTERFACE .)
+
+# dummy target to make sure headers compile
+add_executable(util_dummy dummy.cpp)
diff --git a/tools/src/util/dummy.cpp b/tools/src/util/dummy.cpp
new file mode 100644
index 00000000..c20979ec
--- /dev/null
+++ b/tools/src/util/dummy.cpp
@@ -0,0 +1,5 @@
+#include "util/types.h"
+#include "util/file.h"
+
+int main() {
+}
diff --git a/tools/src/util/util/file.h b/tools/src/util/util/file.h
new file mode 100644
index 00000000..9c3c0132
--- /dev/null
+++ b/tools/src/util/util/file.h
@@ -0,0 +1,25 @@
+#ifndef TOOLS_FILE_H
+#define TOOLS_FILE_H
+
+#include <cstdio>
+#include <memory>
+#include <string>
+#include <string_view>
+
+namespace util {
+namespace {
+auto file_ptr_deleter = [](std::FILE* file) { std::fclose(file); };
+}
+
+using file_ptr_t = std::unique_ptr<std::FILE, decltype(file_ptr_deleter)>;
+
+inline file_ptr_t open_file(std::string_view filename, std::string_view mode) {
+ return { std::fopen(filename.data(), mode.data()), file_ptr_deleter };
+}
+
+inline file_ptr_t open_file(const std::string& filename, std::string_view mode) {
+ return { std::fopen(filename.c_str(), mode.data()), file_ptr_deleter };
+}
+
+} // namespace util
+#endif // TOOLS_FILE_H
diff --git a/tools/src/util/util/types.h b/tools/src/util/util/types.h
new file mode 100644
index 00000000..ef3e98f7
--- /dev/null
+++ b/tools/src/util/util/types.h
@@ -0,0 +1,15 @@
+#ifndef TOOLS_TYPES_H
+#define TOOLS_TYPES_H
+
+#include <cstdint>
+
+using s8 = int8_t;
+using u8 = uint8_t;
+using s16 = int16_t;
+using u16 = uint16_t;
+using s32 = int32_t;
+using u32 = uint32_t;
+using s64 = int64_t;
+using u64 = uint64_t;
+
+#endif // TOOLS_TYPES_H