summaryrefslogtreecommitdiff
path: root/tools/src/asset_processor
diff options
context:
space:
mode:
authorHenny022p <info@henny022.de>2022-01-31 17:30:51 +0100
committerHenny022p <info@henny022.de>2022-01-31 17:30:51 +0100
commit2a53ee4bf2ee7eea617280118c2188bffec77ae5 (patch)
tree85ed631ab330f9ca3734bb77253a374e3ef0de21 /tools/src/asset_processor
parent9b60d9c5e24ed592524c25a9dc013417d6e7ee0c (diff)
parent2b8d880ffc0a4548d25dc613d53a908d36362d84 (diff)
merge master
Diffstat (limited to 'tools/src/asset_processor')
-rw-r--r--tools/src/asset_processor/CMakeLists.txt8
-rw-r--r--tools/src/asset_processor/assets/aif.cpp27
-rw-r--r--tools/src/asset_processor/assets/aif.h11
-rw-r--r--tools/src/asset_processor/assets/animation.cpp32
-rw-r--r--tools/src/asset_processor/assets/animation.h7
-rw-r--r--tools/src/asset_processor/assets/asset.cpp7
-rw-r--r--tools/src/asset_processor/assets/asset.h79
-rw-r--r--tools/src/asset_processor/assets/exitlist.cpp43
-rw-r--r--tools/src/asset_processor/assets/exitlist.h7
-rw-r--r--tools/src/asset_processor/assets/frameobjlists.cpp75
-rw-r--r--tools/src/asset_processor/assets/frameobjlists.h7
-rw-r--r--tools/src/asset_processor/assets/gfx.cpp38
-rw-r--r--tools/src/asset_processor/assets/gfx.h11
-rw-r--r--tools/src/asset_processor/assets/macroasm.cpp20
-rw-r--r--tools/src/asset_processor/assets/macroasm.h17
-rw-r--r--tools/src/asset_processor/assets/midi.cpp147
-rw-r--r--tools/src/asset_processor/assets/midi.h14
-rw-r--r--tools/src/asset_processor/assets/palette.cpp27
-rw-r--r--tools/src/asset_processor/assets/palette.h11
-rw-r--r--tools/src/asset_processor/assets/spriteframe.cpp20
-rw-r--r--tools/src/asset_processor/assets/spriteframe.h7
-rw-r--r--tools/src/asset_processor/assets/tileset.cpp33
-rw-r--r--tools/src/asset_processor/assets/tileset.h11
-rw-r--r--tools/src/asset_processor/main.cpp342
-rw-r--r--tools/src/asset_processor/main.h24
-rw-r--r--tools/src/asset_processor/offsets.cpp9
-rw-r--r--tools/src/asset_processor/offsets.h25
-rw-r--r--tools/src/asset_processor/reader.h38
-rw-r--r--tools/src/asset_processor/util.cpp28
-rw-r--r--tools/src/asset_processor/util.h15
30 files changed, 1140 insertions, 0 deletions
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