diff options
| author | Henny022p <info@henny022.de> | 2022-01-31 17:30:51 +0100 |
|---|---|---|
| committer | Henny022p <info@henny022.de> | 2022-01-31 17:30:51 +0100 |
| commit | 2a53ee4bf2ee7eea617280118c2188bffec77ae5 (patch) | |
| tree | 85ed631ab330f9ca3734bb77253a374e3ef0de21 /tools/src | |
| parent | 9b60d9c5e24ed592524c25a9dc013417d6e7ee0c (diff) | |
| parent | 2b8d880ffc0a4548d25dc613d53a908d36362d84 (diff) | |
merge master
Diffstat (limited to 'tools/src')
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 |
