Merge pull request #17 from Yanis42/feature_daf

[Feature] Introduce Assets Fixer
This commit is contained in:
Yanis42
2022-12-06 21:46:38 +01:00
committed by GitHub
9 changed files with 343 additions and 30 deletions

View File

@@ -194,12 +194,13 @@ setup:
python3 fixbaserom.py
python3 extract_baserom.py
python3 extract_assets.py -j$(N_THREADS)
python3 tools/daf/daf.py -a -p ./
test: $(ROM)
$(EMULATOR) $(EMU_FLAGS) $<
.PHONY: all clean setup test distclean assetclean
.PHONY: all clean setup test distclean assetclean daf
#### Various Recipes ####

View File

@@ -55,10 +55,6 @@ typedef struct {
/* 0x4 */ Vec3s* bgCamFuncData; // s16 data grouped in threes (ex. Vec3s), is usually of type `BgCamFuncData`, but can be a list of points of type `Vec3s` for crawlspaces
} BgCamInfo; // size = 0x8
// ZAPD compatibility typedefs
// TODO: Remove when ZAPD adds support for them
typedef BgCamInfo CamData;
// The structure used for all instances of s16 data from `BgCamInfo` with the exception of crawlspaces.
// See `Camera_Subj4` for Vec3s data usage in crawlspaces
typedef struct {

View File

@@ -59,10 +59,4 @@ s32 SkelCurve_Update(struct PlayState* play, SkelCurve* skelCurve);
void SkelCurve_Draw(Actor* actor, struct PlayState* play, SkelCurve* skelCurve, OverrideCurveLimbDraw overrideLimbDraw,
PostCurveLimbDraw postLimbDraw, s32 lod, void* data);
// ZAPD compatibility typedefs
// TODO: Remove when ZAPD adds support for them
typedef CurveInterpKnot TransformData;
typedef CurveAnimationHeader TransformUpdateIndex;
typedef CurveSkeletonHeader SkelCurveLimbList;
#endif

View File

@@ -43,7 +43,7 @@ typedef struct {
} Spawn;
// TODO: ZAPD Compatibility
typedef Spawn EntranceEntry;
typedef Spawn EntranceEntry;
typedef struct {
/* 0x00 */ u8 count; // number of points in the path
@@ -146,17 +146,6 @@ typedef union {
RoomShapeCullable cullable;
} RoomShape; // "Ground Shape"
// ZAPD compatibility typedefs
// TODO: Remove when ZAPD adds support for them
typedef RoomShapeDListsEntry PolygonDlist;
typedef RoomShapeNormal PolygonType0;
typedef RoomShapeImageSingle MeshHeader1Single;
typedef RoomShapeImageMultiBgEntry BgImage;
typedef RoomShapeImageMulti MeshHeader1Multi;
typedef RoomShapeCullableEntry PolygonDlist2;
typedef RoomShapeCullable PolygonType2;
#define SCENE_CMD_MESH SCENE_CMD_ROOM_SHAPE
#define ROOM_DRAW_OPA (1 << 0)
#define ROOM_DRAW_XLU (1 << 1)

View File

@@ -43,13 +43,6 @@ typedef struct {
/* 0x08 */ Gfx* dlist;
} SkinAnimatedLimbData; // size = 0xC
// ZAPD compatibility typedefs
// TODO: Remove when ZAPD adds support for them
typedef SkinVertex Struct_800A57C0;
typedef SkinTransformation Struct_800A598C_2;
typedef SkinAnimatedLimbData Struct_800A5E28;
typedef SkinLimbModif Struct_800A598C;
#define SKIN_LIMB_TYPE_ANIMATED 4
#define SKIN_LIMB_TYPE_NORMAL 11

28
tools/daf/README.md Normal file
View File

@@ -0,0 +1,28 @@
# Decompilation Assets Fixer (DAF)
The tool used for extract the assets ([ZAPD](https://github.com/zeldaret/ZAPD)) needs to be updated because it's using old names for structs and macros, this project is a workaround attempt until the main tool is updated.
## Usage
- Set the decomp path with the ``-p (--path)`` argument (example: ``daf.py -p ./ (-a || -m MODE)``)
- Run ``daf.py`` (tested under Python 3.10, should work with 3.7+)
- If you have any issues when compiling the decomp, try ``make clean && make -j``, if it's not working feel free to open an issue on this repo
### Operating modes:
- ``daf.py -m (--mode) fix_types``, this will update types and macros, intended to be used after using ZAPD
- ``daf.py -m (--mode) name_entrances``, this will remove hex numbers from exit lists
- ``daf.py -m (--mode) fix_segments``, this will add casts to segment symbols inside room lists
- ``daf.py -a (--all)``, this will run all modes
- ``daf.py -v (--verbose)``, this will display extra informations
### ROM should build OK on an unmodifed codebase
```
f0b7f35375f9cc8ca1b2d59d78e35405 zelda_ocarina_mq_dbg.z64
zelda_ocarina_mq_dbg.z64: OK
```
## Add More Data
If you need to add more data to change, add a dictionnary with the following format: ``"OLD": "NEW"``, then add your dictionnary to ``dataToFix``.
Next, run ``daf.py`` and it should make the changes.
## Contributions are welcome!

61
tools/daf/daf.py Normal file
View File

@@ -0,0 +1,61 @@
from time import time
try:
from data import fileTypes
from functions import replaceOldData, replaceEntranceHex, fixSegments, getArguments
except:
print("[DAF:Error]: Files are missing. Make sure everything is in the same folder.")
quit()
# verbose settings
fixTypeTime = entranceTime = segmentsTime = None
dashAmount = 60
infoPrefix = "[DAF:Info]"
# general things
args, parser = getArguments()
hasArgs = False
runTime = time()
if args.mode == "fix_types" or args.run_all:
startingTime = time()
if args.verbose:
print(f"{infoPrefix}: Fixing types and macros...")
hasArgs = True
for type in fileTypes:
replaceOldData(f"{args.decompPath}/assets/", type)
if args.verbose:
print(f"{infoPrefix}: Done in {(time() - startingTime):.2f}s!\n{'-' * dashAmount}")
if args.mode == "name_entrances" or args.run_all:
startingTime = time()
if args.verbose:
print(f"{infoPrefix}: Removing hexadecimal from exit lists...")
hasArgs = True
replaceEntranceHex(args.decompPath)
if args.verbose:
print(f"{infoPrefix}: Done in {(time() - startingTime):.2f}s!\n{'-' * dashAmount}")
if args.mode == "fix_segments" or args.run_all:
startingTime = time()
if args.verbose:
print(f"{infoPrefix}: Adding missing casts to rooms symbols...")
hasArgs = True
fixSegments(args.decompPath)
if args.verbose:
print(f"{infoPrefix}: Done in {(time() - startingTime):.2f}s!\n{'-' * dashAmount}")
if hasArgs and args.verbose:
print(f"{infoPrefix}: All Done in {(time() - runTime):.2f}s!")
if not hasArgs:
parser.print_help()
quit()

61
tools/daf/data.py Normal file
View File

@@ -0,0 +1,61 @@
### [GENERAL DATA] ###
# Add types here
fileTypes = [".h", ".c"]
# -------------------------------------------------------
### [FIX TYPES MODE] ###
# Format: ``"OLD": "NEW"``
camData = {
"CamData": "BgCamInfo",
}
curveData = {
"TransformData": "CurveInterpKnot",
"TransformUpdateIndex": "CurveAnimationHeader",
"SkelCurveLimbList": "CurveSkeletonHeader",
}
roomData = {
"PolygonDlist": "RoomShapeDListsEntry",
"PolygonType0": "RoomShapeNormal",
"MeshHeader1Single": "RoomShapeImageSingle",
"BgImage": "RoomShapeImageMultiBgEntry",
"MeshHeader1Multi": "RoomShapeImageMulti",
"PolygonDlist2": "RoomShapeCullableEntry",
"PolygonType2": "RoomShapeCullable",
"SCENE_CMD_MESH": "SCENE_CMD_ROOM_SHAPE",
}
skinData = {
"Struct_800A57C0": "SkinVertex",
"Struct_800A598C_2": "SkinTransformation",
"Struct_800A5E28": "SkinAnimatedLimbData",
"Struct_800A598C": "SkinLimbModif",
}
# Add or remove dictionnaries to this list to replace types
dataToFix = [
camData,
curveData,
roomData,
skinData,
]
# -------------------------------------------------------
### [NAME ENTRANCES] ###
# Dictionnary containing special entrance values
entrDictSpecial = {
"0x7FF9": "ENTR_RETURN_YOUSEI_IZUMI_YOKO", # Great Fairy Fountain (spells)
"0x7FFA": "ENTR_RETURN_SYATEKIJYOU", # Shooting gallery
"0x7FFB": "ENTR_RETURN_2", # Unused
"0x7FFC": "ENTR_RETURN_SHOP1", # Bazaar
"0x7FFD": "ENTR_RETURN_4", # Unused
"0x7FFE": "ENTR_RETURN_DAIYOUSEI_IZUMI", # Great Fairy Fountain (magic, double magic, double defense)
"0x7FFF": "ENTR_RETURN_GROTTO", # Grottos and normal Fairy Fountain
}

190
tools/daf/functions.py Normal file
View File

@@ -0,0 +1,190 @@
from os import walk
from re import sub
from argparse import ArgumentParser as Parser
try:
from data import dataToFix, entrDictSpecial
except:
print("[DAF:Error]: ``data.py`` not found! Make sure everything is in the same folder.")
quit()
# -------------------------------------------------------
### [GENERAL FUNCTIONS] ###
def getArguments():
"""Initialisation of the argument parser"""
parser = Parser(description="Fix various things related to assets for the OoT Decomp")
parser.add_argument(
"-m",
"--mode",
dest="mode",
type=str,
default="",
help="available modes: `fix_types`, `name_entrances`, `fix_segments`",
)
parser.add_argument(
"-a",
"--all",
dest="run_all",
default=False,
action="store_true",
help="run every mode",
)
parser.add_argument(
"-v",
"--verbose",
dest="verbose",
default=False,
action="store_true",
help="show extra informations",
)
parser.add_argument(
"-p",
"--path",
dest="decompPath",
default="",
required=True,
help="set decomp root",
)
return parser.parse_args(), parser
def getPaths(path: str, fileType: str):
"""Returns a list of data paths with the specified extension"""
paths = []
for path, dirs, files in walk(path):
for file in files:
if file.endswith(fileType):
paths.append(f"{path}/{file}")
paths.sort()
return paths
def getArrayInfos(data: list, arrayName: str):
"""Returns arrays containing line numbers for the start and the end of relevant C data"""
arrayStartIndices = []
arrayEndIndices = []
hasListStarted = False
for lineNb, line in enumerate(data):
if arrayName in line and line.endswith("] = {\n"):
arrayStartIndices.append(lineNb + 1)
hasListStarted = True
if hasListStarted and line.startswith("};\n"):
arrayEndIndices.append(lineNb)
hasListStarted = False
try:
if len(arrayStartIndices) != len(arrayEndIndices):
raise IndexError
except IndexError:
print("[DAF:Error]: Start Length != End Length")
return arrayStartIndices, arrayEndIndices
# -------------------------------------------------------
### [FIX TYPES MODE] ###
def replaceOldData(path: str, extension: str):
"""Replaces older names by newer ones"""
paths = getPaths(path, extension)
for path in paths:
with open(path, "r") as curFile:
fileData = curFile.read()
for data in dataToFix:
for key in data.keys():
fileData = sub(rf"{key}\b", data[key], fileData)
fileData = sub(rf"{key}_\B", f"{data[key]}_", fileData)
with open(path, "w") as curFile:
curFile.write(fileData)
# -------------------------------------------------------
### [NAME ENTRANCES] ###
def getEntranceDict(path: str):
"""Returns a list containing every entrances"""
entranceList = []
# read the entrance table
try:
with open(f"{path}/include/tables/entrance_table.h", "r") as fileData:
# keep the relevant data
for line in fileData.readlines():
if line.startswith("/* 0x"):
startIndex = line.find("ENTR_")
entranceList.append(line[startIndex : line.find(",", startIndex)])
except FileNotFoundError:
raise print("[DAF:Error]: Can't find entrance_table.h!")
# return a dictionnary from the entrance list
entranceDict = {f"0x{i:04X}": entrance for i, entrance in enumerate(entranceList)}
return dict(entranceDict, **entrDictSpecial)
def getNewFileData(data: list, dataDict: dict, arrayName: str):
"""Returns the current data with the updated values"""
startList, endList = getArrayInfos(data, arrayName)
if len(startList) == len(endList):
for curStart, curEnd in zip(startList, endList):
for i in range(curEnd - curStart):
curLine = curStart + i
try:
data[curLine] = (" " * 4) + f"{dataDict[data[curLine][:-2].lstrip()]},\n"
except KeyError:
# why???
pass
return data
def replaceEntranceHex(decompRoot: str):
"""Updates the entrances from OoT scenes"""
entrDict = getEntranceDict(decompRoot)
scenePaths = getPaths(f"{decompRoot}/assets/scenes/", ".c")
for path in scenePaths:
data = []
sceneName = None
with open(path, "r") as file:
if file.name.find("room") == -1:
data = file.readlines()
sceneName = file.name.split("/")[6][:-2]
if sceneName is not None:
newData = getNewFileData(data, entrDict, f"{sceneName}ExitList")
with open(path, "w") as file:
for line in newData:
file.write(line)
# -------------------------------------------------------
### [FIX SEGMENTS] ###
def fixSegments(decompRoot):
"""Adds u32 casts to room's segment symbols"""
paths = getPaths(f"{decompRoot}/assets/scenes", ".c")
for path in paths:
with open(path, "r") as curFile:
fileData = curFile.read()
fileData = sub(
r"\w*SegmentRom\w*|\(u32\)\w*SegmentRom\w*",
lambda match: f"(u32){match.group(0)}" if not match.group(0).startswith("(u32)") else match.group(0),
fileData,
)
with open(path, "w") as curFile:
curFile.write(fileData)