diff --git a/ZAPDTR/.clang-format b/ZAPDTR/.clang-format
new file mode 100644
index 000000000..784e734e9
--- /dev/null
+++ b/ZAPDTR/.clang-format
@@ -0,0 +1,84 @@
+---
+AccessModifierOffset: -4
+AlignAfterOpenBracket: Align
+AlignConsecutiveAssignments: false
+AlignConsecutiveDeclarations: false
+AlignEscapedNewlinesLeft: false
+AlignOperands: true
+AlignTrailingComments: true
+AllowAllParametersOfDeclarationOnNextLine: true
+AllowShortBlocksOnASingleLine: false
+AllowShortCaseLabelsOnASingleLine: false
+AllowShortFunctionsOnASingleLine: InlineOnly
+AllowShortIfStatementsOnASingleLine: false
+AllowShortLoopsOnASingleLine: false
+AlwaysBreakAfterDefinitionReturnType: None
+AlwaysBreakAfterReturnType: None
+AlwaysBreakBeforeMultilineStrings: false
+AlwaysBreakTemplateDeclarations: true
+BinPackArguments: true
+BinPackParameters: true
+BraceWrapping:
+ AfterCaseLabel: true
+ AfterClass: true
+ AfterControlStatement: true
+ AfterEnum: true
+ AfterFunction: true
+ AfterNamespace: true
+ AfterStruct: true
+ AfterUnion: true
+ BeforeCatch: true
+ BeforeElse: true
+ IndentBraces: false
+BreakBeforeBinaryOperators: None
+BreakBeforeBraces: Custom
+BreakBeforeTernaryOperators: false
+BreakConstructorInitializersBeforeComma: false
+ColumnLimit: 100
+CommentPragmas: '^ (IWYU pragma:|NOLINT)'
+ConstructorInitializerAllOnOneLineOrOnePerLine: false
+ConstructorInitializerIndentWidth: 4
+ContinuationIndentWidth: 4
+Cpp11BracedListStyle: true
+DerivePointerAlignment: false
+DisableFormat: false
+ForEachMacros: [ ]
+IncludeCategories:
+ - Regex: '^<[Ww]indows\.h>$'
+ Priority: 1
+ - Regex: '^<'
+ Priority: 2
+ - Regex: '^"'
+ Priority: 3
+IndentCaseLabels: false
+IndentWidth: 4
+IndentWrappedFunctionNames: false
+KeepEmptyLinesAtTheStartOfBlocks: false
+MacroBlockBegin: ''
+MacroBlockEnd: ''
+MaxEmptyLinesToKeep: 1
+NamespaceIndentation: None
+PenaltyBreakBeforeFirstCallParameter: 19
+PenaltyBreakComment: 300
+PenaltyBreakFirstLessLess: 120
+PenaltyBreakString: 1000
+PenaltyExcessCharacter: 1000000
+PenaltyReturnTypeOnItsOwnLine: 60
+PointerAlignment: Left
+ReflowComments: true
+SortIncludes: true
+SpaceAfterCStyleCast: false
+SpaceBeforeAssignmentOperators: true
+SpaceBeforeParens: ControlStatements
+SpaceInEmptyParentheses: false
+SpacesBeforeTrailingComments: 2
+SpacesInAngles: false
+SpacesInContainerLiterals: true
+SpacesInCStyleCastParentheses: false
+SpacesInParentheses: false
+SpacesInSquareBrackets: false
+Standard: Latest
+TabWidth: 4
+UseTab: AlignWithSpaces
+...
+
diff --git a/ZAPDTR/.gitignore b/ZAPDTR/.gitignore
new file mode 100644
index 000000000..68c502e36
--- /dev/null
+++ b/ZAPDTR/.gitignore
@@ -0,0 +1,341 @@
+## Ignore Visual Studio temporary files, build results, and
+## files generated by popular Visual Studio add-ons.
+##
+## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore
+
+# User-specific files
+*.suo
+*.user
+*.userosscache
+*.sln.docstates
+
+# User-specific files (MonoDevelop/Xamarin Studio)
+*.userprefs
+
+# Build results
+[Dd]ebug/
+[Dd]ebugPublic/
+[Rr]elease/
+[Rr]eleases/
+x64/
+x86/
+bld/
+[Bb]in/
+[Oo]bj/
+[Ll]og/
+
+# Visual Studio 2015/2017 cache/options directory
+.vs/
+# Uncomment if you have tasks that create the project's static files in wwwroot
+#wwwroot/
+
+# Visual Studio 2017 auto generated files
+Generated\ Files/
+
+# MSTest test Results
+[Tt]est[Rr]esult*/
+[Bb]uild[Ll]og.*
+
+# NUNIT
+*.VisualState.xml
+TestResult.xml
+
+# Build Results of an ATL Project
+[Dd]ebugPS/
+[Rr]eleasePS/
+dlldata.c
+
+# Benchmark Results
+BenchmarkDotNet.Artifacts/
+
+# .NET Core
+project.lock.json
+project.fragment.lock.json
+artifacts/
+**/Properties/launchSettings.json
+
+# StyleCop
+StyleCopReport.xml
+
+# Files built by Visual Studio
+*_i.c
+*_p.c
+*_i.h
+*.ilk
+*.meta
+*.obj
+*.iobj
+*.pch
+*.pdb
+*.ipdb
+*.pgc
+*.pgd
+*.rsp
+*.sbr
+*.tlb
+*.tli
+*.tlh
+*.tmp
+*.tmp_proj
+*.log
+*.vspscc
+*.vssscc
+.builds
+*.pidb
+*.svclog
+*.scc
+
+# Chutzpah Test files
+_Chutzpah*
+
+# Visual C++ cache files
+ipch/
+*.aps
+*.ncb
+*.opendb
+*.opensdf
+*.sdf
+*.cachefile
+*.VC.db
+*.VC.VC.opendb
+
+# Visual Studio profiler
+*.psess
+*.vsp
+*.vspx
+*.sap
+
+# Visual Studio Trace Files
+*.e2e
+
+# TFS 2012 Local Workspace
+$tf/
+
+# Guidance Automation Toolkit
+*.gpState
+
+# ReSharper is a .NET coding add-in
+_ReSharper*/
+*.[Rr]e[Ss]harper
+*.DotSettings.user
+
+# JustCode is a .NET coding add-in
+.JustCode
+
+# TeamCity is a build add-in
+_TeamCity*
+
+# DotCover is a Code Coverage Tool
+*.dotCover
+
+# AxoCover is a Code Coverage Tool
+.axoCover/*
+!.axoCover/settings.json
+
+# Visual Studio code coverage results
+*.coverage
+*.coveragexml
+
+# NCrunch
+_NCrunch_*
+.*crunch*.local.xml
+nCrunchTemp_*
+
+# MightyMoose
+*.mm.*
+AutoTest.Net/
+
+# Web workbench (sass)
+.sass-cache/
+
+# Installshield output folder
+[Ee]xpress/
+
+# DocProject is a documentation generator add-in
+DocProject/buildhelp/
+DocProject/Help/*.HxT
+DocProject/Help/*.HxC
+DocProject/Help/*.hhc
+DocProject/Help/*.hhk
+DocProject/Help/*.hhp
+DocProject/Help/Html2
+DocProject/Help/html
+
+# Click-Once directory
+publish/
+
+# Publish Web Output
+*.[Pp]ublish.xml
+*.azurePubxml
+# Note: Comment the next line if you want to checkin your web deploy settings,
+# but database connection strings (with potential passwords) will be unencrypted
+*.pubxml
+*.publishproj
+
+# Microsoft Azure Web App publish settings. Comment the next line if you want to
+# checkin your Azure Web App publish settings, but sensitive information contained
+# in these scripts will be unencrypted
+PublishScripts/
+
+# NuGet Packages
+*.nupkg
+# The packages folder can be ignored because of Package Restore
+**/[Pp]ackages/*
+# except build/, which is used as an MSBuild target.
+!**/[Pp]ackages/build/
+# Uncomment if necessary however generally it will be regenerated when needed
+#!**/[Pp]ackages/repositories.config
+# NuGet v3's project.json files produces more ignorable files
+*.nuget.props
+*.nuget.targets
+
+# Microsoft Azure Build Output
+csx/
+*.build.csdef
+
+# Microsoft Azure Emulator
+ecf/
+rcf/
+
+# Windows Store app package directories and files
+AppPackages/
+BundleArtifacts/
+Package.StoreAssociation.xml
+_pkginfo.txt
+*.appx
+
+# Visual Studio cache files
+# files ending in .cache can be ignored
+*.[Cc]ache
+# but keep track of directories ending in .cache
+!*.[Cc]ache/
+
+# Others
+ClientBin/
+~$*
+*~
+*.dbmdl
+*.dbproj.schemaview
+*.jfm
+*.pfx
+*.publishsettings
+orleans.codegen.cs
+
+# Including strong name files can present a security risk
+# (https://github.com/github/gitignore/pull/2483#issue-259490424)
+#*.snk
+
+# Since there are multiple workflows, uncomment next line to ignore bower_components
+# (https://github.com/github/gitignore/pull/1529#issuecomment-104372622)
+#bower_components/
+
+# RIA/Silverlight projects
+Generated_Code/
+
+# Backup & report files from converting an old project file
+# to a newer Visual Studio version. Backup files are not needed,
+# because we have git ;-)
+_UpgradeReport_Files/
+Backup*/
+UpgradeLog*.XML
+UpgradeLog*.htm
+ServiceFabricBackup/
+*.rptproj.bak
+
+# SQL Server files
+*.mdf
+*.ldf
+*.ndf
+
+# Business Intelligence projects
+*.rdl.data
+*.bim.layout
+*.bim_*.settings
+*.rptproj.rsuser
+
+# Microsoft Fakes
+FakesAssemblies/
+
+# GhostDoc plugin setting file
+*.GhostDoc.xml
+
+# Node.js Tools for Visual Studio
+.ntvs_analysis.dat
+node_modules/
+
+# Visual Studio 6 build log
+*.plg
+
+# Visual Studio 6 workspace options file
+*.opt
+
+# Visual Studio 6 auto-generated workspace file (contains which files were open etc.)
+*.vbw
+
+# Visual Studio LightSwitch build output
+**/*.HTMLClient/GeneratedArtifacts
+**/*.DesktopClient/GeneratedArtifacts
+**/*.DesktopClient/ModelManifest.xml
+**/*.Server/GeneratedArtifacts
+**/*.Server/ModelManifest.xml
+_Pvt_Extensions
+
+# Paket dependency manager
+.paket/paket.exe
+paket-files/
+
+# FAKE - F# Make
+.fake/
+
+# JetBrains Rider
+.idea/
+*.sln.iml
+
+# CodeRush
+.cr/
+
+# Python Tools for Visual Studio (PTVS)
+__pycache__/
+*.pyc
+
+# Cake - Uncomment if you are using it
+# tools/**
+# !tools/packages.config
+
+# Tabs Studio
+*.tss
+
+# Telerik's JustMock configuration file
+*.jmconfig
+
+# BizTalk build output
+*.btp.cs
+*.btm.cs
+*.odx.cs
+*.xsd.cs
+
+# OpenCover UI analysis results
+OpenCover/
+
+# Azure Stream Analytics local run output
+ASALocalRun/
+
+# MSBuild Binary and Structured Log
+*.binlog
+
+# NVidia Nsight GPU debugger configuration file
+*.nvuser
+
+# MFractors (Xamarin productivity tool) working folder
+.mfractor/
+
+*.out
+*.o
+*.d
+lib/libgfxd/libgfxd.a
+ExporterTest/ExporterTest.a
+ZAPDUtils/ZAPDUtils.a
+.vscode/
+build/
+ZAPDUtils/build/
+ZAPD/BuildInfo.h
diff --git a/ZAPDTR/.gitrepo b/ZAPDTR/.gitrepo
new file mode 100644
index 000000000..cd980ceb8
--- /dev/null
+++ b/ZAPDTR/.gitrepo
@@ -0,0 +1,12 @@
+; DO NOT EDIT (unless you know what you are doing)
+;
+; This subdirectory is a git "subrepo", and this file is maintained by the
+; git-subrepo command. See https://github.com/git-commands/git-subrepo#readme
+;
+[subrepo]
+ remote = https://github.com/HarbourMasters/ZAPDTR.git
+ branch = master
+ commit = a53a53ea4216b926253dde2c942ae0ca6e2f2ccd
+ parent = f52a2a6406eb1bbd2b631c65923d879a83983ccb
+ method = rebase
+ cmdver = 0.4.1
diff --git a/ZAPDTR/ExporterTest/CollisionExporter.cpp b/ZAPDTR/ExporterTest/CollisionExporter.cpp
new file mode 100644
index 000000000..e00f5c1b0
--- /dev/null
+++ b/ZAPDTR/ExporterTest/CollisionExporter.cpp
@@ -0,0 +1,74 @@
+#include "CollisionExporter.h"
+
+void ExporterExample_Collision::Save(ZResource* res, [[maybe_unused]] fs::path outPath,
+ BinaryWriter* writer)
+{
+ ZCollisionHeader* col = (ZCollisionHeader*)res;
+
+ writer->Write(col->absMinX);
+ writer->Write(col->absMinY);
+ writer->Write(col->absMinZ);
+
+ writer->Write(col->absMaxX);
+ writer->Write(col->absMaxY);
+ writer->Write(col->absMaxZ);
+
+ writer->Write(col->numVerts);
+ writer->Write(col->vtxAddress);
+
+ writer->Write(col->numPolygons);
+ writer->Write(col->polyAddress);
+ writer->Write(col->polyTypeDefAddress);
+ writer->Write(col->camDataAddress);
+
+ writer->Write(col->numWaterBoxes);
+ writer->Write(col->waterBoxAddress);
+
+ writer->Write(col->vtxSegmentOffset);
+ writer->Write(col->polySegmentOffset);
+ writer->Write(col->polyTypeDefSegmentOffset);
+ writer->Write(col->camDataSegmentOffset);
+ writer->Write(col->waterBoxSegmentOffset);
+
+ uint32_t oldOffset = writer->GetBaseAddress();
+
+ writer->Seek(col->vtxSegmentOffset, SeekOffsetType::Start);
+
+ for (uint16_t i = 0; i < col->vertices.size(); i++)
+ {
+ for (uint32_t j = 0; j < col->vertices[i].dimensions; j++)
+ {
+ writer->Write(col->vertices[i].scalars[j].scalarData.s16);
+ }
+ }
+
+ writer->Seek(col->polySegmentOffset, SeekOffsetType::Start);
+
+ for (uint16_t i = 0; i < col->polygons.size(); i++)
+ {
+ writer->Write(col->polygons[i].type);
+ writer->Write(col->polygons[i].vtxA);
+ writer->Write(col->polygons[i].vtxB);
+ writer->Write(col->polygons[i].vtxC);
+ writer->Write(col->polygons[i].a);
+ writer->Write(col->polygons[i].b);
+ writer->Write(col->polygons[i].c);
+ writer->Write(col->polygons[i].d);
+ }
+
+ writer->Seek(col->polyTypeDefSegmentOffset, SeekOffsetType::Start);
+
+ for (uint16_t i = 0; i < col->polygonTypes.size(); i++)
+ writer->Write(col->polygonTypes[i]);
+
+ writer->Seek(col->camDataSegmentOffset, SeekOffsetType::Start);
+
+ for (auto entry : col->camData->entries)
+ {
+ writer->Write(entry->cameraSType);
+ writer->Write(entry->numData);
+ writer->Write(entry->cameraPosDataSeg);
+ }
+
+ writer->Seek(oldOffset, SeekOffsetType::Start);
+}
diff --git a/ZAPDTR/ExporterTest/CollisionExporter.h b/ZAPDTR/ExporterTest/CollisionExporter.h
new file mode 100644
index 000000000..5f48e6557
--- /dev/null
+++ b/ZAPDTR/ExporterTest/CollisionExporter.h
@@ -0,0 +1,10 @@
+#pragma once
+
+#include "ZCollision.h"
+#include "ZResource.h"
+
+class ExporterExample_Collision : public ZResourceExporter
+{
+public:
+ void Save(ZResource* res, fs::path outPath, BinaryWriter* writer) override;
+};
\ No newline at end of file
diff --git a/ZAPDTR/ExporterTest/ExporterTest.vcxproj b/ZAPDTR/ExporterTest/ExporterTest.vcxproj
new file mode 100644
index 000000000..839d45102
--- /dev/null
+++ b/ZAPDTR/ExporterTest/ExporterTest.vcxproj
@@ -0,0 +1,160 @@
+
+
+
+
+ Debug
+ Win32
+
+
+ Release
+ Win32
+
+
+ Debug
+ x64
+
+
+ Release
+ x64
+
+
+
+ 16.0
+ Win32Proj
+ {65608eb0-1a47-45ad-ab66-192fb64c762c}
+ ExporterTest
+ 10.0
+ ExporterExample
+
+
+
+ Application
+ true
+ v142
+ Unicode
+
+
+ Application
+ false
+ v142
+ true
+ Unicode
+
+
+ StaticLibrary
+ true
+ v142
+ Unicode
+
+
+ Application
+ false
+ v142
+ true
+ Unicode
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ true
+
+
+ false
+
+
+ true
+ $(ProjectDir)..\ZAPD\;$(ProjectDir)..\ZAPDUtils;$(ProjectDir)..\lib\tinyxml2;$(ProjectDir)..\lib\libgfxd;$(ProjectDir)..\lib\elfio;$(ProjectDir)..\lib\stb;$(ProjectDir);$(IncludePath)
+
+
+ false
+
+
+
+ Level3
+ true
+ WIN32;_DEBUG;_CONSOLE;%(PreprocessorDefinitions)
+ true
+
+
+ Console
+ true
+
+
+
+
+ Level3
+ true
+ true
+ true
+ WIN32;NDEBUG;_CONSOLE;%(PreprocessorDefinitions)
+ true
+
+
+ Console
+ true
+ true
+ true
+
+
+
+
+ Level3
+ true
+ _DEBUG;_CONSOLE;_CRT_SECURE_NO_WARNINGS;%(PreprocessorDefinitions)
+ true
+ stdcpp17
+ stdc11
+ MultiThreadedDebug
+
+
+ Console
+ true
+
+
+
+
+ Level3
+ true
+ true
+ true
+ NDEBUG;_CONSOLE;%(PreprocessorDefinitions)
+ true
+
+
+ Console
+ true
+ true
+ true
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/ZAPDTR/ExporterTest/ExporterTest.vcxproj.filters b/ZAPDTR/ExporterTest/ExporterTest.vcxproj.filters
new file mode 100644
index 000000000..166f563a1
--- /dev/null
+++ b/ZAPDTR/ExporterTest/ExporterTest.vcxproj.filters
@@ -0,0 +1,42 @@
+
+
+
+
+ {4FC737F1-C7A5-4376-A066-2A32D752A2FF}
+ cpp;c;cc;cxx;c++;cppm;ixx;def;odl;idl;hpj;bat;asm;asmx
+
+
+ {93995380-89BD-4b04-88EB-625FBE52EBFB}
+ h;hh;hpp;hxx;h++;hm;inl;inc;ipp;xsd
+
+
+ {67DA6AB6-F800-4c08-8B7A-83BB121AAD01}
+ rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms
+
+
+
+
+ Header Files
+
+
+ Header Files
+
+
+ Header Files
+
+
+
+
+ Source Files
+
+
+ Source Files
+
+
+ Source Files
+
+
+ Source Files
+
+
+
\ No newline at end of file
diff --git a/ZAPDTR/ExporterTest/Main.cpp b/ZAPDTR/ExporterTest/Main.cpp
new file mode 100644
index 000000000..07fdbeece
--- /dev/null
+++ b/ZAPDTR/ExporterTest/Main.cpp
@@ -0,0 +1,79 @@
+#include "CollisionExporter.h"
+#include "Globals.h"
+#include "RoomExporter.h"
+#include "TextureExporter.h"
+
+enum class ExporterFileMode
+{
+ ModeExample1 = (int)ZFileMode::Custom + 1,
+ ModeExample2 = (int)ZFileMode::Custom + 2,
+ ModeExample3 = (int)ZFileMode::Custom + 3,
+};
+
+static void ExporterParseFileMode(const std::string& buildMode, ZFileMode& fileMode)
+{
+ if (buildMode == "me1")
+ fileMode = (ZFileMode)ExporterFileMode::ModeExample1;
+ else if (buildMode == "me2")
+ fileMode = (ZFileMode)ExporterFileMode::ModeExample2;
+ else if (buildMode == "me3")
+ fileMode = (ZFileMode)ExporterFileMode::ModeExample3;
+}
+
+static void ExporterParseArgs([[maybe_unused]] int argc, char* argv[], int& i)
+{
+ std::string arg = argv[i];
+
+ if (arg == "--do-x")
+ {
+ }
+ else if (arg == "--do-y")
+ {
+ }
+}
+
+static bool ExporterProcessFileMode(ZFileMode fileMode)
+{
+ // Do whatever work is associated with these custom file modes...
+ // Return true to indicate one of our own file modes is being processed
+ if (fileMode == (ZFileMode)ExporterFileMode::ModeExample1)
+ return true;
+ else if (fileMode == (ZFileMode)ExporterFileMode::ModeExample2)
+ return true;
+ else if (fileMode == (ZFileMode)ExporterFileMode::ModeExample3)
+ return true;
+
+ return false;
+}
+
+static void ExporterFileBegin(ZFile* file)
+{
+ printf("ExporterFileBegin() called on ZFile %s.\n", file->GetName().c_str());
+}
+
+static void ExporterFileEnd(ZFile* file)
+{
+ printf("ExporterFileEnd() called on ZFile %s.\n", file->GetName().c_str());
+}
+
+static void ImportExporters()
+{
+ // In this example we set up a new exporter called "EXAMPLE".
+ // By running ZAPD with the argument -se EXAMPLE, we tell it that we want to use this exporter
+ // for our resources.
+ ExporterSet* exporterSet = new ExporterSet();
+ exporterSet->processFileModeFunc = ExporterProcessFileMode;
+ exporterSet->parseFileModeFunc = ExporterParseFileMode;
+ exporterSet->parseArgsFunc = ExporterParseArgs;
+ exporterSet->beginFileFunc = ExporterFileBegin;
+ exporterSet->endFileFunc = ExporterFileEnd;
+ exporterSet->exporters[ZResourceType::Texture] = new ExporterExample_Texture();
+ exporterSet->exporters[ZResourceType::Room] = new ExporterExample_Room();
+ exporterSet->exporters[ZResourceType::CollisionHeader] = new ExporterExample_Collision();
+
+ Globals::AddExporter("EXAMPLE", exporterSet);
+}
+
+// When ZAPD starts up, it will automatically call the below function, which in turn sets up our
+// exporters.
+REGISTER_EXPORTER(ImportExporters);
diff --git a/ZAPDTR/ExporterTest/Makefile b/ZAPDTR/ExporterTest/Makefile
new file mode 100644
index 000000000..42033b7c0
--- /dev/null
+++ b/ZAPDTR/ExporterTest/Makefile
@@ -0,0 +1,28 @@
+# Only used for standalone compilation, usually inherits these from the main makefile
+CXXFLAGS ?= -Wall -Wextra -O2 -g -std=c++17
+
+SRC_DIRS := $(shell find . -type d -not -path "*build*")
+CPP_FILES := $(foreach dir,$(SRC_DIRS),$(wildcard $(dir)/*.cpp))
+H_FILES := $(foreach dir,$(SRC_DIRS),$(wildcard $(dir)/*.h))
+
+O_FILES := $(foreach f,$(CPP_FILES:.cpp=.o),build/$f)
+LIB := ExporterTest.a
+
+# create build directories
+$(shell mkdir -p $(foreach dir,$(SRC_DIRS),build/$(dir)))
+
+all: $(LIB)
+
+clean:
+ rm -rf build $(LIB)
+
+format:
+ clang-format-11 -i $(CPP_FILES) $(H_FILES)
+
+.PHONY: all clean format
+
+build/%.o: %.cpp
+ $(CXX) $(CXXFLAGS) $(OPTFLAGS) -I ./ -I ../ZAPD -I ../ZAPDUtils -I ../lib/tinyxml2 -c $(OUTPUT_OPTION) $<
+
+$(LIB): $(O_FILES)
+ $(AR) rcs $@ $^
diff --git a/ZAPDTR/ExporterTest/RoomExporter.cpp b/ZAPDTR/ExporterTest/RoomExporter.cpp
new file mode 100644
index 000000000..6c5552d8f
--- /dev/null
+++ b/ZAPDTR/ExporterTest/RoomExporter.cpp
@@ -0,0 +1,372 @@
+#include "RoomExporter.h"
+#include "CollisionExporter.h"
+#include "Utils/BinaryWriter.h"
+#include "Utils/File.h"
+#include "Utils/MemoryStream.h"
+#include "ZRoom/Commands/SetCameraSettings.h"
+#include "ZRoom/Commands/SetCollisionHeader.h"
+#include "ZRoom/Commands/SetCsCamera.h"
+#include "ZRoom/Commands/SetEchoSettings.h"
+#include "ZRoom/Commands/SetEntranceList.h"
+#include "ZRoom/Commands/SetLightingSettings.h"
+#include "ZRoom/Commands/SetMesh.h"
+#include "ZRoom/Commands/SetRoomBehavior.h"
+#include "ZRoom/Commands/SetRoomList.h"
+#include "ZRoom/Commands/SetSkyboxModifier.h"
+#include "ZRoom/Commands/SetSkyboxSettings.h"
+#include "ZRoom/Commands/SetSoundSettings.h"
+#include "ZRoom/Commands/SetSpecialObjects.h"
+#include "ZRoom/Commands/SetStartPositionList.h"
+#include "ZRoom/Commands/SetTimeSettings.h"
+#include "ZRoom/Commands/SetWind.h"
+
+void ExporterExample_Room::Save(ZResource* res, fs::path outPath, BinaryWriter* writer)
+{
+ ZRoom* room = dynamic_cast(res);
+
+ // MemoryStream* memStream = new MemoryStream();
+ // BinaryWriter* writer = new BinaryWriter(memStream);
+
+ for (size_t i = 0; i < room->commands.size() * 8; i++)
+ writer->Write((uint8_t)0);
+
+ for (size_t i = 0; i < room->commands.size(); i++)
+ {
+ ZRoomCommand* cmd = room->commands[i];
+
+ writer->Seek(i * 8, SeekOffsetType::Start);
+
+ writer->Write((uint8_t)cmd->cmdID);
+
+ switch (cmd->cmdID)
+ {
+ case RoomCommand::SetWind:
+ {
+ SetWind* cmdSetWind = (SetWind*)cmd;
+
+ writer->Write((uint8_t)0); // 0x01
+ writer->Write((uint8_t)0); // 0x02
+ writer->Write((uint8_t)0); // 0x03
+
+ writer->Write(cmdSetWind->windWest); // 0x04
+ writer->Write(cmdSetWind->windVertical); // 0x05
+ writer->Write(cmdSetWind->windSouth); // 0x06
+ writer->Write(cmdSetWind->clothFlappingStrength); // 0x07
+ }
+ break;
+ case RoomCommand::SetTimeSettings:
+ {
+ SetTimeSettings* cmdTime = (SetTimeSettings*)cmd;
+
+ writer->Write((uint8_t)0); // 0x01
+ writer->Write((uint8_t)0); // 0x02
+ writer->Write((uint8_t)0); // 0x03
+
+ writer->Write(cmdTime->hour); // 0x04
+ writer->Write(cmdTime->min); // 0x05
+ writer->Write(cmdTime->unk); // 0x06
+ writer->Write((uint8_t)0); // 0x07
+ }
+ break;
+ case RoomCommand::SetSkyboxModifier:
+ {
+ SetSkyboxModifier* cmdSkybox = (SetSkyboxModifier*)cmd;
+
+ writer->Write((uint8_t)0); // 0x01
+ writer->Write((uint8_t)0); // 0x02
+ writer->Write((uint8_t)0); // 0x03
+
+ writer->Write(cmdSkybox->disableSky); // 0x04
+ writer->Write(cmdSkybox->disableSunMoon); // 0x05
+ writer->Write((uint8_t)0); // 0x06
+ writer->Write((uint8_t)0); // 0x07
+ }
+ break;
+ case RoomCommand::SetEchoSettings:
+ {
+ SetEchoSettings* cmdEcho = (SetEchoSettings*)cmd;
+
+ writer->Write((uint8_t)0); // 0x01
+ writer->Write((uint8_t)0); // 0x02
+ writer->Write((uint8_t)0); // 0x03
+ writer->Write((uint8_t)0); // 0x04
+ writer->Write((uint8_t)0); // 0x05
+ writer->Write((uint8_t)0); // 0x06
+ writer->Write((uint8_t)cmdEcho->echo); // 0x07
+ }
+ break;
+ case RoomCommand::SetSoundSettings:
+ {
+ SetSoundSettings* cmdSound = (SetSoundSettings*)cmd;
+
+ writer->Write((uint8_t)cmdSound->reverb); // 0x01
+ writer->Write((uint8_t)0); // 0x02
+ writer->Write((uint8_t)0); // 0x03
+ writer->Write((uint8_t)0); // 0x04
+ writer->Write((uint8_t)0); // 0x05
+
+ writer->Write(cmdSound->nightTimeSFX); // 0x06
+ writer->Write(cmdSound->musicSequence); // 0x07
+ }
+ break;
+ case RoomCommand::SetSkyboxSettings:
+ {
+ SetSkyboxSettings* cmdSkybox = (SetSkyboxSettings*)cmd;
+
+ writer->Write((uint8_t)cmdSkybox->unk1); // 0x01
+ writer->Write((uint8_t)0); // 0x02
+ writer->Write((uint8_t)0); // 0x03
+ writer->Write((uint8_t)cmdSkybox->skyboxNumber); // 0x04
+ writer->Write((uint8_t)cmdSkybox->cloudsType); // 0x05
+ writer->Write((uint8_t)cmdSkybox->isIndoors); // 0x06
+ }
+ break;
+ case RoomCommand::SetRoomBehavior:
+ {
+ SetRoomBehavior* cmdRoom = (SetRoomBehavior*)cmd;
+
+ writer->Write((uint8_t)cmdRoom->gameplayFlags); // 0x01
+ writer->Write((uint8_t)0); // 0x02
+ writer->Write((uint8_t)0); // 0x03
+ writer->Write(cmdRoom->gameplayFlags2); // 0x04
+ }
+ break;
+ case RoomCommand::SetCsCamera:
+ {
+ SetCsCamera* cmdCsCam = (SetCsCamera*)cmd;
+
+ writer->Write((uint8_t)cmdCsCam->cameras.size()); // 0x01
+ writer->Write((uint8_t)0); // 0x02
+ writer->Write((uint8_t)0); // 0x03
+
+ writer->Write(cmdCsCam->segmentOffset); // 0x04
+ }
+ break;
+ case RoomCommand::SetMesh:
+ {
+ SetMesh* cmdMesh = (SetMesh*)cmd;
+
+ int baseStreamEnd = writer->GetStream().get()->GetLength();
+
+ writer->Write((uint8_t)cmdMesh->data); // 0x01
+ writer->Write((uint8_t)0); // 0x02
+ writer->Write((uint8_t)0); // 0x03
+
+ writer->Write(baseStreamEnd); // 0x04
+
+ uint32_t oldOffset = writer->GetBaseAddress();
+ writer->Seek(baseStreamEnd, SeekOffsetType::Start);
+
+ // TODO: NOT DONE
+
+ writer->Write(cmdMesh->meshHeaderType);
+
+ if (cmdMesh->meshHeaderType == 0)
+ {
+ // writer->Write(cmdMesh->)
+ }
+ else if (cmdMesh->meshHeaderType == 1)
+ {
+ }
+ else if (cmdMesh->meshHeaderType == 2)
+ {
+ }
+
+ writer->Seek(oldOffset, SeekOffsetType::Start);
+ }
+ break;
+ case RoomCommand::SetCameraSettings:
+ {
+ SetCameraSettings* cmdCam = (SetCameraSettings*)cmd;
+
+ writer->Write((uint8_t)cmdCam->cameraMovement); // 0x01
+ writer->Write((uint8_t)0); // 0x02
+ writer->Write((uint8_t)0); // 0x03
+ writer->Write(cmdCam->mapHighlight); // 0x04
+ }
+ break;
+ case RoomCommand::SetLightingSettings:
+ {
+ SetLightingSettings* cmdLight = (SetLightingSettings*)cmd;
+
+ writer->Write((uint8_t)cmdLight->settings.size()); // 0x01
+ writer->Write((uint8_t)0); // 0x02
+ writer->Write((uint8_t)0); // 0x03
+ writer->Write(cmdLight->segmentOffset); // 0x04
+
+ uint32_t oldOffset = writer->GetBaseAddress();
+ writer->Seek(cmdLight->segmentOffset, SeekOffsetType::Start);
+
+ for (LightingSettings setting : cmdLight->settings)
+ {
+ writer->Write(setting.ambientClrR);
+ writer->Write(setting.ambientClrG);
+ writer->Write(setting.ambientClrB);
+
+ writer->Write(setting.diffuseClrA_R);
+ writer->Write(setting.diffuseClrA_G);
+ writer->Write(setting.diffuseClrA_B);
+
+ writer->Write(setting.diffuseDirA_X);
+ writer->Write(setting.diffuseDirA_Y);
+ writer->Write(setting.diffuseDirA_Z);
+
+ writer->Write(setting.diffuseClrB_R);
+ writer->Write(setting.diffuseClrB_G);
+ writer->Write(setting.diffuseClrB_B);
+
+ writer->Write(setting.diffuseDirB_X);
+ writer->Write(setting.diffuseDirB_Y);
+ writer->Write(setting.diffuseDirB_Z);
+
+ writer->Write(setting.fogClrR);
+ writer->Write(setting.fogClrG);
+ writer->Write(setting.fogClrB);
+
+ writer->Write(setting.unk);
+ writer->Write(setting.drawDistance);
+ }
+
+ writer->Seek(oldOffset, SeekOffsetType::Start);
+ }
+ break;
+ case RoomCommand::SetRoomList:
+ {
+ SetRoomList* cmdRoom = (SetRoomList*)cmd;
+
+ writer->Write((uint8_t)cmdRoom->romfile->rooms.size()); // 0x01
+ writer->Write((uint8_t)0); // 0x02
+ writer->Write((uint8_t)0); // 0x03
+
+ auto baseStreamEnd = writer->GetLength();
+ writer->Write(baseStreamEnd); // 0x04
+
+ uint32_t oldOffset = writer->GetBaseAddress();
+ writer->Seek(baseStreamEnd, SeekOffsetType::Start);
+
+ for (const auto& entry : cmdRoom->romfile->rooms)
+ {
+ writer->Write(entry.virtualAddressStart);
+ writer->Write(entry.virtualAddressEnd);
+ }
+
+ writer->Seek(oldOffset, SeekOffsetType::Start);
+ }
+ break;
+ case RoomCommand::SetCollisionHeader:
+ {
+ SetCollisionHeader* cmdCollHeader = (SetCollisionHeader*)cmd;
+
+ int streamEnd = writer->GetStream().get()->GetLength();
+
+ writer->Write((uint8_t)0); // 0x01
+ writer->Write((uint8_t)0); // 0x02
+ writer->Write((uint8_t)0); // 0x03
+ writer->Write(streamEnd); // 0x04
+
+ // TODO: NOT DONE
+
+ uint32_t oldOffset = writer->GetBaseAddress();
+ writer->Seek(streamEnd, SeekOffsetType::Start);
+
+ ExporterExample_Collision colExp = ExporterExample_Collision();
+
+ colExp.Save(cmdCollHeader->collisionHeader, outPath, writer);
+
+ writer->Seek(oldOffset, SeekOffsetType::Start);
+ }
+ break;
+ case RoomCommand::SetEntranceList:
+ {
+ SetEntranceList* cmdEntrance = (SetEntranceList*)cmd;
+
+ uint32_t baseStreamEnd = writer->GetStream().get()->GetLength();
+
+ writer->Write((uint8_t)0); // 0x01
+ writer->Write((uint8_t)0); // 0x02
+ writer->Write((uint8_t)0); // 0x03
+ writer->Write(baseStreamEnd); // 0x04
+
+ uint32_t oldOffset = writer->GetBaseAddress();
+ writer->Seek(baseStreamEnd, SeekOffsetType::Start);
+
+ for (EntranceEntry entry : cmdEntrance->entrances)
+ {
+ writer->Write((uint8_t)entry.startPositionIndex);
+ writer->Write((uint8_t)entry.roomToLoad);
+ }
+
+ writer->Seek(oldOffset, SeekOffsetType::Start);
+ }
+ break;
+ case RoomCommand::SetSpecialObjects:
+ {
+ SetSpecialObjects* cmdSpecObj = (SetSpecialObjects*)cmd;
+
+ writer->Write((uint8_t)cmdSpecObj->elfMessage); // 0x01
+ writer->Write((uint8_t)0); // 0x02
+ writer->Write((uint8_t)0); // 0x03
+ writer->Write((uint8_t)0); // 0x04
+ writer->Write((uint8_t)0); // 0x05
+ writer->Write((uint16_t)cmdSpecObj->globalObject); // 0x06
+ }
+ break;
+ case RoomCommand::SetStartPositionList:
+ {
+ SetStartPositionList* cmdStartPos = (SetStartPositionList*)cmd;
+
+ uint32_t baseStreamEnd = writer->GetStream().get()->GetLength();
+
+ writer->Write((uint8_t)cmdStartPos->actors.size()); // 0x01
+ writer->Write((uint8_t)0); // 0x02
+ writer->Write((uint8_t)0); // 0x03
+ writer->Write(baseStreamEnd); // 0x04
+
+ uint32_t oldOffset = writer->GetBaseAddress();
+ writer->Seek(baseStreamEnd, SeekOffsetType::Start);
+
+ for (ActorSpawnEntry entry : cmdStartPos->actors)
+ {
+ writer->Write(entry.actorNum);
+ writer->Write(entry.posX);
+ writer->Write(entry.posY);
+ writer->Write(entry.posZ);
+ writer->Write(entry.rotX);
+ writer->Write(entry.rotY);
+ writer->Write(entry.rotZ);
+ writer->Write(entry.initVar);
+ }
+
+ writer->Seek(oldOffset, SeekOffsetType::Start);
+ }
+ break;
+ case RoomCommand::EndMarker:
+ {
+ writer->Write((uint8_t)0); // 0x01
+ writer->Write((uint8_t)0); // 0x02
+ writer->Write((uint8_t)0); // 0x03
+ writer->Write((uint8_t)0); // 0x04
+ writer->Write((uint8_t)0); // 0x05
+ writer->Write((uint8_t)0); // 0x06
+ writer->Write((uint8_t)0); // 0x07
+ }
+ break;
+ default:
+ printf("UNIMPLEMENTED COMMAND: %i\n", (int)cmd->cmdID);
+
+ writer->Write((uint8_t)0); // 0x01
+ writer->Write((uint8_t)0); // 0x02
+ writer->Write((uint8_t)0); // 0x03
+ writer->Write((uint8_t)0); // 0x04
+ writer->Write((uint8_t)0); // 0x05
+ writer->Write((uint8_t)0); // 0x06
+ writer->Write((uint8_t)0); // 0x07
+
+ break;
+ }
+ }
+
+ // writer->Close();
+ // File::WriteAllBytes(StringHelper::Sprintf("%s", res->GetName().c_str()),
+ // memStream->ToVector());
+}
diff --git a/ZAPDTR/ExporterTest/RoomExporter.h b/ZAPDTR/ExporterTest/RoomExporter.h
new file mode 100644
index 000000000..ee531dc87
--- /dev/null
+++ b/ZAPDTR/ExporterTest/RoomExporter.h
@@ -0,0 +1,10 @@
+#pragma once
+
+#include "ZResource.h"
+#include "ZRoom/ZRoom.h"
+
+class ExporterExample_Room : public ZResourceExporter
+{
+public:
+ void Save(ZResource* res, fs::path outPath, BinaryWriter* writer) override;
+};
\ No newline at end of file
diff --git a/ZAPDTR/ExporterTest/TextureExporter.cpp b/ZAPDTR/ExporterTest/TextureExporter.cpp
new file mode 100644
index 000000000..6488bed3a
--- /dev/null
+++ b/ZAPDTR/ExporterTest/TextureExporter.cpp
@@ -0,0 +1,14 @@
+#include "TextureExporter.h"
+#include "../ZAPD/ZFile.h"
+
+void ExporterExample_Texture::Save(ZResource* res, [[maybe_unused]] fs::path outPath,
+ BinaryWriter* writer)
+{
+ ZTexture* tex = (ZTexture*)res;
+
+ auto data = tex->parent->GetRawData();
+
+ for (offset_t i = tex->GetRawDataIndex(); i < tex->GetRawDataIndex() + tex->GetRawDataSize();
+ i++)
+ writer->Write(data[i]);
+}
diff --git a/ZAPDTR/ExporterTest/TextureExporter.h b/ZAPDTR/ExporterTest/TextureExporter.h
new file mode 100644
index 000000000..41c4e79be
--- /dev/null
+++ b/ZAPDTR/ExporterTest/TextureExporter.h
@@ -0,0 +1,11 @@
+#pragma once
+
+#include "Utils/BinaryWriter.h"
+#include "ZResource.h"
+#include "ZTexture.h"
+
+class ExporterExample_Texture : public ZResourceExporter
+{
+public:
+ void Save(ZResource* res, fs::path outPath, BinaryWriter* writer) override;
+};
\ No newline at end of file
diff --git a/ZAPDTR/Jenkinsfile b/ZAPDTR/Jenkinsfile
new file mode 100644
index 000000000..ec9b56ac5
--- /dev/null
+++ b/ZAPDTR/Jenkinsfile
@@ -0,0 +1,97 @@
+pipeline {
+ agent {
+ label 'ZAPD'
+ }
+
+ stages {
+ // Non-parallel ZAPD stage
+ stage('Build ZAPD') {
+ steps {
+ sh 'make -j WERROR=1'
+ }
+ }
+
+ // CHECKOUT THE REPOS
+ stage('Checkout Repos') {
+ parallel {
+ stage('Checkout oot') {
+ steps {
+ dir('oot') {
+ git url: 'https://github.com/zeldaret/oot.git'
+ }
+ }
+ }
+
+ stage('Checkout mm') {
+ steps{
+ dir('mm') {
+ git url: 'https://github.com/zeldaret/mm.git'
+ }
+ }
+ }
+ }
+ }
+
+ // SETUP THE REPOS
+ stage('Set up repos') {
+ parallel {
+ stage('Setup OOT') {
+ steps {
+ dir('oot') {
+ sh 'cp /usr/local/etc/roms/baserom_oot.z64 baserom_original.z64'
+
+ // Identical to `make setup` except for copying our newer ZAPD.out into oot
+ sh 'git submodule update --init --recursive'
+ sh 'make -C tools'
+ sh 'cp ../ZAPD.out tools/ZAPD/'
+ sh 'python3 fixbaserom.py'
+ sh 'python3 extract_baserom.py'
+ sh 'python3 extract_assets.py'
+ }
+ }
+ }
+
+ stage('Setup MM') {
+ steps {
+ dir('mm') {
+ sh 'cp /usr/local/etc/roms/mm.us.rev1.z64 baserom.mm.us.rev1.z64'
+
+ // Identical to `make setup` except for copying our newer ZAPD.out into mm
+ sh 'make -C tools'
+ sh 'cp ../ZAPD.out tools/ZAPD/'
+ sh 'python3 tools/fixbaserom.py'
+ sh 'python3 tools/extract_baserom.py'
+ sh 'python3 extract_assets.py -t$(nproc)'
+ }
+ }
+ }
+ }
+ }
+
+ // BUILD THE REPOS
+ stage('Build repos') {
+ parallel {
+ stage('Build oot') {
+ steps {
+ dir('oot') {
+ sh 'make -j'
+ }
+ }
+ }
+ stage('Build mm') {
+ steps {
+ dir('mm') {
+ sh 'make -j disasm'
+ sh 'make -j all'
+ }
+ }
+ }
+ }
+ }
+ }
+ post {
+ always {
+ cleanWs()
+ }
+ }
+}
diff --git a/ZAPDTR/LICENSE b/ZAPDTR/LICENSE
new file mode 100644
index 000000000..90b734bde
--- /dev/null
+++ b/ZAPDTR/LICENSE
@@ -0,0 +1,21 @@
+MIT License
+
+Copyright (c) 2020 Zelda Reverse Engineering Team
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
diff --git a/ZAPDTR/Makefile b/ZAPDTR/Makefile
new file mode 100644
index 000000000..2b47a8039
--- /dev/null
+++ b/ZAPDTR/Makefile
@@ -0,0 +1,134 @@
+# use variables in submakes
+export
+OPTIMIZATION_ON ?= 1
+ASAN ?= 0
+DEPRECATION_ON ?= 1
+DEBUG ?= 0
+COPYCHECK_ARGS ?=
+LLD ?= 0
+WERROR ?= 0
+
+# Use clang++ if available, else use g++
+ifeq ($(shell command -v clang++ >/dev/null 2>&1; echo $$?),0)
+ CXX := clang++
+else
+ CXX := g++
+endif
+
+INC := -I ZAPD -I lib/elfio -I lib/libgfxd -I lib/tinyxml2 -I ZAPDUtils
+CXXFLAGS := -fpic -std=c++17 -Wall -Wextra -fno-omit-frame-pointer
+OPTFLAGS :=
+
+ifneq ($(DEBUG),0)
+ OPTIMIZATION_ON = 0
+ CXXFLAGS += -g3 -DDEVELOPMENT -D_DEBUG
+ COPYCHECK_ARGS += --devel
+ DEPRECATION_ON = 0
+endif
+
+ifneq ($(WERROR),0)
+ CXXFLAGS += -Werror
+endif
+
+ifeq ($(OPTIMIZATION_ON),0)
+ OPTFLAGS := -O0
+else
+ OPTFLAGS := -O2
+endif
+
+ifneq ($(ASAN),0)
+ CXXFLAGS += -fsanitize=address -fsanitize=pointer-compare -fsanitize=pointer-subtract -fsanitize=undefined
+endif
+ifneq ($(DEPRECATION_ON),0)
+ CXXFLAGS += -DDEPRECATION_ON
+endif
+# CXXFLAGS += -DTEXTURE_DEBUG
+
+LDFLAGS := -lm -ldl -lpng
+
+# Use LLD if available. Set LLD=0 to not use it
+ifeq ($(shell command -v ld.lld >/dev/null 2>&1; echo $$?),0)
+ LLD := 1
+endif
+
+ifneq ($(LLD),0)
+ LDFLAGS += -fuse-ld=lld
+endif
+
+UNAME := $(shell uname)
+UNAMEM := $(shell uname -m)
+ifneq ($(UNAME), Darwin)
+ LDFLAGS += -Wl,-export-dynamic -lstdc++fs
+ EXPORTERS := -Wl,--whole-archive ExporterTest/ExporterTest.a -Wl,--no-whole-archive
+else
+ EXPORTERS := -Wl,-force_load ExporterTest/ExporterTest.a
+ ifeq ($(UNAMEM),arm64)
+ ifeq ($(shell brew list libpng > /dev/null 2>&1; echo $$?),0)
+ LDFLAGS += -L $(shell brew --prefix)/lib
+ INC += -I $(shell brew --prefix)/include
+ else
+ $(error Please install libpng via Homebrew)
+ endif
+ endif
+endif
+
+
+ZAPD_SRC_DIRS := $(shell find ZAPD -type d)
+SRC_DIRS = $(ZAPD_SRC_DIRS) lib/tinyxml2
+
+ZAPD_CPP_FILES := $(foreach dir,$(ZAPD_SRC_DIRS),$(wildcard $(dir)/*.cpp))
+ZAPD_H_FILES := $(foreach dir,$(ZAPD_SRC_DIRS),$(wildcard $(dir)/*.h))
+
+CPP_FILES += $(ZAPD_CPP_FILES) lib/tinyxml2/tinyxml2.cpp
+O_FILES := $(foreach f,$(CPP_FILES:.cpp=.o),build/$f)
+O_FILES += build/ZAPD/BuildInfo.o
+
+# create build directories
+$(shell mkdir -p $(foreach dir,$(SRC_DIRS),build/$(dir)))
+
+
+# Main targets
+all: ZAPD.out copycheck
+
+build/ZAPD/BuildInfo.o:
+ python3 ZAPD/genbuildinfo.py $(COPYCHECK_ARGS)
+ $(CXX) $(CXXFLAGS) $(OPTFLAGS) $(INC) -c $(OUTPUT_OPTION) build/ZAPD/BuildInfo.cpp
+
+copycheck: ZAPD.out
+ python3 copycheck.py
+
+clean:
+ rm -rf build ZAPD.out
+ $(MAKE) -C lib/libgfxd clean
+ $(MAKE) -C ZAPDUtils clean
+ $(MAKE) -C ExporterTest clean
+
+rebuild: clean all
+
+format:
+ clang-format-11 -i $(ZAPD_CPP_FILES) $(ZAPD_H_FILES)
+ $(MAKE) -C ZAPDUtils format
+ $(MAKE) -C ExporterTest format
+
+.PHONY: all build/ZAPD/BuildInfo.o copycheck clean rebuild format
+
+build/%.o: %.cpp
+ $(CXX) $(CXXFLAGS) $(OPTFLAGS) $(INC) -c $(OUTPUT_OPTION) $<
+
+
+# Submakes
+lib/libgfxd/libgfxd.a:
+ $(MAKE) -C lib/libgfxd
+
+.PHONY: ExporterTest
+ExporterTest:
+ $(MAKE) -C ExporterTest
+
+.PHONY: ZAPDUtils
+ZAPDUtils:
+ $(MAKE) -C ZAPDUtils
+
+
+# Linking
+ZAPD.out: $(O_FILES) lib/libgfxd/libgfxd.a ExporterTest ZAPDUtils
+ $(CXX) $(CXXFLAGS) $(O_FILES) lib/libgfxd/libgfxd.a ZAPDUtils/ZAPDUtils.a $(EXPORTERS) $(LDFLAGS) $(OUTPUT_OPTION)
diff --git a/ZAPDTR/README.md b/ZAPDTR/README.md
new file mode 100644
index 000000000..448aea033
--- /dev/null
+++ b/ZAPDTR/README.md
@@ -0,0 +1,168 @@
+# ZAPD: Zelda Asset Processor for Decomp
+
+## Compiling
+
+### Dependencies
+
+ZAPD needs a compiler with C++17 support.
+
+ZAPD has the following library dependencies:
+
+- `libpng`
+
+In a Debian/Ubuntu based environment, those could be installed with the following command:
+
+```bash
+sudo apt install libpng-dev
+```
+
+On a Mac, you will need to install libpng with Homebrew or MacPorts; we currently only support Homebrew. You can run
+
+```bash
+brew install libpng
+```
+
+to install it via Homebrew.
+
+### Building
+
+#### Linux / *nix
+
+ZAPD uses the clasic `Makefile` approach. To build just run `make` (or even better `make -j` for faster compilations).
+
+You can configure a bit your ZAPD build with the following options:
+
+- `OPTIMIZATION_ON`: If set to `0` optimizations will be disabled (compile with `-O0`). Any other value compiles with `-O2`. Defaults to `1`.
+- `ASAN`: If it is set to a non-zero then ZAPD will be compiled with Address Sanitizer enabled (`-fsanitize=address`). Defaults to `0`.
+- `DEPRECATION_ON`: If it is set to a zero then deprecation warnings will be disabled. Defaults to `1`.
+- `DEBUG`: If non-zero, ZAPD will be compiled in _development mode_. This implies the following:
+ - Debugging symbols enabled (`-g3`). They are disabled by default.
+ - `OPTIMIZATION_ON=0`: Disables optimizations (`-O0`).
+ - `DEPRECATION_ON=0`: Disables deprecation warnings.
+- `LLD=1`: builds with the LLVM linker `ld.lld` instead of the system default.
+
+As an example, if you want to build ZAPD with optimizations disabled and use the address sanitizer, you could use the following command:
+
+```bash
+make -j OPTIMIZATION_ON=0 ASAN=1
+```
+
+#### Windows
+
+This repository contains `vcxproj` files for compiling under Visual Studio environments. See `ZAPD/ZAPD.vcxproj`.
+
+## Invoking ZAPD
+
+ZAPD needs a _File parsing mode_ to be passed as first parameter. The options are:
+
+- `e`: "Extraction" mode.
+ - In this mode, ZAPD expects a XML file as input, a folder as ouput and a path to the baserom files.
+ - ZAPD will read the XML and use it as a guide to extract the contents of the specified asset file from the baserom folder.
+ - For more info of the format of those XMLs, see the [ZAPD extraction XML reference](docs/zapd_extraction_xml_reference.md).
+- `bsf`: "Build source file" mode.
+ - This is an experimental mode.
+ - It was going to be used to let you have XMLs that aren't just for extraction. Might get used, might not. Still need to experiment on that.
+- `btex`: "Build texture" mode.
+ - In this mode, ZAPD expects a PNG file as input, a filename as ouput and a texture type parameter (`-tt`).
+ - ZAPD will try to convert the given PNG into the contents of a `uint64_t` C array.
+- `bren`: "Build (render) background" mode.
+ - In this mode, ZAPD expects a JPG file as input and a filename as ouput.
+ - ZAPD will try to convert the given JPG into the contents of a `uint64_t` C array.
+- `blb`: "Build blob" mode.
+ - In this mode, ZAPD expects a BIN file as input and a filename as ouput.
+ - ZAPD will try to convert the given BIN into the contents of a `uint8_t` C array.
+- `bovl`: "Build overlay" mode.
+ - In this mode, ZAPD expects an overlay C file as input, a filename as ouput and an overlay configuration path (`-cfg`).
+ - ZAPD will generate a reloc `.s` file.
+
+ZAPD also accepts the following list of extra parameters:
+
+- `-i PATH` / `--inputpath PATH`: Set input path.
+- `-o PATH` / `--outputpath PATH`: Set output path.
+- `-b PATH` / `--baserompath`: Set baserom path.
+ - Can be used only in `e` or `bsf` modes.
+- `-osf PATH`: Set source output path. This is the path where the `.c` and `.h` files will be extracted to. If omitted, it will use the value passed to `--outputpath` parameter.
+- `-gsf MODE`: Generate source file during extraction. If `MODE` is `1`, C source files will be generated.
+ - Can be used only in `e` mode.
+- `-crc` / `--output-crc`: Outputs a CRC file for each extracted texture.
+ - Can be used only in `e` or `bsf` modes.
+- `-ulzdl MODE`: Use "Legacy ZDisplayList" instead of `libgfxd`. Set `MODE` to `1` to enable it.
+ - Can be used only in `e` or `bsf` modes.
+- `-profile MODE`: Enable profiling. Set `MODE` to `1` to enable it.
+- `-uer MODE`: Split resources into their individual components (enabled by default). Set `MODE` to non-`1` to disable it.
+- `-tt TYPE`: Set texture type.
+ - Can be used only in mode `btex`.
+ - Valid values:
+ - `rgba32`
+ - `rgb5a1`
+ - `i4`
+ - `i8`
+ - `ia4`
+ - `ia8`
+ - `ia16`
+ - `ci4`
+ - `ci8`
+- `-cfg PATH`: Set cfg path (for overlays).
+ - Can be used only in `bovl` mode.
+- `-rconf PATH` Read Config File.
+- `-eh`: Enable error handler.
+ - Only available in non-Windows environments.
+- `-v MODE`: Enable verbosity. Currently there are 3 possible values:
+ - `0`: Default. Completely silent (except for warnings and errors).
+ - `1`: Information.
+ - `2` (and higher): Debug.
+- `-wu` / `--warn-unaccounted`: Enable warnings for each unaccounted block of data found.
+ - Can be used only in `e` or `bsf` modes.
+- `-vu` / `--verbose-unaccounted`: Changes how unaccounteds are outputted. Max 4 bytes per line (a word) and add a comment with the offset of each of those lines.
+ - Could be useful for looking at raw data or testing.
+ - Can be used only in `e` or `bsf` modes.
+- `-tm MODE`: Test Mode (enables certain experimental features). To enable it, set `MODE` to `1`.
+- `-se` / `--set-exporter` : Sets which exporter to use.
+- `--gcc-compat` : Enables GCC compatibly mode. Slower.
+- `-us` / `--unaccounted-static` : Mark unaccounted data as `static`
+- `-s` / `--static` : Mark every asset as `static`.
+ - This behaviour can be overridden per asset using `Static=` in the respective XML node.
+- `-W...`: warning flags, see below
+
+Additionally, you can pass the flag `--version` to see the current ZAPD version. If that flag is passed, ZAPD will ignore any other parameter passed.
+
+### Warning flags
+
+ZAPD contains a variety of warning types, with similar syntax to GCC or Clang's compiler warnings. Warnings can have three levels:
+
+- Off (does not display anything)
+- Warn (print a warning but continue processing)
+- Err (behave like an error, i.e. print and throw an exception to crash ZAPD when occurs)
+
+Each warning type uses one of these by default, but can be modified with flags, similarly to GCC or Clang:
+
+- `-Wfoo` enables warnings of type `foo`
+- `-Wno-foo` disables warnings of type `foo`
+- `-Werror=foo` escalates `foo` to behave like an error
+- `-Weverything` enables all warnings (they may be turned off using `-Wno-` flags afterwards)
+- `-Werror` escalates all enabled warnings to errors
+
+All warning types currently implemented, with their default levels:
+
+| Warning type | Default level | Description |
+| --------------------------- | ------------- | ------------------------------------------------------------------------ |
+| `-Wdeprecated` | Warn | Deprecated features |
+| `-Whardcoded-pointer` | Warn | ZAPD lacks the info to make a symbol, so must output a hardcoded pointer |
+| `-Wintersection` | Warn | Two assets intersect |
+| `-Winvalid-attribute-value` | Err | Attribute declared in XML is wrong |
+| `-Winvalid-extracted-data` | Err | Extracted data does not have correct form |
+| `-Winvalid-jpeg` | Err | JPEG file does not conform to the game's format requirements |
+| `-Winvalid-png` | Err | Issues arising when processing PNG data |
+| `-Winvalid-xml` | Err | XML has syntax errors |
+| `-Wmissing-attribute` | Warn | Required attribute missing in XML tag |
+| `-Wmissing-offsets` | Warn | Offset attribute missing in XML tag |
+| `-Wmissing-segment` | Warn | Segment not given in File tag in XML |
+| `-Wnot-implemented` | Warn | ZAPD does not currently support this feature |
+| `-Wunaccounted` | Off | Large blocks of unaccounted |
+| `-Wunknown-attribute` | Warn | Unknown attribute in XML entry tag |
+
+There are also errors that do not have a type, and cannot be disabled.
+
+For example, here we have invoked ZAPD in the usual way to extract using a (rather badly-written) XML, but escalating `-Wintersection` to an error:
+
+![ZAPD warnings example](docs/zapd_warning_example.png?raw=true)
diff --git a/ZAPDTR/ZAPD/CRC32.h b/ZAPDTR/ZAPD/CRC32.h
new file mode 100644
index 000000000..4158a5528
--- /dev/null
+++ b/ZAPDTR/ZAPD/CRC32.h
@@ -0,0 +1,23 @@
+#pragma once
+
+static uint32_t CRC32B(unsigned char* message, int32_t size)
+{
+ int32_t byte, crc;
+ int32_t mask;
+
+ crc = 0xFFFFFFFF;
+
+ for (int32_t i = 0; i < size; i++)
+ {
+ byte = message[i];
+ crc = crc ^ byte;
+
+ for (int32_t j = 7; j >= 0; j--)
+ {
+ mask = -(crc & 1);
+ crc = (crc >> 1) ^ (0xEDB88320 & mask);
+ }
+ }
+
+ return ~(uint32_t)(crc);
+}
\ No newline at end of file
diff --git a/ZAPDTR/ZAPD/Declaration.cpp b/ZAPDTR/ZAPD/Declaration.cpp
new file mode 100644
index 000000000..eeb988db7
--- /dev/null
+++ b/ZAPDTR/ZAPD/Declaration.cpp
@@ -0,0 +1,229 @@
+#include "Declaration.h"
+
+#include "Globals.h"
+#include "Utils/StringHelper.h"
+
+Declaration::Declaration(offset_t nAddress, DeclarationAlignment nAlignment, size_t nSize,
+ const std::string& nText)
+{
+ address = nAddress;
+ alignment = nAlignment;
+ size = nSize;
+ text = nText;
+}
+
+Declaration::Declaration(offset_t nAddress, DeclarationAlignment nAlignment, size_t nSize,
+ const std::string& nVarType, const std::string& nVarName, bool nIsArray,
+ const std::string& nText)
+ : Declaration(nAddress, nAlignment, nSize, nText)
+{
+ varType = nVarType;
+ varName = nVarName;
+ isArray = nIsArray;
+}
+
+Declaration::Declaration(offset_t nAddress, DeclarationAlignment nAlignment, size_t nSize,
+ const std::string& nVarType, const std::string& nVarName, bool nIsArray,
+ size_t nArrayItemCnt, const std::string& nText)
+ : Declaration(nAddress, nAlignment, nSize, nText)
+{
+ varType = nVarType;
+ varName = nVarName;
+ isArray = nIsArray;
+ arrayItemCnt = nArrayItemCnt;
+}
+
+Declaration::Declaration(offset_t nAddress, DeclarationAlignment nAlignment, size_t nSize,
+ const std::string& nVarType, const std::string& nVarName, bool nIsArray,
+ const std::string& nArrayItemCntStr, const std::string& nText)
+ : Declaration(nAddress, nAlignment, nSize, nText)
+{
+ varType = nVarType;
+ varName = nVarName;
+ isArray = nIsArray;
+ arrayItemCntStr = nArrayItemCntStr;
+}
+
+Declaration::Declaration(offset_t nAddress, DeclarationAlignment nAlignment, size_t nSize,
+ const std::string& nVarType, const std::string& nVarName, bool nIsArray,
+ size_t nArrayItemCnt, const std::string& nText, bool nIsExternal)
+ : Declaration(nAddress, nAlignment, nSize, nVarType, nVarName, nIsArray, nArrayItemCnt, nText)
+{
+ isExternal = nIsExternal;
+}
+
+Declaration::Declaration(offset_t nAddress, const std::string& nIncludePath, size_t nSize,
+ const std::string& nVarType, const std::string& nVarName)
+ : Declaration(nAddress, DeclarationAlignment::Align4, nSize, "")
+{
+ includePath = nIncludePath;
+ varType = nVarType;
+ varName = nVarName;
+}
+
+bool Declaration::IsStatic() const
+{
+ switch (staticConf)
+ {
+ case StaticConfig::Off:
+ return false;
+
+ case StaticConfig::Global:
+ return Globals::Instance->forceStatic;
+
+ case StaticConfig::On:
+ return true;
+ }
+
+ return false;
+}
+
+std::string Declaration::GetNormalDeclarationStr() const
+{
+ std::string output;
+
+ if (preText != "")
+ output += preText + "\n";
+
+ if (IsStatic())
+ {
+ output += "static ";
+ }
+
+ if (isArray)
+ {
+ if (arrayItemCntStr != "" && (IsStatic() || forceArrayCnt))
+ {
+ output += StringHelper::Sprintf("%s %s[%s];\n", varType.c_str(), varName.c_str(),
+ arrayItemCntStr.c_str());
+ }
+ else if (arrayItemCnt != 0 && (IsStatic() || forceArrayCnt))
+ {
+ output += StringHelper::Sprintf("%s %s[%i] = {\n", varType.c_str(), varName.c_str(),
+ arrayItemCnt);
+ }
+ else
+ {
+ output += StringHelper::Sprintf("%s %s[] = {\n", varType.c_str(), varName.c_str());
+ }
+
+ output += text + "\n";
+ }
+ else
+ {
+ output += StringHelper::Sprintf("%s %s = { ", varType.c_str(), varName.c_str());
+ output += text;
+ }
+
+ if (output.back() == '\n')
+ output += "};";
+ else
+ output += " };";
+
+ if (rightText != "")
+ output += " " + rightText + "";
+
+ output += "\n";
+
+ if (postText != "")
+ output += postText + "\n";
+
+ output += "\n";
+
+ return output;
+}
+
+std::string Declaration::GetExternalDeclarationStr() const
+{
+ std::string output;
+
+ if (preText != "")
+ output += preText + "\n";
+
+ if (IsStatic())
+ {
+ output += "static ";
+ }
+
+ if (arrayItemCntStr != "" && (IsStatic() || forceArrayCnt))
+ output += StringHelper::Sprintf("%s %s[%s] = ", varType.c_str(), varName.c_str(),
+ arrayItemCntStr.c_str());
+ else if (arrayItemCnt != 0 && (IsStatic() || forceArrayCnt))
+ output +=
+ StringHelper::Sprintf("%s %s[%i] = ", varType.c_str(), varName.c_str(), arrayItemCnt);
+ else
+ output += StringHelper::Sprintf("%s %s[] = ", varType.c_str(), varName.c_str());
+
+ output += StringHelper::Sprintf("{\n#include \"%s\"\n};", includePath.c_str());
+
+ if (rightText != "")
+ output += " " + rightText + "";
+
+ output += "\n";
+
+ if (postText != "")
+ output += postText + "\n";
+
+ output += "\n";
+
+ return output;
+}
+
+std::string Declaration::GetExternStr() const
+{
+ if (IsStatic() || varType == "")
+ {
+ return "";
+ }
+
+ if (Globals::Instance->otrMode) /* && (varType == "Gfx" || varType == "u64" || varType == "AnimationHeader" || varType == "LinkAnimationHeader" ||
+ varType == "StandardLimb" || varType == "JointIndex" || varType == "Vtx" || varType == "FlexSkeletonHeader" || varType == "SkeletonHeader") ||
+ varType == "CollisionHeader") */
+ return "";
+
+ if (isArray)
+ {
+ if (arrayItemCntStr != "" && (IsStatic() || forceArrayCnt))
+ {
+ return StringHelper::Sprintf("extern %s %s[%s];\n", varType.c_str(), varName.c_str(),
+ arrayItemCntStr.c_str());
+ }
+ else if (arrayItemCnt != 0 && (IsStatic() || forceArrayCnt))
+ {
+ return StringHelper::Sprintf("extern %s %s[%i];\n", varType.c_str(), varName.c_str(),
+ arrayItemCnt);
+ }
+ else
+ return StringHelper::Sprintf("extern %s %s[];\n", varType.c_str(), varName.c_str());
+ }
+
+ return StringHelper::Sprintf("extern %s %s;\n", varType.c_str(), varName.c_str());
+}
+
+std::string Declaration::GetStaticForwardDeclarationStr() const
+{
+ if (!IsStatic() || isUnaccounted)
+ return "";
+
+ if (isArray)
+ {
+ if (arrayItemCntStr == "" && arrayItemCnt == 0)
+ {
+ // Forward declaring static arrays without specifying the size is not allowed.
+ return "";
+ }
+
+ if (arrayItemCntStr != "")
+ {
+ return StringHelper::Sprintf("static %s %s[%s];\n", varType.c_str(), varName.c_str(),
+ arrayItemCntStr.c_str());
+ }
+ else
+ {
+ return StringHelper::Sprintf("static %s %s[%i];\n", varType.c_str(), varName.c_str(),
+ arrayItemCnt);
+ }
+ }
+
+ return StringHelper::Sprintf("static %s %s;\n", varType.c_str(), varName.c_str());
+}
diff --git a/ZAPDTR/ZAPD/Declaration.h b/ZAPDTR/ZAPD/Declaration.h
new file mode 100644
index 000000000..4a743b50f
--- /dev/null
+++ b/ZAPDTR/ZAPD/Declaration.h
@@ -0,0 +1,80 @@
+#pragma once
+
+#include
+#include
+
+// TODO: should we drop the `_t` suffix because of UNIX compliance?
+typedef uint32_t segptr_t;
+typedef uint32_t offset_t;
+
+#define SEGMENTED_NULL ((segptr_t)0)
+
+enum class DeclarationAlignment
+{
+ Align4,
+ Align8
+};
+
+enum class StaticConfig
+{
+ Off,
+ Global,
+ On
+};
+
+class Declaration
+{
+public:
+ offset_t address;
+ DeclarationAlignment alignment;
+ size_t size;
+ std::string preText;
+ std::string text;
+ std::string rightText;
+ std::string postText;
+ std::string preComment;
+ std::string postComment;
+ std::string varType;
+ std::string varName;
+ std::string includePath;
+
+ bool isExternal = false;
+ bool isArray = false;
+ bool forceArrayCnt = false;
+ size_t arrayItemCnt = 0;
+ std::string arrayItemCntStr = "";
+ std::vector references;
+ bool isUnaccounted = false;
+ bool isPlaceholder = false;
+ bool declaredInXml = false;
+ StaticConfig staticConf = StaticConfig::Global;
+
+ Declaration(offset_t nAddress, DeclarationAlignment nAlignment, size_t nSize,
+ const std::string& nVarType, const std::string& nVarName, bool nIsArray,
+ const std::string& nText);
+ Declaration(offset_t nAddress, DeclarationAlignment nAlignment, size_t nSize,
+ const std::string& nVarType, const std::string& nVarName, bool nIsArray,
+ size_t nArrayItemCnt, const std::string& nText);
+ Declaration(offset_t nAddress, DeclarationAlignment nAlignment, size_t nSize,
+ const std::string& nVarType, const std::string& nVarName, bool nIsArray,
+ const std::string& nArrayItemCntStr, const std::string& nText);
+ Declaration(offset_t nAddress, DeclarationAlignment nAlignment, size_t nSize,
+ const std::string& nVarType, const std::string& nVarName, bool nIsArray,
+ size_t nArrayItemCnt, const std::string& nText, bool nIsExternal);
+
+ Declaration(offset_t nAddress, const std::string& nIncludePath, size_t nSize,
+ const std::string& nVarType, const std::string& nVarName);
+
+ bool IsStatic() const;
+
+ std::string GetNormalDeclarationStr() const;
+ std::string GetExternalDeclarationStr() const;
+
+ std::string GetExternStr() const;
+
+ std::string GetStaticForwardDeclarationStr() const;
+
+protected:
+ Declaration(offset_t nAddress, DeclarationAlignment nAlignment, size_t nSize,
+ const std::string& nText);
+};
diff --git a/ZAPDTR/ZAPD/GameConfig.cpp b/ZAPDTR/ZAPD/GameConfig.cpp
new file mode 100644
index 000000000..ae29ba28f
--- /dev/null
+++ b/ZAPDTR/ZAPD/GameConfig.cpp
@@ -0,0 +1,184 @@
+#include "GameConfig.h"
+
+#include
+#include
+
+#include "Utils/Directory.h"
+#include "Utils/File.h"
+#include "Utils/Path.h"
+#include "ZFile.h"
+#include "tinyxml2.h"
+
+using ConfigFunc = void (GameConfig::*)(const tinyxml2::XMLElement&);
+
+GameConfig::~GameConfig()
+{
+ for (auto& declPair : segmentRefFiles)
+ {
+ for (auto& file : declPair.second)
+ {
+ delete file;
+ }
+ }
+}
+
+void GameConfig::ReadTexturePool(const fs::path& texturePoolXmlPath)
+{
+ tinyxml2::XMLDocument doc;
+ tinyxml2::XMLError eResult = doc.LoadFile(texturePoolXmlPath.string().c_str());
+
+ if (eResult != tinyxml2::XML_SUCCESS)
+ {
+ fprintf(stderr, "Warning: Unable to read texture pool XML with error code %i\n", eResult);
+ return;
+ }
+
+ tinyxml2::XMLNode* root = doc.FirstChild();
+
+ if (root == nullptr)
+ return;
+
+ for (tinyxml2::XMLElement* child = root->FirstChildElement(); child != nullptr;
+ child = child->NextSiblingElement())
+ {
+ if (std::string_view(child->Name()) == "Texture")
+ {
+ std::string crcStr = child->Attribute("CRC");
+ fs::path texPath = child->Attribute("Path");
+ std::string texName;
+
+ uint32_t crc = strtoul(crcStr.c_str(), nullptr, 16);
+
+ texturePool[crc].path = texPath;
+ }
+ }
+}
+
+void GameConfig::GenSymbolMap(const fs::path& symbolMapPath)
+{
+ auto symbolLines = File::ReadAllLines(symbolMapPath);
+
+ for (std::string& symbolLine : symbolLines)
+ {
+ auto split = StringHelper::Split(symbolLine, " ");
+ uint32_t addr = strtoul(split[0].c_str(), nullptr, 16);
+ std::string symbolName = split[1];
+
+ symbolMap[addr] = std::move(symbolName);
+ }
+}
+
+void GameConfig::ConfigFunc_SymbolMap(const tinyxml2::XMLElement& element)
+{
+ std::string fileName = element.Attribute("File");
+ GenSymbolMap(Path::GetDirectoryName(configFilePath) / fileName);
+}
+
+void GameConfig::ConfigFunc_ActorList(const tinyxml2::XMLElement& element)
+{
+ std::string fileName = element.Attribute("File");
+ std::vector lines =
+ File::ReadAllLines(Path::GetDirectoryName(configFilePath) / fileName);
+
+ for (auto& line : lines)
+ actorList.emplace_back(std::move(line));
+}
+
+void GameConfig::ConfigFunc_ObjectList(const tinyxml2::XMLElement& element)
+{
+ std::string fileName = element.Attribute("File");
+ std::vector lines =
+ File::ReadAllLines(Path::GetDirectoryName(configFilePath) / fileName);
+
+ for (auto& line : lines)
+ objectList.emplace_back(std::move(line));
+}
+
+void GameConfig::ConfigFunc_TexturePool(const tinyxml2::XMLElement& element)
+{
+ std::string fileName = element.Attribute("File");
+ ReadTexturePool(Path::GetDirectoryName(configFilePath) / fileName);
+}
+
+void GameConfig::ConfigFunc_BGConfig(const tinyxml2::XMLElement& element)
+{
+ bgScreenWidth = element.IntAttribute("ScreenWidth", 320);
+ bgScreenHeight = element.IntAttribute("ScreenHeight", 240);
+}
+
+void GameConfig::ConfigFunc_ExternalXMLFolder(const tinyxml2::XMLElement& element)
+{
+ const char* pathValue = element.Attribute("Path");
+ if (pathValue == nullptr)
+ {
+ throw std::runtime_error(
+ StringHelper::Sprintf("Parse: Fatal error in configuration file.\n"
+ "\t Missing 'Path' attribute in `ExternalXMLFolder` element.\n"));
+ }
+ if (externalXmlFolder != "")
+ {
+ throw std::runtime_error(StringHelper::Sprintf("Parse: Fatal error in configuration file.\n"
+ "\t `ExternalXMLFolder` is duplicated.\n"));
+ }
+ externalXmlFolder = pathValue;
+}
+
+void GameConfig::ConfigFunc_ExternalFile(const tinyxml2::XMLElement& element)
+{
+ const char* xmlPathValue = element.Attribute("XmlPath");
+ if (xmlPathValue == nullptr)
+ {
+ throw std::runtime_error(
+ StringHelper::Sprintf("Parse: Fatal error in configuration file.\n"
+ "\t Missing 'XmlPath' attribute in `ExternalFile` element.\n"));
+ }
+ const char* outPathValue = element.Attribute("OutPath");
+ if (outPathValue == nullptr)
+ {
+ throw std::runtime_error(
+ StringHelper::Sprintf("Parse: Fatal error in configuration file.\n"
+ "\t Missing 'OutPath' attribute in `ExternalFile` element.\n"));
+ }
+
+ externalFiles.push_back(ExternalFile(fs::path(xmlPathValue), fs::path(outPathValue)));
+}
+
+void GameConfig::ReadConfigFile(const fs::path& argConfigFilePath)
+{
+ static const std::map ConfigFuncDictionary = {
+ {"SymbolMap", &GameConfig::ConfigFunc_SymbolMap},
+ {"ActorList", &GameConfig::ConfigFunc_ActorList},
+ {"ObjectList", &GameConfig::ConfigFunc_ObjectList},
+ {"TexturePool", &GameConfig::ConfigFunc_TexturePool},
+ {"BGConfig", &GameConfig::ConfigFunc_BGConfig},
+ {"ExternalXMLFolder", &GameConfig::ConfigFunc_ExternalXMLFolder},
+ {"ExternalFile", &GameConfig::ConfigFunc_ExternalFile},
+ };
+
+ configFilePath = argConfigFilePath.string();
+ tinyxml2::XMLDocument doc;
+ tinyxml2::XMLError eResult = doc.LoadFile(configFilePath.c_str());
+
+ if (eResult != tinyxml2::XML_SUCCESS)
+ {
+ throw std::runtime_error("Error: Unable to read config file.");
+ }
+
+ tinyxml2::XMLNode* root = doc.FirstChild();
+
+ if (root == nullptr)
+ return;
+
+ for (tinyxml2::XMLElement* child = root->FirstChildElement(); child != nullptr;
+ child = child->NextSiblingElement())
+ {
+ auto it = ConfigFuncDictionary.find(child->Name());
+ if (it == ConfigFuncDictionary.end())
+ {
+ fprintf(stderr, "Unsupported configuration variable: %s\n", child->Name());
+ continue;
+ }
+
+ std::invoke(it->second, *this, *child);
+ }
+}
diff --git a/ZAPDTR/ZAPD/GameConfig.h b/ZAPDTR/ZAPD/GameConfig.h
new file mode 100644
index 000000000..c478d16ce
--- /dev/null
+++ b/ZAPDTR/ZAPD/GameConfig.h
@@ -0,0 +1,58 @@
+#pragma once
+
+#include
+#include