From 68899c2e330a6a9fb31b8a025eaa06707bcddfb0 Mon Sep 17 00:00:00 2001 From: fig02 Date: Fri, 3 Dec 2021 15:57:05 -0500 Subject: [PATCH] git subrepo pull --force tools/ZAPD (#1049) subrepo: subdir: "tools/ZAPD" merged: "a3363333d" upstream: origin: "https://github.com/zeldaret/ZAPD.git" branch: "master" commit: "a3363333d" git-subrepo: version: "0.4.3" origin: "https://github.com/ingydotnet/git-subrepo.git" commit: "2f68596" --- tools/ZAPD/.gitrepo | 4 +- tools/ZAPD/ExporterTest/ExporterTest.vcxproj | 5 +- tools/ZAPD/ExporterTest/Main.cpp | 8 +- tools/ZAPD/ExporterTest/RoomExporter.cpp | 40 +- tools/ZAPD/ExporterTest/TextureExporter.h | 2 +- tools/ZAPD/Jenkinsfile | 56 +-- tools/ZAPD/README.md | 46 +- tools/ZAPD/ZAPD/Declaration.cpp | 34 +- tools/ZAPD/ZAPD/Declaration.h | 4 +- tools/ZAPD/ZAPD/GameConfig.cpp | 4 +- tools/ZAPD/ZAPD/Globals.cpp | 5 +- tools/ZAPD/ZAPD/Globals.h | 5 +- tools/ZAPD/ZAPD/ImageBackend.cpp | 52 +- tools/ZAPD/ZAPD/Main.cpp | 56 ++- tools/ZAPD/ZAPD/NuGet/libpng.static.txt | 0 tools/ZAPD/ZAPD/Overlays/ZOverlay.cpp | 18 +- tools/ZAPD/ZAPD/WarningHandler.cpp | 443 ++++++++++++++++++ tools/ZAPD/ZAPD/WarningHandler.h | 145 ++++++ tools/ZAPD/ZAPD/ZAPD.vcxproj | 35 +- tools/ZAPD/ZAPD/ZAPD.vcxproj.filters | 51 +- tools/ZAPD/ZAPD/ZAnimation.cpp | 9 +- tools/ZAPD/ZAPD/ZAnimation.h | 2 +- tools/ZAPD/ZAPD/ZArray.cpp | 23 +- tools/ZAPD/ZAPD/ZArray.h | 2 +- tools/ZAPD/ZAPD/ZBackground.cpp | 52 +- tools/ZAPD/ZAPD/ZBlob.cpp | 5 - tools/ZAPD/ZAPD/ZBlob.h | 1 - tools/ZAPD/ZAPD/ZCollision.cpp | 4 +- tools/ZAPD/ZAPD/ZCutscene.cpp | 12 +- tools/ZAPD/ZAPD/ZCutscene.h | 2 +- tools/ZAPD/ZAPD/ZCutsceneMM.h | 2 +- tools/ZAPD/ZAPD/ZDisplayList.cpp | 32 +- tools/ZAPD/ZAPD/ZDisplayList.h | 3 +- tools/ZAPD/ZAPD/ZFile.cpp | 169 +++---- tools/ZAPD/ZAPD/ZFile.h | 2 +- tools/ZAPD/ZAPD/ZLimb.cpp | 20 +- tools/ZAPD/ZAPD/ZPath.cpp | 13 +- tools/ZAPD/ZAPD/ZResource.cpp | 72 +-- tools/ZAPD/ZAPD/ZResource.h | 9 +- tools/ZAPD/ZAPD/ZRoom/Commands/SetMesh.cpp | 43 +- tools/ZAPD/ZAPD/ZRoom/Commands/SetMesh.h | 2 +- .../ZAPD/ZAPD/ZRoom/Commands/SetRoomList.cpp | 4 +- tools/ZAPD/ZAPD/ZRoom/Commands/SetRoomList.h | 2 +- tools/ZAPD/ZAPD/ZRoom/ZRoom.cpp | 21 +- tools/ZAPD/ZAPD/ZRoom/ZRoom.h | 2 +- tools/ZAPD/ZAPD/ZScalar.cpp | 5 +- tools/ZAPD/ZAPD/ZScalar.h | 2 +- tools/ZAPD/ZAPD/ZSkeleton.cpp | 22 +- tools/ZAPD/ZAPD/ZSymbol.cpp | 19 +- tools/ZAPD/ZAPD/ZTexture.cpp | 63 +-- tools/ZAPD/ZAPD/ZTextureAnimation.cpp | 124 ++--- tools/ZAPD/ZAPD/ZVector.cpp | 13 +- tools/ZAPD/ZAPD/ZVector.h | 2 +- tools/ZAPD/ZAPD/ZVtx.h | 2 +- tools/ZAPD/ZAPD/any/any/zlib.static.txt | 0 tools/ZAPD/ZAPD/packages.config | 5 +- tools/ZAPD/ZAPDUtils/Color3b.h | 2 +- tools/ZAPD/ZAPDUtils/StrHash.h | 6 +- tools/ZAPD/ZAPDUtils/Utils/BinaryReader.cpp | 6 +- tools/ZAPD/ZAPDUtils/Utils/BitConverter.h | 2 +- tools/ZAPD/ZAPDUtils/Utils/File.h | 2 +- tools/ZAPD/ZAPDUtils/Utils/MemoryStream.cpp | 2 +- tools/ZAPD/ZAPDUtils/Utils/Path.h | 4 +- tools/ZAPD/ZAPDUtils/Utils/Stream.h | 2 +- tools/ZAPD/ZAPDUtils/Utils/StringHelper.h | 14 +- tools/ZAPD/ZAPDUtils/Utils/vt.h | 45 ++ tools/ZAPD/ZAPDUtils/Vec2f.h | 2 +- tools/ZAPD/ZAPDUtils/Vec3f.h | 2 +- tools/ZAPD/ZAPDUtils/Vec3s.h | 2 +- tools/ZAPD/ZAPDUtils/ZAPDUtils.vcxproj | 5 +- .../ZAPD/ZAPDUtils/ZAPDUtils.vcxproj.filters | 6 + tools/ZAPD/docs/zapd_warning_example.png | Bin 0 -> 47521 bytes 72 files changed, 1311 insertions(+), 569 deletions(-) create mode 100644 tools/ZAPD/ZAPD/NuGet/libpng.static.txt create mode 100644 tools/ZAPD/ZAPD/WarningHandler.cpp create mode 100644 tools/ZAPD/ZAPD/WarningHandler.h create mode 100644 tools/ZAPD/ZAPD/any/any/zlib.static.txt create mode 100644 tools/ZAPD/ZAPDUtils/Utils/vt.h create mode 100644 tools/ZAPD/docs/zapd_warning_example.png diff --git a/tools/ZAPD/.gitrepo b/tools/ZAPD/.gitrepo index 04b472ce2d..be24b438e2 100644 --- a/tools/ZAPD/.gitrepo +++ b/tools/ZAPD/.gitrepo @@ -6,7 +6,7 @@ [subrepo] remote = https://github.com/zeldaret/ZAPD.git branch = master - commit = 4f7b8393ec8a3abd59649c2ba669e951fb61f3d2 - parent = efb9badbf211c5d5065c1b25b58f0e0fe2d17ec4 + commit = a3363333d809e8089a55efc3ea32eaea9ddb8d8c + parent = 1cf11907fa8d636babba2df854850035f04bd4fc method = merge cmdver = 0.4.3 diff --git a/tools/ZAPD/ExporterTest/ExporterTest.vcxproj b/tools/ZAPD/ExporterTest/ExporterTest.vcxproj index a709a35091..839d451023 100644 --- a/tools/ZAPD/ExporterTest/ExporterTest.vcxproj +++ b/tools/ZAPD/ExporterTest/ExporterTest.vcxproj @@ -79,7 +79,7 @@ true - $(SolutionDir)\ZAPD\;$(SolutionDir)ZAPDUtils;$(SolutionDir)lib\tinyxml2;$(SolutionDir)lib\libgfxd;$(SolutionDir)lib\elfio;$(SolutionDir)lib\stb;$(ProjectDir);$(IncludePath) + $(ProjectDir)..\ZAPD\;$(ProjectDir)..\ZAPDUtils;$(ProjectDir)..\lib\tinyxml2;$(ProjectDir)..\lib\libgfxd;$(ProjectDir)..\lib\elfio;$(ProjectDir)..\lib\stb;$(ProjectDir);$(IncludePath) false @@ -120,6 +120,7 @@ true stdcpp17 stdc11 + MultiThreadedDebug Console @@ -156,4 +157,4 @@ - + \ No newline at end of file diff --git a/tools/ZAPD/ExporterTest/Main.cpp b/tools/ZAPD/ExporterTest/Main.cpp index 4f683a1ba3..07fdbeeced 100644 --- a/tools/ZAPD/ExporterTest/Main.cpp +++ b/tools/ZAPD/ExporterTest/Main.cpp @@ -1,7 +1,7 @@ -#include -#include -#include -#include +#include "CollisionExporter.h" +#include "Globals.h" +#include "RoomExporter.h" +#include "TextureExporter.h" enum class ExporterFileMode { diff --git a/tools/ZAPD/ExporterTest/RoomExporter.cpp b/tools/ZAPD/ExporterTest/RoomExporter.cpp index c4a6844fb5..6c5552d8f8 100644 --- a/tools/ZAPD/ExporterTest/RoomExporter.cpp +++ b/tools/ZAPD/ExporterTest/RoomExporter.cpp @@ -1,24 +1,24 @@ #include "RoomExporter.h" -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include +#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) { diff --git a/tools/ZAPD/ExporterTest/TextureExporter.h b/tools/ZAPD/ExporterTest/TextureExporter.h index ffe6001dca..41c4e79be2 100644 --- a/tools/ZAPD/ExporterTest/TextureExporter.h +++ b/tools/ZAPD/ExporterTest/TextureExporter.h @@ -1,6 +1,6 @@ #pragma once -#include +#include "Utils/BinaryWriter.h" #include "ZResource.h" #include "ZTexture.h" diff --git a/tools/ZAPD/Jenkinsfile b/tools/ZAPD/Jenkinsfile index 051e5f9982..a12f30a253 100644 --- a/tools/ZAPD/Jenkinsfile +++ b/tools/ZAPD/Jenkinsfile @@ -22,13 +22,13 @@ pipeline { } } - stage('Checkout mm') { - steps{ - dir('mm') { - git url: 'https://github.com/zeldaret/mm.git' - } - } - } + // stage('Checkout mm') { + // steps{ + // dir('mm') { + // git url: 'https://github.com/zeldaret/mm.git' + // } + // } + // } } } @@ -51,20 +51,20 @@ pipeline { } } - stage('Setup MM') { - steps { - dir('mm') { - sh 'cp /usr/local/etc/roms/mm.us.rev1.z64 baserom.mm.us.rev1.z64' + // 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 4' - } - } - } + // // 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 4' + // } + // } + // } } } @@ -78,14 +78,14 @@ pipeline { } } } - stage('Build mm') { - steps { - dir('mm') { - sh 'make -j disasm' - sh 'make -j all' - } - } - } + // stage('Build mm') { + // steps { + // dir('mm') { + // sh 'make -j disasm' + // sh 'make -j all' + // } + // } + // } } } } diff --git a/tools/ZAPD/README.md b/tools/ZAPD/README.md index e2764b6277..e2821bc28a 100644 --- a/tools/ZAPD/README.md +++ b/tools/ZAPD/README.md @@ -109,11 +109,51 @@ ZAPD also accepts the following list of extra parameters: - 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`. -- `-wno` / `--warn-no-offsets` : Enable warnings for nodes that dont have offsets specified. Takes priority over `-eno`/ `--error-no-offsets`. -- `-eno` / `--error-no-offsets` : Enable errors for nodes that dont have offsets specified. - `-se` / `--set-exporter` : Sets which exporter to use. -- `--gcc-compat` : Enables GCC compatible mode. Slower. +- `--gcc-compat` : Enables GCC compatibly mode. Slower. - `-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/tools/ZAPD/ZAPD/Declaration.cpp b/tools/ZAPD/ZAPD/Declaration.cpp index d2a86ffcc5..be06b0bea7 100644 --- a/tools/ZAPD/ZAPD/Declaration.cpp +++ b/tools/ZAPD/ZAPD/Declaration.cpp @@ -92,20 +92,20 @@ std::string Declaration::GetNormalDeclarationStr() const if (isArray) { - if (arrayItemCntStr != "") + if (arrayItemCntStr != "" && (IsStatic() || forceArrayCnt)) { output += StringHelper::Sprintf("%s %s[%s];\n", varType.c_str(), varName.c_str(), arrayItemCntStr.c_str()); } - else if (arrayItemCnt == 0) - { - output += StringHelper::Sprintf("%s %s[] = {\n", varType.c_str(), varName.c_str()); - } - else + 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"; } @@ -145,16 +145,16 @@ std::string Declaration::GetExternalDeclarationStr() const output += "static "; } - if (arrayItemCntStr != "") + 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[%s] = {\n#include \"%s\"\n};", varType.c_str(), - varName.c_str(), arrayItemCntStr.c_str(), includePath.c_str()); - else if (arrayItemCnt != 0) - output += StringHelper::Sprintf("%s %s[%i] = {\n#include \"%s\"\n};", varType.c_str(), - varName.c_str(), arrayItemCnt, includePath.c_str()); + StringHelper::Sprintf("%s %s[%i] = ", varType.c_str(), varName.c_str(), arrayItemCnt); else - output += StringHelper::Sprintf("%s %s[] = {\n#include \"%s\"\n};", varType.c_str(), - varName.c_str(), includePath.c_str()); + 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 + ""; @@ -178,14 +178,16 @@ std::string Declaration::GetExternStr() const if (isArray) { - if (arrayItemCntStr != "") + 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) + 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()); } diff --git a/tools/ZAPD/ZAPD/Declaration.h b/tools/ZAPD/ZAPD/Declaration.h index 138524a4c2..b7bb0d30d2 100644 --- a/tools/ZAPD/ZAPD/Declaration.h +++ b/tools/ZAPD/ZAPD/Declaration.h @@ -38,10 +38,12 @@ public: 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::string arrayItemCntStr = ""; std::vector references; bool isUnaccounted = false; bool isPlaceholder = false; diff --git a/tools/ZAPD/ZAPD/GameConfig.cpp b/tools/ZAPD/ZAPD/GameConfig.cpp index f197493a4a..ae29ba28fa 100644 --- a/tools/ZAPD/ZAPD/GameConfig.cpp +++ b/tools/ZAPD/ZAPD/GameConfig.cpp @@ -25,7 +25,7 @@ GameConfig::~GameConfig() void GameConfig::ReadTexturePool(const fs::path& texturePoolXmlPath) { tinyxml2::XMLDocument doc; - tinyxml2::XMLError eResult = doc.LoadFile(texturePoolXmlPath.c_str()); + tinyxml2::XMLError eResult = doc.LoadFile(texturePoolXmlPath.string().c_str()); if (eResult != tinyxml2::XML_SUCCESS) { @@ -155,7 +155,7 @@ void GameConfig::ReadConfigFile(const fs::path& argConfigFilePath) {"ExternalFile", &GameConfig::ConfigFunc_ExternalFile}, }; - configFilePath = argConfigFilePath; + configFilePath = argConfigFilePath.string(); tinyxml2::XMLDocument doc; tinyxml2::XMLError eResult = doc.LoadFile(configFilePath.c_str()); diff --git a/tools/ZAPD/ZAPD/Globals.cpp b/tools/ZAPD/ZAPD/Globals.cpp index 036e4a5cbf..528a09d256 100644 --- a/tools/ZAPD/ZAPD/Globals.cpp +++ b/tools/ZAPD/ZAPD/Globals.cpp @@ -3,8 +3,9 @@ #include #include -#include -#include +#include "Utils/File.h" +#include "Utils/Path.h" +#include "WarningHandler.h" #include "tinyxml2.h" Globals* Globals::Instance; diff --git a/tools/ZAPD/ZAPD/Globals.h b/tools/ZAPD/ZAPD/Globals.h index 265f1af241..19e193f123 100644 --- a/tools/ZAPD/ZAPD/Globals.h +++ b/tools/ZAPD/ZAPD/Globals.h @@ -20,6 +20,7 @@ typedef bool (*ExporterSetFuncBool)(ZFileMode fileMode); typedef void (*ExporterSetFuncVoid)(int argc, char* argv[], int& i); typedef void (*ExporterSetFuncVoid2)(const std::string& buildMode, ZFileMode& fileMode); typedef void (*ExporterSetFuncVoid3)(); +typedef void (*ExporterSetResSave)(ZResource* res, BinaryWriter& writer); class ExporterSet { @@ -34,6 +35,7 @@ public: ExporterSetFunc endFileFunc = nullptr; ExporterSetFuncVoid3 beginXMLFunc = nullptr; ExporterSetFuncVoid3 endXMLFunc = nullptr; + ExporterSetResSave resSaveFunc = nullptr; }; class Globals @@ -53,9 +55,6 @@ public: TextureType texType; ZGame game; GameConfig cfg; - bool warnUnaccounted = false; - bool warnNoOffset = false; - bool errorNoOffset = false; bool verboseUnaccounted = false; bool gccCompat = false; bool forceStatic = false; diff --git a/tools/ZAPD/ZAPD/ImageBackend.cpp b/tools/ZAPD/ZAPD/ImageBackend.cpp index 137e326c33..8accb6b4e1 100644 --- a/tools/ZAPD/ZAPD/ImageBackend.cpp +++ b/tools/ZAPD/ZAPD/ImageBackend.cpp @@ -6,6 +6,7 @@ #include #include "Utils/StringHelper.h" +#include "WarningHandler.h" /* ImageBackend */ @@ -20,19 +21,28 @@ void ImageBackend::ReadPng(const char* filename) FILE* fp = fopen(filename, "rb"); if (fp == nullptr) - throw std::runtime_error(StringHelper::Sprintf( - "ImageBackend::ReadPng: Error.\n\t Couldn't open file '%s'.", filename)); + { + std::string errorHeader = StringHelper::Sprintf("could not open file '%s'", filename); + HANDLE_ERROR(WarningType::InvalidPNG, errorHeader, ""); + } png_structp png = png_create_read_struct(PNG_LIBPNG_VER_STRING, nullptr, nullptr, nullptr); - if (!png) - throw std::runtime_error("ImageBackend::ReadPng: Error.\n\t Couldn't create png struct."); + if (png == nullptr) + { + HANDLE_ERROR(WarningType::InvalidPNG, "could not create png struct", ""); + } png_infop info = png_create_info_struct(png); - if (!info) - throw std::runtime_error("ImageBackend::ReadPng: Error.\n\t Couldn't create png info."); + if (info == nullptr) + { + HANDLE_ERROR(WarningType::InvalidPNG, "could not create png info", ""); + } if (setjmp(png_jmpbuf(png))) - throw std::runtime_error("ImageBackend::ReadPng: Error.\n\t setjmp(png_jmpbuf(png))."); + { + // TODO: better warning explanation + HANDLE_ERROR(WarningType::InvalidPNG, "setjmp(png_jmpbuf(png))", ""); + } png_init_io(png, fp); @@ -145,20 +155,30 @@ void ImageBackend::WritePng(const char* filename) assert(hasImageData); FILE* fp = fopen(filename, "wb"); - if (!fp) - throw std::runtime_error(StringHelper::Sprintf( - "ImageBackend::WritePng: Error.\n\t Couldn't open file '%s' in write mode.", filename)); + if (fp == nullptr) + { + std::string errorHeader = + StringHelper::Sprintf("could not open file '%s' in write mode", filename); + HANDLE_ERROR(WarningType::InvalidPNG, errorHeader, ""); + } png_structp png = png_create_write_struct(PNG_LIBPNG_VER_STRING, nullptr, nullptr, nullptr); - if (!png) - throw std::runtime_error("ImageBackend::WritePng: Error.\n\t Couldn't create png struct."); + if (png == nullptr) + { + HANDLE_ERROR(WarningType::InvalidPNG, "could not create png struct", ""); + } png_infop info = png_create_info_struct(png); - if (!info) - throw std::runtime_error("ImageBackend::WritePng: Error.\n\t Couldn't create png info."); + if (info == nullptr) + { + HANDLE_ERROR(WarningType::InvalidPNG, "could not create png info", ""); + } if (setjmp(png_jmpbuf(png))) - throw std::runtime_error("ImageBackend::WritePng: Error.\n\t setjmp(png_jmpbuf(png))."); + { + // TODO: better warning description + HANDLE_ERROR(WarningType::InvalidPNG, "setjmp(png_jmpbuf(png))", ""); + } png_init_io(png, fp); @@ -441,7 +461,7 @@ double ImageBackend::GetBytesPerPixel() const return 1 * bitDepth / 8; default: - throw std::invalid_argument("ImageBackend::GetBytesPerPixel():\n\t Invalid color type."); + HANDLE_ERROR(WarningType::InvalidPNG, "invalid color type", ""); } } diff --git a/tools/ZAPD/ZAPD/Main.cpp b/tools/ZAPD/ZAPD/Main.cpp index 298ca02629..fd2ed06dc0 100644 --- a/tools/ZAPD/ZAPD/Main.cpp +++ b/tools/ZAPD/ZAPD/Main.cpp @@ -1,8 +1,9 @@ -#include -#include -#include #include "Globals.h" #include "Overlays/ZOverlay.h" +#include "Utils/Directory.h" +#include "Utils/File.h" +#include "Utils/Path.h" +#include "WarningHandler.h" #include "ZAnimation.h" #include "ZBackground.h" #include "ZBlob.h" @@ -12,10 +13,10 @@ #if !defined(_MSC_VER) && !defined(__CYGWIN__) #include #include +#include #include // for __cxa_demangle #include // for dladdr #include -#include #include #endif @@ -47,6 +48,7 @@ void ErrorHandler(int sig) const char* crashEasterEgg[] = { "\tYou've met with a terrible fate, haven't you?", "\tSEA BEARS FOAM. SLEEP BEARS DREAMS. \n\tBOTH END IN THE SAME WAY: CRASSSH!", + "ZAPD has fallen and cannot get up." }; srand(time(nullptr)); @@ -97,6 +99,9 @@ int main(int argc, char* argv[]) return 1; } + Globals* g = new Globals(); + WarningHandler::Init(argc, argv); + for (int i = 1; i < argc; i++) { if (!strcmp(argv[i], "--version")) @@ -109,12 +114,12 @@ int main(int argc, char* argv[]) printf("Congratulations!\n"); printf("You just found the (unimplemented and undocumented) ZAPD's help message.\n"); printf("Feel free to implement it if you want :D\n"); + + WarningHandler::PrintHelp(); return 0; } } - Globals* g = new Globals; - // Parse other "commands" for (int32_t i = 2; i < argc; i++) { @@ -186,26 +191,15 @@ int main(int argc, char* argv[]) signal(SIGSEGV, ErrorHandler); signal(SIGABRT, ErrorHandler); #else - fprintf(stderr, - "Warning: Tried to set error handler, but this build lacks support for one.\n"); + HANDLE_WARNING(WarningType::Always, + "tried to set error handler, but this ZAPD build lacks support for one", + ""); #endif } else if (arg == "-v") // Verbose { Globals::Instance->verbosity = static_cast(strtol(argv[++i], NULL, 16)); } - else if (arg == "-wu" || arg == "--warn-unaccounted") // Warn unaccounted - { - Globals::Instance->warnUnaccounted = true; - } - else if (arg == "-wno" || arg == "--warn-no-offset") - { - Globals::Instance->warnNoOffset = true; - } - else if (arg == "-eno" || arg == "--error-no-offset") - { - Globals::Instance->errorNoOffset = true; - } else if (arg == "-vu" || arg == "--verbose-unaccounted") // Verbose unaccounted { Globals::Instance->verboseUnaccounted = true; @@ -262,6 +256,11 @@ int main(int argc, char* argv[]) if (Globals::Instance->verbosity >= VerbosityLevel::VERBOSITY_INFO) printf("ZAPD: Zelda Asset Processor For Decomp: %s\n", gBuildHash); + if (Globals::Instance->verbosity >= VerbosityLevel::VERBOSITY_DEBUG) + { + WarningHandler::PrintWarningsDebugInfo(); + } + // TODO: switch if (fileMode == ZFileMode::Extract || fileMode == ZFileMode::BuildSourceFile) { @@ -334,7 +333,9 @@ bool Parse(const fs::path& xmlFilePath, const fs::path& basePath, const fs::path if (eResult != tinyxml2::XML_SUCCESS) { - fprintf(stderr, "Invalid xml file: '%s'\n", xmlFilePath.c_str()); + // TODO: use XMLDocument::ErrorIDToName to get more specific error messages here + HANDLE_ERROR(WarningType::InvalidXML, + StringHelper::Sprintf("invalid XML file: '%s'", xmlFilePath.c_str()), ""); return false; } @@ -342,7 +343,9 @@ bool Parse(const fs::path& xmlFilePath, const fs::path& basePath, const fs::path if (root == nullptr) { - fprintf(stderr, "Missing Root tag in xml file: '%s'\n", xmlFilePath.c_str()); + HANDLE_WARNING( + WarningType::InvalidXML, + StringHelper::Sprintf("missing Root tag in xml file: '%s'", xmlFilePath.c_str()), ""); return false; } @@ -392,10 +395,11 @@ bool Parse(const fs::path& xmlFilePath, const fs::path& basePath, const fs::path } else { - throw std::runtime_error(StringHelper::Sprintf( - "Parse: Fatal error in '%s'.\n\t A resource was found outside of " - "a File element: '%s'\n", - xmlFilePath.c_str(), child->Name())); + std::string errorHeader = + StringHelper::Sprintf("when parsing file '%s'", xmlFilePath.c_str()); + std::string errorBody = StringHelper::Sprintf( + "Found a resource outside a File element: '%s'", child->Name()); + HANDLE_ERROR(WarningType::InvalidXML, errorHeader, errorBody); } } diff --git a/tools/ZAPD/ZAPD/NuGet/libpng.static.txt b/tools/ZAPD/ZAPD/NuGet/libpng.static.txt new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tools/ZAPD/ZAPD/Overlays/ZOverlay.cpp b/tools/ZAPD/ZAPD/Overlays/ZOverlay.cpp index a842a7e7d9..510de19aba 100644 --- a/tools/ZAPD/ZAPD/Overlays/ZOverlay.cpp +++ b/tools/ZAPD/ZAPD/Overlays/ZOverlay.cpp @@ -1,13 +1,13 @@ #include "ZOverlay.h" -#include +#include #include - -#include -#include -#include -#include #include "Globals.h" +#include "Utils/Directory.h" +#include "Utils/File.h" +#include "Utils/Path.h" +#include "Utils/StringHelper.h" +#include "WarningHandler.h" using namespace ELFIO; @@ -90,7 +90,7 @@ ZOverlay* ZOverlay::FromBuild(fs::path buildPath, fs::path cfgFolderPath) std::vector readers; for (size_t i = 1; i < cfgLines.size(); i++) { - std::string elfPath = buildPath / (cfgLines[i].substr(0, cfgLines[i].size() - 2) + ".o"); + std::string elfPath = (buildPath / (cfgLines[i].substr(0, cfgLines[i].size() - 2) + ".o")).string(); elfio* reader = new elfio(); if (!reader->load(elfPath)) @@ -128,7 +128,9 @@ ZOverlay* ZOverlay::FromBuild(fs::path buildPath, fs::path cfgFolderPath) SectionType sectionType = GetSectionTypeFromStr(pSec->get_name()); if (sectionType == SectionType::ERROR) - fprintf(stderr, "WARNING: One of the section types returned ERROR\n"); + { + HANDLE_WARNING(WarningType::Always, "one of the section types returned ERROR", ""); + } relocation_section_accessor relocs(*curReader, pSec); for (Elf_Xword j = 0; j < relocs.get_entries_num(); j++) diff --git a/tools/ZAPD/ZAPD/WarningHandler.cpp b/tools/ZAPD/ZAPD/WarningHandler.cpp new file mode 100644 index 0000000000..29f8352a71 --- /dev/null +++ b/tools/ZAPD/ZAPD/WarningHandler.cpp @@ -0,0 +1,443 @@ +/** + * ZAPD Warning- and Error-handling system + * ======================================= + * + * This provides a common standard way to write ZAPD warnings/errors, which should be used for all + * such. It will pretty-print them in a uniform way, with styles defined in the header. + * + * Warnings/errors should be constructed using the macros given in the header; there are now plenty + * of examples in the codebase of how to do this. Their purposes are noted above each category in + * the header. Each warning has a type, one of the ones in warningStringToInitMap, or + * WarningType::Always, which is used for warnings that cannot be disabled and do not display a + * type. + * + * Currently there are three levels of alert a warning can have: + * - 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) + * + * Flag use: + * - -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 + * - -Werror escalates all enabled warnings to errors + * + * Errors do not have types, and will always throw an exception; they cannot be disabled. + * + * Format + * === + * Each printed warning/error contains the same three sections: + * - Preamble: automatically generated; the content varies depending on category. It will print the + * file and function that the warning is from, and information about the files being processed + * or extracted. + * - Header: begins with 'warning: ' or 'error:', should contain essential information about the + * warning/error, ends with the warning type if applicable. Printed with emphasis to make it + * stand out. Does not start with a capital letter or end with a '.' + * - Body (optional): indented, should contain further diagnostic information useful for identifying + * and fixing the warning/error. Can be a sentence with captialisation and '.' on the end. + * + * Please think of what the end user will find most useful when writing the header and body, and try + * to keep it brief without sacrificing important information! Also remember that if the user is + * only looking at stderr, they will normally have no other context. + * + * Warning vs error + * === + * The principle that we have operated on so far is + * - issue a warning if ZAPD will still be able to produce a valid, compilable C file that will + * match + * - if this cannot happen, use an error. + * but at the end of the day, it is up to the programmer's discretion what it should be possible to + * disable. + * + * Documentation + * === + * Remember that all warnings also need to be documented in the README.md. The help is generated + * automatically. + */ +#include "WarningHandler.h" + +#include +#include "Globals.h" +#include "Utils/StringHelper.h" + +typedef struct +{ + WarningType type; + WarningLevel defaultLevel; + std::string description; +} WarningInfoInit; + +typedef struct +{ + WarningLevel level; + std::string name; + std::string description; +} WarningInfo; + +/** + * Master list of all default warning types and features + * + * To add a warning type, fill in a new row of this map. Think carefully about what its default + * level should be, and try and make the description both brief and informative: it is used in the + * help message, so again, think about what the end user needs to know. + */ +// clang-format off +static const std::unordered_map warningStringToInitMap = { + {"deprecated", {WarningType::Deprecated, +#ifdef DEPRECATION_ON + WarningLevel::Warn, +#else + WarningLevel::Off, +#endif + "Deprecated features"}}, + {"unaccounted", {WarningType::Unaccounted, WarningLevel::Off, "Large blocks of unaccounted"}}, + {"missing-offsets", {WarningType::MissingOffsets, WarningLevel::Warn, "Offset attribute missing in XML tag"}}, + {"intersection", {WarningType::Intersection, WarningLevel::Warn, "Two assets intersect"}}, + {"missing-attribute", {WarningType::MissingAttribute, WarningLevel::Warn, "Required attribute missing in XML tag"}}, + {"invalid-attribute-value", {WarningType::InvalidAttributeValue, WarningLevel::Err, "Attribute declared in XML is wrong"}}, + {"unknown-attribute", {WarningType::UnknownAttribute, WarningLevel::Warn, "Unknown attribute in XML entry tag"}}, + {"invalid-xml", {WarningType::InvalidXML, WarningLevel::Err, "XML has syntax errors"}}, + {"invalid-jpeg", {WarningType::InvalidJPEG, WarningLevel::Err, "JPEG file does not conform to the game's format requirements"}}, + {"invalid-png", {WarningType::InvalidPNG, WarningLevel::Err, "Issues arising when processing PNG data"}}, + {"invalid-extracted-data", {WarningType::InvalidExtractedData, WarningLevel::Err, "Extracted data does not have correct form"}}, + {"missing-segment", {WarningType::MissingSegment, WarningLevel::Warn, "Segment not given in File tag in XML"}}, + {"hardcoded-pointer", {WarningType::HardcodedPointer, WarningLevel::Warn, "ZAPD lacks the info to make a symbol, so must output a hardcoded pointer"}}, + {"not-implemented", {WarningType::NotImplemented, WarningLevel::Warn, "ZAPD does not currently support this feature"}}, +}; + +/** + * Map constructed at runtime to contain the warning features as set by the user using -W flags. + */ +static std::unordered_map warningTypeToInfoMap; + +void WarningHandler::ConstructTypeToInfoMap() { + for (auto& entry : warningStringToInitMap) { + warningTypeToInfoMap[entry.second.type] = {entry.second.defaultLevel, entry.first, entry.second.description}; + } + warningTypeToInfoMap[WarningType::Always] = {WarningLevel::Warn, "always", "you shouldn't be reading this"}; + assert(warningTypeToInfoMap.size() == static_cast(WarningType::Max)); +} + +/** + * Initialises the main warning type map and reads flags passed to set each warning type's level. + */ +void WarningHandler::Init(int argc, char* argv[]) { + ConstructTypeToInfoMap(); + + bool werror = false; + for (int i = 1; i < argc; i++) { + // If it doesn't start with "-W" skip it. + if (argv[i][0] != '-' || argv[i][1] != 'W' || argv[i][2] == '\0') { + continue; + } + + WarningLevel warningTypeOn = WarningLevel::Warn; + size_t startingIndex = 2; + + // "-Wno-" + if (argv[i][2] == 'n' && argv[i][3] == 'o' && argv[i][4] == '-' && argv[i][5] != '\0') { + warningTypeOn = WarningLevel::Off; + startingIndex = 5; + } + + // Read starting after the "-W" or "-Wno-" + std::string_view currentArgv = &argv[i][startingIndex]; + + if (currentArgv == "error") { + werror = warningTypeOn != WarningLevel::Off; + } else if (currentArgv == "everything") { + for (auto& it: warningTypeToInfoMap) { + if (it.second.level <= WarningLevel::Warn) { + it.second.level = warningTypeOn; + } + } + } else { + // "-Werror=" / "-Wno-error=" parser + if (currentArgv.rfind("error=", 0) == 0) { + // Read starting after the "error=" part + currentArgv = &argv[i][startingIndex + 6]; + warningTypeOn = warningTypeOn != WarningLevel::Off ? WarningLevel::Err : WarningLevel::Warn; + } + + auto it = warningStringToInitMap.find(std::string(currentArgv)); + if (it != warningStringToInitMap.end()) { + warningTypeToInfoMap[it->second.type].level = warningTypeOn; + } + else { + HANDLE_WARNING(WarningType::Always, StringHelper::Sprintf("unknown warning flag '%s'", argv[i]), ""); + } + } + } + + if (werror) { + for (auto& it: warningTypeToInfoMap) { + if (it.second.level >= WarningLevel::Warn) { + it.second.level = WarningLevel::Err; + } + } + } +} + +bool WarningHandler::IsWarningEnabled(WarningType warnType) { + assert(static_cast(warnType) >= 0 && warnType < WarningType::Max); + + return warningTypeToInfoMap.at(warnType).level != WarningLevel::Off; +} + +bool WarningHandler::WasElevatedToError(WarningType warnType) { + assert(static_cast(warnType) >= 0 && warnType < WarningType::Max); + + if (!IsWarningEnabled(warnType)) { + return false; + } + + return warningTypeToInfoMap.at(warnType).level >= WarningLevel::Err; +} + +/** + * Print file/line/function info for debugging + */ +void WarningHandler::FunctionPreamble(const char* filename, int32_t line, const char* function) { + if (Globals::Instance->verbosity >= VerbosityLevel::VERBOSITY_DEBUG) { + fprintf(stderr, "%s:%i: in function %s:\n", filename, line, function); + } +} + +/** + * Print the information about the file(s) being processed (XML for extraction, png etc. for building) + */ +void WarningHandler::ProcessedFilePreamble() { + if (Globals::Instance->inputPath != "") { + fprintf(stderr, "When processing file %s: ", Globals::Instance->inputPath.c_str()); + } +} + +/** + * Print information about the binary file being extracted + */ +void WarningHandler::ExtractedFilePreamble(const ZFile *parent, const ZResource* res, const uint32_t offset) { + fprintf(stderr, "in input binary file %s, ", parent->GetName().c_str()); + if (res != nullptr) { + fprintf(stderr, "resource '%s' at ", res->GetName().c_str()); + } + fprintf(stderr, "offset 0x%06X: \n\t", offset); +} + +/** + * Construct the rest of the message, after warning:/error. The message is filled in one character at a time, with indents added after newlines + */ +std::string WarningHandler::ConstructMessage(std::string message, const std::string& header, const std::string& body) { + message.reserve(message.size() + header.size() + body.size() + 10 * (sizeof(HANG_INDT) - 1)); + message += StringHelper::Sprintf(HILITE("%s"), header.c_str()); + message += "\n"; + + if (body == "") { + return message; + } + + message += HANG_INDT; + for (const char* ptr = body.c_str(); *ptr != '\0'; ptr++) { + message += *ptr; + if (*ptr == '\n') { + message += HANG_INDT; + } + } + message += "\n"; + + return message; +} + +/* Error module functions */ + +void WarningHandler::PrintErrorAndThrow(const std::string& header, const std::string& body) { + std::string errorMsg = ERR_FMT("error: "); + throw std::runtime_error(ConstructMessage(errorMsg, header, body)); +} + +/* Error types, to be used via the macros */ + +void WarningHandler::ErrorType(WarningType warnType, const std::string& header, const std::string& body) { + std::string headerMsg = header; + + for (const auto& iter: warningStringToInitMap) { + if (iter.second.type == warnType) { + headerMsg += StringHelper::Sprintf(" [%s]", iter.first.c_str()); + } + } + + PrintErrorAndThrow(headerMsg, body); +} + +void WarningHandler::Error_Plain(const char* filename, int32_t line, const char* function, WarningType warnType, const std::string& header, const std::string& body) { + FunctionPreamble(filename, line, function); + + ErrorType(warnType, header, body); +} + +void WarningHandler::Error_Process(const char* filename, int32_t line, const char* function, WarningType warnType, const std::string& header, const std::string& body) { + FunctionPreamble(filename, line, function); + ProcessedFilePreamble(); + + ErrorType(warnType, header, body); +} + +void WarningHandler::Error_Resource(const char* filename, int32_t line, const char* function, WarningType warnType, const ZFile *parent, const ZResource* res, const uint32_t offset, const std::string& header, const std::string& body) { + assert(parent != nullptr); + + FunctionPreamble(filename, line, function); + ProcessedFilePreamble(); + ExtractedFilePreamble(parent, res, offset); + + ErrorType(warnType, header, body); +} + +/* Warning module functions */ + +void WarningHandler::PrintWarningBody(const std::string& header, const std::string& body) { + std::string errorMsg = WARN_FMT("warning: "); + fprintf(stderr, "%s", ConstructMessage(errorMsg, header, body).c_str()); +} + +void WarningHandler::WarningTypeAndChooseEscalate(WarningType warnType, const std::string& header, const std::string& body) { + std::string headerMsg = header; + + for (const auto& iter: warningStringToInitMap) { + if (iter.second.type == warnType) { + headerMsg += StringHelper::Sprintf(" [-W%s]", iter.first.c_str()); + } + } + + if (WasElevatedToError(warnType)) { + PrintErrorAndThrow(headerMsg, body); + } else { + PrintWarningBody(headerMsg, body); + } +} + + +/* Warning types, to be used via the macros */ + +void WarningHandler::Warning_Plain(const char* filename, int32_t line, const char* function, WarningType warnType, const std::string& header, const std::string& body) { + if (!IsWarningEnabled(warnType)) { + return; + } + + FunctionPreamble(filename, line, function); + + WarningTypeAndChooseEscalate(warnType, header, body); +} + +void WarningHandler::Warning_Process(const char* filename, int32_t line, const char* function, WarningType warnType, const std::string& header, const std::string& body) { + if (!IsWarningEnabled(warnType)) { + return; + } + + FunctionPreamble(filename, line, function); + ProcessedFilePreamble(); + + WarningTypeAndChooseEscalate(warnType, header, body); +} + +void WarningHandler::Warning_Resource(const char* filename, int32_t line, const char* function, WarningType warnType, const ZFile *parent, const ZResource* res, const uint32_t offset, const std::string& header, const std::string& body) { + assert(parent != nullptr); + + if (!IsWarningEnabled(warnType)) { + return; + } + + FunctionPreamble(filename, line, function); + ProcessedFilePreamble(); + ExtractedFilePreamble(parent, res, offset); + + WarningTypeAndChooseEscalate(warnType, header, body); +} + + +/* Help-related functions */ + +#include + +/** + * Print each warning name, default status, and description using the init map + */ +void WarningHandler::PrintHelp() { + std::set sortedKeys; + WarningInfoInit warningInfo; + uint32_t columnWidth = 25; + std::string dt; + + // Sort keys through the magic of `set`, to print in alphabetical order + for (auto& it : warningStringToInitMap) { + sortedKeys.insert(it.first); + } + + printf("\nWarning types ( * means enabled by default)\n"); + for (auto& key : sortedKeys) { + warningInfo = warningStringToInitMap.at(key); + if (warningInfo.defaultLevel <= WarningLevel::Warn) { + dt = "-W"; + dt += key; + if (warningInfo.defaultLevel == WarningLevel::Warn) { + dt += " *"; + } + printf(HELP_DT_INDT "%-*s", columnWidth, dt.c_str()); + + if (dt.length() + 2 > columnWidth) { + printf("\n" HELP_DT_INDT "%-*s", columnWidth, ""); + } + printf("%s\n", warningInfo.description.c_str()); + } + } + + printf("\nDefault errors\n"); + for (auto& key : sortedKeys) { + if (warningInfo.defaultLevel > WarningLevel::Warn) { + dt = "-W"; + dt += key; + printf(HELP_DT_INDT "%-*s", columnWidth, dt.c_str()); + + if (dt.length() + 2 > columnWidth) { + printf("\n" HELP_DT_INDT "%*s", columnWidth, ""); + } + printf("%s\n", warningInfo.description.c_str()); + } + } + + printf("\n"); + printf("Other\n" HELP_DT_INDT "-Weverything will enable all existing warnings.\n" HELP_DT_INDT "-Werror will promote all warnings to errors.\n"); + + printf("\n"); + printf("Warnings can be disabled using -Wno-... instead of -W...; -Weverything will override any -Wno-... flags passed before it.\n"); +} + +/** + * Print which warnings are currently enabled + */ +void WarningHandler::PrintWarningsDebugInfo() +{ + std::string dt; + + printf("Warnings status:\n"); + for (auto& it: warningTypeToInfoMap) { + dt = it.second.name; + dt += ": "; + + printf(HELP_DT_INDT "%-25s", dt.c_str()); + switch (it.second.level) + { + case WarningLevel::Off: + printf(VT_FGCOL(LIGHTGRAY) "Off" VT_RST); + break; + case WarningLevel::Warn: + printf(VT_FGCOL(YELLOW) "Warn" VT_RST); + break; + case WarningLevel::Err: + printf(VT_FGCOL(RED) "Err" VT_RST); + break; + + } + printf("\n"); + } + printf("\n"); +} diff --git a/tools/ZAPD/ZAPD/WarningHandler.h b/tools/ZAPD/ZAPD/WarningHandler.h new file mode 100644 index 0000000000..bb0360a813 --- /dev/null +++ b/tools/ZAPD/ZAPD/WarningHandler.h @@ -0,0 +1,145 @@ +#pragma once + +#include +#include +#include +#include + +#include "Utils/vt.h" +#include "ZFile.h" + +#ifdef _MSC_VER +#define __PRETTY_FUNCTION__ __FUNCSIG__ +#elif not defined(__GNUC__) +#define __PRETTY_FUNCTION__ __func__ +#endif + +// ======================================= +/* Formatting macros */ + +// TODO: move this somewhere else so it can be used by other help +#define HELP_DT_INDT " " + +/* Macros for formatting warnings/errors */ +#define VT_HILITE VT_BOLD_FGCOL(WHITE) +#define VT_WARN VT_BOLD_FGCOL(PURPLE) +#define VT_ERR VT_BOLD_FGCOL(RED) + +#define HILITE(string) (VT_HILITE string VT_RST) +#define WARN_FMT(string) (VT_WARN string VT_RST) +#define ERR_FMT(string) (VT_ERR string VT_RST) + +// Maybe make WARN_LF instead +// Currently 8 spaces +#define WARN_INDT " " +// Currently 16 spaces +#define HANG_INDT " " + +// ======================================= +/* Warning and error macros */ +// TODO: better names + +// General-purpose, plain style (only prints function,file,line in the preamble) +#define HANDLE_ERROR(warningType, header, body) \ + WarningHandler::Error_Plain(__FILE__, __LINE__, __PRETTY_FUNCTION__, warningType, header, body) +#define HANDLE_WARNING(warningType, header, body) \ + WarningHandler::Warning_Plain(__FILE__, __LINE__, __PRETTY_FUNCTION__, warningType, header, \ + body) + +// For processing XMLs or textures/blobs (preamble contains function,file,line; processed file) +#define HANDLE_ERROR_PROCESS(warningType, header, body) \ + WarningHandler::Error_Process(__FILE__, __LINE__, __PRETTY_FUNCTION__, warningType, header, \ + body) +#define HANDLE_WARNING_PROCESS(warningType, header, body) \ + WarningHandler::Warning_Process(__FILE__, __LINE__, __PRETTY_FUNCTION__, warningType, header, \ + body) + +// For ZResource-related stuff (preamble contains function,file,line; processed file; extracted file +// and offset) +#define HANDLE_ERROR_RESOURCE(warningType, parent, resource, offset, header, body) \ + WarningHandler::Error_Resource(__FILE__, __LINE__, __PRETTY_FUNCTION__, warningType, parent, \ + resource, offset, header, body) +#define HANDLE_WARNING_RESOURCE(warningType, parent, resource, offset, header, body) \ + WarningHandler::Warning_Resource(__FILE__, __LINE__, __PRETTY_FUNCTION__, warningType, parent, \ + resource, offset, header, body) + +// ======================================= + +enum class WarningType +{ + Always, // Warnings of this type are always printed, cannot be disabled. + Deprecated, + Unaccounted, + MissingOffsets, + Intersection, + MissingAttribute, + InvalidAttributeValue, + UnknownAttribute, + InvalidXML, + InvalidJPEG, + InvalidPNG, + InvalidExtractedData, + MissingSegment, + HardcodedPointer, + NotImplemented, + Max, +}; + +enum class WarningLevel +{ + Off, + Warn, + Err, +}; + +class WarningHandler +{ +public: + static void ConstructTypeToInfoMap(); + + static void Init(int argc, char* argv[]); + + static bool IsWarningEnabled(WarningType warnType); + static bool WasElevatedToError(WarningType warnType); + + static void FunctionPreamble(const char* filename, int32_t line, const char* function); + static void ProcessedFilePreamble(); + static void ExtractedFilePreamble(const ZFile* parent, const ZResource* res, + const uint32_t offset); + static std::string ConstructMessage(std::string message, const std::string& header, + const std::string& body); + + [[noreturn]] static void PrintErrorAndThrow(const std::string& header, const std::string& body); + static void PrintWarningBody(const std::string& header, const std::string& body); + + [[noreturn]] static void ErrorType(WarningType warnType, const std::string& header, + const std::string& body); + [[noreturn]] static void Error_Plain(const char* filename, int32_t line, const char* function, + WarningType warnType, const std::string& header, + const std::string& body); + [[noreturn]] static void Error_Process(const char* filename, int32_t line, const char* function, + WarningType warnType, const std::string& header, + const std::string& body); + [[noreturn]] static void Error_Resource(const char* filename, int32_t line, + const char* function, WarningType warnType, + const ZFile* parent, const ZResource* res, + const uint32_t offset, const std::string& header, + const std::string& body); + + static void WarningTypeAndChooseEscalate(WarningType warnType, const std::string& header, + const std::string& body); + + static void Warning_Plain(const char* filename, int32_t line, const char* function, + WarningType warnType, const std::string& header, + const std::string& body); + static void Warning_Process(const char* filename, int32_t line, const char* function, + WarningType warnType, const std::string& header, + const std::string& body); + static void Warning_Resource(const char* filename, int32_t line, const char* function, + WarningType warnType, const ZFile* parent, const ZResource* res, + const uint32_t offset, const std::string& header, + const std::string& body); + + static void PrintHelp(); + static void PrintWarningsDebugInfo(); +}; diff --git a/tools/ZAPD/ZAPD/ZAPD.vcxproj b/tools/ZAPD/ZAPD/ZAPD.vcxproj index c74d28cbec..431abb73b9 100644 --- a/tools/ZAPD/ZAPD/ZAPD.vcxproj +++ b/tools/ZAPD/ZAPD/ZAPD.vcxproj @@ -1,5 +1,6 @@ + Debug @@ -71,8 +72,8 @@ - $(SolutionDir)lib\libgfxd;$(SolutionDir)x64\Debug;$(SolutionDir)packages\libpng.1.6.28.1\build\native\lib\x64\v140\dynamic\Debug;$(LibraryPath) - $(SolutionDir)ZAPDUtils;$(SolutionDir)lib\tinyxml2;$(SolutionDir)lib\libgfxd;$(SolutionDir)lib\elfio;$(SolutionDir)lib\stb;$(ProjectDir);$(IncludePath) + $(OutDir);$(ProjectDir)..\lib\libgfxd;$(ProjectDir)..\packages\libpng-v142.1.6.37.2\build\native\lib\x64\v142\Debug\;$(LibraryPath) + $(ProjectDir)..\ZAPDUtils;$(ProjectDir)..\lib\tinyxml2;$(ProjectDir)..\lib\libgfxd;$(ProjectDir)..\lib\elfio;$(ProjectDir)..\lib\stb;$(ProjectDir);$(IncludePath) $(IncludePath) @@ -105,13 +106,16 @@ _CRT_SECURE_NO_WARNINGS;%(PreprocessorDefinitions) EnableFastChecks stdc11 + MultiThreadedDebug true - libpng16.lib;ZAPDUtils.lib;/WHOLEARCHIVE:ExporterExample.lib;%(AdditionalDependencies) + ZAPDUtils.lib;/WHOLEARCHIVE:ExporterExample.lib;%(AdditionalDependencies) + false cd .. +mkdir build\ZAPD python3 ZAPD/genbuildinfo.py @@ -146,6 +150,7 @@ python3 ZAPD/genbuildinfo.py + @@ -153,19 +158,22 @@ python3 ZAPD/genbuildinfo.py - + + + + @@ -213,6 +221,7 @@ python3 ZAPD/genbuildinfo.py + @@ -237,10 +246,13 @@ python3 ZAPD/genbuildinfo.py + + + @@ -253,6 +265,7 @@ python3 ZAPD/genbuildinfo.py + @@ -294,6 +307,7 @@ python3 ZAPD/genbuildinfo.py + @@ -301,24 +315,29 @@ python3 ZAPD/genbuildinfo.py true + + - - + + + This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}. - - + + + + \ No newline at end of file diff --git a/tools/ZAPD/ZAPD/ZAPD.vcxproj.filters b/tools/ZAPD/ZAPD/ZAPD.vcxproj.filters index 2d3050ed42..b8a7daaacf 100644 --- a/tools/ZAPD/ZAPD/ZAPD.vcxproj.filters +++ b/tools/ZAPD/ZAPD/ZAPD.vcxproj.filters @@ -49,6 +49,15 @@ {85600275-99fe-491d-8189-bcc3dc1a8903} + + {ba9990b0-1082-48bb-874c-6108534b5455} + + + {ce9d91b0-ba20-4296-bc2d-8630965bb392} + + + {730beb67-6d59-4849-9d9b-702c4a565fc0} + @@ -135,9 +144,6 @@ Source Files\Z64\ZRoom\Commands - - Source Files\Libraries - Source Files\Z64 @@ -258,6 +264,24 @@ Source Files\Z64 + + Source Files + + + Source Files\Z64 + + + Source Files\Z64 + + + Source Files + + + Source Files\Z64 + + + Source Files + @@ -497,11 +521,32 @@ Header Files\Z64 + + Header Files + + + Header Files\Z64 + + + Header Files\Z64 + + + Header Files\Z64 + + + Header Files + Resource Files + + any\any + + + NuGet + diff --git a/tools/ZAPD/ZAPD/ZAnimation.cpp b/tools/ZAPD/ZAPD/ZAnimation.cpp index 9242f657a8..adb0ae7c9a 100644 --- a/tools/ZAPD/ZAPD/ZAnimation.cpp +++ b/tools/ZAPD/ZAPD/ZAnimation.cpp @@ -6,6 +6,7 @@ #include "Utils/BitConverter.h" #include "Utils/File.h" #include "Utils/StringHelper.h" +#include "WarningHandler.h" #include "ZFile.h" REGISTER_ZFILENODE(Animation, ZNormalAnimation); @@ -218,11 +219,9 @@ void ZCurveAnimation::ParseXML(tinyxml2::XMLElement* reader) std::string skelOffsetXml = registeredAttributes.at("SkelOffset").value; if (skelOffsetXml == "") { - throw std::runtime_error( - StringHelper::Sprintf("ZCurveAnimation::ParseXML: Fatal error in '%s'.\n" - "\t Missing 'SkelOffset' attribute in ZCurveAnimation.\n" - "\t You need to provide the offset of the curve skeleton.", - name.c_str())); + HANDLE_ERROR_RESOURCE(WarningType::MissingAttribute, parent, this, rawDataIndex, + "missing 'SkelOffset' attribute in ", + "You need to provide the offset of the curve skeleton."); } skelOffset = StringHelper::StrToL(skelOffsetXml, 0); } diff --git a/tools/ZAPD/ZAPD/ZAnimation.h b/tools/ZAPD/ZAPD/ZAnimation.h index e5b69d3ef8..2c04b4ff81 100644 --- a/tools/ZAPD/ZAPD/ZAnimation.h +++ b/tools/ZAPD/ZAPD/ZAnimation.h @@ -1,6 +1,6 @@ #pragma once -#include +#include #include #include #include "Vec3s.h" diff --git a/tools/ZAPD/ZAPD/ZArray.cpp b/tools/ZAPD/ZAPD/ZArray.cpp index b1ba3a6693..ebfb13ee74 100644 --- a/tools/ZAPD/ZAPD/ZArray.cpp +++ b/tools/ZAPD/ZAPD/ZArray.cpp @@ -4,6 +4,7 @@ #include "Globals.h" #include "Utils/StringHelper.h" +#include "WarningHandler.h" #include "ZFile.h" REGISTER_ZFILENODE(Array, ZArray); @@ -25,13 +26,18 @@ void ZArray::ParseXML(tinyxml2::XMLElement* reader) ZResource::ParseXML(reader); arrayCnt = reader->IntAttribute("Count", 0); - // TODO: do a better check. - assert(arrayCnt > 0); + if (arrayCnt <= 0) + { + HANDLE_ERROR_RESOURCE(WarningType::InvalidAttributeValue, parent, this, rawDataIndex, + "invalid value found for 'Count' attribute", ""); + } tinyxml2::XMLElement* child = reader->FirstChildElement(); if (child == nullptr) - throw std::runtime_error( - StringHelper::Sprintf("Error! Array needs at least one sub-element.\n")); + { + HANDLE_ERROR_RESOURCE(WarningType::InvalidXML, parent, this, rawDataIndex, + " needs one sub-element", ""); + } childName = child->Name(); @@ -42,9 +48,10 @@ void ZArray::ParseXML(tinyxml2::XMLElement* reader) ZResource* res = nodeMap->at(childName)(parent); if (!res->DoesSupportArray()) { - throw std::runtime_error(StringHelper::Sprintf( - "Error! Resource %s does not support being wrapped in an array!\n", - childName.c_str())); + std::string errorHeader = StringHelper::Sprintf( + "resource <%s> does not support being wrapped in an ", childName.c_str()); + HANDLE_ERROR_RESOURCE(WarningType::InvalidXML, parent, this, rawDataIndex, errorHeader, + ""); } res->parent = parent; res->SetInnerNode(true); @@ -87,7 +94,7 @@ Declaration* ZArray::DeclareVar(const std::string& prefix, const std::string& bo std::string ZArray::GetBodySourceCode() const { - std::string output; + std::string output = ""; for (size_t i = 0; i < arrayCnt; i++) { diff --git a/tools/ZAPD/ZAPD/ZArray.h b/tools/ZAPD/ZAPD/ZArray.h index 46a04d7329..b78a8edfd8 100644 --- a/tools/ZAPD/ZAPD/ZArray.h +++ b/tools/ZAPD/ZAPD/ZArray.h @@ -1,6 +1,6 @@ #pragma once -#include +#include #include #include #include "ZResource.h" diff --git a/tools/ZAPD/ZAPD/ZBackground.cpp b/tools/ZAPD/ZAPD/ZBackground.cpp index 4125f239f3..0ed1eb7471 100644 --- a/tools/ZAPD/ZAPD/ZBackground.cpp +++ b/tools/ZAPD/ZAPD/ZBackground.cpp @@ -5,6 +5,7 @@ #include "Utils/File.h" #include "Utils/Path.h" #include "Utils/StringHelper.h" +#include "WarningHandler.h" #include "ZFile.h" REGISTER_ZFILENODE(Background, ZBackground); @@ -63,52 +64,46 @@ void ZBackground::CheckValidJpeg(const std::string& filepath) uint32_t jpegMarker = BitConverter::ToUInt32BE(data, 0); if (jpegMarker != JPEG_MARKER) { - fprintf(stderr, - "ZBackground::CheckValidJpeg: Warning.\n" - "\t Missing jpeg marker at the beginning of file: '%s'.\n" - "\t The game will skip this jpeg.\n", - filename.c_str()); + HANDLE_WARNING_PROCESS( + WarningType::InvalidJPEG, + StringHelper::Sprintf("missing jpeg marker at beginning of file: '%s'", + filename.c_str()), + "The game will skip this jpeg."); } if (data.at(6) != 'J' || data.at(7) != 'F' || data.at(8) != 'I' || data.at(9) != 'F' || data.at(10) != '\0') { std::string jfifIdentifier(data.begin() + 6, data.begin() + 6 + 5); - fprintf(stderr, - "ZBackground::CheckValidJpeg: Warning.\n" - "\t Missing 'JFIF' identifier. File: '%s'.\n" - "\t This image may be corrupted or not be a jpeg iamge.\n" - "\t The identifier found was '%s'.\n", - filename.c_str(), jfifIdentifier.c_str()); + HANDLE_WARNING_PROCESS( + WarningType::InvalidJPEG, "missing 'JFIF' identifier", + StringHelper::Sprintf( + "This image may be corrupted, or not a jpeg. The identifier found was: '%s'", + jfifIdentifier.c_str())); } uint8_t majorVersion = data.at(11); uint8_t minorVersion = data.at(12); if (majorVersion != 0x01 || minorVersion != 0x01) { - fprintf(stderr, - "ZBackground::CheckValidJpeg: Warning.\n" - "\t Wrong JFIF version '%i.%02i'. File: '%s'.\n" - "\t The expected version is '1.01'. The game may not be able to decode this image " - "properly.\n", - majorVersion, minorVersion, filename.c_str()); + HANDLE_WARNING_PROCESS( + WarningType::InvalidJPEG, + StringHelper::Sprintf("wrong JFIF version '%i.%02i'", majorVersion, minorVersion), + "The expected version is '1.01'. The game may be unable to decode this image " + "correctly."); } if (BitConverter::ToUInt16BE(data, 20) != MARKER_DQT) { // This may happen when creating a custom image with Exif, XMP, thumbnail, progressive, etc. // enabled. - fprintf(stderr, - "ZBackground::CheckValidJpeg: Warning.\n" - "\t There seems to be extra data before the image data in file: '%s'.\n" - "\t The game may not be able to decode this image properly.\n", - filename.c_str()); + HANDLE_WARNING_PROCESS(WarningType::InvalidJPEG, + "there seems to be extra data before the image data in this file", + "The game may not be able to decode this image correctly."); } if (data.size() > GetRawDataSize()) { - fprintf(stderr, - "ZBackground::CheckValidJpeg: Warning.\n" - "\t The image is bigger than the screen buffer. File: '%s'.\n" - "\t Image size: %zu bytes.\n" - "\t Screen buffer size: %zu bytes.\n", - filename.c_str(), data.size(), GetRawDataSize()); + HANDLE_WARNING_PROCESS( + WarningType::InvalidJPEG, "the image is bigger than the screen buffer", + StringHelper::Sprintf("Image size: %zu bytes\nScreen buffer size: %zu bytes", + data.size(), GetRawDataSize())); } } @@ -138,6 +133,7 @@ Declaration* ZBackground::DeclareVar(const std::string& prefix, Declaration* decl = parent->AddDeclarationIncludeArray(rawDataIndex, incStr, GetRawDataSize(), GetSourceTypeName(), auxName, 0); decl->arrayItemCntStr = "SCREEN_WIDTH * SCREEN_HEIGHT / 4"; + decl->forceArrayCnt = true; decl->staticConf = staticConf; return decl; } diff --git a/tools/ZAPD/ZAPD/ZBlob.cpp b/tools/ZAPD/ZAPD/ZBlob.cpp index c1f0788206..6812bfaee9 100644 --- a/tools/ZAPD/ZAPD/ZBlob.cpp +++ b/tools/ZAPD/ZAPD/ZBlob.cpp @@ -83,11 +83,6 @@ std::string ZBlob::GetBodySourceCode() const return sourceOutput; } -std::string ZBlob::GetSourceOutputHeader([[maybe_unused]] const std::string& prefix) -{ - return StringHelper::Sprintf("extern u8 %s[];\n", name.c_str()); -} - void ZBlob::Save(const fs::path& outFolder) { File::WriteAllBytes((outFolder / (name + ".bin")).string(), blobData); diff --git a/tools/ZAPD/ZAPD/ZBlob.h b/tools/ZAPD/ZAPD/ZBlob.h index 86623b5119..d7a7feff12 100644 --- a/tools/ZAPD/ZAPD/ZBlob.h +++ b/tools/ZAPD/ZAPD/ZBlob.h @@ -16,7 +16,6 @@ public: Declaration* DeclareVar(const std::string& prefix, const std::string& bodyStr) override; std::string GetBodySourceCode() const override; - std::string GetSourceOutputHeader(const std::string& prefix) override; void Save(const fs::path& outFolder) override; bool IsExternalResource() const override; diff --git a/tools/ZAPD/ZAPD/ZCollision.cpp b/tools/ZAPD/ZAPD/ZCollision.cpp index f9c0bf7d62..1dfa46b1dd 100644 --- a/tools/ZAPD/ZAPD/ZCollision.cpp +++ b/tools/ZAPD/ZAPD/ZCollision.cpp @@ -88,7 +88,7 @@ void ZCollisionHeader::ParseRawData() void ZCollisionHeader::DeclareReferences(const std::string& prefix) { - std::string declaration; + std::string declaration = ""; std::string auxName = name; if (name == "") @@ -174,7 +174,7 @@ void ZCollisionHeader::DeclareReferences(const std::string& prefix) std::string ZCollisionHeader::GetBodySourceCode() const { - std::string declaration; + std::string declaration = ""; declaration += "\n"; diff --git a/tools/ZAPD/ZAPD/ZCutscene.cpp b/tools/ZAPD/ZAPD/ZCutscene.cpp index 237481079f..ba8fe89638 100644 --- a/tools/ZAPD/ZAPD/ZCutscene.cpp +++ b/tools/ZAPD/ZAPD/ZCutscene.cpp @@ -2,6 +2,7 @@ #include "Utils/BitConverter.h" #include "Utils/StringHelper.h" +#include "WarningHandler.h" #include "ZResource.h" REGISTER_ZFILENODE(Cutscene, ZCutscene); @@ -87,7 +88,7 @@ CutsceneCommandSceneTransFX::~CutsceneCommandSceneTransFX() std::string ZCutscene::GetBodySourceCode() const { - std::string output; + std::string output = ""; uint32_t curPtr = 0; output += StringHelper::Sprintf(" CS_BEGIN_CUTSCENE(%i, %i),\n", commands.size(), endFrame); @@ -225,8 +226,9 @@ void ZCutscene::ParseRawData() cmd = new CutsceneCommandEnd(rawData, currentPtr); break; case CutsceneCommands::Error: - fprintf(stderr, "Cutscene command error %d %s %d\n", (int32_t)cmdID, __FILE__, - __LINE__); + HANDLE_WARNING_RESOURCE(WarningType::NotImplemented, parent, this, rawDataIndex, + StringHelper::Sprintf("cutscene command error %d", cmdID), + ""); break; } @@ -404,7 +406,9 @@ CutsceneCommands ZCutscene::GetCommandFromID(int32_t id) return CutsceneCommands::Unknown; } - fprintf(stderr, "WARNING: Could not identify cutscene command ID 0x%04X\n", id); + HANDLE_WARNING_RESOURCE( + WarningType::NotImplemented, parent, this, rawDataIndex, + StringHelper::Sprintf("could not identify cutscene command. ID 0x%04X", id), ""); return CutsceneCommands::Error; } diff --git a/tools/ZAPD/ZAPD/ZCutscene.h b/tools/ZAPD/ZAPD/ZCutscene.h index cbb6a78b02..8e901e3075 100644 --- a/tools/ZAPD/ZAPD/ZCutscene.h +++ b/tools/ZAPD/ZAPD/ZCutscene.h @@ -1,6 +1,6 @@ #pragma once -#include +#include #include #include #include "ZFile.h" diff --git a/tools/ZAPD/ZAPD/ZCutsceneMM.h b/tools/ZAPD/ZAPD/ZCutsceneMM.h index 41b7de37f3..44b108d6c1 100644 --- a/tools/ZAPD/ZAPD/ZCutsceneMM.h +++ b/tools/ZAPD/ZAPD/ZCutsceneMM.h @@ -1,6 +1,6 @@ #pragma once -#include +#include #include #include #include "ZCutscene.h" diff --git a/tools/ZAPD/ZAPD/ZDisplayList.cpp b/tools/ZAPD/ZAPD/ZDisplayList.cpp index 63c5684228..869b563d96 100644 --- a/tools/ZAPD/ZAPD/ZDisplayList.cpp +++ b/tools/ZAPD/ZAPD/ZDisplayList.cpp @@ -11,6 +11,7 @@ #include "Utils/File.h" #include "Utils/Path.h" #include "Utils/StringHelper.h" +#include "WarningHandler.h" #include "gfxd.h" REGISTER_ZFILENODE(DList, ZDisplayList); @@ -445,11 +446,12 @@ int32_t ZDisplayList::GetDListLength(const std::vector& rawData, uint32 { if (ptr >= rawDataSize) { - throw std::runtime_error(StringHelper::Sprintf( - "%s: Fatal error.\n" - "\t End of file found when trying to find the end of the " - "DisplayList at offset: '0x%X'.\n", - "Raw data size: 0x%zX.\n", __PRETTY_FUNCTION__, rawDataIndex, rawDataSize)); + std::string errorHeader = + StringHelper::Sprintf("reached end of file when trying to find the end of the " + "DisplayList starting at offset 0x%X", + rawDataIndex); + std::string errorBody = StringHelper::Sprintf("Raw data size: 0x%zX.", rawDataSize); + HANDLE_ERROR_PROCESS(WarningType::Always, errorHeader, errorBody); } uint8_t opcode = rawData.at(ptr); @@ -1522,11 +1524,6 @@ void ZDisplayList::Opcode_G_ENDDL([[maybe_unused]] const std::string& prefix, ch TextureGenCheck(); } -std::string ZDisplayList::GetSourceOutputHeader([[maybe_unused]] const std::string& prefix) -{ - return ""; -} - static int32_t GfxdCallback_FormatSingleEntry() { ZDisplayList* self = static_cast(gfxd_udata_get()); @@ -1737,7 +1734,7 @@ static int32_t GfxdCallback_Matrix(uint32_t seg) return 1; } -std::string ZDisplayList::GetSourceOutputCode(const std::string& prefix) +void ZDisplayList::DeclareReferences(const std::string& prefix) { std::string sourceOutput; @@ -1750,7 +1747,7 @@ std::string ZDisplayList::GetSourceOutputCode(const std::string& prefix) if (vertices.size() > 0) { std::vector>> verticesSorted(vertices.begin(), - vertices.end()); + vertices.end()); for (size_t i = 0; i < verticesSorted.size() - 1; i++) { @@ -1775,15 +1772,13 @@ std::string ZDisplayList::GetSourceOutputCode(const std::string& prefix) // Generate Vertex Declarations for (auto& item : vertices) { - std::string declaration; + std::string declaration = ""; offset_t curAddr = item.first; auto& firstVtx = item.second.at(0); for (auto vtx : item.second) - { declaration += StringHelper::Sprintf("\t%s,\n", vtx.GetBodySourceCode().c_str()); - } Declaration* decl = parent->AddDeclarationArray( curAddr, firstVtx.GetDeclarationAlignment(), @@ -1800,7 +1795,7 @@ std::string ZDisplayList::GetSourceOutputCode(const std::string& prefix) if (vertices.size() > 0) { std::vector>> verticesSorted(vertices.begin(), - vertices.end()); + vertices.end()); for (size_t i = 0; i < verticesSorted.size() - 1; i++) { @@ -1863,11 +1858,6 @@ std::string ZDisplayList::GetSourceOutputCode(const std::string& prefix) } } } - - if (parent != nullptr) - return ""; - - return sourceOutput; } std::string ZDisplayList::ProcessLegacy(const std::string& prefix) diff --git a/tools/ZAPD/ZAPD/ZDisplayList.h b/tools/ZAPD/ZAPD/ZDisplayList.h index d538666750..96808315dc 100644 --- a/tools/ZAPD/ZAPD/ZDisplayList.h +++ b/tools/ZAPD/ZAPD/ZDisplayList.h @@ -363,8 +363,7 @@ public: size_t GetRawDataSize() const override; DeclarationAlignment GetDeclarationAlignment() const override; - std::string GetSourceOutputHeader(const std::string& prefix) override; - std::string GetSourceOutputCode(const std::string& prefix) override; + void DeclareReferences(const std::string& prefix) override; std::string ProcessLegacy(const std::string& prefix); std::string ProcessGfxDis(const std::string& prefix); diff --git a/tools/ZAPD/ZAPD/ZFile.cpp b/tools/ZAPD/ZAPD/ZFile.cpp index 7cbfeba886..77387fc72c 100644 --- a/tools/ZAPD/ZAPD/ZFile.cpp +++ b/tools/ZAPD/ZAPD/ZFile.cpp @@ -13,6 +13,7 @@ #include "Utils/MemoryStream.h" #include "Utils/Path.h" #include "Utils/StringHelper.h" +#include "WarningHandler.h" #include "ZAnimation.h" #include "ZArray.h" #include "ZBackground.h" @@ -73,19 +74,13 @@ ZFile::ZFile(ZFileMode nMode, tinyxml2::XMLElement* reader, const fs::path& nBas ZFile::~ZFile() { for (ZResource* res : resources) - { delete res; - } for (auto d : declarations) - { delete d.second; - } for (auto sym : symbolResources) - { delete sym.second; - } } void ZFile::ParseXML(tinyxml2::XMLElement* reader, const std::string& filename) @@ -114,8 +109,11 @@ void ZFile::ParseXML(tinyxml2::XMLElement* reader, const std::string& filename) else if (std::string_view(gameStr) == "OOT") Globals::Instance->game = ZGame::OOT_RETAIL; else - throw std::runtime_error( - StringHelper::Sprintf("Error: Game type %s not supported.", gameStr)); + { + std::string errorHeader = + StringHelper::Sprintf("'Game' type '%s' is not supported.", gameStr); + HANDLE_ERROR_PROCESS(WarningType::InvalidAttributeValue, errorHeader, ""); + } } if (reader->Attribute("BaseAddress") != nullptr) @@ -128,16 +126,22 @@ void ZFile::ParseXML(tinyxml2::XMLElement* reader, const std::string& filename) rangeEnd = StringHelper::StrToL(reader->Attribute("RangeEnd"), 16); if (rangeStart > rangeEnd) - throw std::runtime_error("Error: RangeStart must be before than RangeEnd."); + HANDLE_ERROR_PROCESS( + WarningType::Always, + StringHelper::Sprintf("'RangeStart' 0x%06X must be before 'RangeEnd' 0x%06X", + rangeStart, rangeEnd), + ""); const char* segmentXml = reader->Attribute("Segment"); if (segmentXml != nullptr) { if (!StringHelper::HasOnlyDigits(segmentXml)) { - throw std::runtime_error(StringHelper::Sprintf( - "error: Invalid segment value '%s': must be a decimal between 0 and 15 inclusive", - segmentXml)); + HANDLE_ERROR_PROCESS(WarningType::Always, + StringHelper::Sprintf("error: Invalid segment value '%s': must be " + "a decimal between 0 and 15 inclusive", + segmentXml), + ""); } segment = StringHelper::StrToL(segmentXml, 10); @@ -146,16 +150,19 @@ void ZFile::ParseXML(tinyxml2::XMLElement* reader, const std::string& filename) if (segment == 128) { #ifdef DEPRECATION_ON - fprintf(stderr, "warning: segment 128 is deprecated.\n\tRemove " - "'Segment=\"128\"' from the xml to use virtual addresses\n"); + HANDLE_WARNING_PROCESS( + WarningType::Always, "warning: segment 128 is deprecated.", + "Remove 'Segment=\"128\"' from the xml to use virtual addresses\n"); #endif } else { - throw std::runtime_error( + HANDLE_ERROR_PROCESS( + WarningType::Always, StringHelper::Sprintf("error: invalid segment value '%s': must be a decimal " "number between 0 and 15 inclusive", - segmentXml)); + segmentXml), + ""); } } } @@ -176,18 +183,16 @@ void ZFile::ParseXML(tinyxml2::XMLElement* reader, const std::string& filename) if (mode == ZFileMode::Extract || mode == ZFileMode::ExternalFile) { if (!File::Exists((basePath / name).string())) - throw std::runtime_error( - StringHelper::Sprintf("Error! File %s does not exist.", (basePath / name).c_str())); + { + std::string errorHeader = StringHelper::Sprintf("binary file '%s' does not exist.", + (basePath / name).c_str()); + HANDLE_ERROR_PROCESS(WarningType::Always, errorHeader, ""); + } rawData = File::ReadAllBytes((basePath / name).string()); - /* - * TODO: In OoT repo ovl_Boss_Sst has a wrong RangeEnd (0xAD40 instead of 0xAD70), - * so uncommenting the following produces wrong behavior. - * If somebody fixes that in OoT repo, uncomment this. I'm too tired of fixing XMLs. - */ - // if (reader->Attribute("RangeEnd") == nullptr) - // rangeEnd = rawData.size(); + if (reader->Attribute("RangeEnd") == nullptr) + rangeEnd = rawData.size(); } std::unordered_set nameSet; @@ -211,20 +216,17 @@ void ZFile::ParseXML(tinyxml2::XMLElement* reader, const std::string& filename) if (offsetSet.find(offsetXml) != offsetSet.end()) { - throw std::runtime_error(StringHelper::Sprintf( - "ZFile::ParseXML: Error in '%s'.\n\t Repeated 'Offset' attribute: %s \n", - name.c_str(), offsetXml)); + std::string errorHeader = + StringHelper::Sprintf("repeated 'Offset' attribute: %s", offsetXml); + HANDLE_ERROR_PROCESS(WarningType::InvalidXML, errorHeader, ""); } offsetSet.insert(offsetXml); } - else if (Globals::Instance->warnNoOffset) + else { - fprintf(stderr, "Warning No offset specified for: %s", nameXml); - } - else if (Globals::Instance->errorNoOffset) - { - throw std::runtime_error( - StringHelper::Sprintf("Error no offset specified for %s", nameXml)); + HANDLE_WARNING_RESOURCE(WarningType::MissingOffsets, this, nullptr, rawDataIndex, + StringHelper::Sprintf("no offset specified for %s.", nameXml), + ""); } if (Globals::Instance->verbosity >= VerbosityLevel::VERBOSITY_INFO) @@ -234,9 +236,9 @@ void ZFile::ParseXML(tinyxml2::XMLElement* reader, const std::string& filename) { if (outNameSet.find(outNameXml) != outNameSet.end()) { - throw std::runtime_error(StringHelper::Sprintf( - "ZFile::ParseXML: Error in '%s'.\n\t Repeated 'OutName' attribute: %s \n", - name.c_str(), outNameXml)); + std::string errorHeader = + StringHelper::Sprintf("repeated 'OutName' attribute: %s", outNameXml); + HANDLE_ERROR_PROCESS(WarningType::InvalidXML, errorHeader, ""); } outNameSet.insert(outNameXml); } @@ -244,9 +246,9 @@ void ZFile::ParseXML(tinyxml2::XMLElement* reader, const std::string& filename) { if (nameSet.find(nameXml) != nameSet.end()) { - throw std::runtime_error(StringHelper::Sprintf( - "ZFile::ParseXML: Error in '%s'.\n\t Repeated 'Name' attribute: %s \n", - name.c_str(), nameXml)); + std::string errorHeader = + StringHelper::Sprintf("repeated 'Name' attribute: %s", nameXml); + HANDLE_ERROR_PROCESS(WarningType::InvalidXML, errorHeader, ""); } nameSet.insert(nameXml); } @@ -279,16 +281,14 @@ void ZFile::ParseXML(tinyxml2::XMLElement* reader, const std::string& filename) } else if (std::string_view(child->Name()) == "File") { - throw std::runtime_error(StringHelper::Sprintf( - "ZFile::ParseXML: Error in '%s'.\n\t Can't declare a File inside a File.\n", - name.c_str())); + std::string errorHeader = "Can't declare a inside a "; + HANDLE_ERROR_PROCESS(WarningType::InvalidXML, errorHeader, ""); } else { - throw std::runtime_error( - StringHelper::Sprintf("ZFile::ParseXML: Error in '%s'.\n\t Unknown element found " - "inside a File element: '%s'.\n", - name.c_str(), nodeName.c_str())); + std::string errorHeader = StringHelper::Sprintf( + "Unknown element found inside a element: %s", nodeName.c_str()); + HANDLE_ERROR_PROCESS(WarningType::InvalidXML, errorHeader, ""); } } } @@ -307,7 +307,7 @@ void ZFile::BuildSourceFile() return; if (!Directory::Exists(outputPath)) - Directory::CreateDirectory(outputPath); + Directory::CreateDirectory(outputPath.string()); GenerateSourceFiles(); } @@ -317,6 +317,11 @@ std::string ZFile::GetName() const return name; } +std::string ZFile::GetOutName() const +{ + return outName.string(); +} + ZFileMode ZFile::GetMode() const { return mode; @@ -338,10 +343,10 @@ void ZFile::ExtractResources() return; if (!Directory::Exists(outputPath)) - Directory::CreateDirectory(outputPath); + Directory::CreateDirectory(outputPath.string()); if (!Directory::Exists(GetSourceOutputFolderPath())) - Directory::CreateDirectory(GetSourceOutputFolderPath()); + Directory::CreateDirectory(GetSourceOutputFolderPath().string()); for (size_t i = 0; i < resources.size(); i++) resources[i]->ParseRawDataLate(); @@ -351,8 +356,8 @@ void ZFile::ExtractResources() if (Globals::Instance->genSourceFile) GenerateSourceFiles(); - MemoryStream* memStream = new MemoryStream(); - BinaryWriter writer = BinaryWriter(memStream); + auto memStreamFile = std::shared_ptr(new MemoryStream()); + BinaryWriter writerFile = BinaryWriter(memStreamFile); ExporterSet* exporterSet = Globals::Instance->GetExporterSet(); @@ -361,6 +366,9 @@ void ZFile::ExtractResources() for (ZResource* res : resources) { + auto memStreamRes = std::shared_ptr(new MemoryStream()); + BinaryWriter writerRes = BinaryWriter(memStreamRes); + if (Globals::Instance->verbosity >= VerbosityLevel::VERBOSITY_INFO) printf("Saving resource %s\n", res->GetName().c_str()); @@ -369,18 +377,24 @@ void ZFile::ExtractResources() // Check if we have an exporter "registered" for this resource type ZResourceExporter* exporter = Globals::Instance->GetExporter(res->GetResourceType()); if (exporter != nullptr) - exporter->Save(res, Globals::Instance->outputPath.string(), &writer); + { + //exporter->Save(res, Globals::Instance->outputPath.string(), &writerFile); + exporter->Save(res, Globals::Instance->outputPath.string(), &writerRes); + } + + if (exporterSet != nullptr && exporterSet->resSaveFunc != nullptr) + exporterSet->resSaveFunc(res, writerRes); } - if (memStream->GetLength() > 0) + if (memStreamFile->GetLength() > 0) { File::WriteAllBytes(StringHelper::Sprintf("%s%s.bin", Globals::Instance->outputPath.string().c_str(), GetName().c_str()), - memStream->ToVector()); + memStreamFile->ToVector()); } - writer.Close(); + writerFile.Close(); if (exporterSet != nullptr && exporterSet->endFileFunc != nullptr) exporterSet->endFileFunc(this); @@ -1070,8 +1084,6 @@ std::string ZFile::ProcessDeclarations() } } - output += "\n"; - return output; } @@ -1234,11 +1246,12 @@ bool ZFile::HandleUnaccountedAddress(uint32_t currentAddress, uint32_t lastAddr, { Declaration* currentDecl = declarations.at(currentAddress); - fprintf(stderr, - "WARNING: Intersection detected from 0x%06X:0x%06X (%s), conflicts with " - "0x%06X (%s)\n", - lastAddr, lastAddr + lastSize, lastDecl->varName.c_str(), currentAddress, - currentDecl->varName.c_str()); + std::string intersectionInfo = StringHelper::Sprintf( + "Resource from 0x%06X:0x%06X (%s) conflicts with 0x%06X (%s).", lastAddr, + lastAddr + lastSize, lastDecl->varName.c_str(), currentAddress, + currentDecl->varName.c_str()); + HANDLE_WARNING_RESOURCE(WarningType::Intersection, this, nullptr, currentAddress, + "intersection detected", intersectionInfo); } } @@ -1309,25 +1322,17 @@ bool ZFile::HandleUnaccountedAddress(uint32_t currentAddress, uint32_t lastAddr, diff, src); decl->isUnaccounted = true; - if (Globals::Instance->warnUnaccounted) + if (nonZeroUnaccounted) { - if (nonZeroUnaccounted) - { - fprintf(stderr, - "Warning in file: %s (%s)\n" - "\t A non-zero unaccounted block was found at offset '0x%06X'.\n" - "\t Block size: '0x%X'.\n", - xmlFilePath.c_str(), name.c_str(), unaccountedAddress, diff); - } - else if (diff >= 16) - { - fprintf(stderr, - "Warning in file: %s (%s)\n" - "\t A big (size>=0x10) zero-only unaccounted block was found " - "at offset '0x%06X'.\n" - "\t Block size: '0x%X'.\n", - xmlFilePath.c_str(), name.c_str(), unaccountedAddress, diff); - } + HANDLE_WARNING_RESOURCE(WarningType::Unaccounted, this, nullptr, unaccountedAddress, + "a non-zero unaccounted block was found", + StringHelper::Sprintf("Block size: '0x%X'", diff)); + } + else if (diff >= 16) + { + HANDLE_WARNING_RESOURCE(WarningType::Unaccounted, this, nullptr, unaccountedAddress, + "a big (size>=0x10) zero-only unaccounted block was found", + StringHelper::Sprintf("Block size: '0x%X'", diff)); } } } diff --git a/tools/ZAPD/ZAPD/ZFile.h b/tools/ZAPD/ZAPD/ZFile.h index 9de6950e4a..7918d5f595 100644 --- a/tools/ZAPD/ZAPD/ZFile.h +++ b/tools/ZAPD/ZAPD/ZFile.h @@ -1,6 +1,5 @@ #pragma once -#include #include #include @@ -46,6 +45,7 @@ public: ~ZFile(); std::string GetName() const; + std::string GetOutName() const; ZFileMode GetMode() const; const fs::path& GetXmlFilePath() const; const std::vector& GetRawData() const; diff --git a/tools/ZAPD/ZAPD/ZLimb.cpp b/tools/ZAPD/ZAPD/ZLimb.cpp index 77c66871fe..a47615d6c6 100644 --- a/tools/ZAPD/ZAPD/ZLimb.cpp +++ b/tools/ZAPD/ZAPD/ZLimb.cpp @@ -4,7 +4,7 @@ #include "Globals.h" #include "Utils/BitConverter.h" -#include "Utils/StringHelper.h" +#include "WarningHandler.h" REGISTER_ZFILENODE(Limb, ZLimb); @@ -37,17 +37,15 @@ void ZLimb::ParseXML(tinyxml2::XMLElement* reader) if (limbType == "") { - throw std::runtime_error(StringHelper::Sprintf("ZLimb::ParseXML: Error in '%s'.\n" - "\t Missing 'LimbType' attribute in xml.\n", - name.c_str())); + HANDLE_ERROR_RESOURCE(WarningType::MissingAttribute, parent, this, rawDataIndex, + "missing 'LimbType' attribute in ", ""); } type = GetTypeByAttributeName(limbType); if (type == ZLimbType::Invalid) { - throw std::runtime_error(StringHelper::Sprintf("ZLimb::ParseXML: Error in '%s'.\n" - "\t Invalid 'LimbType' found: '%s'.\n", - name.c_str(), limbType.c_str())); + HANDLE_ERROR_RESOURCE(WarningType::InvalidAttributeValue, parent, this, rawDataIndex, + "invalid value found for 'LimbType' attribute", ""); } } @@ -109,8 +107,12 @@ void ZLimb::ParseRawData() } break; - default: - throw std::runtime_error("Invalid ZLimb type"); + case ZLimbType::Curve: + case ZLimbType::Legacy: + break; + + case ZLimbType::Invalid: + assert(!"whoops"); break; } } diff --git a/tools/ZAPD/ZAPD/ZPath.cpp b/tools/ZAPD/ZAPD/ZPath.cpp index 4a95c5b91e..e19513db34 100644 --- a/tools/ZAPD/ZAPD/ZPath.cpp +++ b/tools/ZAPD/ZAPD/ZPath.cpp @@ -3,6 +3,7 @@ #include "Globals.h" #include "Utils/BitConverter.h" #include "Utils/StringHelper.h" +#include "WarningHandler.h" #include "ZFile.h" REGISTER_ZFILENODE(Path, ZPath); @@ -20,10 +21,12 @@ void ZPath::ParseXML(tinyxml2::XMLElement* reader) numPaths = StringHelper::StrToL(registeredAttributes.at("NumPaths").value); if (numPaths < 1) - throw std::runtime_error( - StringHelper::Sprintf("ZPath::ParseXML: Fatal error in '%s'.\n" - "\t Invalid value for attribute 'NumPaths': '%i'\n", - name.c_str(), numPaths)); + { + HANDLE_ERROR_RESOURCE( + WarningType::InvalidAttributeValue, parent, this, rawDataIndex, + StringHelper::Sprintf("invalid value '%d' found for 'NumPaths' attribute", numPaths), + "Should be at least '1'"); + } } void ZPath::ParseRawData() @@ -144,7 +147,7 @@ void PathwayEntry::DeclareReferences(const std::string& prefix) if (addressFound) return; - std::string declaration; + std::string declaration = ""; size_t index = 0; for (const auto& point : points) diff --git a/tools/ZAPD/ZAPD/ZResource.cpp b/tools/ZAPD/ZAPD/ZResource.cpp index cb811f4c3b..2dfe6d5eaa 100644 --- a/tools/ZAPD/ZAPD/ZResource.cpp +++ b/tools/ZAPD/ZAPD/ZResource.cpp @@ -4,6 +4,7 @@ #include #include "Utils/StringHelper.h" +#include "WarningHandler.h" #include "ZFile.h" ZResource::ZResource(ZFile* nParent) @@ -85,29 +86,33 @@ void ZResource::ParseXML(tinyxml2::XMLElement* reader) } if (!attrDeclared) - fprintf(stderr, - "ZResource::ParseXML: Warning while parsing '%s'.\n" - "\t Unexpected '%s' attribute in resource '%s'.\n", - parent->GetName().c_str(), attrName.c_str(), reader->Name()); + { + HANDLE_WARNING_RESOURCE( + WarningType::UnknownAttribute, parent, this, rawDataIndex, + StringHelper::Sprintf("unexpected '%s' attribute in resource <%s>", + attrName.c_str(), reader->Name()), + ""); + } attrs = attrs->Next(); } if (!canHaveInner && !reader->NoChildren()) { - throw std::runtime_error( - StringHelper::Sprintf("ZResource::ParseXML: Fatal error in '%s'.\n" - "\t Resource '%s' with inner element/child detected.\n", - name.c_str(), reader->Name())); + std::string errorHeader = StringHelper::Sprintf( + "resource '%s' with inner element/child detected", reader->Name()); + HANDLE_ERROR_PROCESS(WarningType::InvalidXML, errorHeader, ""); } for (const auto& attr : registeredAttributes) { if (attr.second.isRequired && attr.second.value == "") - throw std::runtime_error(StringHelper::Sprintf( - "ZResource::ParseXML: Fatal error while parsing '%s'.\n" - "\t Missing required attribute '%s' in resource '%s'.\n" - "\t Aborting...", - parent->GetName().c_str(), attr.first.c_str(), reader->Name())); + { + std::string headerMsg = + StringHelper::Sprintf("missing required attribute '%s' in resource <%s>", + attr.first.c_str(), reader->Name()); + HANDLE_ERROR_RESOURCE(WarningType::MissingAttribute, parent, this, rawDataIndex, + headerMsg, ""); + } } name = registeredAttributes.at("Name").value; @@ -118,10 +123,8 @@ void ZResource::ParseXML(tinyxml2::XMLElement* reader) { if (!std::regex_match(name, r)) { - throw std::domain_error( - StringHelper::Sprintf("ZResource::ParseXML: Fatal error in '%s'.\n" - "\t Resource with invalid 'Name' attribute.\n", - name.c_str())); + HANDLE_ERROR_RESOURCE(WarningType::InvalidAttributeValue, parent, this, + rawDataIndex, "invalid value found for 'Name' attribute", ""); } } @@ -146,7 +149,9 @@ void ZResource::ParseXML(tinyxml2::XMLElement* reader) } else { - throw std::runtime_error("Invalid value for 'Static' attribute."); + HANDLE_ERROR_RESOURCE( + WarningType::InvalidAttributeValue, parent, this, rawDataIndex, + StringHelper::Sprintf("invalid value '%s' for 'Static' attribute", staticConf), ""); } declaredInXml = true; @@ -253,18 +258,21 @@ std::string ZResource::GetDefaultName(const std::string& prefix) const rawDataIndex); } -std::string ZResource::GetSourceOutputCode([[maybe_unused]] const std::string& prefix) +void ZResource::GetSourceOutputCode([[maybe_unused]] const std::string& prefix) { std::string bodyStr = GetBodySourceCode(); - Declaration* decl = parent->GetDeclaration(rawDataIndex); - if (decl == nullptr || decl->isPlaceholder) - decl = DeclareVar(prefix, bodyStr); - else - decl->text = bodyStr; - decl->staticConf = staticConf; + if (bodyStr != "ERROR") + { + Declaration* decl = parent->GetDeclaration(rawDataIndex); - return ""; + if (decl == nullptr || decl->isPlaceholder) + decl = DeclareVar(prefix, bodyStr); + else + decl->text = bodyStr; + + decl->staticConf = staticConf; + } } std::string ZResource::GetSourceOutputHeader([[maybe_unused]] const std::string& prefix) @@ -312,13 +320,13 @@ offset_t Seg2Filespace(segptr_t segmentedAddress, uint32_t parentBaseAddress) uint32_t parentBaseOffset = GETSEGOFFSET(parentBaseAddress); if (parentBaseOffset > currentPtr) { - throw std::runtime_error( - StringHelper::Sprintf("\nSeg2Filespace: Segmented address is smaller than " - "'BaseAddress'. Maybe your 'BaseAddress' is wrong?\n" - "\t SegmentedAddress: 0x%08X\n" - "\t BaseAddress: 0x%08X\n", - segmentedAddress, parentBaseAddress)); + HANDLE_ERROR(WarningType::Always, + StringHelper::Sprintf( + "resource address 0x%08X is smaller than 'BaseAddress' 0x%08X", + segmentedAddress, parentBaseAddress), + "Maybe your 'BaseAddress' is wrong?"); } + currentPtr -= parentBaseOffset; } diff --git a/tools/ZAPD/ZAPD/ZResource.h b/tools/ZAPD/ZAPD/ZResource.h index ff35786fac..4dad398955 100644 --- a/tools/ZAPD/ZAPD/ZResource.h +++ b/tools/ZAPD/ZAPD/ZResource.h @@ -1,16 +1,15 @@ #pragma once -#include +#include #include #include -#include #include #include #include "Declaration.h" +#include "Utils/BinaryWriter.h" +#include "Utils/Directory.h" #include "tinyxml2.h" -#include - #define SEGMENT_SCENE 2 #define SEGMENT_ROOM 3 #define SEGMENT_KEEP 4 @@ -113,7 +112,7 @@ public: */ [[nodiscard]] virtual std::string GetDefaultName(const std::string& prefix) const; - virtual std::string GetSourceOutputCode(const std::string& prefix); + virtual void GetSourceOutputCode(const std::string& prefix); virtual std::string GetSourceOutputHeader(const std::string& prefix); virtual void CalcHash(); /** diff --git a/tools/ZAPD/ZAPD/ZRoom/Commands/SetMesh.cpp b/tools/ZAPD/ZAPD/ZRoom/Commands/SetMesh.cpp index d1c8abd5c5..69668c49c1 100644 --- a/tools/ZAPD/ZAPD/ZRoom/Commands/SetMesh.cpp +++ b/tools/ZAPD/ZAPD/ZRoom/Commands/SetMesh.cpp @@ -1,8 +1,10 @@ #include "SetMesh.h" -#include -#include + +#include "Globals.h" #include "Utils/BitConverter.h" +#include "Utils/Path.h" #include "Utils/StringHelper.h" +#include "WarningHandler.h" #include "ZBackground.h" #include "ZFile.h" #include "ZRoom/ZRoom.h" @@ -34,9 +36,8 @@ void SetMesh::ParseRawData() break; default: - throw std::runtime_error(StringHelper::Sprintf("Error in SetMesh::ParseRawData\n" - "\t Unknown meshHeaderType: %i\n", - meshHeaderType)); + HANDLE_ERROR(WarningType::InvalidExtractedData, + StringHelper::Sprintf("unknown meshHeaderType: %i", meshHeaderType), ""); } polyType->ParseRawData(); @@ -53,11 +54,9 @@ void SetMesh::DeclareReferences(const std::string& prefix) void GenDListDeclarations(ZRoom* zRoom, ZFile* parent, ZDisplayList* dList) { if (dList == nullptr) - { return; - } - std::string sourceOutput = dList->GetSourceOutputCode(zRoom->GetName()); + dList->DeclareReferences(zRoom->GetName()); for (ZDisplayList* otherDList : dList->otherDLists) GenDListDeclarations(zRoom, parent, otherDList); @@ -143,21 +142,17 @@ std::string PolygonDlist::GetBodySourceCode() const return bodyStr; } -std::string PolygonDlist::GetSourceOutputCode(const std::string& prefix) +void PolygonDlist::GetSourceOutputCode(const std::string& prefix) { std::string bodyStr = StringHelper::Sprintf("\n\t%s\n", GetBodySourceCode().c_str()); Declaration* decl = parent->GetDeclaration(rawDataIndex); - if (decl == nullptr) - { - DeclareVar(prefix, bodyStr); - } - else - { - decl->text = bodyStr; - } - return ""; + if (decl == nullptr) + DeclareVar(prefix, bodyStr); + else + decl->text = bodyStr; + } std::string PolygonDlist::GetSourceTypeName() const @@ -472,8 +467,8 @@ void PolygonType1::DeclareReferences(const std::string& prefix) break; default: - throw std::runtime_error(StringHelper::Sprintf( - "Error in PolygonType1::PolygonType1\n\t Unknown format: %i\n", format)); + HANDLE_ERROR(WarningType::InvalidExtractedData, + StringHelper::Sprintf("unknown format: %i", format), ""); break; } } @@ -582,9 +577,11 @@ void PolygonType2::DeclareReferences(const std::string& prefix) polyDListName = StringHelper::Sprintf("%s%s_%06X", prefix.c_str(), polyDlistType.c_str(), GETSEGOFFSET(start)); - parent->AddDeclarationArray(GETSEGOFFSET(start), DeclarationAlignment::Align4, - polyDLists.size() * polyDLists.at(0).GetRawDataSize(), - polyDlistType, polyDListName, polyDLists.size(), declaration); + Declaration* decl = parent->AddDeclarationArray( + GETSEGOFFSET(start), DeclarationAlignment::Align4, + polyDLists.size() * polyDLists.at(0).GetRawDataSize(), polyDlistType, polyDListName, + polyDLists.size(), declaration); + decl->forceArrayCnt = true; } parent->AddDeclaration(GETSEGOFFSET(end), DeclarationAlignment::Align4, 4, "s32", diff --git a/tools/ZAPD/ZAPD/ZRoom/Commands/SetMesh.h b/tools/ZAPD/ZAPD/ZRoom/Commands/SetMesh.h index 566711a9a7..9d9037417b 100644 --- a/tools/ZAPD/ZAPD/ZRoom/Commands/SetMesh.h +++ b/tools/ZAPD/ZAPD/ZRoom/Commands/SetMesh.h @@ -28,7 +28,7 @@ public: std::string GetBodySourceCode() const override; - std::string GetSourceOutputCode(const std::string& prefix) override; + void GetSourceOutputCode(const std::string& prefix) override; std::string GetSourceTypeName() const override; ZResourceType GetResourceType() const override; diff --git a/tools/ZAPD/ZAPD/ZRoom/Commands/SetRoomList.cpp b/tools/ZAPD/ZAPD/ZRoom/Commands/SetRoomList.cpp index 43c3968221..7027fa1f98 100644 --- a/tools/ZAPD/ZAPD/ZRoom/Commands/SetRoomList.cpp +++ b/tools/ZAPD/ZAPD/ZRoom/Commands/SetRoomList.cpp @@ -115,11 +115,9 @@ std::string RomFile::GetBodySourceCode() const return declaration; } -std::string RomFile::GetSourceOutputCode(const std::string& prefix) +void RomFile::GetSourceOutputCode(const std::string& prefix) { DeclareVar(prefix, GetBodySourceCode()); - - return ""; } std::string RomFile::GetSourceTypeName() const diff --git a/tools/ZAPD/ZAPD/ZRoom/Commands/SetRoomList.h b/tools/ZAPD/ZAPD/ZRoom/Commands/SetRoomList.h index 4fb2ced176..2ae48b68df 100644 --- a/tools/ZAPD/ZAPD/ZRoom/Commands/SetRoomList.h +++ b/tools/ZAPD/ZAPD/ZRoom/Commands/SetRoomList.h @@ -24,7 +24,7 @@ public: Declaration* DeclareVar(const std::string& prefix, const std::string& body) override; std::string GetBodySourceCode() const override; - std::string GetSourceOutputCode(const std::string& prefix) override; + void GetSourceOutputCode(const std::string& prefix) override; std::string GetSourceTypeName() const override; virtual ZResourceType GetResourceType() const override; diff --git a/tools/ZAPD/ZAPD/ZRoom/ZRoom.cpp b/tools/ZAPD/ZAPD/ZRoom/ZRoom.cpp index edc0cad029..c7e60d3592 100644 --- a/tools/ZAPD/ZAPD/ZRoom/ZRoom.cpp +++ b/tools/ZAPD/ZAPD/ZRoom/ZRoom.cpp @@ -40,6 +40,7 @@ #include "Utils/File.h" #include "Utils/Path.h" #include "Utils/StringHelper.h" +#include "WarningHandler.h" #include "ZBlob.h" #include "ZCutscene.h" #include "ZFile.h" @@ -123,10 +124,12 @@ void ZRoom::ParseXML(tinyxml2::XMLElement* reader) { hackMode = std::string(reader->Attribute("HackMode")); if (hackMode != "syotes_room") - throw std::runtime_error( - StringHelper::Sprintf("ZRoom::ParseXML: Fatal error in '%s'.\n" - "\t Invalid value for attribute 'HackMode': '%s'\n", - name.c_str(), hackMode.c_str())); + { + std::string headerError = StringHelper::Sprintf( + "invalid value found for 'HackMode' attribute: '%s'", hackMode.c_str()); + HANDLE_ERROR_RESOURCE(WarningType::InvalidAttributeValue, parent, this, rawDataIndex, + headerError, ""); + } } } @@ -392,14 +395,10 @@ size_t ZRoom::GetCommandSizeFromNeighbor(ZRoomCommand* cmd) return 0; } -std::string ZRoom::GetSourceOutputCode([[maybe_unused]] const std::string& prefix) +void ZRoom::GetSourceOutputCode([[maybe_unused]] const std::string& prefix) { - if (hackMode == "syotes_room") - return ""; - - DeclareVar(prefix, GetBodySourceCode()); - - return ""; + if (hackMode != "syotes_room") + DeclareVar(prefix, GetBodySourceCode()); } size_t ZRoom::GetRawDataSize() const diff --git a/tools/ZAPD/ZAPD/ZRoom/ZRoom.h b/tools/ZAPD/ZAPD/ZRoom/ZRoom.h index 0993a9d302..e837ec70ac 100644 --- a/tools/ZAPD/ZAPD/ZRoom/ZRoom.h +++ b/tools/ZAPD/ZAPD/ZRoom/ZRoom.h @@ -34,7 +34,7 @@ public: Declaration* DeclareVar(const std::string& prefix, const std::string& body) override; std::string GetBodySourceCode() const override; - std::string GetSourceOutputCode(const std::string& prefix) override; + void GetSourceOutputCode(const std::string& prefix) override; std::string GetDefaultName(const std::string& prefix) const override; size_t GetDeclarationSizeFromNeighbor(uint32_t declarationAddress); diff --git a/tools/ZAPD/ZAPD/ZScalar.cpp b/tools/ZAPD/ZAPD/ZScalar.cpp index 062fb0e079..7e4be4d57f 100644 --- a/tools/ZAPD/ZAPD/ZScalar.cpp +++ b/tools/ZAPD/ZAPD/ZScalar.cpp @@ -4,6 +4,7 @@ #include "Utils/BitConverter.h" #include "Utils/File.h" #include "Utils/StringHelper.h" +#include "WarningHandler.h" #include "ZFile.h" REGISTER_ZFILENODE(Scalar, ZScalar); @@ -207,8 +208,8 @@ void ZScalar::ParseRawData() scalarData.f64 = BitConverter::ToDoubleBE(rawData, rawDataIndex); break; case ZScalarType::ZSCALAR_NONE: - fprintf(stderr, "Warning in ZScalar: Invalid type. %d %s %d\n", (int32_t)scalarType, - __FILE__, __LINE__); + HANDLE_ERROR_RESOURCE(WarningType::InvalidAttributeValue, parent, this, rawDataIndex, + "invalid value found for 'Type' attribute", "Defaulting to ''"); break; } } diff --git a/tools/ZAPD/ZAPD/ZScalar.h b/tools/ZAPD/ZAPD/ZScalar.h index d269995cca..8f98f261d7 100644 --- a/tools/ZAPD/ZAPD/ZScalar.h +++ b/tools/ZAPD/ZAPD/ZScalar.h @@ -1,6 +1,6 @@ #pragma once -#include +#include #include #include #include "ZResource.h" diff --git a/tools/ZAPD/ZAPD/ZSkeleton.cpp b/tools/ZAPD/ZAPD/ZSkeleton.cpp index 84f00c8187..1a2f93ff7b 100644 --- a/tools/ZAPD/ZAPD/ZSkeleton.cpp +++ b/tools/ZAPD/ZAPD/ZSkeleton.cpp @@ -5,6 +5,7 @@ #include "Globals.h" #include "Utils/BitConverter.h" #include "Utils/StringHelper.h" +#include "WarningHandler.h" REGISTER_ZFILENODE(Skeleton, ZSkeleton); REGISTER_ZFILENODE(LimbTable, ZLimbTable); @@ -27,18 +28,19 @@ void ZSkeleton::ParseXML(tinyxml2::XMLElement* reader) type = ZSkeletonType::Curve; else if (skelTypeXml != "Normal") { - throw std::runtime_error(StringHelper::Sprintf("ZSkeleton::ParseXML: Error in '%s'.\n" - "\t Invalid Type found: '%s'.\n", - name.c_str(), skelTypeXml.c_str())); + HANDLE_ERROR_RESOURCE(WarningType::InvalidAttributeValue, parent, this, rawDataIndex, + "invalid value found for 'Type' attribute", ""); } std::string limbTypeXml = registeredAttributes.at("LimbType").value; limbType = ZLimb::GetTypeByAttributeName(limbTypeXml); if (limbType == ZLimbType::Invalid) { - throw std::runtime_error(StringHelper::Sprintf("ZSkeleton::ParseXML: Error in '%s'.\n" - "\t Invalid LimbType found: '%s'.\n", - name.c_str(), limbTypeXml.c_str())); + HANDLE_ERROR_RESOURCE( + WarningType::InvalidAttributeValue, parent, this, rawDataIndex, + StringHelper::Sprintf("invalid value '%s' found for 'LimbType' attribute", + limbTypeXml.c_str()), + "Defaulting to 'Standard'."); } } @@ -170,11 +172,9 @@ void ZLimbTable::ParseXML(tinyxml2::XMLElement* reader) limbType = ZLimb::GetTypeByAttributeName(limbTypeXml); if (limbType == ZLimbType::Invalid) { - fprintf(stderr, - "ZLimbTable::ParseXML: Warning in '%s'.\n" - "\t Invalid LimbType found: '%s'.\n" - "\t Defaulting to 'Standard'.\n", - name.c_str(), limbTypeXml.c_str()); + HANDLE_WARNING_RESOURCE(WarningType::InvalidAttributeValue, parent, this, rawDataIndex, + "invalid value found for 'LimbType' attribute.", + "Defaulting to 'Standard'."); limbType = ZLimbType::Standard; } diff --git a/tools/ZAPD/ZAPD/ZSymbol.cpp b/tools/ZAPD/ZAPD/ZSymbol.cpp index b24c3de4b0..eabfc2faae 100644 --- a/tools/ZAPD/ZAPD/ZSymbol.cpp +++ b/tools/ZAPD/ZAPD/ZSymbol.cpp @@ -1,6 +1,7 @@ #include "ZSymbol.h" #include "Utils/StringHelper.h" +#include "WarningHandler.h" #include "ZFile.h" REGISTER_ZFILENODE(Symbol, ZSymbol); @@ -20,11 +21,8 @@ void ZSymbol::ParseXML(tinyxml2::XMLElement* reader) if (typeXml == "") { - fprintf(stderr, - "ZSymbol::ParseXML: Warning in '%s'.\n" - "\t Missing 'Type' attribute in xml.\n" - "\t Defaulting to 'void*'.\n", - name.c_str()); + HANDLE_WARNING_RESOURCE(WarningType::MissingAttribute, parent, this, rawDataIndex, + "missing 'Type' attribute in ", "Defaulting to 'void*'."); type = "void*"; } else @@ -35,11 +33,8 @@ void ZSymbol::ParseXML(tinyxml2::XMLElement* reader) std::string typeSizeXml = registeredAttributes.at("TypeSize").value; if (typeSizeXml == "") { - fprintf(stderr, - "ZSymbol::ParseXML: Warning in '%s'.\n" - "\t Missing 'TypeSize' attribute in xml.\n" - "\t Defaulting to '4'.\n", - name.c_str()); + HANDLE_WARNING_RESOURCE(WarningType::MissingAttribute, parent, this, rawDataIndex, + "missing 'TypeSize' attribute in ", "Defaulting to '4'."); typeSize = 4; // Size of a word. } else @@ -58,7 +53,9 @@ void ZSymbol::ParseXML(tinyxml2::XMLElement* reader) if (registeredAttributes.at("Static").value == "On") { - fprintf(stderr, "A can't be marked as static.\n\t Disabling static\n"); + HANDLE_WARNING_RESOURCE(WarningType::InvalidAttributeValue, parent, this, rawDataIndex, + "a cannot be marked as static", + "Disabling static for this resource."); } staticConf = StaticConfig::Off; } diff --git a/tools/ZAPD/ZAPD/ZTexture.cpp b/tools/ZAPD/ZAPD/ZTexture.cpp index 33ee54d1bc..7bd31438b4 100644 --- a/tools/ZAPD/ZAPD/ZTexture.cpp +++ b/tools/ZAPD/ZAPD/ZTexture.cpp @@ -8,6 +8,7 @@ #include "Utils/Directory.h" #include "Utils/File.h" #include "Utils/Path.h" +#include "WarningHandler.h" REGISTER_ZFILENODE(Texture, ZTexture); @@ -57,17 +58,17 @@ void ZTexture::ParseXML(tinyxml2::XMLElement* reader) if (!StringHelper::HasOnlyDigits(widthXml)) { - throw std::runtime_error( - StringHelper::Sprintf("ZTexture::ParseXML: Error in %s\n" - "\t Value of 'Width' attribute has non-decimal digits: '%s'.\n", - name.c_str(), widthXml.c_str())); + std::string errorHeader = StringHelper::Sprintf( + "value of 'Width' attribute has non-decimal digits: '%s'", widthXml.c_str()); + HANDLE_ERROR_RESOURCE(WarningType::InvalidAttributeValue, parent, this, rawDataIndex, + errorHeader, ""); } if (!StringHelper::HasOnlyDigits(heightXml)) { - throw std::runtime_error( - StringHelper::Sprintf("ZTexture::ParseXML: Error in %s\n" - "\t Value of 'Height' attribute has non-decimal digits: '%s'.\n", - name.c_str(), heightXml.c_str())); + std::string errorHeader = StringHelper::Sprintf( + "value of 'Height' attribute has non-decimal digits: '%s'", heightXml.c_str()); + HANDLE_ERROR_RESOURCE(WarningType::InvalidAttributeValue, parent, this, rawDataIndex, + errorHeader, ""); } width = StringHelper::StrToL(widthXml); @@ -77,7 +78,10 @@ void ZTexture::ParseXML(tinyxml2::XMLElement* reader) format = GetTextureTypeFromString(formatStr); if (format == TextureType::Error) - throw std::runtime_error("Format " + formatStr + " is not supported!"); + { + HANDLE_ERROR_RESOURCE(WarningType::InvalidAttributeValue, parent, this, rawDataIndex, + "invalid value found for 'Format' attribute", ""); + } const auto& tlutOffsetAttr = registeredAttributes.at("TlutOffset"); if (tlutOffsetAttr.wasSet) @@ -90,10 +94,9 @@ void ZTexture::ParseXML(tinyxml2::XMLElement* reader) break; default: - throw std::runtime_error(StringHelper::Sprintf( - "ZTexture::ParseXML: Error in %s\n" - "\t 'TlutOffset' declared in non color-indexed (ci4 or ci8) texture.\n", - name.c_str())); + HANDLE_ERROR_RESOURCE(WarningType::InvalidXML, parent, this, rawDataIndex, + "'TlutOffset' declared in non color-indexed (ci4 or ci8) texture", + ""); break; } } @@ -102,10 +105,10 @@ void ZTexture::ParseXML(tinyxml2::XMLElement* reader) void ZTexture::ParseRawData() { if (rawDataIndex % 8 != 0) - fprintf(stderr, - "ZTexture::ParseXML: Warning in '%s'.\n" - "\t This texture is not 64-bit aligned.\n", - name.c_str()); + { + HANDLE_WARNING_RESOURCE(WarningType::NotImplemented, parent, this, rawDataIndex, + "this texture is not 64-bit aligned", ""); + } switch (format) { @@ -136,8 +139,11 @@ void ZTexture::ParseRawData() case TextureType::Palette8bpp: PrepareBitmapPalette8(); break; - default: - throw std::runtime_error("Format is not supported!"); + case TextureType::Error: + HANDLE_ERROR_RESOURCE(WarningType::InvalidAttributeValue, parent, this, rawDataIndex, + StringHelper::Sprintf("Invalid texture format", format), ""); + assert(!"TODO"); + break; } } @@ -375,8 +381,9 @@ void ZTexture::PrepareRawDataFromFile(const fs::path& pngFilePath) case TextureType::Palette8bpp: PrepareRawDataPalette8(pngFilePath); break; - default: - throw std::runtime_error("Format is not supported!"); + case TextureType::Error: + HANDLE_ERROR_PROCESS(WarningType::InvalidPNG, "Input PNG file has invalid format type", ""); + break; } } @@ -860,13 +867,9 @@ TextureType ZTexture::GetTextureTypeFromString(const std::string& str) else if (str == "rgb5a1") { texType = TextureType::RGBA16bpp; -#ifdef DEPRECATION_ON - fprintf(stderr, "ZTexture::GetTextureTypeFromString: Deprecation warning.\n" - "\t The texture format 'rgb5a1' is currently deprecated, and will be " - "removed in a future " - "version.\n" - "\t Use the format 'rgba16' instead.\n"); -#endif + HANDLE_WARNING(WarningType::Deprecated, + "the texture format 'rgb5a1' is currently deprecated", + "It will be removed in a future version. Use the format 'rgba16' instead."); } else if (str == "i4") texType = TextureType::Grayscale4bpp; @@ -883,7 +886,9 @@ TextureType ZTexture::GetTextureTypeFromString(const std::string& str) else if (str == "ci8") texType = TextureType::Palette8bpp; else - fprintf(stderr, "Encountered Unknown Texture format %s \n", str.c_str()); + // TODO: handle this case in a more coherent way + HANDLE_WARNING(WarningType::InvalidAttributeValue, + "invalid value found for 'Type' attribute", "Defaulting to ''."); return texType; } diff --git a/tools/ZAPD/ZAPD/ZTextureAnimation.cpp b/tools/ZAPD/ZAPD/ZTextureAnimation.cpp index 4332fcf1e5..698054fa87 100644 --- a/tools/ZAPD/ZAPD/ZTextureAnimation.cpp +++ b/tools/ZAPD/ZAPD/ZTextureAnimation.cpp @@ -2,8 +2,8 @@ * File: ZTextureAnimation.cpp * ZResources defined: ZTextureAnimation, ZTextureAnimationParams (XML declaration not supported for * the latter) - * Purpose: extracting texture animating structures from asset files Note: data type is exclusive to - * Majora's Mask + * Purpose: extracting texture animating structures from asset files + * Note: data type is exclusive to Majora's Mask * * Structure of data: * A texture animation consists of a main array of data of the form @@ -82,6 +82,7 @@ #include "Globals.h" #include "Utils/BitConverter.h" +#include "WarningHandler.h" #include "ZFile.h" #include "ZResource.h" #include "tinyxml2.h" @@ -115,7 +116,7 @@ void ZTextureAnimationParams::ExtractFromBinary(uint32_t nRawDataIndex) ParseRawData(); } -// Implemented by TextureScrollingParams only[ +// Implemented by TextureScrollingParams only void ZTextureAnimationParams::ExtractFromBinary([[maybe_unused]] uint32_t nRawDataIndex, [[maybe_unused]] int count) { @@ -217,19 +218,8 @@ void TextureColorChangingParams::ParseRawData() ((type == TextureAnimationParamsType::ColorChange) ? animLength : colorListCount); if (listLength == 0) - throw std::runtime_error(StringHelper::Sprintf( - "When processing file %s: in input binary file %s, offset 0x%06X:" - "\n\t" - "\033[97m" - "TextureColorChangingParams::ParseRawData:" - "\033[0m" - "\033[91m" - " error: " - "\033[0m" - "\033[97m" - "color list length cannot be 0\n" - "\033[0m", - Globals::Instance->inputPath.c_str(), parent->GetName().c_str(), rawDataIndex)); + HANDLE_ERROR_RESOURCE(WarningType::Always, parent, this, rawDataIndex, + "color list length cannot be 0", ""); primColorListAddress = BitConverter::ToUInt32BE(rawData, rawDataIndex + 4); envColorListAddress = BitConverter::ToUInt32BE(rawData, rawDataIndex + 8); @@ -378,20 +368,8 @@ void TextureCyclingParams::ParseRawData() cycleLength = BitConverter::ToUInt16BE(rawData, rawDataIndex); if (cycleLength == 0) - throw std::runtime_error( - StringHelper::Sprintf("When processing file %s: in input binary file %s, offset 0x%06X:" - "\n\t" - "\033[97m" - "TextureCyclingParams::ParseRawData:" - "\033[0m" - "\033[91m" - " error: " - "\033[0m" - "\033[97m" - "cycleLength cannot be 0\n" - "\033[0m", - Globals::Instance->inputPath.c_str(), parent->GetName().c_str(), - Seg2Filespace(rawDataIndex, 0))); + HANDLE_ERROR_RESOURCE(WarningType::Always, parent, this, rawDataIndex, + "cycle length cannot be 0", ""); textureListAddress = BitConverter::ToUInt32BE(rawData, rawDataIndex + 4); textureIndexListAddress = BitConverter::ToUInt32BE(rawData, rawDataIndex + 8); @@ -454,21 +432,12 @@ void TextureCyclingParams::DeclareReferences([[maybe_unused]] const std::string& { comment = " // Raw pointer, declare texture in XML to use proper symbol"; - fprintf(stderr, - "When processing file %s: in input binary file %s, offset 0x%06X:" - "\n\t" - "\033[97m" - "TextureCyclingParams::DeclareReferences:" - "\033[0m" - "\033[95m" - " warning: " - "\033[0m" - "\033[97m" - "TexCycle declared here points to unknown texture at address %s. " - "Please declare the texture in the XML to use the proper symbol.\n" - "\033[0m", - Globals::Instance->inputPath.c_str(), parent->GetName().c_str(), - Seg2Filespace(textureListAddress, parent->baseAddress), texName.c_str()); + auto msgHeader = StringHelper::Sprintf( + "TexCycle texture array declared here points to unknown texture at address %s", + texName.c_str()); + HANDLE_WARNING_RESOURCE( + WarningType::HardcodedPointer, parent, this, rawDataIndex, msgHeader, + "Please declare the texture in the XML to use the proper symbol."); } texturesBodyStr += StringHelper::Sprintf("\t%s,%s\n", texName.c_str(), comment.c_str()); } @@ -546,22 +515,14 @@ void ZTextureAnimation::ParseRawData() if ((type < 0) || (type > 6)) { - throw std::runtime_error(StringHelper::Sprintf( - "When processing file %s: in input binary file %s, offset 0x%06X:" - "\n\t" - "\033[97m" - "ZTextureAnimation::ParseRawData:" - "\033[0m" - "\033[91m" - " error: " - "\033[0m" - "\033[97m" - "unknown TextureAnimationParams type 0x%02X in TextureAnimation: entry reads\n\t{ " - "0x%02X, 0x%02X, 0x%08X }\n(type should be between " - "0x00 and 0x06)\n" - "\033[0m", - Globals::Instance->inputPath.c_str(), parent->GetName().c_str(), rawDataIndex, type, - currentEntry.segment, type, currentEntry.paramsPtr)); + HANDLE_ERROR_RESOURCE( + WarningType::Always, parent, this, rawDataIndex, + StringHelper::Sprintf( + "unknown TextureAnimationParams type 0x%02X in TextureAnimation", type), + StringHelper::Sprintf( + "Entry reads { 0x%02X, 0x%02X, 0x%08X } , but type should be " + "between 0x00 and 0x06 inclusive.", + currentEntry.segment, type, currentEntry.paramsPtr)); } if (currentEntry.segment <= 0) @@ -589,13 +550,24 @@ void ZTextureAnimation::DeclareReferences(const std::string& prefix) if (!parent->HasDeclaration(paramsOffset)) { ZTextureAnimationParams* params; - int count = 2; + int count; switch (entry.type) { case TextureAnimationParamsType::SingleScroll: - count = 1; - [[fallthrough]]; - case TextureAnimationParamsType::DualScroll: + if (true) + { + count = 1; + // The else now allows SingleScroll to fall through to params = ... without + // touching the code in the else block + } + else + { + // The contents of this block can only be run by jumping into it with the + // case label + [[fallthrough]]; + case TextureAnimationParamsType::DualScroll: + count = 2; + } params = new TextureScrollingParams(parent); params->ExtractFromBinary(paramsOffset, count); break; @@ -614,22 +586,12 @@ void ZTextureAnimation::DeclareReferences(const std::string& prefix) break; case TextureAnimationParamsType::Empty: - fprintf(stderr, - "When processing file %s: in input binary file %s: offset 0x%06X:" - "\n\t" - "\033[97m" - "ZTextureAnimation::DeclareReferences:" - "\033[0m" - "\033[95m" - " warning: " - "\033[0m" - "\033[97m" - "TextureAnimationParams entry has empty type (6), but params pointer " - "is not NULL. Params read\n\t\t" - "{ 0x%02X, 0x%02X, 0x%08X }\n" - "\033[0m", - Globals::Instance->inputPath.c_str(), parent->GetName().c_str(), - rawDataIndex, entry.segment, (int)entry.type, entry.paramsPtr); + HANDLE_WARNING_RESOURCE( + WarningType::InvalidExtractedData, parent, this, rawDataIndex, + "TextureAnimationParams entry has empty type (6), but params pointer is " + "not NULL", + StringHelper::Sprintf("Params read { 0x%02X, 0x%02X, 0x%08X } .", + entry.segment, (int)entry.type, entry.paramsPtr)); return; default: // Because GCC is worried this could happen diff --git a/tools/ZAPD/ZAPD/ZVector.cpp b/tools/ZAPD/ZAPD/ZVector.cpp index c940b0b0de..a5a059e354 100644 --- a/tools/ZAPD/ZAPD/ZVector.cpp +++ b/tools/ZAPD/ZAPD/ZVector.cpp @@ -6,6 +6,7 @@ #include "Utils/BitConverter.h" #include "Utils/File.h" #include "Utils/StringHelper.h" +#include "WarningHandler.h" #include "ZFile.h" REGISTER_ZFILENODE(Vector, ZVector); @@ -86,20 +87,18 @@ std::string ZVector::GetSourceTypeName() const return "Vec3i"; else { - std::string output = StringHelper::Sprintf( - "Encountered unsupported vector type: %d dimensions, %s type", dimensions, + std::string msgHeader = StringHelper::Sprintf( + "encountered unsupported vector type: %d dimensions, %s type", dimensions, ZScalar::MapScalarTypeToOutputType(scalarType).c_str()); - if (Globals::Instance->verbosity >= VerbosityLevel::VERBOSITY_DEBUG) - printf("%s\n", output.c_str()); - - throw std::runtime_error(output); + HANDLE_ERROR_RESOURCE(WarningType::NotImplemented, parent, this, rawDataIndex, msgHeader, + ""); } } std::string ZVector::GetBodySourceCode() const { - std::string body; + std::string body = ""; for (size_t i = 0; i < scalars.size(); i++) { diff --git a/tools/ZAPD/ZAPD/ZVector.h b/tools/ZAPD/ZAPD/ZVector.h index d1a738968d..a50d3e8083 100644 --- a/tools/ZAPD/ZAPD/ZVector.h +++ b/tools/ZAPD/ZAPD/ZVector.h @@ -1,6 +1,6 @@ #pragma once -#include +#include #include #include #include "ZResource.h" diff --git a/tools/ZAPD/ZAPD/ZVtx.h b/tools/ZAPD/ZAPD/ZVtx.h index 018a1d4a9a..511048791d 100644 --- a/tools/ZAPD/ZAPD/ZVtx.h +++ b/tools/ZAPD/ZAPD/ZVtx.h @@ -1,6 +1,6 @@ #pragma once -#include +#include #include #include #include "ZResource.h" diff --git a/tools/ZAPD/ZAPD/any/any/zlib.static.txt b/tools/ZAPD/ZAPD/any/any/zlib.static.txt new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tools/ZAPD/ZAPD/packages.config b/tools/ZAPD/ZAPD/packages.config index a7f2abbd29..c387aaed70 100644 --- a/tools/ZAPD/ZAPD/packages.config +++ b/tools/ZAPD/ZAPD/packages.config @@ -1,8 +1,9 @@  - - + + + \ No newline at end of file diff --git a/tools/ZAPD/ZAPDUtils/Color3b.h b/tools/ZAPD/ZAPDUtils/Color3b.h index 7e59f6b7f7..507c099f52 100644 --- a/tools/ZAPD/ZAPDUtils/Color3b.h +++ b/tools/ZAPD/ZAPDUtils/Color3b.h @@ -1,6 +1,6 @@ #pragma once -#include +#include struct Color3b { diff --git a/tools/ZAPD/ZAPDUtils/StrHash.h b/tools/ZAPD/ZAPDUtils/StrHash.h index 68d22b9cd6..c611bdddad 100644 --- a/tools/ZAPD/ZAPDUtils/StrHash.h +++ b/tools/ZAPD/ZAPDUtils/StrHash.h @@ -1,8 +1,8 @@ #pragma once -#include -#include -#include +#include +#include +#include typedef uint32_t strhash; diff --git a/tools/ZAPD/ZAPDUtils/Utils/BinaryReader.cpp b/tools/ZAPD/ZAPDUtils/Utils/BinaryReader.cpp index a4cf782294..35412781cc 100644 --- a/tools/ZAPD/ZAPDUtils/Utils/BinaryReader.cpp +++ b/tools/ZAPD/ZAPDUtils/Utils/BinaryReader.cpp @@ -1,5 +1,5 @@ #include "BinaryReader.h" -#include +#include #include #include "Stream.h" @@ -89,7 +89,7 @@ float BinaryReader::ReadSingle() stream->Read((char*)&result, sizeof(float)); - if (isnan(result)) + if (std::isnan(result)) throw std::runtime_error("BinaryReader::ReadSingle(): Error reading stream"); return result; @@ -100,7 +100,7 @@ double BinaryReader::ReadDouble() double result = NAN; stream->Read((char*)&result, sizeof(double)); - if (isnan(result)) + if (std::isnan(result)) throw std::runtime_error("BinaryReader::ReadDouble(): Error reading stream"); return result; diff --git a/tools/ZAPD/ZAPDUtils/Utils/BitConverter.h b/tools/ZAPD/ZAPDUtils/Utils/BitConverter.h index 5cca35b319..e672b97c23 100644 --- a/tools/ZAPD/ZAPDUtils/Utils/BitConverter.h +++ b/tools/ZAPD/ZAPDUtils/Utils/BitConverter.h @@ -1,7 +1,7 @@ #pragma once +#include #include -#include #include class BitConverter diff --git a/tools/ZAPD/ZAPDUtils/Utils/File.h b/tools/ZAPD/ZAPDUtils/Utils/File.h index e3f8880cb6..084152f794 100644 --- a/tools/ZAPD/ZAPDUtils/Utils/File.h +++ b/tools/ZAPD/ZAPDUtils/Utils/File.h @@ -1,8 +1,8 @@ #pragma once +#include #include #include -#include #include #include #include "Directory.h" diff --git a/tools/ZAPD/ZAPDUtils/Utils/MemoryStream.cpp b/tools/ZAPD/ZAPDUtils/Utils/MemoryStream.cpp index 6c27399d6c..6e85c59a04 100644 --- a/tools/ZAPD/ZAPDUtils/Utils/MemoryStream.cpp +++ b/tools/ZAPD/ZAPDUtils/Utils/MemoryStream.cpp @@ -1,5 +1,5 @@ #include "MemoryStream.h" -#include +#include #ifndef _MSC_VER #define memcpy_s(dest, destSize, source, sourceSize) memcpy(dest, source, destSize) diff --git a/tools/ZAPD/ZAPDUtils/Utils/Path.h b/tools/ZAPD/ZAPDUtils/Utils/Path.h index 496a0ae13c..0f7ef27431 100644 --- a/tools/ZAPD/ZAPDUtils/Utils/Path.h +++ b/tools/ZAPD/ZAPDUtils/Utils/Path.h @@ -18,13 +18,13 @@ public: static std::string GetFileName(const fs::path& input) { // https://en.cppreference.com/w/cpp/filesystem/path/filename - return input.filename(); + return input.filename().string(); }; static std::string GetFileNameWithoutExtension(const fs::path& input) { // https://en.cppreference.com/w/cpp/filesystem/path/stem - return input.stem(); + return input.stem().string(); }; static std::string GetFileNameExtension(const std::string& input) diff --git a/tools/ZAPD/ZAPDUtils/Utils/Stream.h b/tools/ZAPD/ZAPDUtils/Utils/Stream.h index 060e23cd27..e73a9a70d0 100644 --- a/tools/ZAPD/ZAPDUtils/Utils/Stream.h +++ b/tools/ZAPD/ZAPDUtils/Utils/Stream.h @@ -1,7 +1,7 @@ #pragma once +#include #include -#include enum class SeekOffsetType { diff --git a/tools/ZAPD/ZAPDUtils/Utils/StringHelper.h b/tools/ZAPD/ZAPDUtils/Utils/StringHelper.h index 74607ce3a2..0b0d676429 100644 --- a/tools/ZAPD/ZAPDUtils/Utils/StringHelper.h +++ b/tools/ZAPD/ZAPDUtils/Utils/StringHelper.h @@ -1,18 +1,12 @@ #pragma once #include +#include #include #include -#include #include #include -#ifdef _MSC_VER -#define __PRETTY_FUNCTION__ __FUNCSIG__ -#elif not defined(__GNUC__) -#define __PRETTY_FUNCTION__ __func__ -#endif - class StringHelper { public: @@ -111,4 +105,10 @@ public: { return std::all_of(str.begin(), str.end(), ::isdigit); } + + static bool IEquals(const std::string& a, const std::string& b) + { + return std::equal(a.begin(), a.end(), b.begin(), b.end(), + [](char a, char b) { return tolower(a) == tolower(b); }); + } }; diff --git a/tools/ZAPD/ZAPDUtils/Utils/vt.h b/tools/ZAPD/ZAPDUtils/Utils/vt.h new file mode 100644 index 0000000000..23f424442b --- /dev/null +++ b/tools/ZAPD/ZAPDUtils/Utils/vt.h @@ -0,0 +1,45 @@ +#ifndef VT_H +#define VT_H + +// clang-format off +#define VT_COLOR_BLACK 0 +#define VT_COLOR_RED 1 +#define VT_COLOR_GREEN 2 +#define VT_COLOR_YELLOW 3 +#define VT_COLOR_BLUE 4 +#define VT_COLOR_PURPLE 5 +#define VT_COLOR_CYAN 6 +#define VT_COLOR_WHITE 7 +#define VT_COLOR_LIGHTGRAY 8 +#define VT_COLOR_DARKGRAY 9 + +#define VT_COLOR_FOREGROUND 3 +#define VT_COLOR_BACKGROUND 4 +// clang-format on + +#define VT_COLOR_EXPAND0(type, color) #type #color +#define VT_COLOR_EXPAND1(type, color) VT_COLOR_EXPAND0(type, color) +#define VT_COLOR(type, color) VT_COLOR_EXPAND1(VT_COLOR_##type, VT_COLOR_##color) + +#define VT_ESC "\x1b" +#define VT_CSI "[" +#define VT_CUP(x, y) VT_ESC VT_CSI y ";" x "H" +#define VT_ED(n) VT_ESC VT_CSI #n "J" +#define VT_SGR(n) VT_ESC VT_CSI n "m" + +// Add more macros if necessary +#define VT_COL(back, fore) VT_SGR(VT_COLOR(BACKGROUND, back) ";" VT_COLOR(FOREGROUND, fore)) +#define VT_FGCOL(color) VT_SGR(VT_COLOR(FOREGROUND, color)) +#define VT_BGCOL(color) VT_SGR(VT_COLOR(BACKGROUND, color)) + +// Bold +#define VT_BOLD "1" + +// Bold color support +#define VT_BOLD_FGCOL(color) VT_SGR(VT_BOLD ";" VT_COLOR(FOREGROUND, color)) +#define VT_BOLD_BGCOL(color) VT_SGR(VT_BOLD ";" VT_COLOR(BACKGROUND, color)) + +#define VT_RST VT_SGR("") +#define VT_CLS VT_ED(2) + +#endif diff --git a/tools/ZAPD/ZAPDUtils/Vec2f.h b/tools/ZAPD/ZAPDUtils/Vec2f.h index 9d4beeb465..73e9259a89 100644 --- a/tools/ZAPD/ZAPDUtils/Vec2f.h +++ b/tools/ZAPD/ZAPDUtils/Vec2f.h @@ -1,6 +1,6 @@ #pragma once -#include +#include struct Vec2f { diff --git a/tools/ZAPD/ZAPDUtils/Vec3f.h b/tools/ZAPD/ZAPDUtils/Vec3f.h index 4bfbb3c254..d6e9c5568f 100644 --- a/tools/ZAPD/ZAPDUtils/Vec3f.h +++ b/tools/ZAPD/ZAPDUtils/Vec3f.h @@ -1,6 +1,6 @@ #pragma once -#include +#include struct Vec3f { diff --git a/tools/ZAPD/ZAPDUtils/Vec3s.h b/tools/ZAPD/ZAPDUtils/Vec3s.h index 23e4673b84..05816eddb5 100644 --- a/tools/ZAPD/ZAPDUtils/Vec3s.h +++ b/tools/ZAPD/ZAPDUtils/Vec3s.h @@ -1,6 +1,6 @@ #pragma once -#include +#include struct Vec3s { diff --git a/tools/ZAPD/ZAPDUtils/ZAPDUtils.vcxproj b/tools/ZAPD/ZAPDUtils/ZAPDUtils.vcxproj index 803cdb5f01..0a09666e03 100644 --- a/tools/ZAPD/ZAPDUtils/ZAPDUtils.vcxproj +++ b/tools/ZAPD/ZAPDUtils/ZAPDUtils.vcxproj @@ -43,7 +43,7 @@ StaticLibrary true v142 - Unicode + MultiByte Application @@ -116,6 +116,8 @@ true _DEBUG;_CONSOLE;%(PreprocessorDefinitions) true + MultiThreadedDebug + Default Console @@ -155,6 +157,7 @@ + diff --git a/tools/ZAPD/ZAPDUtils/ZAPDUtils.vcxproj.filters b/tools/ZAPD/ZAPDUtils/ZAPDUtils.vcxproj.filters index 48117c845f..4765ad5d45 100644 --- a/tools/ZAPD/ZAPDUtils/ZAPDUtils.vcxproj.filters +++ b/tools/ZAPD/ZAPDUtils/ZAPDUtils.vcxproj.filters @@ -19,6 +19,9 @@ {e047919d-7186-49ca-b115-e48fbb5c8743} + + {3de9dd46-0dfd-4d48-9f20-9f24e5b80fe0} + @@ -74,5 +77,8 @@ Source Files\Utils + + Source Files\Libraries + \ No newline at end of file diff --git a/tools/ZAPD/docs/zapd_warning_example.png b/tools/ZAPD/docs/zapd_warning_example.png new file mode 100644 index 0000000000000000000000000000000000000000..a001c64d6a51428bed50d6b599e8082d8effa3ee GIT binary patch literal 47521 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 OFgtn5sOW_2t^Wa$9d#Z6 literal 0 HcmV?d00001