From 210376f2724b2482ce5ab547f300ca42b0583063 Mon Sep 17 00:00:00 2001 From: jonschz <17198703+jonschz@users.noreply.github.com> Date: Wed, 17 Jul 2024 16:03:02 +0200 Subject: [PATCH] Implement `LegoRaceCar::HandleSkeletonKicks` and dependents (#1065) * Implement `LegoRaceCar::HandleSkeletonKicks` and dependents * Fix typo * Spike to fix array comparisons (needs refactor) * Refactor: Dedicated method for array element matching * Address review comments * Reformat with new version of black * Apply more review comments * Address more review comments --------- Co-authored-by: jonschz <jonschz@users.noreply.github.com> --- LEGO1/lego/legoomni/include/carrace.h | 3 + LEGO1/lego/legoomni/include/legoracers.h | 22 ++++- LEGO1/lego/legoomni/include/raceskel.h | 4 +- LEGO1/lego/legoomni/src/race/legoracers.cpp | 81 ++++++++++++++++--- LEGO1/lego/legoomni/src/race/raceskel.cpp | 12 +++ LEGO1/lego/sources/geom/legowegedge.h | 2 + tools/isledecomp/isledecomp/compare/core.py | 67 ++++++++++++++- .../isledecomp/isledecomp/cvdump/analysis.py | 5 +- 8 files changed, 179 insertions(+), 17 deletions(-) diff --git a/LEGO1/lego/legoomni/include/carrace.h b/LEGO1/lego/legoomni/include/carrace.h index fd33c536..5a43f420 100644 --- a/LEGO1/lego/legoomni/include/carrace.h +++ b/LEGO1/lego/legoomni/include/carrace.h @@ -52,6 +52,9 @@ public: MxLong HandleEndAction(MxEndActionNotificationParam&) override; // vtable+0x74 MxLong HandleType0Notification(MxNotificationParam&) override; // vtable+0x78 + // FUNCTION: BETA10 0x100cd060 + undefined4 GetUnk0x150() { return m_unk0x150; } + // SYNTHETIC: LEGO1 0x10016c70 // CarRace::`scalar deleting destructor' diff --git a/LEGO1/lego/legoomni/include/legoracers.h b/LEGO1/lego/legoomni/include/legoracers.h index 290a849f..d66349a5 100644 --- a/LEGO1/lego/legoomni/include/legoracers.h +++ b/LEGO1/lego/legoomni/include/legoracers.h @@ -4,12 +4,24 @@ #include "legocarraceactor.h" #include "legoracemap.h" +#define LEGORACECAR_UNKNOWN_STATE 0 +#define LEGORACECAR_KICK1 2 // name guessed +#define LEGORACECAR_KICK2 4 // name validated by BETA10 0x100cb659 + // SIZE 0x08 struct EdgeReference { const char* m_name; // 0x00 LegoPathBoundary* m_data; // 0x04 }; +// SIZE 0x10 +struct SkeletonKickPhase { + EdgeReference* m_edgeRef; // 0x00 + float m_lower; // 0x04 + float m_upper; // 0x08 + MxU8 m_userState; // 0x0c +}; + // VTABLE: LEGO1 0x100d58a0 LegoRaceActor // VTABLE: LEGO1 0x100d58a8 LegoAnimActor // VTABLE: LEGO1 0x100d58b8 LegoPathActor @@ -41,7 +53,7 @@ public: return !strcmp(p_name, LegoRaceCar::ClassName()) || LegoCarRaceActor::IsA(p_name); } - void ParseAction(char*) override; // vtable+0x20 + void ParseAction(char* p_extra) override; // vtable+0x20 void SetWorldSpeed(MxFloat p_worldSpeed) override; // vtable+0x30 MxU32 VTable0x6c( LegoPathBoundary* p_boundary, @@ -58,8 +70,8 @@ public: MxResult VTable0x9c() override; // vtable+0x9c virtual void SetMaxLinearVelocity(float p_maxLinearVelocity); - virtual void FUN_10012ff0(float); - virtual MxBool FUN_10013130(float); + virtual void FUN_10012ff0(float p_param); + virtual MxU32 HandleSkeletonKicks(float p_param1); // SYNTHETIC: LEGO1 0x10014240 // LegoRaceCar::`scalar deleting destructor' @@ -74,7 +86,9 @@ private: LegoPathBoundary* m_unk0x7c; // 0x7c static EdgeReference g_edgeReferences[]; - static const EdgeReference* g_pEdgeReferences; + static const SkeletonKickPhase g_skeletonKickPhases[]; // TODO: better name + + static const char* g_soundSkel3; }; #endif // LEGORACERS_H diff --git a/LEGO1/lego/legoomni/include/raceskel.h b/LEGO1/lego/legoomni/include/raceskel.h index 6d9a270b..fca57dde 100644 --- a/LEGO1/lego/legoomni/include/raceskel.h +++ b/LEGO1/lego/legoomni/include/raceskel.h @@ -12,8 +12,10 @@ class RaceSkel : public LegoAnimActor { public: RaceSkel(); + void GetCurrentAnimData(float* p_outCurAnimPosition, float* p_outCurAnimDuration); + private: - undefined4 m_unk0x1c; // 0x1c + float m_animPosition; // 0x1c }; #endif // RACESKEL_H diff --git a/LEGO1/lego/legoomni/src/race/legoracers.cpp b/LEGO1/lego/legoomni/src/race/legoracers.cpp index 2a4e2185..720ea13c 100644 --- a/LEGO1/lego/legoomni/src/race/legoracers.cpp +++ b/LEGO1/lego/legoomni/src/race/legoracers.cpp @@ -1,15 +1,21 @@ #include "legoracers.h" #include "anim/legoanim.h" +#include "carrace.h" #include "define.h" +#include "legocachesoundmanager.h" #include "legocameracontroller.h" #include "legorace.h" +#include "legosoundmanager.h" #include "misc.h" +#include "mxdebug.h" #include "mxmisc.h" #include "mxnotificationmanager.h" #include "mxutilities.h" +#include "raceskel.h" DECOMP_SIZE_ASSERT(EdgeReference, 0x08) +DECOMP_SIZE_ASSERT(SkeletonKickPhase, 0x10) DECOMP_SIZE_ASSERT(LegoRaceCar, 0x200) // GLOBAL: LEGO1 0x100f0a20 @@ -41,7 +47,24 @@ EdgeReference LegoRaceCar::g_edgeReferences[] = { }; // GLOBAL: LEGO1 0x100f0a50 -const EdgeReference* LegoRaceCar::g_pEdgeReferences = g_edgeReferences; +const SkeletonKickPhase LegoRaceCar::g_skeletonKickPhases[] = { + {&LegoRaceCar::g_edgeReferences[0], 0.1, 0.2, LEGORACECAR_KICK2}, + {&LegoRaceCar::g_edgeReferences[1], 0.2, 0.3, LEGORACECAR_KICK2}, + {&LegoRaceCar::g_edgeReferences[2], 0.3, 0.4, LEGORACECAR_KICK2}, + {&LegoRaceCar::g_edgeReferences[2], 0.6, 0.7, LEGORACECAR_KICK1}, + {&LegoRaceCar::g_edgeReferences[1], 0.7, 0.8, LEGORACECAR_KICK1}, + {&LegoRaceCar::g_edgeReferences[0], 0.8, 0.9, LEGORACECAR_KICK1}, + {&LegoRaceCar::g_edgeReferences[3], 0.1, 0.2, LEGORACECAR_KICK1}, + {&LegoRaceCar::g_edgeReferences[4], 0.2, 0.3, LEGORACECAR_KICK1}, + {&LegoRaceCar::g_edgeReferences[5], 0.3, 0.4, LEGORACECAR_KICK1}, + {&LegoRaceCar::g_edgeReferences[5], 0.6, 0.7, LEGORACECAR_KICK2}, + {&LegoRaceCar::g_edgeReferences[4], 0.7, 0.8, LEGORACECAR_KICK2}, + {&LegoRaceCar::g_edgeReferences[3], 0.8, 0.9, LEGORACECAR_KICK2}, +}; + +// GLOBAL: LEGO1 0x100f0b70 +// STRING: LEGO1 0x100f08bc +const char* LegoRaceCar::g_soundSkel3 = "skel3"; // FUNCTION: LEGO1 0x10012950 LegoRaceCar::LegoRaceCar() @@ -140,13 +163,11 @@ void LegoRaceCar::FUN_10012ff0(float p_param) LegoAnimActorStruct* a; // called `a` in BETA10 float deltaTime; - if (m_userState == 2) { + if (m_userState == LEGORACECAR_KICK1) { a = m_unk0x70; } else { - // TODO: Possibly an enum? - const char legoracecarKick2 = 4; // original name: LEGORACECAR_KICK2 - assert(m_userState == legoracecarKick2); + assert(m_userState == LEGORACECAR_KICK2); a = m_unk0x74; } @@ -156,7 +177,7 @@ void LegoRaceCar::FUN_10012ff0(float p_param) deltaTime = p_param - m_unk0x58; if (a->GetDuration() <= deltaTime || deltaTime < 0.0) { - if (m_userState == 2) { + if (m_userState == LEGORACECAR_KICK1) { LegoEdge** edges = m_unk0x78->GetEdges(); m_destEdge = (LegoUnknown100db7f4*) (edges[2]); m_boundary = m_unk0x78; @@ -167,7 +188,7 @@ void LegoRaceCar::FUN_10012ff0(float p_param) m_boundary = m_unk0x7c; } - m_userState = 0; + m_userState = LEGORACECAR_UNKNOWN_STATE; } else if (a->GetAnimTreePtr()->GetCamAnim()) { MxMatrix transformationMatrix; @@ -189,10 +210,50 @@ void LegoRaceCar::FUN_10012ff0(float p_param) } } -// STUB: LEGO1 0x10013130 -MxBool LegoRaceCar::FUN_10013130(float) +// FUNCTION: LEGO1 0x10013130 +// FUNCTION: BETA10 0x100cce50 +MxU32 LegoRaceCar::HandleSkeletonKicks(float p_param1) { - // TODO + const SkeletonKickPhase* current = g_skeletonKickPhases; + + // TODO: Type is guesswork so far + CarRace* r = (CarRace*) CurrentWorld(); // called `r` in BETA10 + assert(r); + + RaceSkel* s = (RaceSkel*) r->GetUnk0x150(); // called `s` in BETA10 + assert(s); + + float skeletonCurAnimPosition; + float skeletonCurAnimDuration; + + s->GetCurrentAnimData(&skeletonCurAnimPosition, &skeletonCurAnimDuration); + + float skeletonCurAnimPhase = skeletonCurAnimPosition / skeletonCurAnimDuration; + + for (MxS32 i = 0; i < sizeOfArray(g_skeletonKickPhases); i++) { + if (m_boundary == current->m_edgeRef->m_data && current->m_lower <= skeletonCurAnimPhase && + skeletonCurAnimPhase <= current->m_upper) { + m_userState = current->m_userState; + } + current = ¤t[1]; + } + + if (m_userState != LEGORACECAR_KICK1 && m_userState != LEGORACECAR_KICK2) { + MxTrace( + // STRING: BETA10 0x101f64c8 + "Got kicked in boundary %s %d %g:%g %g\n", + // TODO: same as in above comparison + m_boundary->GetName(), + skeletonCurAnimPosition, + skeletonCurAnimDuration, + skeletonCurAnimPhase + ); + return FALSE; + } + + m_unk0x58 = p_param1; + SoundManager()->GetCacheSoundManager()->Play(g_soundSkel3, NULL, FALSE); + return TRUE; } diff --git a/LEGO1/lego/legoomni/src/race/raceskel.cpp b/LEGO1/lego/legoomni/src/race/raceskel.cpp index 5ef950e2..dd7e87f3 100644 --- a/LEGO1/lego/legoomni/src/race/raceskel.cpp +++ b/LEGO1/lego/legoomni/src/race/raceskel.cpp @@ -1,5 +1,7 @@ #include "raceskel.h" +#include <cassert> + DECOMP_SIZE_ASSERT(RaceSkel, 0x178) // STUB: LEGO1 0x100719b0 @@ -7,3 +9,13 @@ RaceSkel::RaceSkel() { // TODO } + +// FUNCTION: LEGO1 0x10071cb0 +// FUNCTION: BETA10 0x100f158b +void RaceSkel::GetCurrentAnimData(float* p_outCurAnimPosition, float* p_outCurAnimDuration) +{ + *p_outCurAnimPosition = m_animPosition; + + assert(m_curAnim >= 0); + *p_outCurAnimDuration = m_animMaps[m_curAnim]->GetDuration(); +} diff --git a/LEGO1/lego/sources/geom/legowegedge.h b/LEGO1/lego/sources/geom/legowegedge.h index 716f830f..23bdf340 100644 --- a/LEGO1/lego/sources/geom/legowegedge.h +++ b/LEGO1/lego/sources/geom/legowegedge.h @@ -43,6 +43,8 @@ public: LegoU32 GetFlag0x10() { return m_flags & c_bit5 ? FALSE : TRUE; } Mx4DPointFloat* GetUnknown0x14() { return &m_unk0x14; } Mx4DPointFloat* GetEdgeNormal(int index) { return &m_edgeNormals[index]; } + + // FUNCTION: BETA10 0x1001c9b0 LegoChar* GetName() { return m_name; } void SetFlag0x10(LegoU32 p_disable) diff --git a/tools/isledecomp/isledecomp/compare/core.py b/tools/isledecomp/isledecomp/compare/core.py index 1587ef81..3cf827e9 100644 --- a/tools/isledecomp/isledecomp/compare/core.py +++ b/tools/isledecomp/isledecomp/compare/core.py @@ -8,6 +8,7 @@ from typing import Any, Callable, Iterable, List, Optional from isledecomp.bin import Bin as IsleBin, InvalidVirtualAddressError from isledecomp.cvdump.demangler import demangle_string_const from isledecomp.cvdump import Cvdump, CvdumpAnalysis +from isledecomp.cvdump.types import scalar_type_pointer from isledecomp.parser import DecompCodebase from isledecomp.dir import walk_source_dir from isledecomp.types import SymbolType @@ -220,7 +221,8 @@ class Compare: var.offset, var.name, var.parent_function ) else: - self._db.match_variable(var.offset, var.name) + if self._db.match_variable(var.offset, var.name): + self._check_if_array_and_match_elements(var.offset, var.name) for tbl in codebase.iter_vtables(): self._db.match_vtable(tbl.offset, tbl.name, tbl.base_class) @@ -245,6 +247,69 @@ class Compare: self._db.match_string(string.offset, string.name) + def _check_if_array_and_match_elements(self, orig_addr: int, name: str): + """ + Checks if the global variable at `orig_addr` is an array. + If yes, adds a match for all its elements. If it is an array of structs, all fields in that struct are also matched. + Note that there is no recursion, so an array of arrays would not be handled entirely. + This step is necessary e.g. for `0x100f0a20` (LegoRacers.cpp). + """ + + def _add_match_in_array( + name: str, type_id: str, orig_addr: int, recomp_addr: int + ): + self._db.set_recomp_symbol( + recomp_addr, + SymbolType.POINTER if scalar_type_pointer(type_id) else SymbolType.DATA, + name, + name, + # we only need the matches when they are referenced elsewhere, hence we don't need the size + size=None, + ) + self._db.set_pair(orig_addr, recomp_addr) + + matchinfo = self._db.get_by_orig(orig_addr) + if matchinfo is None or matchinfo.recomp_addr is None: + return + recomp_addr = matchinfo.recomp_addr + + node = next( + (x for x in self.cvdump_analysis.nodes if x.addr == recomp_addr), + None, + ) + if node is None or node.data_type is None: + return + + if not node.data_type.key.startswith("0x"): + # scalar type, so clearly not an array + return + + data_type = self.cv.types.keys[node.data_type.key.lower()] + + if data_type["type"] == "LF_ARRAY": + array_element_type = self.cv.types.get(data_type["array_type"]) + + assert node.data_type.members is not None + + for array_element in node.data_type.members: + orig_element_base_addr = orig_addr + array_element.offset + recomp_element_base_addr = recomp_addr + array_element.offset + if array_element_type.members is None: + _add_match_in_array( + f"{name}{array_element.name}", + array_element_type.key, + orig_element_base_addr, + recomp_element_base_addr, + ) + else: + for member in array_element_type.members: + _add_match_in_array( + f"{name}{array_element.name}.{member.name}", + array_element_type.key, + orig_element_base_addr + member.offset, + recomp_element_base_addr + member.offset, + ) + def _find_original_strings(self): """Go to the original binary and look for the specified string constants to find a match. This is a (relatively) expensive operation so we only diff --git a/tools/isledecomp/isledecomp/cvdump/analysis.py b/tools/isledecomp/isledecomp/cvdump/analysis.py index 40ef292e..e030035e 100644 --- a/tools/isledecomp/isledecomp/cvdump/analysis.py +++ b/tools/isledecomp/isledecomp/cvdump/analysis.py @@ -5,7 +5,7 @@ from isledecomp.cvdump import SymbolsEntry from isledecomp.types import SymbolType from .parser import CvdumpParser from .demangler import demangle_string_const, demangle_vtable -from .types import CvdumpKeyError, CvdumpIntegrityError +from .types import CvdumpKeyError, CvdumpIntegrityError, TypeInfo class CvdumpNode: @@ -35,6 +35,8 @@ class CvdumpNode: section_contribution: Optional[int] = None addr: Optional[int] = None symbol_entry: Optional[SymbolsEntry] = None + # Preliminary - only used for non-static variables at the moment + data_type: Optional[TypeInfo] = None def __init__(self, section: int, offset: int) -> None: self.section = section @@ -127,6 +129,7 @@ class CvdumpAnalysis: # get information for built-in "T_" types. g_info = parser.types.get(glo.type) node_dict[key].confirmed_size = g_info.size + node_dict[key].data_type = g_info # Previously we set the symbol type to POINTER here if # the variable was known to be a pointer. We can derive this # information later when it's time to compare the variable,