From 706304fab42296d91a78f5952a95a2255b3a66ad Mon Sep 17 00:00:00 2001 From: Pseurae Date: Tue, 25 Jul 2023 11:59:28 +0530 Subject: [PATCH] Add makeips --- .gitignore | 3 +- Makefile | 18 +++- main.asm | 2 +- tools/elfedit/elfedit.c | 22 +++++ tools/elfedit/elfedit.h | 22 +++++ tools/makeips/.gitignore | 1 + tools/makeips/Makefile | 18 ++++ tools/makeips/makeips.cpp | 202 ++++++++++++++++++++++++++++++++++++++ tools/makeips/makeips.h | 72 ++++++++++++++ 9 files changed, 355 insertions(+), 5 deletions(-) create mode 100644 tools/makeips/.gitignore create mode 100644 tools/makeips/Makefile create mode 100644 tools/makeips/makeips.cpp create mode 100644 tools/makeips/makeips.h diff --git a/.gitignore b/.gitignore index a6c7b06..d611c47 100644 --- a/.gitignore +++ b/.gitignore @@ -5,4 +5,5 @@ output.* snakewood.* pokeruby.map .D[Ss]_[Ss]tore -[Dd]esktop.ini \ No newline at end of file +[Dd]esktop.ini +*.ips \ No newline at end of file diff --git a/Makefile b/Makefile index 4979bb6..cb6b870 100644 --- a/Makefile +++ b/Makefile @@ -1,5 +1,8 @@ #!/usr/bin/env make +INPUT := snakewood.gba +OUTPUT := output.gba + ifeq ($(strip $(DEVKITARM)),) $(error "Please set DEVKITARM in your environment. export DEVKITARM=devkitARM") endif @@ -29,9 +32,12 @@ LD = $(PREFIX)ld LDFLAGS = -i rom.ld -T linker.ld ARMIPS := armips +ARMIPSFLAGS := -strequ INPUT_FILE $(INPUT) -strequ OUTPUT_FILE $(OUTPUT) + ELFEDIT := tools/elfedit/elfedit$(EXE) PREPROC := tools/preproc/preproc$(EXE) SCANINC := tools/scaninc/scaninc$(EXE) +MAKEIPS := tools/makeips/makeips$(EXE) .DEFAULT_GOAL = all @@ -41,18 +47,24 @@ ifeq (,$(filter-out all,$(MAKECMDGOALS))) $(shell $(MAKE) tools > /dev/null) endif -all: test.gba - $(ARMIPS) main.asm -sym2 output.map +all: $(OUTPUT) + @$(ARMIPS) $(ARMIPSFLAGS) main.asm -sym2 output.map + @echo "$(ARMIPS) main.asm -sym2 output.map" + +patch: all + $(MAKEIPS) $(OUTPUT) $(INPUT) patch.ips -test.gba: snakewood.gba build/linked_processed.o +$(OUTPUT): $(INPUT) build/linked_processed.o tools: @$(MAKE) -C tools/elfedit + @$(MAKE) -C tools/makeips @$(MAKE) -C tools/preproc @$(MAKE) -C tools/scaninc clean_tools: @$(MAKE) -C tools/elfedit clean + @$(MAKE) -C tools/makeips clean @$(MAKE) -C tools/preproc clean @$(MAKE) -C tools/scaninc clean diff --git a/main.asm b/main.asm index 99a7bb9..bcdc80d 100644 --- a/main.asm +++ b/main.asm @@ -1,7 +1,7 @@ .gba .thumb -.open "snakewood.gba", "output.gba", 0x8000000 +.open INPUT_FILE, OUTPUT_FILE, 0x8000000 .loadtable "charmap.tbl" diff --git a/tools/elfedit/elfedit.c b/tools/elfedit/elfedit.c index 4d11a30..6c3294a 100644 --- a/tools/elfedit/elfedit.c +++ b/tools/elfedit/elfedit.c @@ -1,3 +1,25 @@ +/* + Copyright 2023 Pseurae + + 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 "elfedit.h" static int check_file(t_elf *const elf) diff --git a/tools/elfedit/elfedit.h b/tools/elfedit/elfedit.h index cd974ce..02dab16 100644 --- a/tools/elfedit/elfedit.h +++ b/tools/elfedit/elfedit.h @@ -1,3 +1,25 @@ +/* + Copyright 2023 Pseurae + + 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 ELFEDIT_H #define ELFEDIT_H diff --git a/tools/makeips/.gitignore b/tools/makeips/.gitignore new file mode 100644 index 0000000..5e79d88 --- /dev/null +++ b/tools/makeips/.gitignore @@ -0,0 +1 @@ +makeips \ No newline at end of file diff --git a/tools/makeips/Makefile b/tools/makeips/Makefile new file mode 100644 index 0000000..31750b8 --- /dev/null +++ b/tools/makeips/Makefile @@ -0,0 +1,18 @@ +CXX = g++ + +CXXFLAGS = -Wall -std=c++20 -O2 + +SRCS = makeips.cpp + +HEADERS := makeips.h + +.PHONY: all clean + +all: makeips + @: + +makeips: $(SRCS) $(HEADERS) + $(CXX) $(CXXFLAGS) $(SRCS) -o $@ $(LDFLAGS) + +clean: + $(RM) makeips makeips.exe diff --git a/tools/makeips/makeips.cpp b/tools/makeips/makeips.cpp new file mode 100644 index 0000000..2ad710c --- /dev/null +++ b/tools/makeips/makeips.cpp @@ -0,0 +1,202 @@ +/* + Copyright 2023 Pseurae + + 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 "makeips.h" + +void fill_records(Context &ctx) +{ + std::ifstream input(ctx.argv[1], std::ios::binary | std::ios::ate), base(ctx.argv[2], std::ios::binary | std::ios::ate); + if (input.fail()) + throw std::runtime_error("cannot open input file"); + if (base.fail()) + throw std::runtime_error("cannot open base file"); + + size_t inputsize = input.tellg(), basesize = base.tellg(); + + input.seekg(0), base.seekg(0); + + bool changed = false; + + size_t offset = 0; + Record::Bytes bufferInput = {}, bufferBase = {}; + + while (!input.eof() || !base.eof()) + { + char i, b; + input.read(&i, 1); + base.read(&b, 1); + + if (!changed) + { + if (i != b) + { + offset = (size_t)base.tellg() - 1; + bufferInput.push_back(i); + bufferBase.push_back(b); + changed = true; + } + } + else + { + if (i == b) + { + ctx.records.emplace_back(offset, bufferInput, bufferBase); + bufferInput.clear(); + bufferBase.clear(); + changed = false; + } + else + { + bufferInput.push_back(i); + bufferBase.push_back(b); + } + } + } + + if (changed) + { + ctx.records.emplace_back(offset, bufferInput, bufferBase); + bufferInput.clear(); + bufferBase.clear(); + } + + if (inputsize > basesize) + { + bufferInput.resize(inputsize - basesize); + input.read((char *)bufferInput.data(), inputsize - basesize); + + bufferBase.resize(inputsize - basesize); + std::memset((char *)bufferBase.data(), 0, inputsize - basesize); + + ctx.records.emplace_back(basesize, bufferInput, bufferBase); + bufferInput.clear(); + bufferBase.clear(); + } + + ctx.inputsize = inputsize; + ctx.basesize = basesize; + + input.close(); + base.close(); +} + +void generate_ips_patch(Context &ctx) +{ + std::ofstream patch(ctx.argv[3], std::ios::binary); + patch.write("PATCH", 5); + + for (auto &r : ctx.records) + { + unsigned int address = r.GetOffset(); + + if (address >= 0x1000000) + throw std::runtime_error("address too big"); + + auto data = r.GetInput(); + unsigned short size = data.size(); + unsigned int offset = 0; + + if (size == 0) + throw std::runtime_error("size is 0"); + + while (size) + { + unsigned char block[5] = { 0 }; + unsigned char rle_block[3] = { 0 }; + unsigned int localAddress = address + offset; + unsigned short localSize = size % __UINT16_MAX__; + size -= localSize; + + auto addressBytes = reinterpret_cast(&localAddress); + auto sizeBytes = reinterpret_cast(&localSize); + + if (std::adjacent_find(data.begin(), data.end(), std::not_equal_to()) == data.end() && size > 2) + { + + block[0] = addressBytes[2]; + block[1] = addressBytes[1]; + block[2] = addressBytes[0]; + + block[3] = 0; + block[4] = 0; + + patch.write((char *)block, 5); + + rle_block[0] = sizeBytes[1]; + rle_block[1] = sizeBytes[0]; + rle_block[2] = data[0]; + + patch.write((char *)rle_block, 3); + } + else + { + block[0] = addressBytes[2]; + block[1] = addressBytes[1]; + block[2] = addressBytes[0]; + + block[3] = sizeBytes[1]; + block[4] = sizeBytes[0]; + + patch.write((char *)block, 5); + patch.write((char *)data.data(), localSize); + } + + offset += localSize; + } + } + + patch.write("EOF", 3); + patch.close(); +} + +void generate_patch(Context &ctx) +{ + if (ctx.records.empty()) + throw std::runtime_error("no file diff"); + + generate_ips_patch(ctx); +} + +void create_patch(Context &ctx) +{ + fill_records(ctx); + generate_patch(ctx); +} + +int main(int argc, char *argv[]) +{ + if (argc < 4) + THROW_ERROR("error: patch [INPUT] [BASE] [OUTPUT]"); + + Context ctx { argv }; + + try + { + create_patch(ctx); + } + catch (std::runtime_error &err) + { + THROW_ERROR("error: %s", err.what()); + } + + return 0; +} \ No newline at end of file diff --git a/tools/makeips/makeips.h b/tools/makeips/makeips.h new file mode 100644 index 0000000..b8b0e2a --- /dev/null +++ b/tools/makeips/makeips.h @@ -0,0 +1,72 @@ +/* + Copyright 2023 Pseurae + + 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 MAKEIPS_H +#define MAKEIPS_H + +#include +#include +#include +#include + +#define THROW_ERROR(...) \ + (std::fprintf(stderr, __VA_ARGS__), std::fprintf(stderr, "\n"), std::exit(1)) + +class Record final +{ +public: + using Bytes = std::vector; + Record(size_t offset, const Bytes &input, const Bytes &base) noexcept : m_Offset(offset) + { + CopyBuffer(base, m_Base); + CopyBuffer(input, m_Input); + } + ~Record() = default; + Record(const Record&) = delete; + Record(Record&&) noexcept = delete; + Record& operator=(const Record&) = delete; + Record& operator=(Record&&) noexcept = delete; + + const auto &GetInput() const noexcept { return m_Input; } + const auto &GetBase() const noexcept { return m_Base; } + const inline auto GetOffset() const noexcept { return m_Offset; } + +private: + void CopyBuffer(const Bytes &src, Bytes &dst) + { + dst.resize(src.size()); + memcpy(dst.data(), src.data(), src.size()); + } + + Bytes m_Input; + Bytes m_Base; + size_t m_Offset; +}; + +struct Context final +{ + char **const argv; + size_t inputsize, basesize; + std::list records; +}; + +#endif // MAKEIPS_H \ No newline at end of file