diff options
Diffstat (limited to 'tools/src')
32 files changed, 1168 insertions, 0 deletions
diff --git a/tools/src/CMakeLists.txt b/tools/src/CMakeLists.txt new file mode 100644 index 00000000..aca9c06a --- /dev/null +++ b/tools/src/CMakeLists.txt @@ -0,0 +1 @@ +add_subdirectory(asset_processor) diff --git a/tools/src/asset_processor/CMakeLists.txt b/tools/src/asset_processor/CMakeLists.txt new file mode 100644 index 00000000..7681fc18 --- /dev/null +++ b/tools/src/asset_processor/CMakeLists.txt @@ -0,0 +1,5 @@ +file(GLOB_RECURSE sources *.cpp) + +add_executable(asset_processor ${sources}) +target_include_directories(asset_processor PRIVATE .) +target_link_libraries(asset_processor PRIVATE nlohmann_json::nlohmann_json filesystem) diff --git a/tools/src/asset_processor/assets/aif.cpp b/tools/src/asset_processor/assets/aif.cpp new file mode 100644 index 00000000..f0c056af --- /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 path = this->path; + return 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 / "aif2pcm" / "aif2pcm"); + cmd.push_back(this->path); + cmd.push_back(this->assetPath); + check_call(cmd); +} + +void AifAsset::buildToBinary() { + std::filesystem::path toolsPath = "tools"; + std::vector<std::string> cmd; + cmd.push_back(toolsPath / "aif2pcm" / "aif2pcm"); + cmd.push_back(this->assetPath); + cmd.push_back(this->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..334d8093 --- /dev/null +++ b/tools/src/asset_processor/assets/animation.cpp @@ -0,0 +1,36 @@ +#include "animation.h" +#include "reader.h" +#include <fstream> +#include <iostream> + +void AnimationAsset::convertToHumanReadable(const std::vector<char>& baserom) { + Reader reader(baserom, this->start, this->size); + std::vector<std::string> lines; + bool end_of_animation = false; + while (!end_of_animation && reader.cursor + 3 < this->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; + lines.push_back(string_format("\tkeyframe frame_index=%d", frame_index)); + lines.push_back(opt_param(", duration=%d", 0, keyframe_duration)); + lines.push_back(opt_param(", bitfield=0x%x", 0, bitfield)); + lines.push_back(opt_param(", bitfield2=0x%x", 0, bitfield2)); + lines.push_back("\n"); + } + + if (!end_of_animation) { + lines.push_back("@ TODO why no terminator?\n"); + } + + while (reader.cursor < this->size) { + u8 keyframe_count = reader.read_u8(); + lines.push_back(string_format("\t.byte %d @ keyframe count\n", keyframe_count)); + } + std::ofstream out(this->assetPath); + for (const auto& line : lines) { + out << line; + } + out.close(); +} 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..139f11ad --- /dev/null +++ b/tools/src/asset_processor/assets/asset.cpp @@ -0,0 +1,11 @@ +#include "asset.h" +#include <fstream> + +void BaseAsset::extractBinary(const std::vector<char>& baserom) { + auto first = baserom.begin() + this->start; + auto last = baserom.begin() + this->start + this->size; + std::vector<char> data(first, last); + std::fstream file(this->path, std::ios::out | std::ios::binary); + file.write(&data[0], data.size()); + file.close(); +}
\ No newline at end of file diff --git a/tools/src/asset_processor/assets/asset.h b/tools/src/asset_processor/assets/asset.h new file mode 100644 index 00000000..96231b0b --- /dev/null +++ b/tools/src/asset_processor/assets/asset.h @@ -0,0 +1,78 @@ +#ifndef ASSET_H +#define ASSET_H + +#include "util.h" +#include <filesystem> +#include <nlohmann/json_fwd.hpp> +#include <string> +#include <iostream> + +class BaseAsset { + public: + BaseAsset(std::filesystem::path path, int start, int size, const nlohmann::json& asset) + : path(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 = this->generateAssetPath(); + buildPath = this->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(const std::vector<char>& baserom) { + (void)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. + std::filesystem::path getPath() const { + return path; + } + // Returns the path to the human readable asset file. + std::filesystem::path getAssetPath() const { + return assetPath; + } + // Returns the path to the resulting file after building. + std::filesystem::path getBuildPath() const { + return buildPath; + } + + // Returns the base of the filename of the asset. + std::string getSymbol() const { + // Need to get the stem twice to remove both of the .4bpp.lz extensions. + return (this->path.stem()).stem(); + } + + // Returns the start address of this asset. + 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 this->path; + } + virtual std::filesystem::path generateBuildPath() { + return this->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..c4e193c6 --- /dev/null +++ b/tools/src/asset_processor/assets/exitlist.cpp @@ -0,0 +1,48 @@ +#include "exitlist.h" +#include "reader.h" +#include <fstream> +#include <iostream> + +void ExitListAsset::convertToHumanReadable(const std::vector<char>& baserom) { + Reader reader(baserom, this->start, this->size); + std::vector<std::string> lines; + while (reader.cursor < this->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) { + lines.push_back("\texit_list_end\n"); + break; + } + lines.push_back(string_format("\texit transition=%d", transition_type)); + lines.push_back(opt_param(", x=0x%x", 0, x_pos)); + lines.push_back(opt_param(", y=0x%x", 0, y_pos)); + lines.push_back(opt_param(", destX=0x%x", 0, dest_x)); + lines.push_back(opt_param(", destY=0x%x", 0, dest_y)); + lines.push_back(opt_param(", screenEdge=0x%x", 0, screen_edge)); + lines.push_back(opt_param(", destArea=0x%x", 0, dest_area)); + lines.push_back(opt_param(", destRoom=0x%x", 0, dest_room)); + lines.push_back(opt_param(", unknown=0x%x", 0, unknown_2)); + lines.push_back(opt_param(", unknown2=0x%x", 0, unknown_3)); + lines.push_back(opt_param(", unknown3=0x%x", 0, unknown_4)); + lines.push_back(opt_param(", unknown4=0x%x", 0, unknown_5)); + lines.push_back(opt_param(", padding=0x%x", 0, padding_1)); + lines.push_back("\n"); + } + + std::ofstream out(this->assetPath); + for (const auto& line : lines) { + out << line; + } + out.close(); +}
\ 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..fa78fddd --- /dev/null +++ b/tools/src/asset_processor/assets/frameobjlists.cpp @@ -0,0 +1,80 @@ +#include "frameobjlists.h" +#include "reader.h" +#include <algorithm> +#include <fstream> +#include <iostream> + +void FrameObjListsAsset::convertToHumanReadable(const std::vector<char>& baserom) { + Reader reader(baserom, this->start, this->size); + + std::vector<int> first_level; + std::vector<int> second_level; + + std::vector<std::string> lines; + lines.push_back("@ First level of offsets\n"); + + while (reader.cursor < this->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); + lines.push_back(string_format("\t.4byte 0x%x\n", pointer)); + } + + lines.push_back("\n@ Second level of offsets\n"); + + while (reader.cursor < this->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); + lines.push_back(string_format("\t.4byte 0x%x\n", pointer)); + } + + int max_second_level = *std::max_element(second_level.begin(), second_level.end()); + + while (reader.cursor < this->size) { + if (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 > reader.cursor && (next == -1 || i < next)) { + next = i; + } + } + int diff = next - reader.cursor; + lines.push_back(string_format("@ Skipping %d bytes\n", diff)); + for (int i = 0; i < diff; i++) { + u8 byte = reader.read_u8(); + lines.push_back(string_format("\t.byte %d\n", byte)); + } + } + u8 num_objects = reader.read_u8(); + lines.push_back(string_format("\t.byte %d @ 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(); + lines.push_back(string_format("\tobj x=%d, y=%d", x_offset, y_offset)); + lines.push_back(opt_param(", bitfield=0x%x", 0, bitfield)); + lines.push_back(opt_param(", bitfield2=0x%x", 0, bitfield2)); + lines.push_back("\n"); + } + } + + std::ofstream out(this->assetPath); + for (const auto& line : lines) { + out << line; + } + out.close(); +}
\ 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..03feca77 --- /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 = this->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 / "gbagfx" / "gbagfx"); + cmd.push_back(this->path); + cmd.push_back(this->assetPath); + if (this->asset.contains("options")) { + for (const auto& it : this->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 / "gbagfx" / "gbagfx"); + cmd.push_back(this->assetPath); + cmd.push_back(this->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..c87f355b --- /dev/null +++ b/tools/src/asset_processor/assets/macroasm.cpp @@ -0,0 +1,20 @@ +#include "macroasm.h" +#include <fstream> + +std::filesystem::path BaseMacroAsmAsset::generateAssetPath() { + std::filesystem::path path = this->path; + return path.replace_extension(".s"); +} + +std::filesystem::path BaseMacroAsmAsset::generateBuildPath() { + std::filesystem::path path = this->path; + return 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. + std::ofstream out(this->assetPath); + out << "\t.incbin " << this->path << "\n"; + out.close(); +}
\ 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..6444b05e --- /dev/null +++ b/tools/src/asset_processor/assets/midi.cpp @@ -0,0 +1,156 @@ +#include "midi.h" +#include "reader.h" +#include "util.h" +#include <filesystem> +#include <iostream> +#include <fstream> +#include <nlohmann/json.hpp> + +extern std::string gBaseromPath; + +std::filesystem::path MidiAsset::generateAssetPath() { + std::filesystem::path path = this->path; + return path.replace_extension(".mid"); +} + +std::filesystem::path MidiAsset::generateBuildPath() { + std::filesystem::path path = this->path; + return 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 = this->path.stem(); + + std::filesystem::path tracksPath = this->path; + tracksPath.replace_filename(label + "_tracks.bin"); + std::filesystem::path headerPath = this->path; + headerPath.replace_filename(label + "_header.bin"); + + int headerOffset = this->asset["options"]["headerOffset"]; + + // Extract tracks + { + auto first = baserom.begin() + this->start; + auto last = baserom.begin() + this->start + headerOffset; + std::vector<char> data(first, last); + std::fstream file(tracksPath, std::ios::out | std::ios::binary); + file.write(&data[0], data.size()); + file.close(); + } + // Extract header + { + auto first = baserom.begin() + this->start + headerOffset; + auto last = baserom.begin() + this->start + this->size; + std::vector<char> data(first, last); + std::fstream file(headerPath, std::ios::out | std::ios::binary); + file.write(&data[0], data.size()); + file.close(); + } + + // Create dummy .s file. + std::ofstream out(this->buildPath); + out << "\t.incbin " << tracksPath << "\n"; + out << label << "::\n"; + out << "\t.incbin " << headerPath << "\n"; + out.close(); +} + +void MidiAsset::parseOptions(std::vector<std::string>& commonParams, std::vector<std::string>& agb2midParams) { + bool exactGateTime = true; + + for (const auto& it : this->asset["options"].items()) { + const std::string& key = it.key(); + if (key == "group" || key == "G") { + commonParams.push_back("-G"); + commonParams.push_back(to_string(it.value())); + } else if (key == "priority" || key == "P") { + commonParams.push_back("-P"); + commonParams.push_back(to_string(it.value())); + } else if (key == "reverb" || key == "R") { + commonParams.push_back("-R"); + commonParams.push_back(to_string(it.value())); + } else if (key == "nominator") { + agb2midParams.push_back("-n"); + agb2midParams.push_back(to_string(it.value())); + } else if (key == "denominator") { + agb2midParams.push_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.push_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.push_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.push_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; + this->parseOptions(commonParams, agb2midParams); + + int headerOffset = this->asset["options"]["headerOffset"]; + + std::filesystem::path toolPath = "tools"; + std::vector<std::string> cmd; + cmd.push_back(toolPath / "agb2mid" / "agb2mid"); + cmd.push_back(gBaseromPath); + cmd.push_back(string_format("0x%x", this->start + headerOffset)); + cmd.push_back(gBaseromPath); // TODO deduplicate? + cmd.push_back(this->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 / "mid2agb" / "mid2agb"); + cmd.push_back(this->assetPath); + cmd.push_back(this->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; + this->parseOptions(commonParams, agb2midParams); + std::filesystem::path toolPath = "tools"; + std::vector<std::string> cmd; + cmd.push_back(toolPath / "mid2agb" / "mid2agb"); + cmd.push_back(this->assetPath); + cmd.push_back(this->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..74cf94c3 --- /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 path = this->path; + return 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 / "gbagfx" / "gbagfx"); + cmd.push_back(this->path); + cmd.push_back(this->assetPath); + check_call(cmd); +} + +void PaletteAsset::buildToBinary() { + std::filesystem::path toolsPath = "tools"; + std::vector<std::string> cmd; + cmd.push_back(toolsPath / "gbagfx" / "gbagfx"); + cmd.push_back(this->assetPath); + cmd.push_back(this->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..6aea52cf --- /dev/null +++ b/tools/src/asset_processor/assets/spriteframe.cpp @@ -0,0 +1,25 @@ +#include "spriteframe.h" +#include "reader.h" +#include <fstream> +#include <iostream> + +void SpriteFrameAsset::convertToHumanReadable(const std::vector<char>& baserom) { + Reader reader(baserom, this->start, this->size); + std::vector<std::string> lines; + while (reader.cursor < this->size) { + u8 num_gfx_tiles = reader.read_u8(); + u8 unk = reader.read_u8(); + u16 first_gfx_tile_index = reader.read_u16(); + + lines.push_back(string_format("\tsprite_frame first_tile_index=0x%x", first_gfx_tile_index)); + lines.push_back(opt_param(", num_tiles=%d", 0, num_gfx_tiles)); + lines.push_back(opt_param(", unknown=0x%x", 0, unk)); + lines.push_back("\n"); + } + + std::ofstream out(this->assetPath); + for (const auto& line : lines) { + out << line; + } + out.close(); +}
\ 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..52caaa6b --- /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 = this->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 / "gbagfx" / "gbagfx"); + cmd.push_back(this->path); + cmd.push_back(this->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 / "gbagfx" / "gbagfx"); + cmd.push_back(this->assetPath); + cmd.push_back(this->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..347f27c5 --- /dev/null +++ b/tools/src/asset_processor/main.cpp @@ -0,0 +1,313 @@ +#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> + +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); + std::streamsize size = file.tellg(); + file.seekg(0, std::ios::beg); + + std::vector<char> baserom(size); + if (!file.read(baserom.data(), size)) { + std::cerr << "Could not read baserom " << gBaseromPath << std::endl; + 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; + + uint 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 + std::filesystem::path path = gAssetsFolder; + path = path / asset["calculateOffsets"]; + int 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 (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 (shouldBuildAsset(assetHandler)) { + if (gVerbose) { + std::cout << "Building " << assetHandler->getAssetPath() << "..." << std::endl; + } + buildAsset(assetHandler); + } + 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 == "") { + // Unknown binary asset + assetHandler = std::make_unique<BaseAsset>(path, start, size, asset); + } else { + std::cerr << "Error: Unimplemented asset type `" << type << "`" << std::endl; + 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 = std::filesystem::path(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..45d991a3 --- /dev/null +++ b/tools/src/asset_processor/offsets.cpp @@ -0,0 +1,9 @@ +#include "offsets.h" + +OffsetCalculator::OffsetCalculator(std::filesystem::path outputFile, int baseOffset): baseOffset(baseOffset) { + output = std::ofstream(outputFile); +} + +void OffsetCalculator::addAsset(int start, std::string symbol) { + this->output << "\t.equiv offset_" << symbol << ", " << start - this->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..2130f6cb --- /dev/null +++ b/tools/src/asset_processor/offsets.h @@ -0,0 +1,18 @@ +#ifndef OFFSETS_H +#define OFFSETS_H +#include <filesystem> +#include <fstream> +#include <string> + +class OffsetCalculator { + +public: + OffsetCalculator(std::filesystem::path offsetsFile, int baseOffset); + void addAsset(int start, std::string symbol); + +private: + std::ofstream output; + int baseOffset; +}; + +#endif
\ No newline at end of file diff --git a/tools/src/asset_processor/reader.cpp b/tools/src/asset_processor/reader.cpp new file mode 100644 index 00000000..4ce7842e --- /dev/null +++ b/tools/src/asset_processor/reader.cpp @@ -0,0 +1,10 @@ +#include "reader.h" +#include "util.h" +#include <string> + +std::string opt_param(const std::string& format, int defaultVal, int value) { + if (value != defaultVal) { + return string_format(format, value); + } + return ""; +}
\ 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..4ab548de --- /dev/null +++ b/tools/src/asset_processor/reader.h @@ -0,0 +1,50 @@ +#ifndef READER_H +#define READER_H + +#include <stdint.h> +#include <vector> + +typedef uint8_t u8; +typedef uint16_t u16; +typedef uint32_t u32; +typedef uint64_t u64; +typedef int8_t s8; +typedef int16_t s16; +typedef int32_t s32; +typedef int64_t s64; + +class Reader { + public: + Reader(const std::vector<char>& baserom, int start, int size) { + auto first = baserom.begin() + start; + auto last = baserom.begin() + start + size; + data = std::vector<char>(first, last); + } + + u8 read_u8() { + return this->data[this->cursor++]; + } + + s8 read_s8() { + return (s8)this->read_u8(); + } + + u16 read_u16() { + u16 val = (u8)this->data[this->cursor] + (((u8)this->data[this->cursor + 1]) << 8); + this->cursor += 2; + return val; + } + + u32 read_u32() { + u32 val = ((u8)this->data[this->cursor]) + (((u8)this->data[this->cursor + 1]) << 8) + + (((u8)this->data[this->cursor + 2]) << 16) + (((u8)this->data[this->cursor + 3]) << 24); + this->cursor += 4; + return val; + } + int cursor = 0; + + private: + std::vector<char> data; +}; + +#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..f6ac050f --- /dev/null +++ b/tools/src/asset_processor/util.cpp @@ -0,0 +1,20 @@ +#include "util.h" +#include <iostream> + +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); + } +} diff --git a/tools/src/asset_processor/util.h b/tools/src/asset_processor/util.h new file mode 100644 index 00000000..f7e5c5f5 --- /dev/null +++ b/tools/src/asset_processor/util.h @@ -0,0 +1,36 @@ +#ifndef UTIL_H +#define UTIL_H + +#include <nlohmann/json_fwd.hpp> +#include <memory> +#include <stdexcept> +#include <stdint.h> +#include <stdlib.h> +#include <string> +#include <vector> + +typedef uint8_t u8; +typedef uint16_t u16; +typedef uint32_t u32; +typedef uint64_t u64; +typedef int8_t s8; +typedef int16_t s16; +typedef int32_t s32; +typedef int64_t s64; + +void check_call(const std::vector<std::string>& cmd); + +template <typename... Args> std::string string_format(const std::string& format, Args... args) { + int size_s = std::snprintf(nullptr, 0, format.c_str(), args...) + 1; // Extra space for '\0' + if (size_s <= 0) { + throw std::runtime_error("Error during formatting."); + } + auto size = static_cast<size_t>(size_s); + auto buf = std::make_unique<char[]>(size); + std::snprintf(buf.get(), size, format.c_str(), args...); + return std::string(buf.get(), buf.get() + size - 1); // We don't want the '\0' inside +} + +std::string opt_param(const std::string& format, int defaultVal, int value); + +#endif
\ No newline at end of file |
