diff --git a/fast64-animations_updater/fast64-animations_updater_status.json b/fast64-animations_updater/fast64-animations_updater_status.json new file mode 100644 index 000000000..952445e49 --- /dev/null +++ b/fast64-animations_updater/fast64-animations_updater_status.json @@ -0,0 +1,9 @@ +{ + "last_check": "", + "backup_date": "", + "update_ready": false, + "ignore": false, + "just_restored": false, + "just_updated": false, + "version_text": {} +} \ No newline at end of file diff --git a/fast64_internal/sm64/animation/classes.py b/fast64_internal/sm64/animation/classes.py index 8f38a274c..7558f40af 100644 --- a/fast64_internal/sm64/animation/classes.py +++ b/fast64_internal/sm64/animation/classes.py @@ -341,10 +341,11 @@ def toAction(self, action): actionProps.customMaxFrame = max([1] + [len(x.values) for x in self.pairs]) - for header in self.headers: + for i in range(len(self.headers) - 1): actionProps.header_variants.add() - headerProps = actionProps.header_variants[-1] - header.toHeaderProps(action, headerProps) + + for header, header_props in zip(self.headers, actionProps.get_headers()): + header.toHeaderProps(action, header_props) updateHeaderVariantNumbers(actionProps.header_variants) diff --git a/fast64_internal/sm64/animation/importing.py b/fast64_internal/sm64/animation/importing.py index 8731d9cd9..575645376 100644 --- a/fast64_internal/sm64/animation/importing.py +++ b/fast64_internal/sm64/animation/importing.py @@ -1,21 +1,17 @@ +import dataclasses import math -from typing import Optional +import os +from typing import BinaryIO, Optional import mathutils import bpy from bpy.types import Object -from ...utility import PluginError, decodeSegmentedAddr +from ...utility import PluginError, decodeSegmentedAddr, path_checks from ...utility_anim import stashActionInArmature from ..sm64_utility import import_rom_checks -from .utility import animationOperatorChecks, get_anim_pose_bones, sm64ToRadian - -from .classes import SM64_Anim, SM64_AnimPair -from .reading import ( - importBinaryDMAAnimation, - importBinaryHeader, - importBinaryTable, - importCAnimations, -) +from .utility import CArrayReader, RomReading, get_anim_pose_bones, sm64ToRadian + +from .classes import SM64_Anim, SM64_AnimHeader, SM64_AnimPair from ..sm64_level_parser import SM64_Level, parseLevelAtPointer from ..sm64_constants import ( @@ -159,7 +155,6 @@ def importBinaryAnimations( import_type: str, read_entire_table: bool, table_index: int, - dma_table_address: int, ROMData, dataDict, tableList, @@ -174,7 +169,7 @@ def importBinaryAnimations( elif import_type == "DMA": importBinaryDMAAnimation( ROMData, - dma_table_address, + address, table_index, read_entire_table, dataDict, @@ -186,7 +181,176 @@ def importBinaryAnimations( raise PluginError("Unimplemented binary import type.") -def importAnimationToBlender( +def importBinaryHeader( + ROMData: BinaryIO, + headerAddress: int, + isDMA: bool, + segmentData: dict[int, tuple[int, int]], + dataDict: dict[str, SM64_Anim], +): + header = SM64_AnimHeader() + header.readBinary(ROMData, headerAddress, segmentData, isDMA) + + dataKey: str = f"{header.indices}-{header.values}" + if dataKey in dataDict: + data = dataDict[dataKey] + else: + data = SM64_Anim() + data.readBinary(ROMData, header) + dataDict[dataKey] = data + + header.headerVariant = len(data.headers) + header.data = data + data.headers.append(header) + return header + + +@dataclasses.dataclass +class DMATableEntrie: + offsetFromTable: int + address: int + size: int + + +def readBinaryDMATableEntries(ROMData: BinaryIO, address: int) -> list[DMATableEntrie]: + entries: list[DMATableEntrie] = [] + DMATableReader = RomReading(ROMData, address) + + numEntries = DMATableReader.readValue(4) + addrPlaceholder = DMATableReader.readValue(4) + + for i in range(numEntries): + offset = DMATableReader.readValue(4) + size = DMATableReader.readValue(4) + entries.append(DMATableEntrie(offset, address + offset, size)) + return entries + + +def importBinaryDMAAnimation( + ROMData: BinaryIO, + address: int, + entrieNum: int, + read_entire_table: bool, + dataDict: dict[str, SM64_Anim], + tableList: list[SM64_Anim], +): + entries: list[DMATableEntrie] = readBinaryDMATableEntries(ROMData, address) + if read_entire_table: + for entrie in entries: + header = importBinaryHeader(ROMData, entrie.address, True, None, dataDict) + tableList.append(header) + else: + if not (0 <= entrieNum < len(entries)): + raise PluginError("Entrie outside of defined table.") + + entrie: DMATableEntrie = entries[entrieNum] + header = importBinaryHeader(ROMData, entrie.address, True, None, dataDict) + tableList.append(header) + return header + + +def importBinaryTable( + ROMData: BinaryIO, + address: int, + read_entire_table: bool, + tableIndex: int, + segmentData: dict[int, tuple[int, int]], + dataDict: dict[str, SM64_Anim], + tableList: list[SM64_Anim], +): + for i in range(255): + ROMData.seek(address + (4 * i)) + ptr = int.from_bytes(ROMData.read(4), "big", signed=False) + if ptr == 0: + if not read_entire_table: + raise PluginError("Table Index not in table.") + break + + isCorrectIndex = not read_entire_table and i == tableIndex + if read_entire_table or isCorrectIndex: + ptrInBytes: bytes = ptr.to_bytes(4, "big") + if ptrInBytes[0] not in segmentData: + raise PluginError( + f"\ +Header at table index {i} located at {ptr} does not belong to the current segment." + ) + headerAddress = decodeSegmentedAddr(ptrInBytes, segmentData) + + header = importBinaryHeader(ROMData, headerAddress, False, segmentData, dataDict) + tableList.append(header) + + if isCorrectIndex: + break + else: + raise PluginError( + "\ +Table address is invalid, iterated through 255 indices and no NULL was found." + ) + + +def importAnimation(dataDict, array: "ReadArray", arrays: list["ReadArray"]): + print(f"Reading animation {array.name}") + header = SM64_AnimHeader() + header.readC(array) + print(header) + + dataKey: str = f"{header.indices}-{header.values}" + if dataKey in dataDict: + data: SM64_Anim = dataDict[dataKey] + else: + data = SM64_Anim() + data.readC(header, arrays) + dataDict[dataKey] = data + + header.headerVariant = len(data.headers) + header.data = data + data.headers.append(header) + + return header + + +def importCAnimations( + path: str, + dataDict: dict[str, SM64_Anim], + tableList: list[SM64_Anim], +): + path_checks(path) + + if os.path.isfile(path): + filePaths: list[str] = [path] + elif os.path.isdir(path): + fileNames = os.listdir(path) + filePaths: list[str] = [os.path.join(path, fileName) for fileName in fileNames] + else: + raise PluginError(f"Path ({path}) is not a file or a directory.") + + filePaths.sort() + + print("Reading arrays in path") + + arrays: dict[ReadArray] = {} + for filePath in filePaths: + if not filePath.endswith(".c"): + continue + + print(f"Reading file {filePath}") + with open(filePath, "r") as file: + arrayReader = CArrayReader() + arrays.update(arrayReader.findAllCArraysInFile(file.read(), filePath)) + + header = None + for array in arrays.values(): + if "*const" in array.keywords or "Animation*" in array.keywords: # Table + continue + elif not "Animation" in array.keywords: + continue + header = importAnimation(dataDict, array, arrays) + tableList.append(header) + + return header + + +def import_animation_to_blender( armature_obj: Object, import_type: str, sm64_to_blender_scale: float, @@ -199,7 +363,6 @@ def importAnimationToBlender( binary_import_type: Optional[str] = None, read_entire_table: Optional[bool] = None, table_index: Optional[int] = None, - dma_table_address: Optional[int] = None, ): dataDict: dict[str, SM64_Anim] = {} tableList: list[str] = [] @@ -214,7 +377,6 @@ def importAnimationToBlender( binary_import_type, read_entire_table, table_index, - dma_table_address, ROMData, dataDict, tableList, diff --git a/fast64_internal/sm64/animation/operators.py b/fast64_internal/sm64/animation/operators.py index 7f43b3687..0fdd337ef 100644 --- a/fast64_internal/sm64/animation/operators.py +++ b/fast64_internal/sm64/animation/operators.py @@ -1,4 +1,3 @@ -import cProfile import math import os import bpy @@ -10,8 +9,12 @@ IntProperty, ) -from .reading import importBinaryDMAAnimation -from .importing import animationDataToBlender, animation_table_to_blender, importAnimationToBlender +from .importing import ( + importBinaryDMAAnimation, + animationDataToBlender, + animation_table_to_blender, + import_animation_to_blender, +) from .exporting import ( exportAnimation, @@ -347,20 +350,24 @@ def execute(self, context): try: sm64_props = context.scene.fast64.sm64 anim_import_props = sm64_props.anim_import - importAnimationToBlender( + + animationOperatorChecks(context, False) + + import_animation_to_blender( context.selected_objects[0], anim_import_props.import_type, sm64_props.blender_to_sm64_scale, sm64_props.anim_export.table.elements, abspath(anim_import_props.path), abspath(sm64_props.import_rom), - int(anim_import_props.address, 16), + int(anim_import_props.DMATableAddress, 16) + if anim_import_props.binary_import_type == "DMA" + else int(anim_import_props.address, 16), anim_import_props.isSegmentedPointer(), anim_import_props.level, anim_import_props.binary_import_type, anim_import_props.read_entire_table, anim_import_props.tableIndex, - int(anim_import_props.DMATableAddress, 16) ) self.report({"INFO"}, "Success!") return {"FINISHED"} diff --git a/fast64_internal/sm64/animation/properties.py b/fast64_internal/sm64/animation/properties.py index b4b378bea..414dcb8b9 100644 --- a/fast64_internal/sm64/animation/properties.py +++ b/fast64_internal/sm64/animation/properties.py @@ -6,7 +6,6 @@ StringProperty, EnumProperty, IntProperty, - FloatProperty, CollectionProperty, PointerProperty, ) @@ -47,6 +46,7 @@ customExportWarning, decompFolderMessage, makeWriteInfoBox, + path_ui_warnings, prop_split, writeBoxExportType, ) @@ -698,6 +698,7 @@ def drawBinary(self, layout: bpy.types.UILayout): def drawC(self, layout: bpy.types.UILayout): col = layout.column() col.prop(self, "path") + path_ui_warnings(col, self.path) def draw_props(self, layout: bpy.types.UILayout): col = layout.column() diff --git a/fast64_internal/sm64/animation/reading.py b/fast64_internal/sm64/animation/reading.py deleted file mode 100644 index 009847bce..000000000 --- a/fast64_internal/sm64/animation/reading.py +++ /dev/null @@ -1,178 +0,0 @@ -import dataclasses -import os -from typing import BinaryIO -import bpy - -from .classes import SM64_AnimHeader, SM64_Anim -from .utility import CArrayReader, ReadArray, RomReading -from ...utility import PluginError, decodeSegmentedAddr - - -def importBinaryHeader( - ROMData: BinaryIO, - headerAddress: int, - isDMA: bool, - segmentData: dict[int, tuple[int, int]], - dataDict: dict[str, SM64_Anim], -): - header = SM64_AnimHeader() - header.readBinary(ROMData, headerAddress, segmentData, isDMA) - - dataKey: str = f"{header.indices}-{header.values}" - if dataKey in dataDict: - data = dataDict[dataKey] - else: - data = SM64_Anim() - data.readBinary(ROMData, header) - dataDict[dataKey] = data - - header.headerVariant = len(data.headers) - header.data = data - data.headers.append(header) - return header - - -@dataclasses.dataclass -class DMATableEntrie: - offsetFromTable: int - address: int - size: int - - -def readBinaryDMATableEntries(ROMData: BinaryIO, address: int) -> list[DMATableEntrie]: - entries: list[DMATableEntrie] = [] - DMATableReader = RomReading(ROMData, address) - - numEntries = DMATableReader.readValue(4) - addrPlaceholder = DMATableReader.readValue(4) - - for i in range(numEntries): - offset = DMATableReader.readValue(4) - size = DMATableReader.readValue(4) - entries.append(DMATableEntrie(offset, address + offset, size)) - return entries - - -def importBinaryDMAAnimation( - ROMData: BinaryIO, - address: int, - entrieNum: int, - read_entire_table: bool, - dataDict: dict[str, SM64_Anim], - tableList: list[SM64_Anim], -): - entries: list[DMATableEntrie] = readBinaryDMATableEntries(ROMData, address) - if read_entire_table: - for entrie in entries: - header = importBinaryHeader(ROMData, entrie.address, True, None, dataDict) - tableList.append(header) - else: - if not (0 <= entrieNum < len(entries)): - raise PluginError("Entrie outside of defined table.") - - entrie: DMATableEntrie = entries[entrieNum] - header = importBinaryHeader(ROMData, entrie.address, True, None, dataDict) - tableList.append(header) - return header - - -def importBinaryTable( - ROMData: BinaryIO, - address: int, - read_entire_table: bool, - tableIndex: int, - segmentData: dict[int, tuple[int, int]], - dataDict: dict[str, SM64_Anim], - tableList: list[SM64_Anim], -): - for i in range(255): - ROMData.seek(address + (4 * i)) - ptr = int.from_bytes(ROMData.read(4), "big", signed=False) - if ptr == 0: - if not read_entire_table: - raise PluginError("Table Index not in table.") - break - - isCorrectIndex = not read_entire_table and i == tableIndex - if read_entire_table or isCorrectIndex: - ptrInBytes: bytes = ptr.to_bytes(4, "big") - if ptrInBytes[0] not in segmentData: - raise PluginError( - f"\ -Header at table index {i} located at {ptr} does not belong to the current segment." - ) - headerAddress = decodeSegmentedAddr(ptrInBytes, segmentData) - - header = importBinaryHeader(ROMData, headerAddress, False, segmentData, dataDict) - tableList.append(header) - - if isCorrectIndex: - break - else: - raise PluginError( - "\ -Table address is invalid, iterated through 255 indices and no NULL was found." - ) - - -def importAnimation(dataDict, array: "ReadArray", arrays: list["ReadArray"]): - print(f"Reading animation {array.name}") - header = SM64_AnimHeader() - header.readC(array) - print(header) - - dataKey: str = f"{header.indices}-{header.values}" - if dataKey in dataDict: - data: SM64_Anim = dataDict[dataKey] - else: - data = SM64_Anim() - data.readC(header, arrays) - dataDict[dataKey] = data - - header.headerVariant = len(data.headers) - header.data = data - data.headers.append(header) - - return header - - -def importCAnimations( - path: str, - dataDict: dict[str, SM64_Anim], - tableList: list[SM64_Anim], -): - if not os.path.exists(path): - raise PluginError(f"Path ({path}) does not exist.") - - if os.path.isfile(path): - filePaths: list[str] = [path] - elif os.path.isdir(path): - fileNames = os.listdir(path) - filePaths: list[str] = [os.path.join(path, fileName) for fileName in fileNames] - else: - raise PluginError(f"Path ({path}) is not a file or a directory.") - - filePaths.sort() - - print("Reading arrays in path") - - arrays: dict[ReadArray] = {} - for filePath in filePaths: - if not filePath.endswith(".c"): - continue - - print(f"Reading file {filePath}") - with open(filePath, "r") as file: - arrayReader = CArrayReader() - arrays.update(arrayReader.findAllCArraysInFile(file.read(), filePath)) - - header = None - for array in arrays.values(): - if "*const" in array.keywords or "Animation*" in array.keywords: # Table - continue - elif not "Animation" in array.keywords: - continue - header = importAnimation(dataDict, array, arrays) - tableList.append(header) - - return header diff --git a/fast64_internal/utility.py b/fast64_internal/utility.py index 2e9aedab1..4d9fc48a3 100644 --- a/fast64_internal/utility.py +++ b/fast64_internal/utility.py @@ -1250,6 +1250,31 @@ def filepath_ui_warnings( return False +def path_checks( + filepath: str, + empty_error: str = "Empty path.", + doesnt_exist_error: str = "Path does not exist." +): + if filepath == "": + raise PluginError(empty_error) + elif not os.path.exists(filepath): + raise PluginError(doesnt_exist_error) + + +def path_ui_warnings( + layout: bpy.types.UILayout, + filepath: str, + empty_error: str = "Empty path.", + doesnt_exist_error: str = "Path does not exist." +) -> bool: + try: + path_checks(filepath, empty_error, doesnt_exist_error) + return True + except Exception as e: + multilineLabel(layout.box(), str(e), "ERROR") + return False + + def toAlnum(name, exceptions=[]): if name is None or name == "": return None diff --git a/mario.blend b/mario.blend index 8ba071893..efbd9b36f 100644 Binary files a/mario.blend and b/mario.blend differ