Implement LegoRaceCar::HandleSkeletonKicks and dependents ()

* 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>
This commit is contained in:
jonschz 2024-07-17 16:03:02 +02:00 committed by GitHub
parent 0760e4e7d7
commit 210376f272
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
8 changed files with 179 additions and 17 deletions
LEGO1/lego
tools/isledecomp/isledecomp
compare
cvdump

View file

@ -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'

View file

@ -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

View file

@ -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

View file

@ -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 = &current[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;
}

View file

@ -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();
}

View file

@ -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)

View file

@ -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

View file

@ -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,