tmc/tools/asset_processor/main.cpp

313 lines
12 KiB
C++

#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 <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();
}