Rewrite asset processor in C++

This commit is contained in:
octorock 2021-11-06 00:37:37 +01:00
parent a6d133e42d
commit 8d6c3fe541
35 changed files with 27666 additions and 40 deletions

View File

@ -164,7 +164,8 @@ setup: $(TOOLDIRS)
# TODO temporary workaround to have translation bins as dependencies manually here as scaninc somehow does not work?
extractassets: translations/USA.bin translations/English.bin translations/French.bin translations/German.bin translations/Spanish.bin translations/Italian.bin
python3 tools/asset_extractor/asset_extractor.py $(GAME_VERSION) $(ASSET_BUILDDIR)
tools/asset_processor/asset_processor extract $(GAME_VERSION) $(ASSET_BUILDDIR)
# python3 tools/asset_extractor/asset_extractor.py $(GAME_VERSION) $(ASSET_BUILDDIR)
$(TOOLDIRS):
@$(MAKE) -C $@
@ -188,8 +189,6 @@ tidy:
rm -f tmc_eu.gba tmc_eu.elf tmc_eu.map
rm -r build/*
include graphics_file_rules.mk
include songs.mk
%.s: ;
%.png: ;
@ -203,8 +202,6 @@ include songs.mk
%.gbapal: %.png ; $(GFX) $< $@
%.lz: % ; $(GFX) $< $@
%.rl: % ; $(GFX) $< $@
sound/%.bin: sound/%.aif ; $(AIF) $< $@
sound/songs/%.s: sound/songs/%.mid
cd $(@D) && ../../$(MID) $(<F)
translations/USA.bin: translations/USA.json ; tools/tmc_strings/tmc_strings -p --source $< --dest $@ --size 0x499E0
translations/English.bin: translations/English.json ; tools/tmc_strings/tmc_strings -p --source $< --dest $@ --size 0x488C0

View File

@ -56995,7 +56995,7 @@
"path": "data_08132B30/gSprite_Link.4bpp",
"start": 1289748,
"size": 581216,
"type": "graphic",
"type": "gfx",
"options": {
"width": 4
}
@ -57004,7 +57004,7 @@
"path": "data_08132B30/gSprite_081C8C74.4bpp",
"start": 1870964,
"size": 59904,
"type": "graphic",
"type": "gfx",
"options": {
"width": 4
}
@ -57013,7 +57013,7 @@
"path": "data_08132B30/gSprite_081D7674.4bpp",
"start": 1930868,
"size": 33216,
"type": "graphic",
"type": "gfx",
"options": {
"width": 4
}
@ -57022,7 +57022,7 @@
"path": "data_08132B30/gSprite_081DF834.4bpp",
"start": 1964084,
"size": 129408,
"type": "graphic",
"type": "gfx",
"options": {
"width": 4
}
@ -57031,7 +57031,7 @@
"path": "data_08132B30/gSprite_081FF1B4.4bpp",
"start": 2093492,
"size": 103168,
"type": "graphic",
"type": "gfx",
"options": {
"width": 4
}
@ -57040,7 +57040,7 @@
"path": "graphics/npc/postman.4bpp",
"start": 2196660,
"size": 22528,
"type": "graphic",
"type": "gfx",
"options": {
"width": 2
}
@ -57049,7 +57049,7 @@
"path": "graphics/npc/malon.4bpp",
"start": 2219188,
"size": 3136,
"type": "graphic",
"type": "gfx",
"options": {
"width": 2
}
@ -57058,7 +57058,7 @@
"path": "data_08132B30/gSprite_081FF1B4_1.4bpp",
"start": 2222324,
"size": 11527,
"type": "graphic",
"type": "gfx",
"options": {
"width": 4
}
@ -57072,7 +57072,7 @@
"EU": 2231527
},
"size": 1014,
"type": "graphic",
"type": "gfx",
"options": {
"width": 4
}
@ -57087,7 +57087,7 @@
],
"start": 2233851,
"size": 1014,
"type": "graphic",
"type": "gfx",
"options": {
"width": 4
}
@ -57096,7 +57096,7 @@
"path": "data_08132B30/gSprite_081FF1B4_4.4bpp",
"start": 2234865,
"size": 154339,
"type": "graphic",
"type": "gfx",
"options": {
"width": 4
}
@ -57105,7 +57105,7 @@
"path": "data_08132B30/gSprite_082474D4.4bpp",
"start": 2389204,
"size": 578496,
"type": "graphic",
"type": "gfx",
"options": {
"width": 4
}
@ -57114,13 +57114,13 @@
"path": "graphics/pot_portal.4bpp",
"start": 2967700,
"size": 4608,
"type": "graphic"
"type": "gfx"
},
{
"path": "data_08132B30/gSprite_082D4894.4bpp",
"start": 2972308,
"size": 69216,
"type": "graphic",
"type": "gfx",
"options": {
"width": 4
}
@ -57129,7 +57129,7 @@
"path": "data_08132B30/gSprite_082E68F4.4bpp",
"start": 3041524,
"size": 48896,
"type": "graphic",
"type": "gfx",
"options": {
"width": 4
}
@ -57138,7 +57138,7 @@
"path": "data_08132B30/gSprite_082F27F4.4bpp",
"start": 3090420,
"size": 5504,
"type": "graphic",
"type": "gfx",
"options": {
"width": 4
}

View File

@ -1590,4 +1590,4 @@ gSpriteAnimations_4:: @ 08007B28
.4byte gSpriteAnimations_4_93
.4byte gSpriteAnimations_4_94
.4byte gSpriteAnimations_1_226
.4byte 00000000
.4byte 00000000

1
graphics/.gitignore vendored
View File

@ -1 +0,0 @@
# placeholder

View File

@ -1,16 +0,0 @@
GFXDIR := graphics
$(GFXDIR)/intro/nintendo_capcom.4bpp: %.4bpp: %.png
$(GFX) $< $@ -num_tiles 123
$(GFXDIR)/font.4bpp: %.4bpp: %.png
$(GFX) $< $@
$(GFXDIR)/pot_portal.4bpp: %.4bpp: %.png
$(GFX) $< $@
$(GFXDIR)/npc/postman.4bpp: %.4bpp: %.png
$(GFX) $< $@
$(GFXDIR)/npc/malon.4bpp: %.4bpp: %.png
$(GFX) $< $@

View File

@ -1 +0,0 @@

1
tools/asset_processor/.gitignore vendored Normal file
View File

@ -0,0 +1 @@
asset_processor

View File

@ -0,0 +1,14 @@
CXX = g++
CXXFLAGS = -O3 -Wall -Wextra -std=c++17
#CXXFLAGS += -g # debug
INCLUDES = -I include/
SRCS = src/main.cpp src/asset.cpp src/animation.cpp src/spriteframe.cpp src/reader.cpp src/exitlist.cpp src/frameobjlists.cpp src/midi.cpp src/aif.cpp src/gfx.cpp src/util.cpp src/tileset.cpp src/palette.cpp
all: asset_processor
asset_processor: $(SRCS)
$(CXX) $(CXXFLAGS) $(INCLUDES) -o asset_processor $(SRCS)
clean:
rm asset_processor

View File

@ -0,0 +1,7 @@
#include "asset.h"
class AifAsset : public BaseAsset {
public:
using BaseAsset::BaseAsset;
virtual void convertToHumanReadable(const std::vector<char>& baserom);
};

View File

@ -0,0 +1,7 @@
#include "asset.h"
class AnimationAsset : public BaseAsset {
public:
using BaseAsset::BaseAsset;
virtual void convertToHumanReadable(const std::vector<char>& baserom);
};

View File

@ -0,0 +1,34 @@
#ifndef ASSET_H
#define ASSET_H
#include <assert.h>
#include <string>
#include <filesystem>
#include <json.hpp>
#include "util.h"
class BaseAsset {
public:
BaseAsset(std::filesystem::path path, int start, int size, nlohmann::json asset)
: path(path), start(start), size(size), asset(asset) {
binaryPath = path;//.replace_extension("bin");
}
virtual void extractBinary(const std::vector<char>& baserom);
virtual void convertToHumanReadable(const std::vector<char>& baserom) {
(void)baserom;
assert("not implemented");
}
virtual void convertToBinary() {
assert("not implemented");
}
protected:
std::filesystem::path path;
std::filesystem::path binaryPath;
int start;
int size;
nlohmann::json asset;
};
#endif

View File

@ -0,0 +1,7 @@
#include "asset.h"
class ExitListAsset : public BaseAsset {
public:
using BaseAsset::BaseAsset;
virtual void convertToHumanReadable(const std::vector<char>& baserom);
};

View File

@ -0,0 +1,7 @@
#include "asset.h"
class FrameObjListsAsset : public BaseAsset {
public:
using BaseAsset::BaseAsset;
virtual void convertToHumanReadable(const std::vector<char>& baserom);
};

View File

@ -0,0 +1,7 @@
#include "asset.h"
class GfxAsset : public BaseAsset {
public:
using BaseAsset::BaseAsset;
virtual void convertToHumanReadable(const std::vector<char>& baserom);
};

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,21 @@
#ifndef MAIN_H
#define MAIN_H
#include "util.h"
bool shouldExtractFile(const std::filesystem::path& path, const std::filesystem::file_time_type& configModified);
void extractFile(const std::filesystem::path& path, const nlohmann::json& asset, const int& start, const std::vector<char>& baserom);
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

View File

@ -0,0 +1,7 @@
#include "asset.h"
class MidiAsset : public BaseAsset {
public:
using BaseAsset::BaseAsset;
virtual void convertToHumanReadable(const std::vector<char>& baserom);
};

View File

@ -0,0 +1,7 @@
#include "asset.h"
class PaletteAsset : public BaseAsset {
public:
using BaseAsset::BaseAsset;
virtual void convertToHumanReadable(const std::vector<char>& baserom);
};

View File

@ -0,0 +1,69 @@
#ifndef READER_H
#define READER_H
#include <stdint.h>
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;
#include <vector>
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;
};
// TODO move to utils?
#include <memory>
#include <string>
#include <stdexcept>
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

View File

@ -0,0 +1,7 @@
#include "asset.h"
class SpriteFrameAsset : public BaseAsset {
public:
using BaseAsset::BaseAsset;
virtual void convertToHumanReadable(const std::vector<char>& baserom);
};

View File

@ -0,0 +1,7 @@
#include "asset.h"
class TilesetAsset : public BaseAsset {
public:
using BaseAsset::BaseAsset;
virtual void convertToHumanReadable(const std::vector<char>& baserom);
};

View File

@ -0,0 +1,23 @@
#ifndef UTIL_H
#define UTIL_H
#include <stdint.h>
#include <stdlib.h>
#include <string>
#include <vector>
#include <json.hpp>
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);
std::string to_string(const nlohmann::json& j);
#endif

View File

@ -0,0 +1,15 @@
#include "aif.h"
#include "util.h"
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);
std::filesystem::path aifPath = this->path;
aifPath.replace_extension(".aif");
cmd.push_back(aifPath);
check_call(cmd);
}

View File

@ -0,0 +1,36 @@
#include "animation.h"
#include "reader.h"
#include <iostream>
#include <fstream>
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->path.replace_extension("s"));
for (const auto& line : lines) {
out << line;
}
out.close();
}

View File

@ -0,0 +1,11 @@
#include <fstream>
#include "asset.h"
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->binaryPath, std::ios::out | std::ios::binary);
file.write(&data[0], data.size());
file.close();
}

View File

@ -0,0 +1,48 @@
#include "exitlist.h"
#include "reader.h"
#include <iostream>
#include <fstream>
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->path.replace_extension("s"));
for (const auto& line : lines) {
out << line;
}
out.close();
}

View File

@ -0,0 +1,95 @@
#include "frameobjlists.h"
#include "reader.h"
#include <iostream>
#include <fstream>
#include <algorithm>
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;
}
// std::cout << string_format("0x%x", reader.read_u8()) << std::endl;
// std::cout << string_format("0x%x", reader.read_u8()) << std::endl;
// std::cout << string_format("0x%x", reader.read_u8()) << std::endl;
// std::cout << string_format("0x%x", reader.read_u8()) << std::endl;
// reader.cursor -= 4;
u32 pointer = reader.read_u32();
// std::cout << pointer << std::endl;
// std::cout << string_format("0x%x", pointer) << std::endl;
// if (pointer > 0x10000 || pointer == 0xec0) {
// assert(false);
// }
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->path.replace_extension("s"));
for (const auto& line : lines) {
out << line;
}
out.close();
// assert(false);
}

View File

@ -0,0 +1,23 @@
#include "gfx.h"
#include "util.h"
void GfxAsset::convertToHumanReadable(const std::vector<char>& baserom) {
(void)baserom;
std::filesystem::path pngPath = this->path;
if (pngPath.extension() == ".lz") {
pngPath.replace_extension("");
}
pngPath.replace_extension(".png");
std::filesystem::path toolsPath = "tools";
std::vector<std::string> cmd;
cmd.push_back(toolsPath / "gbagfx" / "gbagfx");
cmd.push_back(this->path);
cmd.push_back(pngPath);
for (const auto& it : this->asset["options"].items()) {
cmd.push_back("-" + it.key());
cmd.push_back(to_string(it.value()));
}
check_call(cmd);
}

View File

@ -0,0 +1,253 @@
#include <iostream>
#include <fstream>
#include <filesystem>
#include <json.hpp>
#include "main.h"
#include "tileset.h"
#include "animation.h"
#include "spriteframe.h"
#include "exitlist.h"
#include "frameobjlists.h"
#include "midi.h"
#include "aif.h"
#include "gfx.h"
#include "palette.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 TODO Convert data to human readable format\n"
<< "build TODO Build binary 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 << "Unrecognized argument `" << args[0] << "`" << std::endl;
std::exit(1);
}
}
if (argCount != 3) {
std::cerr << "Not enough arguments: " << argCount << std::endl;
usage();
}
if (strcmp(args[0], "extract") == 0) {
gMode = EXTRACT;
} else if (strcmp(args[0], "convert") == 0) {
gMode = CONVERT;
std::cerr << "Mode `convert` not yet implemented." << std::endl;
std::exit(1);
} else if (strcmp(args[0], "build") == 0) {
gMode = BUILD;
std::cerr << "Mode `build` not yet implemented." << std::endl;
std::exit(1);
} else {
std::cerr << "Unsupported mode `" << args[0] << "`" << std::endl;
std::exit(1);
}
gVariant = args[1]; // TODO check valid variant
if (gVariant != "USA" && gVariant != "EU" && gVariant != "JP" && gVariant != "DEMO_USA" && gVariant != "DEMO_JP") {
std::cerr << "Unsupported variant `" << gVariant << "`" << std::endl;
std::exit(1);
}
gAssetsFolder = args[2];
gBaseromPath = baseroms[gVariant];
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)) {
assert(false); // Could not read baserom
}
file.close();
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);
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("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"];
// std::cout << path << std::endl;
if (shouldExtractFile(path, configModified)) {
if (gVerbose) {
std::cout << "Extracting " << path << "..." << std::endl;
}
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];
}
extractFile(path, asset, start, baserom);
}
}
}
}
if (gVerbose) {
std::cout << "done" << std::endl;
}
}
bool shouldExtractFile(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 extractFile(const std::filesystem::path& path, const json& asset, const int& start, const std::vector<char>& baserom) {
std::string type = "";
// std::cout << asset << std::endl;
if (asset.contains("type")) {
type = asset["type"];
}
// Create the parent directory
std::filesystem::path parentDir = std::filesystem::path(path);
parentDir.remove_filename();
std::filesystem::create_directories(parentDir);
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?
} // If an asset has no size, the extraction tool reads the baserom iself.
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_frames") { // TODO rename to 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") {
assetHandler = std::make_unique<BaseAsset>(path, start, size, asset);
// TODO implement conversions
} else if (type == "") {
assetHandler = std::make_unique<BaseAsset>(path, start, size, asset);
} else {
std::cerr << "Unimplemented type " << type << std::endl;
std::exit(1);
}
assetHandler->extractBinary(baserom);
assetHandler->convertToHumanReadable(baserom);
}

View File

@ -0,0 +1,86 @@
#include <filesystem>
#include <iostream>
#include "midi.h"
#include "reader.h"
#include "main.h"
#include "util.h"
void MidiAsset::convertToHumanReadable(const std::vector<char>& baserom) {
(void)baserom;
// Convert the options
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 {
commonParams.push_back("-" + key);
commonParams.push_back(to_string(it.value()));
}
}
if (exactGateTime) {
commonParams.push_back("-E");
}
std::filesystem::path toolPath = "tools";
std::filesystem::path midPath = this->path;
midPath.replace_extension(".mid");
std::vector<std::string> cmd;
cmd.push_back(toolPath / "agb2mid" / "agb2mid");
cmd.push_back(gBaseromPath);
cmd.push_back(string_format("0x%x", this->start));
cmd.push_back(gBaseromPath); // TODO deduplicate?
cmd.push_back(midPath);
cmd.insert(cmd.end(), commonParams.begin(), commonParams.end());
cmd.insert(cmd.end(), agb2midParams.begin(), agb2midParams.end());
check_call(cmd);
cmd.clear();
cmd.push_back(toolPath / "mid2agb" / "mid2agb");
cmd.push_back(midPath);
cmd.push_back(this->path);
cmd.insert(cmd.end(), commonParams.begin(), commonParams.end());
check_call(cmd);
}

View File

@ -0,0 +1,16 @@
#include "palette.h"
#include "util.h"
void PaletteAsset::convertToHumanReadable(const std::vector<char>& baserom) {
(void)baserom;
std::filesystem::path palPath = this->path;
palPath.replace_extension(".pal");
std::filesystem::path toolsPath = "tools";
std::vector<std::string> cmd;
cmd.push_back(toolsPath / "gbagfx" / "gbagfx");
cmd.push_back(this->path);
cmd.push_back(palPath);
check_call(cmd);
}

View File

@ -0,0 +1,8 @@
#include "reader.h"
std::string opt_param(const std::string& format, int defaultVal, int value) {
if (value != defaultVal) {
return string_format(format, value);
}
return "";
}

View File

@ -0,0 +1,25 @@
#include "spriteframe.h"
#include "reader.h"
#include <iostream>
#include <fstream>
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->path.replace_extension("s"));
for (const auto& line : lines) {
out << line;
}
out.close();
}

View File

@ -0,0 +1,21 @@
#include "tileset.h"
#include "util.h"
void TilesetAsset::convertToHumanReadable(const std::vector<char>& baserom) {
(void)baserom;
std::filesystem::path pngPath = this->path;
if (pngPath.extension() == ".lz") {
pngPath.replace_extension("");
}
pngPath.replace_extension(".png");
std::filesystem::path toolsPath = "tools";
std::vector<std::string> cmd;
cmd.push_back(toolsPath / "gbagfx" / "gbagfx");
cmd.push_back(this->path);
cmd.push_back(pngPath);
cmd.push_back("-mwidth");
cmd.push_back("32");
check_call(cmd);
}

View File

@ -0,0 +1,32 @@
#include "util.h"
#include <cassert>
#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;
}
// std::cout << cmdstr << std::endl;
// assert(false);
int code = system(cmdstr.c_str());
if (code != 0) {
std::cerr << cmdstr << " failed with return code " << code << std::endl;
std::exit(1);
}
}
// https://github.com/nlohmann/json/issues/642#issuecomment-311937344
std::string to_string(const nlohmann::json& j) {
if (j.type() == nlohmann::json::value_t::string) {
return j.get<std::string>();
}
return j.dump();
}