From 9ca4ec7604bada9f26d7500ba5e4c508b4afd283 Mon Sep 17 00:00:00 2001 From: EllipticEllipsis <73679967+EllipticEllipsis@users.noreply.github.com> Date: Thu, 16 Dec 2021 04:05:29 +0000 Subject: [PATCH] ZAPD fixes in `sys_initial_check`, update subrepos (#507) * git subrepo pull --force tools/ZAPD 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" * git subrepo pull tools/asm-differ --force subrepo: subdir: "tools/asm-differ" merged: "70c33cc12" upstream: origin: "https://github.com/simonlindholm/asm-differ.git" branch: "main" commit: "70c33cc12" git-subrepo: version: "0.4.3" origin: "https://github.com/ingydotnet/git-subrepo.git" commit: "2f68596" * git subrepo pull (merge) tools/z64compress --force subrepo: subdir: "tools/z64compress" merged: "ac5b1a0d0" upstream: origin: "https://github.com/z64me/z64compress.git" branch: "main" commit: "ac5b1a0d0" git-subrepo: version: "0.4.3" origin: "https://github.com/ingydotnet/git-subrepo.git" commit: "2f68596" * Use defines for texture sizes in sys_initial_check * Update extract_assets.py * Add null check * git subrepo pull --force tools/ZAPD subrepo: subdir: "tools/ZAPD" merged: "50242eca9" upstream: origin: "https://github.com/zeldaret/ZAPD.git" branch: "master" commit: "50242eca9" git-subrepo: version: "0.4.3" origin: "https://github.com/ingydotnet/git-subrepo.git" commit: "2f68596" --- extract_assets.py | 31 +- src/code/sys_initial_check.c | 21 +- tools/ZAPD/.gitrepo | 4 +- tools/ZAPD/ExporterTest/ExporterTest.vcxproj | 5 +- tools/ZAPD/ExporterTest/Main.cpp | 8 +- tools/ZAPD/ExporterTest/Makefile | 2 +- tools/ZAPD/ExporterTest/RoomExporter.cpp | 40 +- tools/ZAPD/ExporterTest/TextureExporter.h | 2 +- tools/ZAPD/Jenkinsfile | 58 +-- tools/ZAPD/Makefile | 24 +- tools/ZAPD/README.md | 54 ++- 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 | 59 +-- 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 | 27 +- 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/Makefile | 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 tools/asm-differ/.gitrepo | 4 +- tools/asm-differ/diff.py | 304 +++++++----- tools/z64compress/.gitrepo | 4 +- tools/z64compress/src/main.c | 3 +- tools/z64compress/src/rom.c | 85 ++-- 82 files changed, 1646 insertions(+), 757 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/extract_assets.py b/extract_assets.py index 066c1de3c..a7825af1f 100755 --- a/extract_assets.py +++ b/extract_assets.py @@ -1,8 +1,10 @@ #!/usr/bin/env python3 -import argparse, json, os, signal, time +import argparse, json, os, signal, time, colorama from multiprocessing import Pool, Event, Manager +colorama.init(); + EXTRACTED_ASSETS_NAMEFILE = ".extracted-assets.json" def SignalHandler(sig, frame): @@ -15,10 +17,10 @@ def ExtractFile(xmlPath, outputPath, outputSourcePath): # Don't extract if another file wasn't extracted properly. return - execStr = "tools/ZAPD/ZAPD.out e -eh -i %s -b baserom/ -o %s -osf %s -gsf 1 -rconf tools/ZAPDConfigs/MM/Config.xml" % (xmlPath, outputPath, outputSourcePath) + execStr = f"tools/ZAPD/ZAPD.out e -eh -i {xmlPath} -b baserom/ -o {outputPath} -osf {outputSourcePath} -gsf 1 -rconf tools/ZAPDConfigs/MM/Config.xml {ZAPDArgs}" if globalUnaccounted: - execStr += " -wu" + execStr += " -Wunaccounted" print(execStr) exitValue = os.system(execStr) @@ -72,8 +74,27 @@ def main(): parser.add_argument("-t", "--threads", help="Number of cpu cores to extract with.") parser.add_argument("-f", "--force", help="Force the extraction of every xml instead of checking the touched ones.", action="store_true") parser.add_argument("-u", "--unaccounted", help="Enables ZAPD unaccounted detector warning system.", action="store_true") + parser.add_argument("-Z", help="Pass the argument on to ZAPD, e.g. `-ZWunaccounted` to warn about unaccounted blocks in XMLs. Each argument should be passed separately, *without* the leading dash.", metavar="ZAPD_ARG", action="append") args = parser.parse_args() + global ZAPDArgs; + ZAPDArgs = ""; + if args.Z is not None: + badZAPDArg = False; + for i in range(len(args.Z)): + z = args.Z[i] + if z[0] == '-': + print(f"{colorama.Fore.LIGHTRED_EX}error{colorama.Fore.RESET}: argument \"{z}\" starts with \"-\", which is not supported.", file=os.sys.stderr); + badZAPDArg = True; + else: + args.Z[i] = "-" + z; + + if badZAPDArg: + exit(1); + + ZAPDArgs = " ".join(args.Z); + print("Using extra ZAPD arguments: " + ZAPDArgs); + global mainAbort mainAbort = Event() manager = Manager() @@ -88,7 +109,7 @@ def main(): if asset_path is not None: fullPath = os.path.join("assets", "xml", asset_path + ".xml") if not os.path.exists(fullPath): - print(f"Error. File {fullPath} doesn't exists.", file=os.sys.stderr) + print(f"Error. File {fullPath} does not exist.", file=os.sys.stderr) exit(1) initializeWorker(mainAbort, args.unaccounted, extractedAssetsTracker, manager) @@ -107,7 +128,7 @@ def main(): numCores = int(args.threads or 0) if numCores <= 0: numCores = 1 - print("Extracting assets with " + str(numCores) + " CPU cores.") + print("Extracting assets with " + str(numCores) + " CPU core" + ("s" if numCores > 1 else "") + ".") with Pool(numCores, initializer=initializeWorker, initargs=(mainAbort, args.unaccounted, extractedAssetsTracker, manager)) as p: p.map(ExtractFunc, xmlFiles) diff --git a/src/code/sys_initial_check.c b/src/code/sys_initial_check.c index 84871255b..ec61d07b5 100644 --- a/src/code/sys_initial_check.c +++ b/src/code/sys_initial_check.c @@ -10,8 +10,16 @@ #include "misc/locerrmsg/locerrmsg.h" #include "misc/memerrmsg/memerrmsg.h" -#define SIZEOF_LOCERRMSG (sizeof(gNotDesignedForSystemErrorTex)) -#define SIZEOF_MEMERRMSG (sizeof(gExpansionPakNotInstalledErrorTex) + sizeof(gSeeInstructionBookletErrorTex)) +#define LOCERRMSG_WIDTH 208 +#define LOCERRMSG_HEIGHT 16 +#define NUMBEROF_LOCERRMSGS 1 + +#define MEMERRMSG_WIDTH 128 +#define MEMERRMSG_HEIGHT 37 +#define NUMBEROF_MEMERRMSGS 2 + +#define SIZEOF_LOCERRMSG (LOCERRMSG_WIDTH * LOCERRMSG_HEIGHT / 2 * NUMBEROF_LOCERRMSGS) +#define SIZEOF_MEMERRMSG (MEMERRMSG_WIDTH * MEMERRMSG_HEIGHT / 2 * NUMBEROF_MEMERRMSGS) // Address with enough room after to load either of the error message image files before the fault screen buffer at the // end of RDRAM @@ -64,9 +72,9 @@ void Check_ClearRGBA16(u16* buffer) { void Check_DrawExpansionPakErrorMessage(void) { DmaMgr_SendRequest0(CHECK_ERRMSG_STATIC_SEGMENT, SEGMENT_ROM_START(memerrmsg), SEGMENT_SIZE(memerrmsg)); Check_ClearRGBA16((u16*)FAULT_FB_ADDRESS); - Check_DrawI4Texture((u16*)FAULT_FB_ADDRESS, 96, 71, 128, 37, CHECK_ERRMSG_STATIC_SEGMENT); - Check_DrawI4Texture((u16*)FAULT_FB_ADDRESS, 96, 127, 128, 37, - CHECK_ERRMSG_STATIC_SEGMENT + sizeof(gExpansionPakNotInstalledErrorTex)); + Check_DrawI4Texture((u16*)FAULT_FB_ADDRESS, 96, 71, MEMERRMSG_WIDTH, MEMERRMSG_HEIGHT, CHECK_ERRMSG_STATIC_SEGMENT); + Check_DrawI4Texture((u16*)FAULT_FB_ADDRESS, 96, 127, MEMERRMSG_WIDTH, MEMERRMSG_HEIGHT, + CHECK_ERRMSG_STATIC_SEGMENT + MEMERRMSG_WIDTH * MEMERRMSG_HEIGHT / 2); osWritebackDCacheAll(); osViSwapBuffer((u16*)FAULT_FB_ADDRESS); osViBlack(false); @@ -78,7 +86,8 @@ void Check_DrawExpansionPakErrorMessage(void) { void Check_DrawRegionLockErrorMessage(void) { DmaMgr_SendRequest0(CHECK_ERRMSG_STATIC_SEGMENT, SEGMENT_ROM_START(locerrmsg), SEGMENT_SIZE(locerrmsg)); Check_ClearRGBA16((u16*)FAULT_FB_ADDRESS); - Check_DrawI4Texture((u16*)FAULT_FB_ADDRESS, 56, 112, 208, 16, CHECK_ERRMSG_STATIC_SEGMENT); + Check_DrawI4Texture((u16*)FAULT_FB_ADDRESS, 56, 112, LOCERRMSG_WIDTH, LOCERRMSG_HEIGHT, + CHECK_ERRMSG_STATIC_SEGMENT); osWritebackDCacheAll(); osViSwapBuffer((u16*)FAULT_FB_ADDRESS); osViBlack(false); diff --git a/tools/ZAPD/.gitrepo b/tools/ZAPD/.gitrepo index 94da913d0..a942638a2 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 = 346df1bbc8ca21673fe63d884b6734c23a4d9f4b + commit = 50242eca96a9c36fd84f438bab24548e73b42303 + parent = 744955732b1fc9e8a835c62440aa97799cfe8940 method = merge cmdver = 0.4.3 diff --git a/tools/ZAPD/ExporterTest/ExporterTest.vcxproj b/tools/ZAPD/ExporterTest/ExporterTest.vcxproj index a709a3509..839d45102 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 4f683a1ba..07fdbeece 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/Makefile b/tools/ZAPD/ExporterTest/Makefile index a2bfa9a81..42033b7c0 100644 --- a/tools/ZAPD/ExporterTest/Makefile +++ b/tools/ZAPD/ExporterTest/Makefile @@ -1,7 +1,7 @@ # Only used for standalone compilation, usually inherits these from the main makefile CXXFLAGS ?= -Wall -Wextra -O2 -g -std=c++17 -SRC_DIRS := $(shell find -type d -not -path "*build*") +SRC_DIRS := $(shell find . -type d -not -path "*build*") CPP_FILES := $(foreach dir,$(SRC_DIRS),$(wildcard $(dir)/*.cpp)) H_FILES := $(foreach dir,$(SRC_DIRS),$(wildcard $(dir)/*.h)) diff --git a/tools/ZAPD/ExporterTest/RoomExporter.cpp b/tools/ZAPD/ExporterTest/RoomExporter.cpp index c4a6844fb..6c5552d8f 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 ffe6001dc..41c4e79be 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 051e5f998..8db69a1e3 100644 --- a/tools/ZAPD/Jenkinsfile +++ b/tools/ZAPD/Jenkinsfile @@ -7,7 +7,7 @@ pipeline { // Non-parallel ZAPD stage stage('Build ZAPD') { steps { - sh 'make -j' + sh 'make -j WERROR=1' } } @@ -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/Makefile b/tools/ZAPD/Makefile index 737b85ecf..2b47a8039 100644 --- a/tools/ZAPD/Makefile +++ b/tools/ZAPD/Makefile @@ -6,6 +6,7 @@ DEPRECATION_ON ?= 1 DEBUG ?= 0 COPYCHECK_ARGS ?= LLD ?= 0 +WERROR ?= 0 # Use clang++ if available, else use g++ ifeq ($(shell command -v clang++ >/dev/null 2>&1; echo $$?),0) @@ -23,14 +24,16 @@ ifneq ($(DEBUG),0) CXXFLAGS += -g3 -DDEVELOPMENT -D_DEBUG COPYCHECK_ARGS += --devel DEPRECATION_ON = 0 -else +endif + +ifneq ($(WERROR),0) CXXFLAGS += -Werror endif ifeq ($(OPTIMIZATION_ON),0) OPTFLAGS := -O0 else - OPTFLAGS := -O2 -march=native -mtune=native + OPTFLAGS := -O2 endif ifneq ($(ASAN),0) @@ -53,10 +56,23 @@ ifneq ($(LLD),0) endif UNAME := $(shell uname) +UNAMEM := $(shell uname -m) ifneq ($(UNAME), Darwin) - LDFLAGS += -Wl,-export-dynamic -lstdc++fs + LDFLAGS += -Wl,-export-dynamic -lstdc++fs + EXPORTERS := -Wl,--whole-archive ExporterTest/ExporterTest.a -Wl,--no-whole-archive +else + EXPORTERS := -Wl,-force_load ExporterTest/ExporterTest.a + ifeq ($(UNAMEM),arm64) + ifeq ($(shell brew list libpng > /dev/null 2>&1; echo $$?),0) + LDFLAGS += -L $(shell brew --prefix)/lib + INC += -I $(shell brew --prefix)/include + else + $(error Please install libpng via Homebrew) + endif + endif endif + ZAPD_SRC_DIRS := $(shell find ZAPD -type d) SRC_DIRS = $(ZAPD_SRC_DIRS) lib/tinyxml2 @@ -115,4 +131,4 @@ ZAPDUtils: # Linking ZAPD.out: $(O_FILES) lib/libgfxd/libgfxd.a ExporterTest ZAPDUtils - $(CXX) $(CXXFLAGS) $(O_FILES) lib/libgfxd/libgfxd.a ZAPDUtils/ZAPDUtils.a -Wl,--whole-archive ExporterTest/ExporterTest.a -Wl,--no-whole-archive $(LDFLAGS) $(OUTPUT_OPTION) + $(CXX) $(CXXFLAGS) $(O_FILES) lib/libgfxd/libgfxd.a ZAPDUtils/ZAPDUtils.a $(EXPORTERS) $(LDFLAGS) $(OUTPUT_OPTION) diff --git a/tools/ZAPD/README.md b/tools/ZAPD/README.md index e2764b627..44d26b8b3 100644 --- a/tools/ZAPD/README.md +++ b/tools/ZAPD/README.md @@ -16,6 +16,14 @@ In a Debian/Ubuntu based environment, those could be installed with the followin sudo apt install libpng-dev ``` +On a Mac, you will need to install libpng with Homebrew or MacPorts; we currently only support Homebrew. You can run + +```bash +brew install libpng +``` + +to install it via Homebrew. + ### Building #### Linux / *nix @@ -109,11 +117,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 d2a86ffcc..be06b0bea 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 138524a4c..b7bb0d30d 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 f197493a4..ae29ba28f 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 036e4a5cb..528a09d25 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 265f1af24..19e193f12 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 137e326c3..8accb6b4e 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 298ca0262..fd2ed06dc 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 000000000..e69de29bb diff --git a/tools/ZAPD/ZAPD/Overlays/ZOverlay.cpp b/tools/ZAPD/ZAPD/Overlays/ZOverlay.cpp index a842a7e7d..510de19ab 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 000000000..29f8352a7 --- /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 000000000..bb0360a81 --- /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 c74d28cbe..431abb73b 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 2d3050ed4..b8a7daaac 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 9242f657a..adb0ae7c9 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 e5b69d3ef..2c04b4ff8 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 b1ba3a669..ebfb13ee7 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 46a04d732..b78a8edfd 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 4125f239f..0ed1eb747 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 c1f078820..6812bfaee 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 86623b511..d7a7feff1 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 f9c0bf7d6..1dfa46b1d 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 237481079..ba8fe8963 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 cbb6a78b0..8e901e307 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 41b7de37f..44b108d6c 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 63c568422..bdb7374fe 100644 --- a/tools/ZAPD/ZAPD/ZDisplayList.cpp +++ b/tools/ZAPD/ZAPD/ZDisplayList.cpp @@ -3,6 +3,7 @@ #include #include #include +#include #include #include "Globals.h" @@ -11,6 +12,7 @@ #include "Utils/File.h" #include "Utils/Path.h" #include "Utils/StringHelper.h" +#include "WarningHandler.h" #include "gfxd.h" REGISTER_ZFILENODE(DList, ZDisplayList); @@ -105,7 +107,7 @@ void ZDisplayList::ParseF3DZEX(F3DZEXOpcode opcode, uint64_t data, int32_t i, switch (opcode) { case F3DZEXOpcode::G_NOOP: - sprintf(line, "gsDPNoOpTag(0x%08lX),", data & 0xFFFFFFFF); + sprintf(line, "gsDPNoOpTag(0x%08" PRIX64 "),", data & 0xFFFFFFFF); break; case F3DZEXOpcode::G_DL: Opcode_G_DL(data, prefix, line); @@ -220,7 +222,7 @@ void ZDisplayList::ParseF3DZEX(F3DZEXOpcode opcode, uint64_t data, int32_t i, break; case F3DZEXOpcode::G_POPMTX: { - sprintf(line, "gsSPPopMatrix(%li),", data); + sprintf(line, "gsSPPopMatrix(%" PRIi64 "),", data); } break; case F3DZEXOpcode::G_LOADTLUT: @@ -297,7 +299,7 @@ void ZDisplayList::ParseF3DEX(F3DEXOpcode opcode, uint64_t data, const std::stri switch (opcode) { case F3DEXOpcode::G_NOOP: - sprintf(line, "gsDPNoOpTag(0x%08lX),", data & 0xFFFFFFFF); + sprintf(line, "gsDPNoOpTag(0x%08" PRIX64 "),", data & 0xFFFFFFFF); break; case F3DEXOpcode::G_VTX: Opcode_G_VTX(data, line); @@ -445,11 +447,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); @@ -686,20 +689,20 @@ void ZDisplayList::Opcode_G_DL(uint64_t data, const std::string& prefix, char* l if (pp != 0) { if (!Globals::Instance->HasSegment(segNum)) - sprintf(line, "gsSPBranchList(0x%08lX),", data & 0xFFFFFFFF); + sprintf(line, "gsSPBranchList(0x%08" PRIX64 "),", data & 0xFFFFFFFF); else if (dListDecl != nullptr) sprintf(line, "gsSPBranchList(%s),", dListDecl->varName.c_str()); else - sprintf(line, "gsSPBranchList(%sDlist0x%06lX),", prefix.c_str(), GETSEGOFFSET(data)); + sprintf(line, "gsSPBranchList(%sDlist0x%06" PRIX64 "),", prefix.c_str(), GETSEGOFFSET(data)); } else { if (!Globals::Instance->HasSegment(segNum)) - sprintf(line, "gsSPDisplayList(0x%08lX),", data & 0xFFFFFFFF); + sprintf(line, "gsSPDisplayList(0x%08" PRIX64 "),", data & 0xFFFFFFFF); else if (dListDecl != nullptr) sprintf(line, "gsSPDisplayList(%s),", dListDecl->varName.c_str()); else - sprintf(line, "gsSPDisplayList(%sDlist0x%06lX),", prefix.c_str(), GETSEGOFFSET(data)); + sprintf(line, "gsSPDisplayList(%sDlist0x%06" PRIX64 "),", prefix.c_str(), GETSEGOFFSET(data)); } // if (segNum == 8 || segNum == 9 || segNum == 10 || segNum == 11 || segNum == 12 || segNum == @@ -707,9 +710,9 @@ void ZDisplayList::Opcode_G_DL(uint64_t data, const std::string& prefix, char* l if (!Globals::Instance->HasSegment(segNum)) { if (pp != 0) - sprintf(line, "gsSPBranchList(0x%08lX),", data & 0xFFFFFFFF); + sprintf(line, "gsSPBranchList(0x%08" PRIX64 "),", data & 0xFFFFFFFF); else - sprintf(line, "gsSPDisplayList(0x%08lX),", data & 0xFFFFFFFF); + sprintf(line, "gsSPDisplayList(0x%08" PRIX64 "),", data & 0xFFFFFFFF); } else { @@ -941,7 +944,7 @@ void ZDisplayList::Opcode_G_SETTIMG(uint64_t data, const std::string& prefix, ch sprintf(texStr, "%sTex_%06X", prefix.c_str(), texAddress); else { - sprintf(texStr, "0x%08lX", data & 0xFFFFFFFF); + sprintf(texStr, "0x%08" PRIX64, data & 0xFFFFFFFF); } sprintf(line, "gsDPSetTextureImage(%s, %s, %i, %s),", fmtTbl[fmt], sizTbl[siz], www + 1, @@ -1522,11 +1525,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 +1735,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 +1748,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 +1773,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 +1796,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 +1859,6 @@ std::string ZDisplayList::GetSourceOutputCode(const std::string& prefix) } } } - - if (parent != nullptr) - return ""; - - return sourceOutput; } std::string ZDisplayList::ProcessLegacy(const std::string& prefix) @@ -1899,10 +1890,10 @@ std::string ZDisplayList::ProcessLegacy(const std::string& prefix) } auto end = std::chrono::steady_clock::now(); - auto diff = std::chrono::duration_cast(end - start).count(); + int64_t diff = std::chrono::duration_cast(end - start).count(); if (Globals::Instance->verbosity >= VerbosityLevel::VERBOSITY_DEBUG && diff > 5) - printf("F3DOP: 0x%02X, TIME: %lims\n", opcode, diff); + printf("F3DOP: 0x%02X, TIME: %" PRIi64 "ms\n", opcode, diff); sourceOutput += line; @@ -1983,7 +1974,7 @@ bool ZDisplayList::TextureGenCheck(int32_t texWidth, int32_t texHeight, uint32_t } else { - // Try to find a non-external file (ie, one we are actually extracting) + // Try to find a non-external file (i.e., one we are actually extracting) // which has the same segment number we are looking for. for (auto& otherFile : Globals::Instance->cfg.segmentRefFiles[segmentNumber]) { diff --git a/tools/ZAPD/ZAPD/ZDisplayList.h b/tools/ZAPD/ZAPD/ZDisplayList.h index d53866675..96808315d 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 7cbfeba88..77387fc72 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 9de6950e4..7918d5f59 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 77c66871f..a47615d6c 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 4a95c5b91..e19513db3 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 cb811f4c3..2dfe6d5ea 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 ff35786fa..4dad39895 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 d1c8abd5c..69668c49c 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 566711a9a..9d9037417 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 43c396822..7027fa1f9 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 4fb2ced17..2ae48b68d 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 edc0cad02..d47a2e75c 100644 --- a/tools/ZAPD/ZAPD/ZRoom/ZRoom.cpp +++ b/tools/ZAPD/ZAPD/ZRoom/ZRoom.cpp @@ -2,7 +2,9 @@ #include #include #include +#include #include + #include "Commands/EndMarker.h" #include "Commands/SetActorCutsceneList.h" #include "Commands/SetActorList.h" @@ -40,6 +42,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 +126,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, ""); + } } } @@ -256,9 +261,9 @@ void ZRoom::ParseRawData() if (Globals::Instance->profile) { auto end = std::chrono::steady_clock::now(); - auto diff = std::chrono::duration_cast(end - start).count(); + int64_t diff = std::chrono::duration_cast(end - start).count(); if (diff > 50) - printf("OP: %s, TIME: %lims\n", cmd->GetCommandCName().c_str(), diff); + printf("OP: %s, TIME: %" PRIi64 "ms\n", cmd->GetCommandCName().c_str(), diff); } cmd->cmdIndex = currentIndex; @@ -392,14 +397,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 0993a9d30..e837ec70a 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 062fb0e07..7e4be4d57 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 d269995cc..8f98f261d 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 84f00c818..1a2f93ff7 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 b24c3de4b..eabfc2faa 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 33ee54d1b..7bd31438b 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 4332fcf1e..698054fa8 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 c940b0b0d..a5a059e35 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 d1a738968..a50d3e808 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 018a1d4a9..511048791 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 000000000..e69de29bb diff --git a/tools/ZAPD/ZAPD/packages.config b/tools/ZAPD/ZAPD/packages.config index a7f2abbd2..c387aaed7 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 7e59f6b7f..507c099f5 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/Makefile b/tools/ZAPD/ZAPDUtils/Makefile index aef678031..e8941ed77 100644 --- a/tools/ZAPD/ZAPDUtils/Makefile +++ b/tools/ZAPD/ZAPDUtils/Makefile @@ -1,7 +1,7 @@ # Only used for standalone compilation, usually inherits these from the main makefile CXXFLAGS ?= -Wall -Wextra -O2 -g -std=c++17 -SRC_DIRS := $(shell find -type d -not -path "*build*") +SRC_DIRS := $(shell find . -type d -not -path "*build*") CPP_FILES := $(foreach dir,$(SRC_DIRS),$(wildcard $(dir)/*.cpp)) H_FILES := $(foreach dir,$(SRC_DIRS),$(wildcard $(dir)/*.h)) diff --git a/tools/ZAPD/ZAPDUtils/StrHash.h b/tools/ZAPD/ZAPDUtils/StrHash.h index 68d22b9cd..c611bddda 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 a4cf78229..35412781c 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 5cca35b31..e672b97c2 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 e3f8880cb..084152f79 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 6c27399d6..6e85c59a0 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 496a0ae13..0f7ef2743 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 060e23cd2..e73a9a70d 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 74607ce3a..0b0d67642 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 000000000..23f424442 --- /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 9d4beeb46..73e9259a8 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 4bfbb3c25..d6e9c5568 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 23e4673b8..05816eddb 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 803cdb5f0..0a09666e0 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 48117c845..4765ad5d4 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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∾LlTLen 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 GLK6Uy9d#Z6 literal 0 HcmV?d00001 diff --git a/tools/asm-differ/.gitrepo b/tools/asm-differ/.gitrepo index d3e4418c9..241c0ad6b 100644 --- a/tools/asm-differ/.gitrepo +++ b/tools/asm-differ/.gitrepo @@ -6,7 +6,7 @@ [subrepo] remote = https://github.com/simonlindholm/asm-differ.git branch = main - commit = f30d43aceba6291aa3c08b7317b0458b6d734321 - parent = e977dfeb374c6de4076ca9a0f44ba5a6a701d849 + commit = 70c33cc125666495f84f8c7bee60d3158b4b1164 + parent = 62341d8db0c29d1d4f0c360196e428309ac373b6 method = merge cmdver = 0.4.3 diff --git a/tools/asm-differ/diff.py b/tools/asm-differ/diff.py index d7ac3f344..2ec117a12 100755 --- a/tools/asm-differ/diff.py +++ b/tools/asm-differ/diff.py @@ -640,6 +640,7 @@ class Text: class TableMetadata: headers: Tuple[Text, ...] current_score: int + max_score: int previous_score: Optional[int] @@ -832,6 +833,7 @@ class JsonFormatter(Formatter): for h, name in zip(meta.headers, ("base", "current", "previous")) } output["current_score"] = meta.current_score + output["max_score"] = meta.max_score if meta.previous_score is not None: output["previous_score"] = meta.previous_score output_rows: List[Dict[str, Any]] = [] @@ -1184,8 +1186,9 @@ def parse_elf_data_references(data: bytes) -> List[Tuple[int, int, str]]: assert len(symtab_sections) == 1 symtab = sections[symtab_sections[0]] - text_sections = [i for i in range(e_shnum) if sec_names[i] == b".text"] - assert len(text_sections) == 1 + text_sections = [i for i in range(e_shnum) if sec_names[i] == b".text" and sections[i].sh_size != 0] + if len(text_sections) != 1: + return [] text_section = text_sections[0] ret: List[Tuple[int, int, str]] = [] @@ -1327,11 +1330,23 @@ def dump_binary( (objdump_flags + flags2, project.myimg, None), ) +DATA_POOL_PLACEHOLDER = "DATA_POOL_PLACEHOLDER-OFFSET_{}" +DATA_POOL_PLACEHOLDER_PATTERN = re.compile( + r"DATA_POOL_PLACEHOLDER-OFFSET_([a-zA-z0-9]+)" +) -class DifferenceNormalizer: +# Example: "ldr r4, [pc, #56] ; (4c )" +ARM32_LOAD_POOL_PATTERN = r"(ldr\s+r([0-9]|1[0-3]),\s+\[pc,.*;\s*)(\([a-fA-F0-9]+.*\))" + + +# The base class is a no-op. +class AsmProcessor: def __init__(self, config: Config) -> None: self.config = config + def process_reloc(self, row: str, prev: str) -> str: + return prev + def normalize(self, mnemonic: str, row: str) -> str: """This should be called exactly once for each line.""" arch = self.config.arch @@ -1342,9 +1357,143 @@ class DifferenceNormalizer: def _normalize_arch_specific(self, mnemonic: str, row: str) -> str: return row + + def post_process(self, lines: List["Line"]) -> None: + return -class DifferenceNormalizerAArch64(DifferenceNormalizer): +class AsmProcessorMIPS(AsmProcessor): + def process_reloc(self, row: str, prev: str) -> str: + arch = self.config.arch + if "R_MIPS_NONE" in row: + # GNU as emits no-op relocations immediately after real ones when + # assembling with -mabi=64. Return without trying to parse 'imm' as an + # integer. + return prev + before, imm, after = parse_relocated_line(prev) + repl = row.split()[-1] + if imm != "0": + # MIPS uses relocations with addends embedded in the code as immediates. + # If there is an immediate, show it as part of the relocation. Ideally + # we'd show this addend in both %lo/%hi, but annoyingly objdump's output + # doesn't include enough information to pair up %lo's and %hi's... + # TODO: handle unambiguous cases where all addends for a symbol are the + # same, or show "+???". + mnemonic = prev.split()[0] + if ( + mnemonic in arch.instructions_with_address_immediates + and not imm.startswith("0x") + ): + imm = "0x" + imm + repl += "+" + imm if int(imm, 0) > 0 else imm + if "R_MIPS_LO16" in row: + repl = f"%lo({repl})" + elif "R_MIPS_HI16" in row: + # Ideally we'd pair up R_MIPS_LO16 and R_MIPS_HI16 to generate a + # correct addend for each, but objdump doesn't give us the order of + # the relocations, so we can't find the right LO16. :( + repl = f"%hi({repl})" + elif "R_MIPS_26" in row: + # Function calls + pass + elif "R_MIPS_PC16" in row: + # Branch to glabel. This gives confusing output, but there's not much + # we can do here. + pass + else: + assert False, f"unknown relocation type '{row}' for line '{prev}'" + return before + repl + after + + +class AsmProcessorPPC(AsmProcessor): + def process_reloc(self, row: str, prev: str) -> str: + arch = self.config.arch + assert any( + r in row for r in ["R_PPC_REL24", "R_PPC_ADDR16", "R_PPC_EMB_SDA21"] + ), f"unknown relocation type '{row}' for line '{prev}'" + before, imm, after = parse_relocated_line(prev) + repl = row.split()[-1] + if "R_PPC_REL24" in row: + # function calls + pass + elif "R_PPC_ADDR16_HI" in row: + # absolute hi of addr + repl = f"{repl}@h" + elif "R_PPC_ADDR16_HA" in row: + # adjusted hi of addr + repl = f"{repl}@ha" + elif "R_PPC_ADDR16_LO" in row: + # lo of addr + repl = f"{repl}@l" + elif "R_PPC_ADDR16" in row: + # 16-bit absolute addr + if "+0x7" in repl: + # remove the very large addends as they are an artifact of (label-_SDA(2)_BASE_) + # computations and are unimportant in a diff setting. + if int(repl.split("+")[1], 16) > 0x70000000: + repl = repl.split("+")[0] + elif "R_PPC_EMB_SDA21" in row: + # small data area + pass + return before + repl + after + + +class AsmProcessorARM32(AsmProcessor): + def process_reloc(self, row: str, prev: str) -> str: + arch = self.config.arch + before, imm, after = parse_relocated_line(prev) + repl = row.split()[-1] + return before + repl + after + + def _normalize_arch_specific(self, mnemonic: str, row: str) -> str: + if self.config.ignore_addr_diffs: + row = self._normalize_bl(mnemonic, row) + row = self._normalize_data_pool(row) + return row + + def _normalize_bl(self, mnemonic: str, row: str) -> str: + if mnemonic != "bl": + return row + + row, _ = split_off_address(row) + return row + "" + + def _normalize_data_pool(self, row: str) -> str: + pool_match = re.search(ARM32_LOAD_POOL_PATTERN, row) + if pool_match: + offset = pool_match.group(3).split(" ")[0][1:] + repl = DATA_POOL_PLACEHOLDER.format(offset) + return pool_match.group(1) + repl + return row + + def post_process(self, lines: List["Line"]) -> None: + lines_by_line_number = {} + for line in lines: + lines_by_line_number[line.line_num] = line + for line in lines: + reloc_match = re.search( + DATA_POOL_PLACEHOLDER_PATTERN, line.normalized_original + ) + if reloc_match is None: + continue + + # Get value at relocation + reloc = reloc_match.group(0) + line_number = re.search( + DATA_POOL_PLACEHOLDER_PATTERN, reloc).group(1) + line_original = lines_by_line_number[int(line_number, 16)].original + value = line_original.split()[1] + + # Replace relocation placeholder with value + replaced = re.sub( + DATA_POOL_PLACEHOLDER_PATTERN, + f"={value} ({line_number})", + line.normalized_original, + ) + line.original = replaced + + +class AsmProcessorAArch64(AsmProcessor): def __init__(self, config: Config) -> None: super().__init__(config) self._adrp_pair_registers: Set[str] = set() @@ -1394,23 +1543,6 @@ class DifferenceNormalizerAArch64(DifferenceNormalizer): return row -class DifferenceNormalizerARM32(DifferenceNormalizer): - def __init__(self, config: Config) -> None: - super().__init__(config) - - def _normalize_arch_specific(self, mnemonic: str, row: str) -> str: - if self.config.ignore_addr_diffs: - row = self._normalize_bl(mnemonic, row) - return row - - def _normalize_bl(self, mnemonic: str, row: str) -> str: - if mnemonic != "bl": - return row - - row, _ = split_off_address(row) - return row + "" - - @dataclass class ArchSettings: name: str @@ -1420,14 +1552,15 @@ class ArchSettings: re_sprel: Pattern[str] re_large_imm: Pattern[str] re_imm: Pattern[str] + re_reloc: Pattern[str] branch_instructions: Set[str] instructions_with_address_immediates: Set[str] forbidden: Set[str] = field(default_factory=lambda: set(string.ascii_letters + "_")) arch_flags: List[str] = field(default_factory=list) branch_likely_instructions: Set[str] = field(default_factory=set) - difference_normalizer: Type[DifferenceNormalizer] = DifferenceNormalizer + proc: Type[AsmProcessor] = AsmProcessor big_endian: Optional[bool] = True - + delay_slot_instructions: Set[str] = field(default_factory=set) MIPS_BRANCH_LIKELY_INSTRUCTIONS = { "beql", @@ -1543,10 +1676,13 @@ MIPS_SETTINGS = ArchSettings( re_sprel=re.compile(r"(?<=,)([0-9]+|0x[0-9a-f]+)\(sp\)"), re_large_imm=re.compile(r"-?[1-9][0-9]{2,}|-?0x[0-9a-f]{3,}"), re_imm=re.compile(r"(\b|-)([0-9]+|0x[0-9a-fA-F]+)\b(?!\(sp)|%(lo|hi)\([^)]*\)"), + re_reloc=re.compile(r"R_MIPS_"), arch_flags=["-m", "mips:4300"], branch_likely_instructions=MIPS_BRANCH_LIKELY_INSTRUCTIONS, branch_instructions=MIPS_BRANCH_INSTRUCTIONS, instructions_with_address_immediates=MIPS_BRANCH_INSTRUCTIONS.union({"jal", "j"}), + delay_slot_instructions=MIPS_BRANCH_INSTRUCTIONS.union({"j", "jal", "jr", "jalr"}), + proc=AsmProcessorMIPS, ) MIPSEL_SETTINGS = replace(MIPS_SETTINGS, name="mipsel", big_endian=False) @@ -1566,9 +1702,10 @@ ARM32_SETTINGS = ArchSettings( re_sprel=re.compile(r"sp, #-?(0x[0-9a-fA-F]+|[0-9]+)\b"), re_large_imm=re.compile(r"-?[1-9][0-9]{2,}|-?0x[0-9a-f]{3,}"), re_imm=re.compile(r"(? Tuple[str, str, str]: return before, imm, after -def process_mips_reloc(row: str, prev: str, arch: ArchSettings) -> str: - if "R_MIPS_NONE" in row: - # GNU as emits no-op relocations immediately after real ones when - # assembling with -mabi=64. Return without trying to parse 'imm' as an - # integer. - return prev - before, imm, after = parse_relocated_line(prev) - repl = row.split()[-1] - if imm != "0": - # MIPS uses relocations with addends embedded in the code as immediates. - # If there is an immediate, show it as part of the relocation. Ideally - # we'd show this addend in both %lo/%hi, but annoyingly objdump's output - # doesn't include enough information to pair up %lo's and %hi's... - # TODO: handle unambiguous cases where all addends for a symbol are the - # same, or show "+???". - mnemonic = prev.split()[0] - if ( - mnemonic in arch.instructions_with_address_immediates - and not imm.startswith("0x") - ): - imm = "0x" + imm - repl += "+" + imm if int(imm, 0) > 0 else imm - if "R_MIPS_LO16" in row: - repl = f"%lo({repl})" - elif "R_MIPS_HI16" in row: - # Ideally we'd pair up R_MIPS_LO16 and R_MIPS_HI16 to generate a - # correct addend for each, but objdump doesn't give us the order of - # the relocations, so we can't find the right LO16. :( - repl = f"%hi({repl})" - elif "R_MIPS_26" in row: - # Function calls - pass - elif "R_MIPS_PC16" in row: - # Branch to glabel. This gives confusing output, but there's not much - # we can do here. - pass - else: - assert False, f"unknown relocation type '{row}' for line '{prev}'" - return before + repl + after - - -def process_ppc_reloc(row: str, prev: str) -> str: - assert any( - r in row for r in ["R_PPC_REL24", "R_PPC_ADDR16", "R_PPC_EMB_SDA21"] - ), f"unknown relocation type '{row}' for line '{prev}'" - before, imm, after = parse_relocated_line(prev) - repl = row.split()[-1] - if "R_PPC_REL24" in row: - # function calls - pass - elif "R_PPC_ADDR16_HI" in row: - # absolute hi of addr - repl = f"{repl}@h" - elif "R_PPC_ADDR16_HA" in row: - # adjusted hi of addr - repl = f"{repl}@ha" - elif "R_PPC_ADDR16_LO" in row: - # lo of addr - repl = f"{repl}@l" - elif "R_PPC_ADDR16" in row: - # 16-bit absolute addr - if "+0x7" in repl: - # remove the very large addends as they are an artifact of (label-_SDA(2)_BASE_) - # computations and are unimportant in a diff setting. - if int(repl.split("+")[1], 16) > 0x70000000: - repl = repl.split("+")[0] - elif "R_PPC_EMB_SDA21" in row: - # small data area - pass - return before + repl + after - - -def process_arm_reloc(row: str, prev: str, arch: ArchSettings) -> str: - before, imm, after = parse_relocated_line(prev) - repl = row.split()[-1] - return before + repl + after - - def pad_mnemonic(line: str) -> str: if "\t" not in line: return line @@ -1741,7 +1803,7 @@ class Line: def process(dump: str, config: Config) -> List[Line]: arch = config.arch - normalizer = arch.difference_normalizer(config) + processor = arch.proc(config) skip_next = False source_lines = [] source_filename = None @@ -1783,7 +1845,7 @@ def process(dump: str, config: Config) -> List[Line]: ) break - if not re.match(r"^ +[0-9a-f]+:\t", row): + if not re.match(r"^\s+[0-9a-f]+:\s+", row): # This regex is conservative, and assumes the file path does not contain "weird" # characters like colons, tabs, or angle brackets. if re.match( @@ -1797,10 +1859,11 @@ def process(dump: str, config: Config) -> List[Line]: m_comment = re.search(arch.re_comment, row) comment = m_comment[0] if m_comment else None row = re.sub(arch.re_comment, "", row) + line_num_str = row.split(":")[0] row = row.rstrip() tabs = row.split("\t") row = "\t".join(tabs[2:]) - line_num = eval_line_num(tabs[0].strip()) + line_num = eval_line_num(line_num_str.strip()) if line_num in data_refs: refs = data_refs[line_num] @@ -1835,20 +1898,13 @@ def process(dump: str, config: Config) -> List[Line]: while i < len(lines): reloc_row = lines[i] - if "R_AARCH64_" in reloc_row: - # TODO: handle relocation - pass - elif "R_MIPS_" in reloc_row: - original = process_mips_reloc(reloc_row, original, arch) - elif "R_PPC_" in reloc_row: - original = process_ppc_reloc(reloc_row, original) - elif "R_ARM_" in reloc_row: - original = process_arm_reloc(reloc_row, original, arch) + if re.search(arch.re_reloc, reloc_row): + original = processor.process_reloc(reloc_row, original) else: break i += 1 - normalized_original = normalizer.normalize(mnemonic, original) + normalized_original = processor.normalize(mnemonic, original) scorable_line = normalized_original if not config.score_stack_differences: @@ -1904,6 +1960,7 @@ def process(dump: str, config: Config) -> List[Line]: elif stop_after_delay_slot: break + processor.post_process(output) return output @@ -2123,8 +2180,15 @@ class OutputLine: class Diff: lines: List[OutputLine] score: int + max_score: int +def trim_nops(lines: List[Line], arch: ArchSettings) -> List[Line]: + lines = lines[:] + while lines and lines[-1].mnemonic == "nop" and (len(lines) == 1 or lines[-2].mnemonic not in arch.delay_slot_instructions): + lines.pop() + return lines + def do_diff(lines1: List[Line], lines2: List[Line], config: Config) -> Diff: if config.show_source: import cxxfilt @@ -2152,8 +2216,12 @@ def do_diff(lines1: List[Line], lines2: List[Line], config: Config) -> Diff: btset.add(bt) sc(str(bt)) + lines1 = trim_nops(lines1, arch) + lines2 = trim_nops(lines2, arch) + diffed_lines = diff_lines(lines1, lines2, config.algorithm) score = score_diff_lines(diffed_lines, config) + max_score = len(lines1) * config.penalty_deletion line_num_base = -1 line_num_offset = 0 @@ -2385,7 +2453,7 @@ def do_diff(lines1: List[Line], lines2: List[Line], config: Config) -> Diff: ) output = output[config.skip_lines :] - return Diff(lines=output, score=score) + return Diff(lines=output, score=score, max_score=max_score) def chunk_diff_lines( @@ -2461,6 +2529,7 @@ def align_diffs( Text(f"{padding}PREVIOUS ({old_diff.score})"), ), current_score=new_diff.score, + max_score=new_diff.max_score, previous_score=old_diff.score, ) old_chunks = chunk_diff_lines(old_diff.lines) @@ -2504,6 +2573,7 @@ def align_diffs( Text(f"{padding}CURRENT ({new_diff.score})"), ), current_score=new_diff.score, + max_score=new_diff.max_score, previous_score=None, ) diff_lines = [(line, line) for line in new_diff.lines] diff --git a/tools/z64compress/.gitrepo b/tools/z64compress/.gitrepo index 9507f81ce..905b330d9 100644 --- a/tools/z64compress/.gitrepo +++ b/tools/z64compress/.gitrepo @@ -6,7 +6,7 @@ [subrepo] remote = https://github.com/z64me/z64compress.git branch = main - commit = 98ef0ac25f7adb47235c839838e29496d3546917 - parent = 69cc19eea8c30e50e280f400e8cf06fe66efca60 + commit = ac5b1a0d0b23346362a68a107da3478227f8e703 + parent = 33e3aa92181a0543826adc10a5f3304d60b391dd method = merge cmdver = 0.4.3 diff --git a/tools/z64compress/src/main.c b/tools/z64compress/src/main.c index 71d1d2040..bd044059d 100644 --- a/tools/z64compress/src/main.c +++ b/tools/z64compress/src/main.c @@ -221,7 +221,7 @@ wow_main } } - fprintf(printer, "welcome to z64compress 1.0.1 \n"); + fprintf(printer, "welcome to z64compress 1.0.2 \n"); if (argc <= 1) { @@ -356,7 +356,6 @@ wow_main ARG_ZERO_TEST(Ain , "--in" ); ARG_ZERO_TEST(Aout , "--out" ); - ARG_ZERO_TEST(Amb , "--mb" ); ARG_ZERO_TEST(Acodec, "--codec"); #undef ARG_ZERO_TEST diff --git a/tools/z64compress/src/rom.c b/tools/z64compress/src/rom.c index decf8ca1d..008597b52 100644 --- a/tools/z64compress/src/rom.c +++ b/tools/z64compress/src/rom.c @@ -68,7 +68,9 @@ for (dma = rom->dma; (unsigned)(dma - rom->dma) < rom->dma_num; ++dma) #define PROGRESS_A_B (int)(dma - rom->dma), rom->dma_num -#define ALIGN16(x) (((x) + 0xF) & ~0xF) +#define ALIGN(x, n) (((x) + ((n)-1)) & ~((n)-1)) +#define ALIGN16(x) ALIGN(x, 16) +#define ALIGN8MB(x) ALIGN(x, 8 * 0x100000) /* * @@ -936,7 +938,7 @@ void rom_compress(struct rom *rom, int mb, int numThreads, bool matching) cache = rom->cache; - if (compsz > rom->data_sz || mb <= 0) + if (compsz > rom->data_sz || mb < 0) die("invalid mb argument %d", mb); /* get encoding functions */ @@ -1057,31 +1059,13 @@ void rom_compress(struct rom *rom, int mb, int numThreads, bool matching) /* sort by original start, ascending */ DMASORT(rom, sortfunc_dma_start_ascend); - if (matching) - { - /* fill the entire (compressed) rom space with 00010203...FF... - in order to match retail rom padding */ - unsigned char n = 0; // will intentionally overflow - for (unsigned int j = 0; j < compsz; j++, n++) - { - rom->data[j] = n; - } - } - else - { - /* zero the entire (compressed) rom space */ - memset(rom->data, 0, compsz); - } - - /* go through dma table, injecting compressed files */ + /* determine physical addresses for each segment */ comp_total = 0; DMA_FOR_EACH { - unsigned char *dst; char *fn = dma->compname; unsigned int sz; unsigned int sz16; - fprintf(printer, "\r""injecting file %d/%d: ", PROGRESS_A_B); if (dma->deleted) continue; @@ -1114,9 +1098,6 @@ void rom_compress(struct rom *rom, int mb, int numThreads, bool matching) if (sz16 & 15) sz16 += 16 - (sz16 & 15); - /* put the files in */ - dst = rom->data + comp_total; - dma->Pstart = comp_total; if (dma->compress) { @@ -1130,26 +1111,66 @@ void rom_compress(struct rom *rom, int mb, int numThreads, bool matching) dma->Pend = 0; comp_total += sz16; - if (dma->Pend > compsz) + if (mb != 0 && dma->Pend > compsz) die("ran out of compressed rom space"); + } + + /* adaptive final size */ + if (mb == 0) + compsz = ALIGN8MB(comp_total); + + if (matching) + { + /* fill the entire (compressed) rom space with 00010203...FF... + in order to match retail rom padding */ + unsigned char n = 0; /* will intentionally overflow */ + for (unsigned int j = 0; j < compsz; j++, n++) + { + rom->data[j] = n; + } + } + else + { + /* zero the entire (compressed) rom space */ + memset(rom->data, 0, compsz); + } + + /* inject compressed files */ + comp_total = 0; + DMA_FOR_EACH + { + unsigned char *dst; + char *fn = dma->compname; + unsigned int sz; + fprintf(printer, "\r""injecting file %d/%d: ", PROGRESS_A_B); + + if (dma->deleted) + continue; + + dst = rom->data + dma->Pstart; /* external cached file logic */ if (cache) { + /* skip entries that don't reference compressed files */ + if (!fn) + continue; + /* load file into rom at offset */ dst = file_load_into(cache_codec, fn, &sz, dst); } - /* otherwise, a simple memcpy */ else { memcpy(dst, dma->compbuf, dma->compSz); - if (matching) - { - // since matching rom padding is not zero but file padding is zero, - // fill file padding space with zeros - memset(dst + dma->compSz, 0, ALIGN16(dma->compSz) - dma->compSz); - } + sz = dma->compSz; + } + + if (matching) + { + /* since matching rom padding is not zero but file padding is zero, + fill file padding space with zeros */ + memset(dst + sz, 0, ALIGN16(sz) - sz); } } fprintf(printer, "\r""injecting file %d/%d: ", dma_num, dma_num);