Asset Revision 4

This commit is contained in:
David
2024-04-10 15:02:22 -04:00
parent fcc9c24c59
commit ecfd38f686
350 changed files with 53719 additions and 7792 deletions
+10 -6
View File
@@ -1,11 +1,16 @@
OPT := -O2
CC := gcc
CXX := g++
CFLAGS := -I . -Wall -Wextra -Wno-unused-parameter -pedantic -std=c99 -O2 -s
CXXFLAGS := -I . -Wall -Wextra -std=c++17
CFLAGS := -I . -Wall -Wextra -Wno-unused-parameter -pedantic -std=c99 $(OPT) -s
# List of specific errors to ignore
IGNORE_W_ERRORS := -Wno-unused-parameter -Wno-misleading-indentation -Wno-extra
CXXFLAGS := -I . -I dkr_assets_tool_src/ -Werror -Wall $(IGNORE_W_ERRORS) -std=c++17 $(OPT) -lpcre2-8
LDFLAGS := -lm
PROGRAMS := n64crc dkr_assets_tool
BUILD_DIR := dkr_assets_tool_classes/_build
BUILD_DIR := dkr_assets_tool_src/_build
ENUMS_HEADER := ../include/enums.h
@@ -29,10 +34,9 @@ distclean: clean
n64crc: n64crc.c
$(CC) $(CFLAGS) -o $@ $< $(LDFLAGS)
dkr_assets_tool_CPP_FILES := dkr_assets_tool.cpp \
$(call rwildcard,dkr_assets_tool_classes,*.cpp)
dkr_assets_tool_CPP_FILES := $(call rwildcard,dkr_assets_tool_src,*.cpp)
dkr_assets_tool_C_FILES := $(call rwildcard,dkr_assets_tool_classes,*.c)
dkr_assets_tool_C_FILES := $(call rwildcard,dkr_assets_tool_src,*.c)
dkr_assets_tool_OBJ_FILES := $(foreach file,$(dkr_assets_tool_C_FILES),$(BUILD_DIR)/$(file:.c=.o)) \
$(foreach file,$(dkr_assets_tool_CPP_FILES),$(BUILD_DIR)/$(file:.cpp=.o))
-204
View File
@@ -1,204 +0,0 @@
/**
* This tool handles everything assets-related including extracting and building.
*/
#include <iostream>
#include <string>
#include <vector>
#include "dkr_assets_tool_classes/common/util.h"
#include "dkr_assets_tool_classes/extractor/extractor.h"
#include "dkr_assets_tool_classes/builder/builder.h"
#include "dkr_assets_tool_classes/make_asset_files/asset_enums_header.h"
#include "dkr_assets_tool_classes/make_asset_files/asset_asm_data.h"
#include "dkr_assets_tool_classes/compiler/compiler.h"
/**************************************************************************/
void print_usage() {
std::cout << "---- Usages -------------------------------------------------------------" << std::endl;
std::cout << "Extracting assets: ./dkr_assets_tool -e <version> <configs path> <baseroms path> <output path>" << std::endl;
std::cout << "Building an asset: ./dkr_assets_tool -b <version> <assets path> <source path> <output path>" << std::endl;
std::cout << "Build enums cache: ./dkr_assets_tool -bc <version> <enums.h path>" << std::endl;
std::cout << "Creating includes: ./dkr_assets_tool -i <version> <assets path> <include directory> <build directory> <asm output directory>" << std::endl;
std::cout << "Compiling assets: ./dkr_assets_tool -c <version> <assets path>" << std::endl;
std::cout << "Compress file: ./dkr_assets_tool -fc <input_filename> <output_filename>" << std::endl;
std::cout << "Decompress file: ./dkr_assets_tool -fd <input_filename> <output_filename>" << std::endl;
std::cout << "-------------------------------------------------------------------------" << std::endl;
}
/**************************************************************************/
struct Command {
std::string flag;
int args;
void (*func)(const std::vector<std::string>&);
};
void extract(const std::vector<std::string>& args);
void build(const std::vector<std::string>& args);
void asset_includes(const std::vector<std::string>& args);
void asset_compile(const std::vector<std::string>& args);
void asset_compress(const std::vector<std::string>& args);
void asset_decompress(const std::vector<std::string>& args);
void build_enums_cache(const std::vector<std::string>& args);
static const std::vector<Command> COMMANDS = {
// Extract
{
.flag = "-e",
.args = 5,
.func = &extract,
},
// Build
{
.flag = "-b",
.args = 4,
.func = build,
},
// Build enums cache
{
.flag = "-bc",
.args = 2,
.func = build_enums_cache,
},
// Asset includes
{
.flag = "-i",
.args = 5,
.func = asset_includes,
},
// Asset compile
{
.flag = "-c",
.args = 2,
.func = asset_compile,
},
// Asset compress file
{
.flag = "-fc",
.args = 2,
.func = asset_compress,
},
// Asset decompress file
{
.flag = "-fd",
.args = 2,
.func = asset_decompress,
},
};
void extract(const std::vector<std::string>& args) {
std::string version = args[0]; // us_1.0
std::string assetsDir = args[1]; // <root>/assets
std::string configsPath = args[2]; // <root>/extract-ver
std::string baseromsPath = args[3]; // <root>/baseroms
std::string outputPath = args[4]; // <root>
set_assets_folder_path(assetsDir + "/vanilla");
set_version(version);
Extractor extractor(version, configsPath, baseromsPath, outputPath);
}
void build(const std::vector<std::string>& args) {
std::string version = args[0]; // us_1.0
std::string assetsDir = args[1]; // <root>/assets
std::string sourcePath = args[2]; // Input .json filepath
std::string outputPath = args[3]; // Output .bin filepath
set_assets_folder_path(assetsDir);
set_version(version);
Builder builder(sourcePath, outputPath);
}
void asset_includes(const std::vector<std::string>& args) {
std::string version = args[0]; // us_1.0
std::string assetsDir = args[1]; // <root>/assets
std::string includeDir = args[2]; // <root>/include
std::string buildDir = args[3]; // <root>/build
std::string assetsAsmDir = args[4]; // <root>/asm/assets
set_assets_folder_path(assetsDir);
set_version(version);
AssetEnumsHeader assetEnumsHeader(includeDir);
AssetAsmIncludes assetAsmIncludes(assetsDir + "/" + version, buildDir + "/" + version + "/assets", assetsAsmDir);
}
void asset_compile(const std::vector<std::string>& args) {
std::string version = args[0]; // us_1.0
std::string assetsDir = args[1]; // <root>/assets
set_assets_folder_path(assetsDir);
set_version(version);
AssetCompiler compiler;
}
void asset_compress(const std::vector<std::string>& args) {
DKRCompression compression;
std::string inputFilename = args[0];
std::string outputFilename = args[1];
std::vector<uint8_t> inputBinary, outputBinary;
if(compression.readBinaryFile(inputBinary, inputFilename)) {
if(inputBinary.size() == 0) {
compression.writeBinaryFile(inputBinary, outputFilename);
} else {
outputBinary = compression.compressBuffer(inputBinary);
compression.writeBinaryFile(outputBinary, outputFilename);
}
} else {
display_error_and_abort("Could not read file: ", inputFilename);
}
}
void asset_decompress(const std::vector<std::string>& args) {
DKRCompression compression;
std::string inputFilename = args[0];
std::string outputFilename = args[1];
std::vector<uint8_t> inputBinary, outputBinary;
if(compression.readBinaryFile(inputBinary, inputFilename)) {
if(inputBinary.size() == 0) {
compression.writeBinaryFile(inputBinary, outputFilename);
} else {
outputBinary = compression.decompressBuffer(inputBinary);
compression.writeBinaryFile(outputBinary, outputFilename);
}
} else {
display_error_and_abort("Could not read file: ", inputFilename);
}
}
void build_enums_cache(const std::vector<std::string>& args) {
std::string version = args[0]; // us_1.0
std::string enumsHeaderPath = args[1]; // <root>/include/enums.h
set_version(version);
generate_enums_cache(enumsHeaderPath);
}
int main(int argc, char *argv[]) {
if (argc == 1) {
print_usage();
return 1;
}
std::string flag = argv[1];
auto it = std::find_if(COMMANDS.begin(), COMMANDS.end(), [&flag](const Command& cmd) {
return cmd.flag == flag;
});
if (it == COMMANDS.end()) {
print_usage();
return 1;
}
if (argc - 2 != it->args) {
display_error_and_abort("Expected ", it->args, " args but was given ", argc - 2, ".");
return 1;
}
std::vector<std::string> args(argv + 2, argv + argc);
it->func(args);
return 0;
}
@@ -1,137 +0,0 @@
#include "build_fonts.h"
#define SIZEOF_FONTS_HEADER 4
// Note that these are just for DKR afaik. Other rare games have different sizes.
#define SIZEOF_FONT 0x400 // Larger in later Rare games
#define MAX_NUMBER_OF_TEXTURES 32 // Extended to 64 in later Rare games
#define CHAR_NODE_START_OFFSET 32
#define NUMBER_OF_CHAR_NODES 96 // Extended to 224 in later Rare games
#define CHAR_NODE_END_OFFSET CHAR_NODE_START_OFFSET + NUMBER_OF_CHAR_NODES
#define SIZEOF_CHAR_NODE 8
BuildFonts::BuildFonts(std::string srcPath, std::string dstPath) {
std::string fontFolderPath = get_directory_from_full_path(srcPath);
std::vector<std::string> fontIds = get_array_from_section("ASSET_FONTS", "fonts-order");
int numberOfFonts = fontIds.size();
std::vector<uint8_t> out(align16(SIZEOF_FONT * numberOfFonts + SIZEOF_FONTS_HEADER));
// Write header.
write_big_endian_word(out, 0, numberOfFonts);
// Write fonts.
for(int font = 0; font < numberOfFonts; font++) {
std::string fontPath = fontFolderPath + "/" + get_string_from_json(srcPath, "fonts", fontIds[font]);
int fontOffset = font * SIZEOF_FONT + SIZEOF_FONTS_HEADER;
insert_ascii(out, fontOffset, get_string_from_json(fontPath, "name"));
write_big_endian_halfword(out, fontOffset + 0x20, get_int_from_json(fontPath, "fixed-width"));
write_big_endian_halfword(out, fontOffset + 0x22, get_int_from_json(fontPath, "y-offset"));
write_big_endian_halfword(out, fontOffset + 0x24, get_int_from_json(fontPath, "special-character-width"));
write_big_endian_halfword(out, fontOffset + 0x26, get_int_from_json(fontPath, "tab-width"));
insert_ascii(out, fontOffset + 0x28, get_string_from_json(fontPath, "unknown-text"));
int numberOfTextures = get_array_length_from_json(fontPath, "textures");
int texOffset = fontOffset + 0x40;
int texIndex = 0;
// Write texture ids.
for(; texIndex < numberOfTextures; texIndex++) {
write_big_endian_halfword(out, texOffset, get_tex_2d_index_from_build_id(get_string_from_json(fontPath, "textures", texIndex)));
texOffset += 2;
}
// Fill rest of the space with -1.
for(; texIndex < MAX_NUMBER_OF_TEXTURES; texIndex++) {
write_big_endian_halfword(out, texOffset, -1);
texOffset += 2;
}
std::string encoding = get_string_from_json(fontPath, "encoding", "type");
make_uppercase(encoding);
bool uppercaseOnly = false;
if(json_has_key(fontPath, "uppercase-only")) {
uppercaseOnly = get_bool_from_json(fontPath, "uppercase-only");
}
// Write character nodes
if(encoding == "ASCII") {
write_ascii_char_nodes(out, fontPath, fontOffset + 0x100, uppercaseOnly);
} else if(encoding == "CUSTOM") {
write_custom_char_nodes(out, fontPath, fontOffset + 0x100, uppercaseOnly);
} else {
display_error_and_abort("Unsupported font encoding type: \"", encoding, "\"");
}
}
write_binary_file(out, dstPath);
}
BuildFonts::~BuildFonts() {
}
void write_char_node(std::vector<uint8_t> &out, std::string fontPath, int nodeOffset, std::string &chr) {
if(json_has_key(fontPath, "characters", chr)) {
out[nodeOffset] = get_int_from_json(fontPath, "characters", chr, "tex-index");
out[nodeOffset + 1] = get_int_from_json(fontPath, "characters", chr, "char-width");
out[nodeOffset + 2] = get_int_from_json(fontPath, "characters", chr, "offset", 0); // X offset
out[nodeOffset + 3] = get_int_from_json(fontPath, "characters", chr, "offset", 1); // Y offset
out[nodeOffset + 4] = get_int_from_json(fontPath, "characters", chr, "uv", 0); // U coordinate
out[nodeOffset + 5] = get_int_from_json(fontPath, "characters", chr, "uv", 1); // V coordinate
out[nodeOffset + 6] = get_int_from_json(fontPath, "characters", chr, "tex-size", 0); // Width
out[nodeOffset + 7] = get_int_from_json(fontPath, "characters", chr, "tex-size", 1); // Height
} else { // Node doesn't exist, so just mark it as empty.
out[nodeOffset] = -1;
out[nodeOffset + 1] = 0;
out[nodeOffset + 2] = 0;
out[nodeOffset + 3] = 0;
out[nodeOffset + 4] = 0;
out[nodeOffset + 5] = 0;
out[nodeOffset + 6] = 0;
out[nodeOffset + 7] = 0;
}
}
void BuildFonts::write_ascii_char_nodes(std::vector<uint8_t> &out, std::string fontPath, int offset, bool uppercaseOnly) {
for(int node = CHAR_NODE_START_OFFSET; node < CHAR_NODE_END_OFFSET; node++) {
std::string chr;
if(node == 127) {
chr = "DEL"; // Special case because the delete character itself is sometimes weird.
} else if (node == 92) {
chr = "\\\\";
} else if (node == 34) {
chr = "\\\"";
} else {
chr = (char)node;
}
int nodeOffset = offset + (node - CHAR_NODE_START_OFFSET) * SIZEOF_CHAR_NODE;
if(uppercaseOnly && node >= (int)'a' && node <= (int)'z') {
make_uppercase(chr);
}
write_char_node(out, fontPath, nodeOffset, chr);
}
}
void BuildFonts::write_custom_char_nodes(std::vector<uint8_t> &out, std::string fontPath, int offset, bool uppercaseOnly) {
int index = 0;
std::vector<std::string> order = get_array_from_json(fontPath, "encoding", "order");
for(std::string &chr : order) {
if(index >= NUMBER_OF_CHAR_NODES) {
std::string fontName = get_string_from_json(fontPath, "name");
display_error_and_abort("Too many character nodes in ", fontName, ". The limit is ", NUMBER_OF_CHAR_NODES);
}
int nodeOffset = offset + (index++ * SIZEOF_CHAR_NODE);
write_char_node(out, fontPath, nodeOffset, chr);
}
// Set the rest of the nodes to show nothing.
while(index < NUMBER_OF_CHAR_NODES) {
int nodeOffset = offset + (index++ * SIZEOF_CHAR_NODE);
out[nodeOffset] = -1;
}
}
@@ -1,13 +0,0 @@
#pragma once
#include "../../common/util.h"
//#include "macros.h"
class BuildFonts {
public:
BuildFonts(std::string srcPath, std::string dstPath);
~BuildFonts();
private:
void write_ascii_char_nodes(std::vector<uint8_t> &out, std::string srcPath, int offset, bool uppercaseOnly);
void write_custom_char_nodes(std::vector<uint8_t> &out, std::string srcPath, int offset, bool uppercaseOnly);
};
@@ -1,133 +0,0 @@
#include "build_gametext.h"
// Dialog
#define END_OF_LINES 0x00
#define ACTIVE_LINE 0x01
#define CONTINUE 0x0C
// Textbox
#define NEXT_PAGE 0x01
BuildGameText::BuildGameText(std::string srcPath, std::string dstPath) {
std::vector<uint8_t> out;
// TODO: Figure this out!
std::string textType = get_string_from_json(srcPath, "text-type");
if(textType == "Dialog") {
// This is temporary until the game text format is figured out
int numRawBytes = get_array_length_from_json(srcPath, "raw");
for(int i = 0; i < numRawBytes; i++) {
out.push_back(get_int_from_json(srcPath, "raw", i));
}
//build_dialog(out, srcPath);
} else {
build_textbox(out, srcPath);
}
while(out.size() % 8 != 0) {
out.push_back(0); // Make sure the data is 8-byte aligned.
}
write_binary_file(out, dstPath);
}
BuildGameText::~BuildGameText() {
}
/*
void BuildGameText::build_dialog(std::vector<uint8_t> &out, std::string &srcPath) {
int numberOfLines = GET_ARRAY_LENGTH("lines");
for(int i = 0; i < numberOfLines; i++) {
if(inputJSON["lines"][i].JSONType() == json::JSON::Class::String) {
out.push_back(CONTINUE);
continue;
}
if(HAS_KEY("unknown-data", "lines", i)) { // Check if the key "unknown-data" exists.
for(int j = 0; j < GET_ARRAY_LENGTH("lines", i, "unknown-data"); j++) {
out.push_back(get_int_from_json(srcPath, "lines", i, "unknown-data", j));
}
break;
}
out.push_back(ACTIVE_LINE);
out.push_back(get_int_from_json(srcPath, "lines", i, "color", "red"));
out.push_back(get_int_from_json(srcPath, "lines", i, "color", "green"));
out.push_back(get_int_from_json(srcPath, "lines", i, "color", "blue"));
out.push_back(get_int_from_json(srcPath, "lines", i, "color", "alpha"));
out.push_back(get_int_from_json(srcPath, "lines", i, "font"));
out.push_back(get_int_from_json(srcPath, "lines", i, "alignment"));
out.push_back((uint8_t)(GET_FLOAT("lines", i, "time") * 5));
write_ascii(out, get_string_from_json(srcPath, "lines", i, "text"));
//if(i < numberOfLines - 1 && !HAS_KEY("unknown-data", "lines", i + 1)) {
// out.push_back(CONTINUE);
//}
}
}
*/
void write_basic_command(std::vector<uint8_t> &out, uint8_t cmdId, uint8_t value) {
out.push_back(cmdId);
out.push_back(value);
}
void write_basic_command_4_args(std::vector<uint8_t> &out, uint8_t cmdId, uint8_t a, uint8_t b, uint8_t c, uint8_t d) {
out.push_back(cmdId);
out.push_back(a);
out.push_back(b);
out.push_back(c);
out.push_back(d);
}
void BuildGameText::build_textbox(std::vector<uint8_t> &out, std::string &srcPath) {
int numberOfPages = get_array_length_from_json(srcPath, "pages");
for(int page = 0; page < numberOfPages; page++) {
int numberOfCommandsInPage = get_array_length_from_json(srcPath, "pages", page);
for(int cmd = 0; cmd < numberOfCommandsInPage; cmd++) {
std::string cmdType = get_string_from_json(srcPath, "pages", page, cmd, "command");
if(cmdType == "Text") {
write_ascii(out, get_string_from_json(srcPath, "pages", page, cmd, "value"));
} else if(cmdType == "SetFont") {
std::string fontId = get_string_from_json(srcPath, "pages", page, cmd, "value");
int fontIndex = get_index_of_string_array_value_from_section("ASSET_FONTS", fontId, "fonts-order");
if(fontIndex == -1) {
display_error_and_abort("Could not find the font \"", fontId, "\" in the \"fonts-order\" array in asset_fonts.json.");
}
write_basic_command(out, 3, fontIndex);
} else if(cmdType == "SetBorder") {
write_basic_command_4_args(out, 4,
get_int_from_json(srcPath, "pages", page, cmd, "value", "left"),
get_int_from_json(srcPath, "pages", page, cmd, "value", "top"),
get_int_from_json(srcPath, "pages", page, cmd, "value", "right"),
get_int_from_json(srcPath, "pages", page, cmd, "value", "bottom")
);
} else if(cmdType == "SetColour") {
write_basic_command_4_args(out, 5,
get_int_from_json(srcPath, "pages", page, cmd, "value", "red"),
get_int_from_json(srcPath, "pages", page, cmd, "value", "green"),
get_int_from_json(srcPath, "pages", page, cmd, "value", "blue"),
get_int_from_json(srcPath, "pages", page, cmd, "value", "alpha")
);
} else if(cmdType == "SetAlignment") {
write_basic_command(out, 6, get_string_from_json(srcPath, "pages", page, cmd, "value") == "center" ? 0 : 4);
} else if(cmdType == "Unknown") {
write_basic_command(out, 7, get_int_from_json(srcPath, "pages", page, cmd, "value"));
} else if(cmdType == "AddVerticalSpacing") {
write_basic_command(out, 8, get_int_from_json(srcPath, "pages", page, cmd, "value"));
} else if(cmdType == "SetLineHeight") {
write_basic_command(out, 9, get_int_from_json(srcPath, "pages", page, cmd, "value"));
} else if(cmdType == "SetTimer") {
write_basic_command(out, 10, get_int_from_json(srcPath, "pages", page, cmd, "value"));
} else if(cmdType == "AllowUserInput") {
write_basic_command(out, 11, get_bool_from_json(srcPath, "pages", page, cmd, "value") ? 1 : 0);
}
}
if(page < numberOfPages - 1) {
out.push_back(NEXT_PAGE);
}
}
}
@@ -1,14 +0,0 @@
#pragma once
#include "../../common/util.h"
class BuildGameText {
public:
BuildGameText(std::string srcPath, std::string dstPath);
~BuildGameText();
private:
/*
void build_dialog(std::vector<uint8_t> &out, std::string &srcPath);
*/
void build_textbox(std::vector<uint8_t> &out, std::string &srcPath);
};
@@ -1,116 +0,0 @@
#include "build_levelheader.h"
#define GET_UNKNOWN_VALUE(name, index) get_int_from_json(srcPath, "unknown-data", name, index)
#define GET_UNKNOWN_VALUES(out, start, end, name) \
for(int i = start; i < end; i++) { \
out[i] = GET_UNKNOWN_VALUE(name, i - start); \
}
int get_bitfield_from_enum_values(std::string &srcPath, std::string propertyName, std::string enumName) {
int numEntries = get_array_length_from_json(srcPath, propertyName);
int out = 0;
for(int i = 0; i < numEntries; i++) {
out |= 1 << get_enum_value_from_string(enumName, get_string_from_json(srcPath, propertyName, i));
}
return out;
}
BuildLevelHeader::BuildLevelHeader(std::string srcPath, std::string dstPath) {
std::vector<uint8_t> out(SIZEOF_LEVEL_HEADER);
// World ID
out[0] = get_enum_value_from_string("World", get_string_from_json(srcPath, "world"));
out[1] = get_int_from_json(srcPath, "unknown-data", "unk1");
out[2] = get_int_from_json(srcPath, "unknown-data", "unk2");
out[3] = get_int_from_json(srcPath, "unknown-data", "unk3");
GET_UNKNOWN_VALUES(out, 4, 8, "unk4");
// Ceiling height for planes
write_big_endian_float(out, 8, get_float_from_json(srcPath, "ceiling-height"));
GET_UNKNOWN_VALUES(out, 0xC, 0x34, "unkC");
// Track ID to use
write_big_endian_halfword(out, 0x34, get_index_from_build_id("ASSET_LEVEL_MODELS", get_string_from_json(srcPath, "model")));
// Collectables bitflags (bananas, balloons, etc)
write_big_endian_halfword(out, 0x36, get_int_from_json(srcPath, "collectables"));
// Skydome object model ID
write_big_endian_halfword(out, 0x38, get_int_from_json(srcPath, "skybox"));
// Fog
write_big_endian_halfword(out, 0x3A, get_int_from_json(srcPath, "fog", "range", "min"));
write_big_endian_halfword(out, 0x3C, get_int_from_json(srcPath, "fog", "range", "max"));
write_big_endian_halfword(out, 0x3E, get_int_from_json(srcPath, "fog", "colour", "red"));
write_big_endian_halfword(out, 0x40, get_int_from_json(srcPath, "fog", "colour", "green"));
write_big_endian_halfword(out, 0x42, get_int_from_json(srcPath, "fog", "colour", "blue"));
GET_UNKNOWN_VALUES(out, 0x44, 0x4B, "unk44");
// Number of laps to complete the race.
out[0x4B] = get_int_from_json(srcPath, "number-laps");
// Type of race (standard, boss race, hub world, etc.
out[0x4C] = get_enum_value_from_string("RaceType", get_string_from_json(srcPath, "race-type"));
// Default vehicle used for this track. (Either Car, Hovercraft, or Plane)
out[0x4D] = get_enum_value_from_string("Vehicle", get_string_from_json(srcPath, "default-vehicle"));
// Which vehicles can be used in this track (Either Car, Hovercraft, or Plane)
out[0x4E] = get_bitfield_from_enum_values(srcPath, "available-vehicles", "Vehicle");
GET_UNKNOWN_VALUES(out, 0x4F, 0x52, "unk4F");
// Music ID to use for this track
out[0x52] = get_int_from_json(srcPath, "music");
// Unknown, but *probably* related to audio.
out[0x53] = get_int_from_json(srcPath, "unknown-data", "unk53");
// Instruments bitflags. Most levels have 0xFFFF, except for the central hub which is 0xB
write_big_endian_halfword(out, 0x54, get_int_from_json(srcPath, "instruments"));
GET_UNKNOWN_VALUES(out, 0x56, 0x74, "unk56");
for(int i = 0; i < 7; i++) {
write_big_endian_word(out, 0x74 + (i * 4), get_int_from_json(srcPath, "unknown-data", "unk74", i));
}
// Weather values currently unknown.
write_big_endian_halfword(out, 0x90, get_int_from_json(srcPath, "unknown-data", "weather", "particle-density"));
write_big_endian_halfword(out, 0x92, get_int_from_json(srcPath, "unknown-data", "weather", "type"));
out[0x94] = get_int_from_json(srcPath, "unknown-data", "weather", "intensity");
out[0x95] = get_int_from_json(srcPath, "unknown-data", "weather", "opacity");
write_big_endian_halfword(out, 0x96, get_int_from_json(srcPath, "unknown-data", "weather", "x-velocity"));
write_big_endian_halfword(out, 0x98, get_int_from_json(srcPath, "unknown-data", "weather", "y-velocity"));
write_big_endian_halfword(out, 0x9A, get_int_from_json(srcPath, "unknown-data", "weather", "z-velocity"));
// Field of view
out[0x9C] = get_int_from_json(srcPath, "fov");
// Background clear color
out[0x9D] = get_int_from_json(srcPath, "background-colour", "red");
out[0x9E] = get_int_from_json(srcPath, "background-colour", "green");
out[0x9F] = get_int_from_json(srcPath, "background-colour", "blue");
GET_UNKNOWN_VALUES(out, 0xA0, 0xA4, "unkA0");
write_big_endian_word(out, 0xA4, get_int_from_json(srcPath, "unknown-data", "unkA4"));
write_big_endian_halfword(out, 0xA8, get_int_from_json(srcPath, "unknown-data", "unkA8"));
write_big_endian_halfword(out, 0xAA, get_int_from_json(srcPath, "unknown-data", "unkAA"));
// Pulsating lights are only used in spaceport alpha
int pulsatingLights = -1;
if(json_has_key(srcPath, "pulsating-lights")) {
pulsatingLights = get_index_from_build_id("ASSET_MISC", get_string_from_json(srcPath, "pulsating-lights"));
}
write_big_endian_word(out, 0xAC, pulsatingLights);
GET_UNKNOWN_VALUES(out, 0xB0, 0xC4, "unkB0");
write_big_endian_word(out, 0xC4, get_int_from_json(srcPath, "unknown-data", "unkC4"));
write_binary_file(out, dstPath);
}
BuildLevelHeader::~BuildLevelHeader() {
}
@@ -1,11 +0,0 @@
#pragma once
#include "../../common/util.h"
#define SIZEOF_LEVEL_HEADER 0xC8
class BuildLevelHeader {
public:
BuildLevelHeader(std::string srcPath, std::string dstPath);
~BuildLevelHeader();
};
@@ -1,32 +0,0 @@
#include "build_levelname.h"
BuildLevelName::BuildLevelName(std::string srcPath, std::string dstPath) {
std::vector<uint8_t> out;
std::vector<std::string> languages = get_enum_keys("Language");
int numLangWritten = 0;
// Search through and add all the avaliable languages.
// Note: If a language is not defined in the enum, then it won't be added to the output!
for(auto &langName : languages) {
if(json_has_key(srcPath, "languages", langName)) { // Check if the level name json has the language.
write_ascii(out, get_string_from_json(srcPath, "languages", langName));
numLangWritten++;
} else {
// Language not found. For now, this means the text will be empty.
// TODO: Maybe have an option to default to english?
out.push_back('\0'); // Push back a null terminator
}
}
if(numLangWritten == 0) {
std::cout << "Warning: No languages were found." << std::endl;
}
write_binary_file(out, dstPath);
}
BuildLevelName::~BuildLevelName() {
}
@@ -1,9 +0,0 @@
#pragma once
#include "../../common/util.h"
class BuildLevelName {
public:
BuildLevelName(std::string srcPath, std::string dstPath);
~BuildLevelName();
};
@@ -1,46 +0,0 @@
#include "build_menutext.h"
BuildMenuText::BuildMenuText(std::string srcPath, std::string dstPath) {
//int numberOfEntries = get_array_length_from_json(srcPath, "order");
std::vector<std::string> ids = get_array_from_section("ASSET_MENU_TEXT", "menu-text-build-ids");
int numberOfEntries = ids.size();
std::vector<uint8_t> table(numberOfEntries * 4);
std::vector<uint8_t> text;
int textOffset = numberOfEntries * 4;
for(int i = 0; i < numberOfEntries; i++) {
// Get the build id for this index.
std::string buildId = ids[i];
// If the build ID is not in the sections, then just write -1 to the table.
if(!json_has_key(srcPath, "sections", buildId)) {
write_big_endian_word(table, i * 4, -1);
continue;
}
// Get the text string from the inputJSON.
std::string entryText = get_string_from_json(srcPath, "sections", buildId);
// Write ascii text to vector
write_ascii(text, entryText);
// Write the offset to the text
write_big_endian_word(table, i * 4, textOffset);
// Increment the current offset by the length of the string (+1 for null terminator)
textOffset += entryText.length() + 1;
}
// Append the text data to the table data.
table.insert(table.end(), text.begin(), text.end());
while(table.size() % 8 != 0) {
table.push_back(0); // Make sure the data is 8-byte aligned.
}
write_binary_file(table, dstPath);
}
BuildMenuText::~BuildMenuText(){
}
@@ -1,9 +0,0 @@
#pragma once
#include "../../common/util.h"
class BuildMenuText {
public:
BuildMenuText(std::string srcPath, std::string dstPath);
~BuildMenuText();
};
@@ -1,29 +0,0 @@
#include "build_sprite.h"
#define SIZEOF_SPRITE_HEADER 12
BuildSprite::BuildSprite(std::string srcPath, std::string dstPath) {
int numberOfFrames = get_array_length_from_json(srcPath, "frame-tex-count");
std::vector<uint8_t> out(align16(SIZEOF_SPRITE_HEADER + numberOfFrames + 1));
// Write header
write_big_endian_halfword(out, 0, get_tex_2d_index_from_build_id(get_string_from_json(srcPath, "start-texture")));
write_big_endian_halfword(out, 2, numberOfFrames);
write_big_endian_halfword(out, 4, get_int_from_json(srcPath, "unk4"));
write_big_endian_halfword(out, 6, get_int_from_json(srcPath, "unk6"));
// The word at offset 8 is always zero in the rom.
int texOffset = 0;
for(int i = 0; i < numberOfFrames + 1; i++) {
out[SIZEOF_SPRITE_HEADER + i] = texOffset;
texOffset += get_int_from_json(srcPath, "frame-tex-count", i);
}
write_binary_file(out, dstPath);
}
BuildSprite::~BuildSprite() {
}
@@ -1,9 +0,0 @@
#pragma once
#include "../../common/util.h"
class BuildSprite {
public:
BuildSprite(std::string srcPath, std::string dstPath);
~BuildSprite();
};
@@ -1,221 +0,0 @@
#include "build_texture.h"
#define SET_BOOL_FLAG(flagName, flagVal) \
flags |= get_bool_from_json(srcPath, "flags", flagName) ? flagVal : 0
#define SET_STRING_FLAG(flagName, strVal, flagVal) \
flags |= get_string_from_json(srcPath, "flags", flagName) == strVal ? flagVal : 0
int get_png_data_size(int width, int height, int format) {
switch(format) {
case TEX_FORMAT_I8:
case TEX_FORMAT_I4:
case TEX_FORMAT_IA16:
case TEX_FORMAT_IA8:
case TEX_FORMAT_IA4:
return (width * height * 2);
default:
return (width * height * 4);
}
}
int get_png_offset(int i, int width, int height, int format) {
return get_png_data_size(width, height, format) * i;
}
BuildTexture::BuildTexture(std::string srcPath, std::string dstPath) {
std::string directory = get_directory_from_full_path(srcPath);
std::string imgPath = directory + "/" + get_string_from_json(srcPath, "img");
bool isCompressed = get_bool_from_json(srcPath, "compressed");
bool flipTexture = get_bool_from_json(srcPath, "flipped-image");
bool interlaced = get_bool_from_json(srcPath, "flags", "interlaced");
int width = get_int_from_json(srcPath, "width");
int height = get_int_from_json(srcPath, "height");
std::string formatString = get_string_from_json(srcPath, "format");
int format = get_texture_format_from_string(formatString);
int texType = get_int_from_json(srcPath, "tex-type");
int spriteX = json_has_key(srcPath, "sprite-x") ? get_int_from_json(srcPath, "sprite-x") : 0;
int spriteY = json_has_key(srcPath, "sprite-y") ? get_int_from_json(srcPath, "sprite-y") : 0;
bool writeSize = get_bool_from_json(srcPath, "write-size");
// TODO: Figure out all the flags
int flags = 0;
SET_BOOL_FLAG("flag-01", 0x0001);
SET_BOOL_FLAG("flag-04", 0x0004);
SET_BOOL_FLAG("flag-10", 0x0010);
SET_STRING_FLAG("wrap-s", "Clamp", 0x0040);
SET_STRING_FLAG("wrap-t", "Clamp", 0x0080);
SET_BOOL_FLAG("interlaced", 0x0400);
int textureSize = get_texture_size(width, height, format);
int numberOfTextures = get_int_from_json(srcPath, "frame-count");
int frameAdvance = json_has_key(srcPath, "frame-advance-delay") ? get_int_from_json(srcPath, "frame-advance-delay") : 0;
std::vector<uint8_t> uncompressed(textureSize * numberOfTextures);
int pngWidth, pngHeight;
std::vector<uint8_t> pngData = load_texture_from_png(imgPath, format, &pngWidth, &pngHeight);
for(int i = 0; i < numberOfTextures; i++) {
int off = i * textureSize;
int pngOff = get_png_offset(i, width, height, format);
// Texture Header
uncompressed[off] = width;
uncompressed[off + 0x01] = height;
uncompressed[off + 0x02] = (texType << 4) | format;
uncompressed[off + 0x03] = spriteX;
uncompressed[off + 0x04] = spriteY;
uncompressed[off + 0x05] = 1; // Number of instances. Always 1 in the ROM.
write_big_endian_halfword(uncompressed, off + 0x06, flags);
uncompressed[off + 0x12] = numberOfTextures;
write_big_endian_halfword(uncompressed, off + 0x14, frameAdvance);
if(writeSize) {
write_big_endian_halfword(uncompressed, off + 0x16, align16(textureSize));
} else {
uncompressed[off + 0x1D] = 0x01;
}
// Subsection of pngData
std::vector<uint8_t> rgbaData(pngData.begin() + pngOff, pngData.begin() + pngOff + get_png_data_size(width, height, format));
write_texture_data(uncompressed, off + TEX_HEADER_SIZE, rgbaData, width, height, format, interlaced, flipTexture);
}
if(!writeSize) {
while(uncompressed.size() % 16 != 0) {
uncompressed.push_back(0);
}
}
// isCompressed = false; // Uncomment to help with debugging.
if(isCompressed) {
std::vector<uint8_t> compressedHeader;
compressedHeader.insert(compressedHeader.end(), uncompressed.begin(), uncompressed.begin() + TEX_HEADER_SIZE);
compressedHeader[0x1D] = 0x01;
DKRCompression compression;
std::vector<uint8_t> compressed = compression.compressBuffer(uncompressed);
// Note: uncompressed is garbled at this point.
compressed.insert(compressed.begin(), compressedHeader.begin(), compressedHeader.end());
write_binary_file(compressed, dstPath);
} else {
write_binary_file(uncompressed, dstPath);
}
//write_binary_file(out, dstPath);
}
BuildTexture::~BuildTexture() {
}
void BuildTexture::write_texture_data(std::vector<uint8_t> &out, int outOffset, std::vector<uint8_t> &rgbaData,
int width, int height, int format, bool interlace, bool flipTexture) {
if(flipTexture) {
switch(format) {
case TEX_FORMAT_I8:
case TEX_FORMAT_I4:
case TEX_FORMAT_IA16:
case TEX_FORMAT_IA8:
case TEX_FORMAT_IA4:
flip_texture_vertically(rgbaData, 0, width, height, TEX_FORMAT_IA16);
break;
default:
flip_texture_vertically(rgbaData, 0, width, height, TEX_FORMAT_RGBA32);
break;
}
}
switch(format) {
case TEX_FORMAT_RGBA32:
{
if (interlace) {
deinterlace(rgbaData, 0, width, height, 32, 8);
}
std::copy(rgbaData.begin(), rgbaData.end(), out.begin() + outOffset);
break;
}
case TEX_FORMAT_RGBA16:
{
uint8_t* rgba16Data = (uint8_t*)malloc(width * height * 2);
rgba2raw(rgba16Data, (const rgba*)&rgbaData[0], width, height, 16);
std::vector<uint8_t> rgba16(rgba16Data, rgba16Data + (width * height * 2));
if (interlace) {
deinterlace(rgba16, 0, width, height, 16, 4);
}
std::copy(rgba16.begin(), rgba16.end(), out.begin() + outOffset);
break;
}
case TEX_FORMAT_I8:
{
uint8_t* i8Data = (uint8_t*)malloc(width * height);
i2raw(i8Data, (const ia*)&rgbaData[0], width, height, 8);
std::vector<uint8_t> i8(i8Data, i8Data + (width * height));
if (interlace) {
deinterlace(i8, 0, width, height, 8, 4);
}
std::copy(i8.begin(), i8.end(), out.begin() + outOffset);
break;
}
case TEX_FORMAT_I4:
{
uint8_t* i4Data = (uint8_t*)malloc(width * height / 2);
i2raw(i4Data, (const ia*)&rgbaData[0], width, height, 4);
std::vector<uint8_t> i4(i4Data, i4Data + (width * height / 2));
if (interlace) {
deinterlace(i4, 0, width, height, 4, 4);
}
std::copy(i4.begin(), i4.end(), out.begin() + outOffset);
break;
}
case TEX_FORMAT_IA16:
{
uint8_t* ia16Data = (uint8_t*)malloc(width * height * 2);
ia2raw(ia16Data, (const ia*)&rgbaData[0], width, height, 16);
std::vector<uint8_t> ia16(ia16Data, ia16Data + (width * height * 2));
if (interlace) {
deinterlace(ia16, 0, width, height, 16, 4);
}
std::copy(ia16.begin(), ia16.end(), out.begin() + outOffset);
break;
}
case TEX_FORMAT_IA8:
{
uint8_t* ia8Data = (uint8_t*)malloc(width * height);
ia2raw(ia8Data, (const ia*)&rgbaData[0], width, height, 8);
std::vector<uint8_t> ia8(ia8Data, ia8Data + (width * height));
if (interlace) {
deinterlace(ia8, 0, width, height, 8, 4);
}
std::copy(ia8.begin(), ia8.end(), out.begin() + outOffset);
break;
}
case TEX_FORMAT_IA4:
{
uint8_t* ia4Data = (uint8_t*)malloc(width * height / 2);
ia2raw(ia4Data, (const ia*)&rgbaData[0], width, height, 4);
std::vector<uint8_t> ia4(ia4Data, ia4Data + (width * height / 2));
if (interlace) {
deinterlace(ia4, 0, width, height, 4, 4);
}
std::copy(ia4.begin(), ia4.end(), out.begin() + outOffset);
break;
}
case TEX_FORMAT_CI4:
case TEX_FORMAT_CI8:
{
display_error_and_abort("CI4/CI8 texture formats are not currently supported.");
}
}
}
@@ -1,13 +0,0 @@
#pragma once
#include "../../common/util.h"
#include "../../common/textures.h"
class BuildTexture {
public:
BuildTexture(std::string srcPath, std::string dstPath);
~BuildTexture();
private:
void write_texture_data(std::vector<uint8_t> &out, int outOffset, std::vector<uint8_t> &rgbaData,
int width, int height, int format, bool interlace, bool flipTexture);
};
@@ -1,33 +0,0 @@
#include "build_ttghost.h"
#include <chrono>
BuildTTGhost::BuildTTGhost(std::string srcPath, std::string dstPath) {
int numberOfNodes = get_array_length_from_json(srcPath, "nodes");
std::vector<uint8_t> out(align16(SIZEOF_TTGHOST_HEADER + numberOfNodes * SIZEOF_TTGHOST_NODE));
// Write header
out[0] = get_index_from_build_id("ASSET_LEVEL_HEADERS", get_string_from_json(srcPath, "header", "level")); // get_int_from_json(srcPath, "header", "level");
out[1] = get_enum_value_from_string("Vehicle", get_string_from_json(srcPath, "header", "vehicle"));
out[2] = 9; // Always 9?
write_big_endian_halfword(out, 4, get_int_from_json(srcPath, "header", "time"));
write_big_endian_halfword(out, 6, numberOfNodes);
// Write the nodes
for(int i = 0; i < numberOfNodes; i++) {
int offset = (i * SIZEOF_TTGHOST_NODE) + SIZEOF_TTGHOST_HEADER;
write_big_endian_halfword(out, offset, get_int_from_json(srcPath, "nodes", i, "x"));
write_big_endian_halfword(out, offset + 2, get_int_from_json(srcPath, "nodes", i, "y"));
write_big_endian_halfword(out, offset + 4, get_int_from_json(srcPath, "nodes", i, "z"));
// Yes, this order is correct.
write_big_endian_halfword(out, offset + 6, get_int_from_json(srcPath, "nodes", i, "rz"));
write_big_endian_halfword(out, offset + 8, get_int_from_json(srcPath, "nodes", i, "rx"));
write_big_endian_halfword(out, offset + 10, get_int_from_json(srcPath, "nodes", i, "ry"));
}
write_binary_file(out, dstPath);
}
BuildTTGhost::~BuildTTGhost() {
}
@@ -1,12 +0,0 @@
#pragma once
#include "../../common/util.h"
#define SIZEOF_TTGHOST_HEADER 8
#define SIZEOF_TTGHOST_NODE 12
class BuildTTGhost {
public:
BuildTTGhost(std::string srcPath, std::string dstPath);
~BuildTTGhost();
};
@@ -1,36 +0,0 @@
#include "builder.h"
Builder::Builder(std::string srcPath, std::string dstPath) {
if(!path_exists(srcPath)) {
display_error_and_abort("Invalid path \"", srcPath, "\"");
}
if(ends_with(srcPath, ".json")) {
std::string type = get_string_from_json(srcPath, "type");
if(type == "Textures") {
BuildTexture(srcPath, dstPath);
} else if (type == "TTGhosts") {
BuildTTGhost(srcPath, dstPath);
} else if (type == "Sprites") {
BuildSprite(srcPath, dstPath);
} else if (type == "LevelNames") {
BuildLevelName(srcPath, dstPath);
} else if (type == "LevelHeaders") {
BuildLevelHeader(srcPath, dstPath);
} else if (type == "GameText") {
BuildGameText(srcPath, dstPath);
} else if (type == "MenuText") {
BuildMenuText(srcPath, dstPath);
} else if (type == "Fonts") {
BuildFonts(srcPath, dstPath);
} else if (type == "") {
display_error_and_abort("No type found for \"", srcPath, "\".\nMake sure the \"type\" property is defined in the json file!");
} else {
display_error_and_abort("Unknown type \"", type, "\" in \"", srcPath, "\"");
}
}
}
Builder::~Builder() {
}
@@ -1,28 +0,0 @@
#pragma once
#include <string>
#include <vector>
#include <iostream>
#include <fstream>
#include <sstream>
#include <regex>
#include <thread>
#include "../common/util.h"
#include "../common/types.h"
#include "build_files/build_fonts.h"
#include "build_files/build_gametext.h"
#include "build_files/build_levelname.h"
#include "build_files/build_levelheader.h"
#include "build_files/build_menutext.h"
#include "build_files/build_sprite.h"
#include "build_files/build_texture.h"
#include "build_files/build_ttghost.h"
class Builder {
public:
Builder(std::string srcPath, std::string dstPath);
~Builder();
private:
};

Some files were not shown because too many files have changed in this diff Show More