From 1f251ff817b0e068d67503defc7bb7c3af484e11 Mon Sep 17 00:00:00 2001 From: jonschz <17198703+jonschz@users.noreply.github.com> Date: Sun, 28 Jul 2024 20:13:18 +0200 Subject: [PATCH 01/10] Implement/match `LegoCarRaceActor::FUN_10080590` (#1070) * Implement/match `LegoCarRaceActor::FUN_10080590` * Add vbtable annotations * disable formatter for assertion * Fix BETA10 annotations * Address review comments --------- Co-authored-by: jonschz --- .../lego/legoomni/include/legocarraceactor.h | 36 ++++++++--- LEGO1/lego/legoomni/src/common/misc.cpp | 2 + .../legoomni/src/entity/legocarraceactor.cpp | 61 +++++++++++++++++-- LEGO1/lego/legoomni/src/race/raceskel.cpp | 2 +- LEGO1/lego/sources/geom/legounkown100db7f4.h | 6 ++ LEGO1/lego/sources/geom/legoweedge.h | 2 + LEGO1/realtime/orientableroi.h | 3 + 7 files changed, 100 insertions(+), 12 deletions(-) diff --git a/LEGO1/lego/legoomni/include/legocarraceactor.h b/LEGO1/lego/legoomni/include/legocarraceactor.h index 6f1c72f4..e1d41dce 100644 --- a/LEGO1/lego/legoomni/include/legocarraceactor.h +++ b/LEGO1/lego/legoomni/include/legocarraceactor.h @@ -7,12 +7,17 @@ // VTABLE: LEGO1 0x100da0c8 LegoAnimActor // VTABLE: LEGO1 0x100da0d8 LegoPathActor // VTABLE: LEGO1 0x100da1a8 LegoCarRaceActor +// VTABLE: BETA10 0x101bea74 LegoRaceActor +// VTABLE: BETA10 0x101bea78 LegoAnimActor +// VTABLE: BETA10 0x101bea90 LegoPathActor +// VTABLE: BETA10 0x101beb80 LegoCarRaceActor // SIZE 0x1a0 class LegoCarRaceActor : public virtual LegoRaceActor { public: LegoCarRaceActor(); // FUNCTION: LEGO1 0x10081660 + // FUNCTION: BETA10 0x100aab10 const char* ClassName() const override // vtable+0x0c { // STRING: LEGO1 0x100f0568 @@ -20,6 +25,7 @@ class LegoCarRaceActor : public virtual LegoRaceActor { } // FUNCTION: LEGO1 0x10081680 + // FUNCTION: BETA10 0x100aa9e0 MxBool IsA(const char* p_name) const override // vtable+0x10 { return !strcmp(p_name, LegoCarRaceActor::ClassName()) || LegoRaceActor::IsA(p_name); @@ -38,7 +44,7 @@ class LegoCarRaceActor : public virtual LegoRaceActor { override; // vtable+0x98 MxResult VTable0x9c() override; // vtable+0x9c - virtual void FUN_10080590(float); + virtual void FUN_10080590(float p_float); // FUNCTION: LEGO1 0x10012bb0 virtual void FUN_10012bb0(float p_unk0x14) { m_unk0x14 = p_unk0x14; } @@ -67,12 +73,28 @@ class LegoCarRaceActor : public virtual LegoRaceActor { // LegoCarRaceActor::`scalar deleting destructor' protected: - float m_unk0x08; // 0x08 - MxU8 m_unk0x0c; // 0x0c - float m_unk0x10; // 0x10 - float m_unk0x14; // 0x14 - float m_unk0x18; // 0x18 - undefined4 m_unk0x1c; // 0x1c + MxFloat m_unk0x08; // 0x08 + MxU8 m_unk0x0c; // 0x0c + + // Could be a multiplier for the maximum speed when going straight + MxFloat m_unk0x10; // 0x10 + + // Could be the acceleration + MxFloat m_unk0x14; // 0x14 + + MxFloat m_unk0x18; // 0x18 + + // Could be the current timestamp for time-based movement + MxFloat m_unk0x1c; // 0x1c }; +// GLOBAL: LEGO1 0x100da0b0 +// LegoCarRaceActor::`vbtable' + +// GLOBAL: LEGO1 0x100da0a8 +// LegoCarRaceActor::`vbtable'{for `LegoAnimActor'} + +// GLOBAL: LEGO1 0x100da098 +// LegoCarRaceActor::`vbtable'{for `LegoRaceActor'} + #endif // LEGOCARRACEACTOR_H diff --git a/LEGO1/lego/legoomni/src/common/misc.cpp b/LEGO1/lego/legoomni/src/common/misc.cpp index 6d879aa1..901810d0 100644 --- a/LEGO1/lego/legoomni/src/common/misc.cpp +++ b/LEGO1/lego/legoomni/src/common/misc.cpp @@ -67,8 +67,10 @@ LegoNavController* NavController() } // FUNCTION: LEGO1 0x10015790 +// FUNCTION: BETA10 0x100e49ff LegoPathActor* UserActor() { + assert(LegoOmni::GetInstance()); return LegoOmni::GetInstance()->GetUserActor(); } diff --git a/LEGO1/lego/legoomni/src/entity/legocarraceactor.cpp b/LEGO1/lego/legoomni/src/entity/legocarraceactor.cpp index b74a652b..3dd009da 100644 --- a/LEGO1/lego/legoomni/src/entity/legocarraceactor.cpp +++ b/LEGO1/lego/legoomni/src/entity/legocarraceactor.cpp @@ -1,5 +1,8 @@ #include "legocarraceactor.h" +#include "geom/legounkown100db7f4.h" +#include "legopathboundary.h" +#include "misc.h" #include "mxmisc.h" #include "mxvariabletable.h" @@ -10,6 +13,7 @@ DECOMP_SIZE_ASSERT(LegoCarRaceActor, 0x1a0) const char* g_fuel = "FUEL"; // FUNCTION: LEGO1 0x10080350 +// FUNCTION: BETA10 0x100cd6b0 LegoCarRaceActor::LegoCarRaceActor() { m_unk0x08 = 1.0f; @@ -27,9 +31,57 @@ LegoCarRaceActor::LegoCarRaceActor() VariableTable()->SetVariable(g_fuel, "0.8"); } -// STUB: LEGO1 0x10080590 -void LegoCarRaceActor::FUN_10080590(float) +// FUNCTION: LEGO1 0x10080590 +// FUNCTION: BETA10 0x100cd8cf +void LegoCarRaceActor::FUN_10080590(float p_float) { + MxFloat maxSpeed = m_maxLinearVel; + Mx3DPointFloat destEdgeUnknownVector; + Mx3DPointFloat worldDirection = Mx3DPointFloat(m_roi->GetWorldDirection()); + + m_destEdge->FUN_1002ddc0(*m_boundary, destEdgeUnknownVector); + + if (abs(destEdgeUnknownVector.Dot(destEdgeUnknownVector.GetData(), worldDirection.GetData())) > 0.5) { + maxSpeed *= m_unk0x10; + } + + MxS32 deltaUnk0x70; + LegoPathActor* userActor = UserActor(); + + if (userActor) { + // All known implementations of LegoPathActor->VTable0x5c() return LegoPathActor::m_unk0x70 + deltaUnk0x70 = m_unk0x70 - userActor->VTable0x5c(); + } + else { + deltaUnk0x70 = 0; + } + + if (deltaUnk0x70 > 1) { + if (deltaUnk0x70 > 3) { + deltaUnk0x70 = 3; + } + + maxSpeed *= (m_unk0x18 * (--deltaUnk0x70) * -0.25f + 1.0f); + } + else if (deltaUnk0x70 < -1) { + maxSpeed *= 1.3; + } + + MxFloat deltaSpeed = maxSpeed - m_worldSpeed; + MxFloat changeInSpeed = (p_float - m_unk0x1c) * m_unk0x14; + m_unk0x1c = p_float; + + if (deltaSpeed < 0.0f) { + changeInSpeed = -changeInSpeed; + } + + MxFloat newWorldSpeed = changeInSpeed + m_worldSpeed; + + if (newWorldSpeed > maxSpeed) { + newWorldSpeed = maxSpeed; + } + + SetWorldSpeed(newWorldSpeed); } // STUB: LEGO1 0x10080740 @@ -37,10 +89,11 @@ void LegoCarRaceActor::VTable0x1c() { } -// STUB: LEGO1 0x10080b40 +// FUNCTION: LEGO1 0x10080b40 +// FUNCTION: BETA10 0x100cdb3c void LegoCarRaceActor::SwitchBoundary(LegoPathBoundary*& p_boundary, LegoUnknown100db7f4*& p_edge, float& p_unk0xe4) { - // TODO + LegoPathActor::SwitchBoundary(m_boundary, m_destEdge, m_unk0xe4); } // STUB: LEGO1 0x10080b70 diff --git a/LEGO1/lego/legoomni/src/race/raceskel.cpp b/LEGO1/lego/legoomni/src/race/raceskel.cpp index dd7e87f3..2d5c71bf 100644 --- a/LEGO1/lego/legoomni/src/race/raceskel.cpp +++ b/LEGO1/lego/legoomni/src/race/raceskel.cpp @@ -1,6 +1,6 @@ #include "raceskel.h" -#include +#include DECOMP_SIZE_ASSERT(RaceSkel, 0x178) diff --git a/LEGO1/lego/sources/geom/legounkown100db7f4.h b/LEGO1/lego/sources/geom/legounkown100db7f4.h index 4e6ffead..68f4c9f2 100644 --- a/LEGO1/lego/sources/geom/legounkown100db7f4.h +++ b/LEGO1/lego/sources/geom/legounkown100db7f4.h @@ -5,6 +5,8 @@ #include "legowegedge.h" #include "mxgeometry/mxgeometry3d.h" +#include + // VTABLE: LEGO1 0x100db7f4 // SIZE 0x40 struct LegoUnknown100db7f4 : public LegoEdge { @@ -28,6 +30,10 @@ struct LegoUnknown100db7f4 : public LegoEdge { p_point[2] = -m_unk0x28[2]; } else { + // clang-format off + // FIXME: There is no * dereference in the original assertion + assert(p_f.IsEqual( *m_faceB )); + // clang-format on p_point = m_unk0x28; } diff --git a/LEGO1/lego/sources/geom/legoweedge.h b/LEGO1/lego/sources/geom/legoweedge.h index 74dc6a45..6e5f4548 100644 --- a/LEGO1/lego/sources/geom/legoweedge.h +++ b/LEGO1/lego/sources/geom/legoweedge.h @@ -20,6 +20,8 @@ class LegoWEEdge { // FUNCTION: BETA10 0x1001cc30 LegoEdge** GetEdges() { return m_edges; } + // TODO: The assertion at BETA10 0x10037352 suggests that this function might take a pointer instead of a reference + // FUNCTION: BETA10 0x100373f0 LegoU32 IsEqual(LegoWEEdge& p_other) { return this == &p_other; } void SetEdges(LegoEdge** p_edges, LegoU8 p_numEdges) diff --git a/LEGO1/realtime/orientableroi.h b/LEGO1/realtime/orientableroi.h index 54059f20..ad84d9ed 100644 --- a/LEGO1/realtime/orientableroi.h +++ b/LEGO1/realtime/orientableroi.h @@ -43,7 +43,10 @@ class OrientableROI : public ROI { const Matrix4& GetLocal2World() const { return m_local2world; } const float* GetWorldPosition() const { return m_local2world[3]; } + + // FUNCTION: BETA10 0x10011780 const float* GetWorldDirection() const { return m_local2world[2]; } + const float* GetWorldUp() const { return m_local2world[1]; } OrientableROI* GetParentROI() const { return m_parentROI; } From 412200ecbc1cdf6dbbd23592a0c6fdffc92df43f Mon Sep 17 00:00:00 2001 From: jonschz <17198703+jonschz@users.noreply.github.com> Date: Tue, 30 Jul 2024 19:02:15 +0200 Subject: [PATCH 02/10] Ghidra Import: Support virtual inheritance (#1071) * Implement/fix Ghidra imports for multiple and virtual inheritance Unfortunately, the handling in Ghidra is still far from perfect. This is a good place to start, though. * Support offsets in vbase pointers * Support `this adjust` * minor stylistic improvement * Improvements to documentation --------- Co-authored-by: jonschz --- .../lego_util/function_importer.py | 137 ++++++++-- .../ghidra_scripts/lego_util/ghidra_helper.py | 18 +- .../lego_util/pdb_extraction.py | 6 + .../ghidra_scripts/lego_util/type_importer.py | 245 +++++++++++++++--- tools/isledecomp/isledecomp/cvdump/types.py | 86 +++++- tools/isledecomp/tests/test_cvdump_types.py | 129 +++++++++ 6 files changed, 549 insertions(+), 72 deletions(-) diff --git a/tools/ghidra_scripts/lego_util/function_importer.py b/tools/ghidra_scripts/lego_util/function_importer.py index 80176fc5..6ccc6935 100644 --- a/tools/ghidra_scripts/lego_util/function_importer.py +++ b/tools/ghidra_scripts/lego_util/function_importer.py @@ -10,6 +10,12 @@ from ghidra.program.flatapi import FlatProgramAPI from ghidra.program.model.listing import ParameterImpl from ghidra.program.model.symbol import SourceType +from ghidra.program.model.data import ( + TypeDef, + TypedefDataType, + Pointer, + ComponentOffsetSettingsDefinition, +) from lego_util.pdb_extraction import ( PdbFunction, @@ -17,12 +23,13 @@ CppStackSymbol, ) from lego_util.ghidra_helper import ( - add_pointer_type, + add_data_type_or_reuse_existing, + get_or_add_pointer_type, get_ghidra_namespace, sanitize_name, ) -from lego_util.exceptions import StackOffsetMismatchError +from lego_util.exceptions import StackOffsetMismatchError, Lego1Exception from lego_util.type_importer import PdbTypeImporter @@ -91,7 +98,10 @@ def matches_ghidra_function(self, ghidra_function: Function) -> bool: if ( (not return_type_match) and (self.return_type.getLength() > 4) - and (add_pointer_type(self.api, self.return_type) == ghidra_return_type) + and ( + get_or_add_pointer_type(self.api, self.return_type) + == ghidra_return_type + ) and any( param for param in ghidra_function.getParameters() @@ -103,19 +113,22 @@ def matches_ghidra_function(self, ghidra_function: Function) -> bool: ) return_type_match = True - # match arguments: decide if thiscall or not + # match arguments: decide if thiscall or not, and whether the `this` type matches thiscall_matches = ( self.signature.call_type == ghidra_function.getCallingConventionName() ) + ghidra_params_without_this = list(ghidra_function.getParameters()) + + if thiscall_matches and self.signature.call_type == "__thiscall": + this_argument = ghidra_params_without_this.pop(0) + thiscall_matches = self._this_type_match(this_argument) + if self.is_stub: # We do not import the argument list for stubs, so it should be excluded in matches args_match = True elif thiscall_matches: - if self.signature.call_type == "__thiscall": - args_match = self._matches_thiscall_parameters(ghidra_function) - else: - args_match = self._matches_non_thiscall_parameters(ghidra_function) + args_match = self._parameter_lists_match(ghidra_params_without_this) else: args_match = False @@ -136,16 +149,22 @@ def matches_ghidra_function(self, ghidra_function: Function) -> bool: and args_match ) - def _matches_non_thiscall_parameters(self, ghidra_function: Function) -> bool: - return self._parameter_lists_match(ghidra_function.getParameters()) + def _this_type_match(self, this_parameter: Parameter) -> bool: + if this_parameter.getName() != "this": + logger.info("Expected first argument to be `this` in __thiscall") + return False - def _matches_thiscall_parameters(self, ghidra_function: Function) -> bool: - ghidra_params = list(ghidra_function.getParameters()) + if self.signature.this_adjust != 0: + # In this case, the `this` argument should be custom defined + if not isinstance(this_parameter.getDataType(), TypeDef): + logger.info( + "`this` argument is not a typedef while `this adjust` = %d", + self.signature.this_adjust, + ) + return False + # We are not checking for the _correct_ `this` type here, which we could do in the future - # remove the `this` argument which we don't generate ourselves - ghidra_params.pop(0) - - return self._parameter_lists_match(ghidra_params) + return True def _parameter_lists_match(self, ghidra_params: "list[Parameter]") -> bool: # Remove return storage pointer from comparison if present. @@ -194,6 +213,25 @@ def _parameter_lists_match(self, ghidra_params: "list[Parameter]") -> bool: def overwrite_ghidra_function(self, ghidra_function: Function): """Replace the function declaration in Ghidra by the one derived from C++.""" + + if ghidra_function.hasCustomVariableStorage(): + # Unfortunately, calling `ghidra_function.setCustomVariableStorage(False)` + # leads to two `this` parameters. Therefore, we first need to remove all `this` parameters + # and then re-generate a new one + ghidra_function.replaceParameters( + Function.FunctionUpdateType.DYNAMIC_STORAGE_ALL_PARAMS, # this implicitly sets custom variable storage to False + True, + SourceType.USER_DEFINED, + [ + param + for param in ghidra_function.getParameters() + if param.getName() != "this" + ], + ) + + if ghidra_function.hasCustomVariableStorage(): + raise Lego1Exception("Failed to disable custom variable storage.") + ghidra_function.setName(self.name, SourceType.USER_DEFINED) ghidra_function.setParentNamespace(self.namespace) ghidra_function.setReturnType(self.return_type, SourceType.USER_DEFINED) @@ -203,16 +241,18 @@ def overwrite_ghidra_function(self, ghidra_function: Function): logger.debug( "%s is a stub, skipping parameter import", self.get_full_name() ) - return + else: + ghidra_function.replaceParameters( + Function.FunctionUpdateType.DYNAMIC_STORAGE_ALL_PARAMS, + True, # force + SourceType.USER_DEFINED, + self.arguments, + ) + self._import_parameter_names(ghidra_function) - ghidra_function.replaceParameters( - Function.FunctionUpdateType.DYNAMIC_STORAGE_ALL_PARAMS, - True, # force - SourceType.USER_DEFINED, - self.arguments, - ) - - self._import_parameter_names(ghidra_function) + # Special handling for `this adjust` and virtual inheritance + if self.signature.this_adjust != 0: + self._set_this_adjust(ghidra_function) def _import_parameter_names(self, ghidra_function: Function): # When we call `ghidra_function.replaceParameters`, Ghidra will generate the layout. @@ -284,3 +324,50 @@ def get_matching_register_symbol( ), None, ) + + def _set_this_adjust( + self, + ghidra_function: Function, + ): + """ + When `this adjust` is non-zero, the pointer type of `this` needs to be replaced by an offset version. + The offset can only be set on a typedef on the pointer. We also must enable custom storage so we can modify + the auto-generated `this` parameter. + """ + + # Necessary in order to overwite the auto-generated `this` + ghidra_function.setCustomVariableStorage(True) + + this_parameter = next( + ( + param + for param in ghidra_function.getParameters() + if param.isRegisterVariable() and param.getName() == "this" + ), + None, + ) + + if this_parameter is None: + logger.error( + "Failed to find `this` parameter in a function with `this adjust = %d`", + self.signature.this_adjust, + ) + else: + current_ghidra_type = this_parameter.getDataType() + assert isinstance(current_ghidra_type, Pointer) + class_name = current_ghidra_type.getDataType().getName() + typedef_name = f"{class_name}PtrOffset0x{self.signature.this_adjust:x}" + + typedef_ghidra_type = TypedefDataType( + current_ghidra_type.getCategoryPath(), + typedef_name, + current_ghidra_type, + ) + ComponentOffsetSettingsDefinition.DEF.setValue( + typedef_ghidra_type.getDefaultSettings(), self.signature.this_adjust + ) + typedef_ghidra_type = add_data_type_or_reuse_existing( + self.api, typedef_ghidra_type + ) + + this_parameter.setDataType(typedef_ghidra_type, SourceType.USER_DEFINED) diff --git a/tools/ghidra_scripts/lego_util/ghidra_helper.py b/tools/ghidra_scripts/lego_util/ghidra_helper.py index f7ea4ec7..f6726482 100644 --- a/tools/ghidra_scripts/lego_util/ghidra_helper.py +++ b/tools/ghidra_scripts/lego_util/ghidra_helper.py @@ -11,10 +11,8 @@ # Disable spurious warnings in vscode / pylance # pyright: reportMissingModuleSource=false -from ghidra.program.model.data import PointerDataType -from ghidra.program.model.data import DataTypeConflictHandler from ghidra.program.flatapi import FlatProgramAPI -from ghidra.program.model.data import DataType +from ghidra.program.model.data import DataType, DataTypeConflictHandler, PointerDataType from ghidra.program.model.symbol import Namespace logger = logging.getLogger(__name__) @@ -37,9 +35,15 @@ def get_ghidra_type(api: FlatProgramAPI, type_name: str): raise MultipleTypesFoundInGhidraError(type_name, result) -def add_pointer_type(api: FlatProgramAPI, pointee: DataType) -> DataType: - new_data_type = PointerDataType(pointee) - new_data_type.setCategoryPath(pointee.getCategoryPath()) +def get_or_add_pointer_type(api: FlatProgramAPI, pointee: DataType) -> DataType: + new_pointer_data_type = PointerDataType(pointee) + new_pointer_data_type.setCategoryPath(pointee.getCategoryPath()) + return add_data_type_or_reuse_existing(api, new_pointer_data_type) + + +def add_data_type_or_reuse_existing( + api: FlatProgramAPI, new_data_type: DataType +) -> DataType: result_data_type = ( api.getCurrentProgram() .getDataTypeManager() @@ -47,7 +51,7 @@ def add_pointer_type(api: FlatProgramAPI, pointee: DataType) -> DataType: ) if result_data_type is not new_data_type: logger.debug( - "New pointer replaced by existing one. Fresh pointer: %s (class: %s)", + "Reusing existing data type instead of new one: %s (class: %s)", result_data_type, result_data_type.__class__, ) diff --git a/tools/ghidra_scripts/lego_util/pdb_extraction.py b/tools/ghidra_scripts/lego_util/pdb_extraction.py index 0c2ef7dc..4ba1ac71 100644 --- a/tools/ghidra_scripts/lego_util/pdb_extraction.py +++ b/tools/ghidra_scripts/lego_util/pdb_extraction.py @@ -36,6 +36,8 @@ class FunctionSignature: return_type: str class_type: Optional[str] stack_symbols: list[CppStackOrRegisterSymbol] + # if non-zero: an offset to the `this` parameter in a __thiscall + this_adjust: int @dataclass @@ -119,6 +121,9 @@ def get_func_signature(self, fn: SymbolsEntry) -> Optional[FunctionSignature]: call_type = self._call_type_map[function_type["call_type"]] + # parse as hex number, default to 0 + this_adjust = int(function_type.get("this_adjust", "0"), 16) + return FunctionSignature( original_function_symbol=fn, call_type=call_type, @@ -126,6 +131,7 @@ def get_func_signature(self, fn: SymbolsEntry) -> Optional[FunctionSignature]: return_type=function_type["return_type"], class_type=class_type, stack_symbols=stack_symbols, + this_adjust=this_adjust, ) def get_function_list(self) -> list[PdbFunction]: diff --git a/tools/ghidra_scripts/lego_util/type_importer.py b/tools/ghidra_scripts/lego_util/type_importer.py index c645ebf8..1f4a077e 100644 --- a/tools/ghidra_scripts/lego_util/type_importer.py +++ b/tools/ghidra_scripts/lego_util/type_importer.py @@ -1,5 +1,5 @@ import logging -from typing import Any, Callable, TypeVar +from typing import Any, Callable, Iterator, Optional, TypeVar # Disable spurious warnings in vscode / pylance # pyright: reportMissingModuleSource=false @@ -7,6 +7,7 @@ # pylint: disable=too-many-return-statements # a `match` would be better, but for now we are stuck with Python 3.9 # pylint: disable=no-else-return # Not sure why this rule even is a thing, this is great for checking exhaustiveness +from isledecomp.cvdump.types import VirtualBasePointer from lego_util.exceptions import ( ClassOrNamespaceNotFoundInGhidraError, TypeNotFoundError, @@ -15,7 +16,8 @@ StructModificationError, ) from lego_util.ghidra_helper import ( - add_pointer_type, + add_data_type_or_reuse_existing, + get_or_add_pointer_type, create_ghidra_namespace, get_ghidra_namespace, get_ghidra_type, @@ -33,6 +35,8 @@ EnumDataType, StructureDataType, StructureInternal, + TypedefDataType, + ComponentOffsetSettingsDefinition, ) from ghidra.util.task import ConsoleTaskMonitor @@ -56,10 +60,19 @@ def __init__(self, api: FlatProgramAPI, extraction: PdbFunctionExtractor): def types(self): return self.extraction.compare.cv.types - def import_pdb_type_into_ghidra(self, type_index: str) -> DataType: + def import_pdb_type_into_ghidra( + self, type_index: str, slim_for_vbase: bool = False + ) -> DataType: """ Recursively imports a type from the PDB into Ghidra. @param type_index Either a scalar type like `T_INT4(...)` or a PDB reference like `0x10ba` + @param slim_for_vbase If true, the current invocation + imports a superclass of some class where virtual inheritance is involved (directly or indirectly). + This case requires special handling: Let's say we have `class C: B` and `class B: virtual A`. Then cvdump + reports a size for B that includes both B's fields as well as the A contained at an offset within B, + which is not the correct structure to be contained in C. Therefore, we need to create a "slim" version of B + that fits inside C. + This value should always be `False` when the referenced type is not (a pointer to) a class. """ type_index_lower = type_index.lower() if type_index_lower.startswith("t_"): @@ -76,14 +89,19 @@ def import_pdb_type_into_ghidra(self, type_index: str) -> DataType: # follow forward reference (class, struct, union) if type_pdb.get("is_forward_ref", False): - return self._import_forward_ref_type(type_index_lower, type_pdb) + return self._import_forward_ref_type( + type_index_lower, type_pdb, slim_for_vbase + ) if type_category == "LF_POINTER": - return add_pointer_type( - self.api, self.import_pdb_type_into_ghidra(type_pdb["element_type"]) + return get_or_add_pointer_type( + self.api, + self.import_pdb_type_into_ghidra( + type_pdb["element_type"], slim_for_vbase + ), ) elif type_category in ["LF_CLASS", "LF_STRUCTURE"]: - return self._import_class_or_struct(type_pdb) + return self._import_class_or_struct(type_pdb, slim_for_vbase) elif type_category == "LF_ARRAY": return self._import_array(type_pdb) elif type_category == "LF_ENUM": @@ -120,7 +138,10 @@ def _import_scalar_type(self, type_index_lower: str) -> DataType: return get_ghidra_type(self.api, scalar_cpp_type) def _import_forward_ref_type( - self, type_index, type_pdb: dict[str, Any] + self, + type_index, + type_pdb: dict[str, Any], + slim_for_vbase: bool = False, ) -> DataType: referenced_type = type_pdb.get("udt") or type_pdb.get("modifies") if referenced_type is None: @@ -136,7 +157,7 @@ def _import_forward_ref_type( type_index, referenced_type, ) - return self.import_pdb_type_into_ghidra(referenced_type) + return self.import_pdb_type_into_ghidra(referenced_type, slim_for_vbase) def _import_array(self, type_pdb: dict[str, Any]) -> DataType: inner_type = self.import_pdb_type_into_ghidra(type_pdb["array_type"]) @@ -182,12 +203,18 @@ def _import_enum(self, type_pdb: dict[str, Any]) -> DataType: return result - def _import_class_or_struct(self, type_in_pdb: dict[str, Any]) -> DataType: + def _import_class_or_struct( + self, + type_in_pdb: dict[str, Any], + slim_for_vbase: bool = False, + ) -> DataType: field_list_type: str = type_in_pdb["field_list_type"] field_list = self.types.keys[field_list_type.lower()] class_size: int = type_in_pdb["size"] class_name_with_namespace: str = sanitize_name(type_in_pdb["name"]) + if slim_for_vbase: + class_name_with_namespace += "_vbase_slim" if class_name_with_namespace in self.handled_structs: logger.debug( @@ -205,11 +232,11 @@ def _import_class_or_struct(self, type_in_pdb: dict[str, Any]) -> DataType: self._get_or_create_namespace(class_name_with_namespace) - data_type = self._get_or_create_struct_data_type( + new_ghidra_struct = self._get_or_create_struct_data_type( class_name_with_namespace, class_size ) - if (old_size := data_type.getLength()) != class_size: + if (old_size := new_ghidra_struct.getLength()) != class_size: logger.warning( "Existing class %s had incorrect size %d. Setting to %d...", class_name_with_namespace, @@ -220,39 +247,189 @@ def _import_class_or_struct(self, type_in_pdb: dict[str, Any]) -> DataType: logger.info("Adding class data type %s", class_name_with_namespace) logger.debug("Class information: %s", type_in_pdb) - data_type.deleteAll() - data_type.growStructure(class_size) + components: list[dict[str, Any]] = [] + components.extend(self._get_components_from_base_classes(field_list)) + # can be missing when no new fields are declared + components.extend(self._get_components_from_members(field_list)) + components.extend( + self._get_components_from_vbase( + field_list, class_name_with_namespace, new_ghidra_struct + ) + ) + + components.sort(key=lambda c: c["offset"]) + + if slim_for_vbase: + # Make a "slim" version: shrink the size to the fields that are actually present. + # This makes a difference when the current class uses virtual inheritance + assert ( + len(components) > 0 + ), f"Error: {class_name_with_namespace} should not be empty. There must be at least one direct or indirect vbase pointer." + last_component = components[-1] + class_size = last_component["offset"] + last_component["type"].getLength() + + self._overwrite_struct( + class_name_with_namespace, + new_ghidra_struct, + class_size, + components, + ) + + logger.info("Finished importing class %s", class_name_with_namespace) + + return new_ghidra_struct + + def _get_components_from_base_classes(self, field_list) -> Iterator[dict[str, Any]]: + non_virtual_base_classes: dict[str, int] = field_list.get("super", {}) + + for super_type, offset in non_virtual_base_classes.items(): + # If we have virtual inheritance _and_ a non-virtual base class here, we play safe and import slim version. + # This is technically not needed if only one of the superclasses uses virtual inheritance, but I am not aware of any instance. + import_slim_vbase_version_of_superclass = "vbase" in field_list + ghidra_type = self.import_pdb_type_into_ghidra( + super_type, slim_for_vbase=import_slim_vbase_version_of_superclass + ) + + yield { + "type": ghidra_type, + "offset": offset, + "name": "base" if offset == 0 else f"base_{ghidra_type.getName()}", + } + + def _get_components_from_members(self, field_list: dict[str, Any]): + members: list[dict[str, Any]] = field_list.get("members") or [] + for member in members: + yield member | {"type": self.import_pdb_type_into_ghidra(member["type"])} + + def _get_components_from_vbase( + self, + field_list: dict[str, Any], + class_name_with_namespace: str, + current_type: StructureInternal, + ) -> Iterator[dict[str, Any]]: + vbasepointer: Optional[VirtualBasePointer] = field_list.get("vbase", None) + + if vbasepointer is not None and any(x.direct for x in vbasepointer.bases): + vbaseptr_type = get_or_add_pointer_type( + self.api, + self._import_vbaseptr( + current_type, class_name_with_namespace, vbasepointer + ), + ) + yield { + "type": vbaseptr_type, + "offset": vbasepointer.vboffset, + "name": "vbase_offset", + } + + def _import_vbaseptr( + self, + current_type: StructureInternal, + class_name_with_namespace: str, + vbasepointer: VirtualBasePointer, + ) -> StructureInternal: + pointer_size = 4 # hard-code to 4 because of 32 bit + + components = [ + { + "offset": 0, + "type": get_or_add_pointer_type(self.api, current_type), + "name": "o_self", + } + ] + for vbase in vbasepointer.bases: + vbase_ghidra_type = self.import_pdb_type_into_ghidra(vbase.type) + + type_name = vbase_ghidra_type.getName() + + vbase_ghidra_pointer = get_or_add_pointer_type(self.api, vbase_ghidra_type) + vbase_ghidra_pointer_typedef = TypedefDataType( + vbase_ghidra_pointer.getCategoryPath(), + f"{type_name}PtrOffset", + vbase_ghidra_pointer, + ) + # Set a default value of -4 for the pointer offset. While this appears to be correct in many cases, + # it does not always lead to the best decompile. It can be fine-tuned by hand; the next function call + # makes sure that we don't overwrite this value on re-running the import. + ComponentOffsetSettingsDefinition.DEF.setValue( + vbase_ghidra_pointer_typedef.getDefaultSettings(), -4 + ) + + vbase_ghidra_pointer_typedef = add_data_type_or_reuse_existing( + self.api, vbase_ghidra_pointer_typedef + ) + + components.append( + { + "offset": vbase.index * pointer_size, + "type": vbase_ghidra_pointer_typedef, + "name": f"o_{type_name}", + } + ) + + size = len(components) * pointer_size + + new_ghidra_struct = self._get_or_create_struct_data_type( + f"{class_name_with_namespace}::VBasePtr", size + ) + + self._overwrite_struct( + f"{class_name_with_namespace}::VBasePtr", + new_ghidra_struct, + size, + components, + ) + + return new_ghidra_struct + + def _overwrite_struct( + self, + class_name_with_namespace: str, + new_ghidra_struct: StructureInternal, + class_size: int, + components: list[dict[str, Any]], + ): + new_ghidra_struct.deleteAll() + new_ghidra_struct.growStructure(class_size) # this case happened e.g. for IUnknown, which linked to an (incorrect) existing library, and some other types as well. # Unfortunately, we don't get proper error handling for read-only types. # However, we really do NOT want to do this every time because the type might be self-referential and partially imported. - if data_type.getLength() != class_size: - data_type = self._delete_and_recreate_struct_data_type( - class_name_with_namespace, class_size, data_type + if new_ghidra_struct.getLength() != class_size: + new_ghidra_struct = self._delete_and_recreate_struct_data_type( + class_name_with_namespace, class_size, new_ghidra_struct ) - # can be missing when no new fields are declared - components: list[dict[str, Any]] = field_list.get("members") or [] - - super_type = field_list.get("super") - if super_type is not None: - components.insert(0, {"type": super_type, "offset": 0, "name": "base"}) - for component in components: - ghidra_type = self.import_pdb_type_into_ghidra(component["type"]) - logger.debug("Adding component to class: %s", component) + offset: int = component["offset"] + logger.debug( + "Adding component %s to class: %s", component, class_name_with_namespace + ) try: - # for better logs - data_type.replaceAtOffset( - component["offset"], ghidra_type, -1, component["name"], None + # Make sure there is room for the new structure and that we have no collision. + existing_type = new_ghidra_struct.getComponentAt(offset) + assert ( + existing_type is not None + ), f"Struct collision: Offset {offset} in {class_name_with_namespace} is overlapped by another component" + + if existing_type.getDataType().getName() != "undefined": + # collision of structs beginning in the same place -> likely due to unions + logger.warning( + "Struct collision: Offset %d of %s already has a field (likely an inline union)", + offset, + class_name_with_namespace, + ) + + new_ghidra_struct.replaceAtOffset( + offset, + component["type"], + -1, # set to -1 for fixed-size components + component["name"], # name + None, # comment ) except Exception as e: - raise StructModificationError(type_in_pdb) from e - - logger.info("Finished importing class %s", class_name_with_namespace) - - return data_type + raise StructModificationError(class_name_with_namespace) from e def _get_or_create_namespace(self, class_name_with_namespace: str): colon_split = class_name_with_namespace.split("::") diff --git a/tools/isledecomp/isledecomp/cvdump/types.py b/tools/isledecomp/isledecomp/cvdump/types.py index b39ea248..42a9e985 100644 --- a/tools/isledecomp/isledecomp/cvdump/types.py +++ b/tools/isledecomp/isledecomp/cvdump/types.py @@ -1,3 +1,4 @@ +from dataclasses import dataclass import re import logging from typing import Any, Dict, List, NamedTuple, Optional @@ -26,6 +27,19 @@ class FieldListItem(NamedTuple): type: str +@dataclass +class VirtualBaseClass: + type: str + index: int + direct: bool + + +@dataclass +class VirtualBasePointer: + vboffset: int + bases: list[VirtualBaseClass] + + class ScalarType(NamedTuple): offset: int name: Optional[str] @@ -157,6 +171,16 @@ class CvdumpTypesParser: r"^\s+list\[\d+\] = LF_BCLASS, (?P\w+), type = (?P.*), offset = (?P\d+)" ) + # LF_FIELDLIST virtual direct/indirect base pointer, line 1/2 + VBCLASS_RE = re.compile( + r"^\s+list\[\d+\] = LF_(?PI?)VBCLASS, .* base type = (?P.*)$" + ) + + # LF_FIELDLIST virtual direct/indirect base pointer, line 2/2 + VBCLASS_LINE_2_RE = re.compile( + r"^\s+virtual base ptr = .+, vbpoff = (?P\d+), vbind = (?P\d+)$" + ) + # LF_FIELDLIST member name (2/2) MEMBER_RE = re.compile(r"^\s+member name = '(?P.*)'$") @@ -206,7 +230,7 @@ class CvdumpTypesParser: re.compile(r"\s*Arg list type = (?P[\w()]+)$"), re.compile( r"\s*This adjust = (?P[\w()]+)$" - ), # TODO: figure out the meaning + ), # By how much the incoming pointers are shifted in virtual inheritance; hex value without `0x` prefix re.compile( r"\s*Func attr = (?P[\w()]+)$" ), # Only for completeness, is always `none` @@ -282,12 +306,12 @@ def _get_field_list(self, type_obj: Dict[str, Any]) -> List[FieldListItem]: members: List[FieldListItem] = [] - super_id = field_obj.get("super") - if super_id is not None: + super_ids = field_obj.get("super", []) + for super_id in super_ids: # May need to resolve forward ref. superclass = self.get(super_id) if superclass.members is not None: - members = superclass.members + members += superclass.members raw_members = field_obj.get("members", []) members += [ @@ -526,7 +550,57 @@ def read_fieldlist_line(self, line: str): # Superclass is set here in the fieldlist rather than in LF_CLASS elif (match := self.SUPERCLASS_RE.match(line)) is not None: - self._set("super", normalize_type_id(match.group("type"))) + superclass_list: dict[str, int] = self.keys[self.last_key].setdefault( + "super", {} + ) + superclass_list[normalize_type_id(match.group("type"))] = int( + match.group("offset") + ) + + # virtual base class (direct or indirect) + elif (match := self.VBCLASS_RE.match(line)) is not None: + virtual_base_pointer = self.keys[self.last_key].setdefault( + "vbase", + VirtualBasePointer( + vboffset=-1, # default to -1 until we parse the correct value + bases=[], + ), + ) + assert isinstance( + virtual_base_pointer, VirtualBasePointer + ) # type checker only + + virtual_base_pointer.bases.append( + VirtualBaseClass( + type=match.group("type"), + index=-1, # default to -1 until we parse the correct value + direct=match.group("indirect") != "I", + ) + ) + + elif (match := self.VBCLASS_LINE_2_RE.match(line)) is not None: + virtual_base_pointer = self.keys[self.last_key].get("vbase", None) + assert isinstance( + virtual_base_pointer, VirtualBasePointer + ), "Parsed the second line of an (I)VBCLASS without the first one" + vboffset = int(match.group("vboffset")) + + if virtual_base_pointer.vboffset == -1: + # default value + virtual_base_pointer.vboffset = vboffset + elif virtual_base_pointer.vboffset != vboffset: + # vboffset is always equal to 4 in our examples. We are not sure if there can be multiple + # virtual base pointers, and if so, how the layout is supposed to look. + # We therefore assume that there is always only one virtual base pointer. + logger.error( + "Unhandled: Found multiple virtual base pointers at offsets %d and %d", + virtual_base_pointer.vboffset, + vboffset, + ) + + virtual_base_pointer.bases[-1].index = int(match.group("vbindex")) + # these come out of order, and the lists are so short that it's fine to sort them every time + virtual_base_pointer.bases.sort(key=lambda x: x.index) # Member offset and type given on the first of two lines. elif (match := self.LIST_RE.match(line)) is not None: @@ -579,7 +653,7 @@ def read_arglist_line(self, line: str): else: logger.error("Unmatched line in arglist: %s", line[:-1]) - def read_pointer_line(self, line): + def read_pointer_line(self, line: str): if (match := self.LF_POINTER_ELEMENT.match(line)) is not None: self._set("element_type", match.group("element_type")) else: diff --git a/tools/isledecomp/tests/test_cvdump_types.py b/tools/isledecomp/tests/test_cvdump_types.py index 324870eb..3bd6afa0 100644 --- a/tools/isledecomp/tests/test_cvdump_types.py +++ b/tools/isledecomp/tests/test_cvdump_types.py @@ -6,6 +6,9 @@ CvdumpTypesParser, CvdumpKeyError, CvdumpIntegrityError, + FieldListItem, + VirtualBaseClass, + VirtualBasePointer, ) TEST_LINES = """ @@ -245,10 +248,111 @@ list[12] = LF_MEMBER, private, type = T_USHORT(0021), offset = 12 member name = 'm_length' + +0x4dee : Length = 406, Leaf = 0x1203 LF_FIELDLIST + list[0] = LF_VBCLASS, public, direct base type = 0x15EA + virtual base ptr = 0x43E9, vbpoff = 4, vbind = 3 + list[1] = LF_IVBCLASS, public, indirect base type = 0x1183 + virtual base ptr = 0x43E9, vbpoff = 4, vbind = 1 + list[2] = LF_IVBCLASS, public, indirect base type = 0x1468 + virtual base ptr = 0x43E9, vbpoff = 4, vbind = 2 + list[3] = LF_VFUNCTAB, type = 0x2B95 + list[4] = LF_ONEMETHOD, public, VANILLA, index = 0x15C2, name = 'LegoRaceMap' + list[5] = LF_ONEMETHOD, public, VIRTUAL, index = 0x15C3, name = '~LegoRaceMap' + list[6] = LF_ONEMETHOD, public, VIRTUAL, index = 0x15C5, name = 'Notify' + list[7] = LF_ONEMETHOD, public, VIRTUAL, index = 0x15C4, name = 'ParseAction' + list[8] = LF_ONEMETHOD, public, VIRTUAL, index = 0x4DED, name = 'VTable0x70' + list[9] = LF_ONEMETHOD, public, INTRODUCING VIRTUAL, index = 0x15C2, + vfptr offset = 0, name = 'FUN_1005d4b0' + list[10] = LF_MEMBER, private, type = T_UCHAR(0020), offset = 8 + member name = 'm_parentClass2Field1' + list[11] = LF_MEMBER, private, type = T_32PVOID(0403), offset = 12 + member name = 'm_parentClass2Field2' + +0x4def : Length = 34, Leaf = 0x1504 LF_CLASS + # members = 21, field list type 0x4dee, CONSTRUCTOR, + Derivation list type 0x0000, VT shape type 0x12a0 + Size = 436, class name = LegoRaceMap, UDT(0x00004def) + 0x4db6 : Length = 30, Leaf = 0x1504 LF_CLASS # members = 16, field list type 0x4db5, CONSTRUCTOR, OVERLOAD, Derivation list type 0x0000, VT shape type 0x1266 Size = 16, class name = MxString, UDT(0x00004db6) + +0x5591 : Length = 570, Leaf = 0x1203 LF_FIELDLIST + list[0] = LF_VBCLASS, public, direct base type = 0x15EA + virtual base ptr = 0x43E9, vbpoff = 4, vbind = 3 + list[1] = LF_IVBCLASS, public, indirect base type = 0x1183 + virtual base ptr = 0x43E9, vbpoff = 4, vbind = 1 + list[2] = LF_IVBCLASS, public, indirect base type = 0x1468 + virtual base ptr = 0x43E9, vbpoff = 4, vbind = 2 + list[3] = LF_VFUNCTAB, type = 0x4E11 + list[4] = LF_ONEMETHOD, public, VANILLA, index = 0x1ABD, name = 'LegoCarRaceActor' + list[5] = LF_ONEMETHOD, public, VIRTUAL, index = 0x1AE0, name = 'ClassName' + list[6] = LF_ONEMETHOD, public, VIRTUAL, index = 0x1AE1, name = 'IsA' + list[7] = LF_ONEMETHOD, public, VIRTUAL, index = 0x1ADD, name = 'VTable0x6c' + list[8] = LF_ONEMETHOD, public, VIRTUAL, index = 0x1ADB, name = 'VTable0x70' + list[9] = LF_ONEMETHOD, public, VIRTUAL, index = 0x1ADA, name = 'SwitchBoundary' + list[10] = LF_ONEMETHOD, public, VIRTUAL, index = 0x1ADC, name = 'VTable0x9c' + list[11] = LF_ONEMETHOD, public, INTRODUCING VIRTUAL, index = 0x558E, + vfptr offset = 0, name = 'FUN_10080590' + list[12] = LF_ONEMETHOD, public, INTRODUCING VIRTUAL, index = 0x1AD8, + vfptr offset = 4, name = 'FUN_10012bb0' + list[13] = LF_ONEMETHOD, public, INTRODUCING VIRTUAL, index = 0x1AD9, + vfptr offset = 8, name = 'FUN_10012bc0' + list[14] = LF_ONEMETHOD, public, INTRODUCING VIRTUAL, index = 0x1AD8, + vfptr offset = 12, name = 'FUN_10012bd0' + list[15] = LF_ONEMETHOD, public, INTRODUCING VIRTUAL, index = 0x1AD9, + vfptr offset = 16, name = 'FUN_10012be0' + list[16] = LF_ONEMETHOD, public, INTRODUCING VIRTUAL, index = 0x1AD8, + vfptr offset = 20, name = 'FUN_10012bf0' + list[17] = LF_ONEMETHOD, public, INTRODUCING VIRTUAL, index = 0x1AD9, + vfptr offset = 24, name = 'FUN_10012c00' + list[18] = LF_ONEMETHOD, public, INTRODUCING VIRTUAL, index = 0x1ABD, + vfptr offset = 28, name = 'VTable0x1c' + list[19] = LF_MEMBER, protected, type = T_REAL32(0040), offset = 8 + member name = 'm_parentClass1Field1' + list[25] = LF_ONEMETHOD, public, VIRTUAL, (compgenx), index = 0x15D1, name = '~LegoCarRaceActor' + +0x5592 : Length = 38, Leaf = 0x1504 LF_CLASS + # members = 26, field list type 0x5591, CONSTRUCTOR, + Derivation list type 0x0000, VT shape type 0x34c7 + Size = 416, class name = LegoCarRaceActor, UDT(0x00005592) + +0x5593 : Length = 638, Leaf = 0x1203 LF_FIELDLIST + list[0] = LF_BCLASS, public, type = 0x5592, offset = 0 + list[1] = LF_BCLASS, public, type = 0x4DEF, offset = 32 + list[2] = LF_IVBCLASS, public, indirect base type = 0x1183 + virtual base ptr = 0x43E9, vbpoff = 4, vbind = 1 + list[3] = LF_IVBCLASS, public, indirect base type = 0x1468 + virtual base ptr = 0x43E9, vbpoff = 4, vbind = 2 + list[4] = LF_IVBCLASS, public, indirect base type = 0x15EA + virtual base ptr = 0x43E9, vbpoff = 4, vbind = 3 + list[5] = LF_ONEMETHOD, public, VANILLA, index = 0x15CD, name = 'LegoRaceCar' + list[6] = LF_ONEMETHOD, public, VIRTUAL, index = 0x15CE, name = '~LegoRaceCar' + list[7] = LF_ONEMETHOD, public, VIRTUAL, index = 0x15D2, name = 'Notify' + list[8] = LF_ONEMETHOD, public, VIRTUAL, index = 0x15E8, name = 'ClassName' + list[9] = LF_ONEMETHOD, public, VIRTUAL, index = 0x15E9, name = 'IsA' + list[10] = LF_ONEMETHOD, public, VIRTUAL, index = 0x15D5, name = 'ParseAction' + list[11] = LF_ONEMETHOD, public, VIRTUAL, index = 0x15D3, name = 'SetWorldSpeed' + list[12] = LF_ONEMETHOD, public, VIRTUAL, index = 0x15DF, name = 'VTable0x6c' + list[13] = LF_ONEMETHOD, public, VIRTUAL, index = 0x15D3, name = 'VTable0x70' + list[14] = LF_ONEMETHOD, public, VIRTUAL, index = 0x15DC, name = 'VTable0x94' + list[15] = LF_ONEMETHOD, public, VIRTUAL, index = 0x15E5, name = 'SwitchBoundary' + list[16] = LF_ONEMETHOD, public, VIRTUAL, index = 0x15DD, name = 'VTable0x9c' + list[17] = LF_ONEMETHOD, public, INTRODUCING VIRTUAL, index = 0x15D4, + vfptr offset = 32, name = 'SetMaxLinearVelocity' + list[18] = LF_ONEMETHOD, public, INTRODUCING VIRTUAL, index = 0x15D4, + vfptr offset = 36, name = 'FUN_10012ff0' + list[19] = LF_ONEMETHOD, public, INTRODUCING VIRTUAL, index = 0x5588, + vfptr offset = 40, name = 'HandleSkeletonKicks' + list[20] = LF_MEMBER, private, type = T_UCHAR(0020), offset = 84 + member name = 'm_childClassField' + +0x5594 : Length = 34, Leaf = 0x1504 LF_CLASS + # members = 30, field list type 0x5593, CONSTRUCTOR, + Derivation list type 0x0000, VT shape type 0x2d1e + Size = 512, class name = LegoRaceCar, UDT(0x000055bb) """ @@ -309,6 +413,31 @@ def test_members(parser: CvdumpTypesParser): (12, "m_length", "T_USHORT"), ] + # LegoRaceCar with multiple superclasses + assert parser.get("0x5594").members == [ + FieldListItem(offset=0, name="vftable", type="T_32PVOID"), + FieldListItem(offset=0, name="vftable", type="T_32PVOID"), + FieldListItem(offset=8, name="m_parentClass1Field1", type="T_REAL32"), + FieldListItem(offset=8, name="m_parentClass2Field1", type="T_UCHAR"), + FieldListItem(offset=12, name="m_parentClass2Field2", type="T_32PVOID"), + FieldListItem(offset=84, name="m_childClassField", type="T_UCHAR"), + ] + + +def test_virtual_base_classes(parser: CvdumpTypesParser): + """Make sure that virtual base classes are parsed correctly.""" + + lego_car_race_actor = parser.keys.get("0x5591") + assert lego_car_race_actor is not None + assert lego_car_race_actor["vbase"] == VirtualBasePointer( + vboffset=4, + bases=[ + VirtualBaseClass(type="0x1183", index=1, direct=False), + VirtualBaseClass(type="0x1468", index=2, direct=False), + VirtualBaseClass(type="0x15EA", index=3, direct=True), + ], + ) + def test_members_recursive(parser: CvdumpTypesParser): """Make sure that we unwrap the dependency tree correctly.""" From 4a41671759b8d5e752b231e879816ebbe568188b Mon Sep 17 00:00:00 2001 From: jonschz <17198703+jonschz@users.noreply.github.com> Date: Sat, 3 Aug 2024 07:29:21 +0200 Subject: [PATCH 03/10] Implement/match `LegoRaceActor` (#1072) * Implement LegoRaceActor, add annotations * Change code duplication comments --------- Co-authored-by: jonschz --- LEGO1/define.cpp | 4 + LEGO1/define.h | 1 + LEGO1/lego/legoomni/include/legoraceactor.h | 20 +++- .../lego/legoomni/src/race/legoraceactor.cpp | 112 ++++++++++++++++-- LEGO1/lego/legoomni/src/race/legoracers.cpp | 1 + LEGO1/realtime/matrix.h | 1 + LEGO1/realtime/vector.h | 2 + 7 files changed, 125 insertions(+), 16 deletions(-) diff --git a/LEGO1/define.cpp b/LEGO1/define.cpp index 4460107f..1e1ac43a 100644 --- a/LEGO1/define.cpp +++ b/LEGO1/define.cpp @@ -139,3 +139,7 @@ const char* g_strBMP_ISMAP = "BMP_ISMAP"; // GLOBAL: LEGO1 0x101020e4 // STRING: LEGO1 0x10101eac const char* g_parseExtraTokens = ":;"; + +// GLOBAL: LEGO1 0x100f0c14 +// STRING: LEGO1 0x100f0c04 +const char* g_strHIT_ACTOR_SOUND = "HIT_ACTOR_SOUND"; diff --git a/LEGO1/define.h b/LEGO1/define.h index 1704d045..14a814ea 100644 --- a/LEGO1/define.h +++ b/LEGO1/define.h @@ -38,5 +38,6 @@ extern const char* g_strDB_CREATE; extern const char* g_strPERMIT_NAVIGATE; extern const char* g_strPATH; extern const char* g_strCOLLIDEBOX; +extern const char* g_strHIT_ACTOR_SOUND; #endif // DEFINE_H diff --git a/LEGO1/lego/legoomni/include/legoraceactor.h b/LEGO1/lego/legoomni/include/legoraceactor.h index 57325256..1fb1b28c 100644 --- a/LEGO1/lego/legoomni/include/legoraceactor.h +++ b/LEGO1/lego/legoomni/include/legoraceactor.h @@ -8,12 +8,16 @@ class Matrix4; // VTABLE: LEGO1 0x100d5b78 LegoAnimActor // VTABLE: LEGO1 0x100d5b88 LegoPathActor // VTABLE: LEGO1 0x100d5c54 LegoRaceActor +// VTABLE: BETA10 0x101be380 LegoAnimActor +// VTABLE: BETA10 0x101be398 LegoPathActor +// VTABLE: BETA10 0x101be488 LegoRaceActor // SIZE 0x180 class LegoRaceActor : public virtual LegoAnimActor { public: LegoRaceActor(); // FUNCTION: LEGO1 0x10014b00 + // FUNCTION: BETA10 0x100aaae0 const char* ClassName() const override // vtable+0x0c { // STRING: LEGO1 0x100f0bf4 @@ -26,9 +30,9 @@ class LegoRaceActor : public virtual LegoAnimActor { return !strcmp(p_name, LegoRaceActor::ClassName()) || LegoAnimActor::IsA(p_name); } - MxS32 VTable0x68(Vector3&, Vector3&, Vector3&) override; // vtable+0x68 - MxU32 VTable0x90(float, Matrix4&) override; // vtable+0x90 - MxResult VTable0x94(LegoPathActor* p_actor, MxBool p_bool) override; // vtable+0x94 + MxS32 VTable0x68(Vector3& p_v1, Vector3& p_v2, Vector3& p_v3) override; // vtable+0x68 + MxU32 VTable0x90(float p_float, Matrix4& p_matrix) override; // vtable+0x90 + MxResult VTable0x94(LegoPathActor* p_actor, MxBool p_bool) override; // vtable+0x94 // FUNCTION: LEGO1 0x10014aa0 virtual MxResult FUN_10014aa0() { return SUCCESS; } @@ -40,7 +44,15 @@ class LegoRaceActor : public virtual LegoAnimActor { // LegoRaceActor::`scalar deleting destructor' private: - undefined4 m_unk0x08; // 0x08 + MxFloat m_unk0x08; // 0x08 + + static Mx3DPointFloat g_unk0x10102b08; }; +// GLOBAL: LEGO1 0x100d5b68 +// LegoRaceActor::`vbtable'{for `LegoRaceActor'} + +// GLOBAL: LEGO1 0x100d5b60 +// LegoRaceActor::`vbtable'{for `LegoAnimActor'} + #endif // LEGORACEACTOR_H diff --git a/LEGO1/lego/legoomni/src/race/legoraceactor.cpp b/LEGO1/lego/legoomni/src/race/legoraceactor.cpp index 9bc47709..32455e4a 100644 --- a/LEGO1/lego/legoomni/src/race/legoraceactor.cpp +++ b/LEGO1/lego/legoomni/src/race/legoraceactor.cpp @@ -1,7 +1,20 @@ #include "legoraceactor.h" +#include "define.h" +#include "legocachesoundmanager.h" +#include "legosoundmanager.h" +#include "misc.h" +#include "mxmisc.h" +#include "mxtimer.h" +#include "mxvariabletable.h" +#include "roi/legoroi.h" + DECOMP_SIZE_ASSERT(LegoRaceActor, 0x180) +// Initialized at LEGO1 0x100145a0 +// GLOBAL: LEGO1 0x10102b08 +Mx3DPointFloat LegoRaceActor::g_unk0x10102b08 = Mx3DPointFloat(0.0, 2.0, 0.0); + // FUNCTION: LEGO1 0x100145d0 LegoRaceActor::LegoRaceActor() { @@ -9,23 +22,98 @@ LegoRaceActor::LegoRaceActor() m_unk0x08 = 0; } -// STUB: LEGO1 0x10014750 -MxS32 LegoRaceActor::VTable0x68(Vector3&, Vector3&, Vector3&) +// FUNCTION: LEGO1 0x10014750 +// FUNCTION: BETA10 0x100c9bba +MxS32 LegoRaceActor::VTable0x68(Vector3& p_v1, Vector3& p_v2, Vector3& p_v3) { - // TODO + MxS32 result = LegoPathActor::VTable0x68(p_v1, p_v2, p_v3); + + if (m_userNavFlag && result) { + MxLong time = Timer()->GetTime(); + if (time - g_unk0x100f3308 > 1000) { + g_unk0x100f3308 = time; + const char* soundKey = VariableTable()->GetVariable(g_strHIT_ACTOR_SOUND); + + if (soundKey && *soundKey) { + SoundManager()->GetCacheSoundManager()->Play(soundKey, NULL, FALSE); + } + } + } + + return result; +} + +// FUNCTION: LEGO1 0x100147f0 +// FUNCTION: BETA10 0x100c9c93 +MxU32 LegoRaceActor::VTable0x90(float p_float, Matrix4& p_transform) +{ + // Note: Code duplication with LegoExtraActor::VTable0x90 + switch (m_state) { + case 0: + case 1: + return 1; + + case 2: + m_unk0x08 = p_float + 2000.0f; + m_state = 3; + m_actorTime += (p_float - m_lastTime) * m_worldSpeed; + m_lastTime = p_float; + return 0; + + case 3: + assert(!m_userNavFlag); + Vector3 positionRef(p_transform[3]); + + p_transform = m_roi->GetLocal2World(); + + if (m_unk0x08 > p_float) { + Mx3DPointFloat position; + + position = positionRef; + positionRef.Clear(); + p_transform.RotateX(0.6); + positionRef = position; + + m_actorTime += (p_float - m_lastTime) * m_worldSpeed; + m_lastTime = p_float; + + VTable0x74(p_transform); + return 0; + } + else { + m_state = 0; + m_unk0x08 = 0; + + ((Vector3&) positionRef).Sub(g_unk0x10102b08); + m_roi->FUN_100a58f0(p_transform); + return 1; + } + } + return 0; } -// STUB: LEGO1 0x100147f0 -MxU32 LegoRaceActor::VTable0x90(float, Matrix4&) -{ - // TODO - return 0; -} - -// STUB: LEGO1 0x10014a00 +// FUNCTION: LEGO1 0x10014a00 +// FUNCTION: BETA10 0x100c9f5c MxResult LegoRaceActor::VTable0x94(LegoPathActor* p_actor, MxBool p_bool) { - // TODO + if (!p_actor->GetUserNavFlag()) { + if (p_actor->GetState()) { + return FAILURE; + } + + if (p_bool) { + LegoROI* roi = p_actor->GetROI(); // name verified by BETA10 0x100c9fcf + assert(roi); + MxMatrix matr; + matr = roi->GetLocal2World(); + + Vector3(matr[3]).Add(g_unk0x10102b08); + + roi->FUN_100a58f0(matr); + + p_actor->SetState(2); + } + } return 0; } diff --git a/LEGO1/lego/legoomni/src/race/legoracers.cpp b/LEGO1/lego/legoomni/src/race/legoracers.cpp index 5bebe2da..16963723 100644 --- a/LEGO1/lego/legoomni/src/race/legoracers.cpp +++ b/LEGO1/lego/legoomni/src/race/legoracers.cpp @@ -405,6 +405,7 @@ void LegoRaceCar::VTable0x70(float p_float) // FUNCTION: BETA10 0x100cbb84 MxResult LegoRaceCar::VTable0x94(LegoPathActor* p_actor, MxBool p_bool) { + // Note: Code duplication with LegoRaceActor::VTable0x94 if (!p_actor->GetUserNavFlag()) { if (p_actor->GetState()) { return FAILURE; diff --git a/LEGO1/realtime/matrix.h b/LEGO1/realtime/matrix.h index 92c7ac7d..a0508c4d 100644 --- a/LEGO1/realtime/matrix.h +++ b/LEGO1/realtime/matrix.h @@ -122,6 +122,7 @@ class Matrix4 { } } + // FUNCTION: BETA10 0x1001c6a0 void RotateX(const float& p_angle) { float s = sin(p_angle); diff --git a/LEGO1/realtime/vector.h b/LEGO1/realtime/vector.h index 86e389ef..920b24b1 100644 --- a/LEGO1/realtime/vector.h +++ b/LEGO1/realtime/vector.h @@ -162,6 +162,7 @@ class Vector2 { }; // VTABLE: LEGO1 0x100d4518 +// VTABLE: BETA10 0x101b8398 // SIZE 0x08 class Vector3 : public Vector2 { public: @@ -263,6 +264,7 @@ class Vector3 : public Vector2 { void EqualsImpl(float* p_data) override { memcpy(m_data, p_data, sizeof(float) * 3); } // vtable+0x20 // FUNCTION: LEGO1 0x10003bc0 + // FUNCTION: BETA10 0x101b84fc void Clear() override { memset(m_data, 0, sizeof(float) * 3); } // vtable+0x2c // FUNCTION: LEGO1 0x10003bd0 From f94d39bf630013f3a8c9248f56fe1540a73d1ff1 Mon Sep 17 00:00:00 2001 From: Christian Semmler Date: Sat, 3 Aug 2024 10:59:12 -0700 Subject: [PATCH 04/10] Implement/match TowTrack::HandleClick (#1073) --- LEGO1/lego/legoomni/include/towtrack.h | 7 +- LEGO1/lego/legoomni/src/actors/towtrack.cpp | 92 +++++++++++++++++++-- 2 files changed, 89 insertions(+), 10 deletions(-) diff --git a/LEGO1/lego/legoomni/include/towtrack.h b/LEGO1/lego/legoomni/include/towtrack.h index 3d9c7a1a..c20d4d36 100644 --- a/LEGO1/lego/legoomni/include/towtrack.h +++ b/LEGO1/lego/legoomni/include/towtrack.h @@ -5,6 +5,8 @@ #include "islepathactor.h" #include "legostate.h" +class MxEndActionNotificationParam; + // VTABLE: LEGO1 0x100d7fd8 // SIZE 0x28 class TowTrackMissionState : public LegoState { @@ -48,7 +50,7 @@ class TowTrackMissionState : public LegoState { // TowTrackMissionState::`scalar deleting destructor' undefined4 m_unk0x08; // 0x08 - MxS32 m_unk0x0c; // 0x0c + MxLong m_unk0x0c; // 0x0c MxBool m_unk0x10; // 0x10 MxS16 m_unk0x12; // 0x12 MxS16 m_unk0x14; // 0x14 @@ -90,6 +92,7 @@ class TowTrack : public IslePathActor { MxLong HandleEndAnim(LegoEndAnimNotificationParam& p_param) override; // vtable+0xd8 MxLong HandlePathStruct(LegoPathStructNotificationParam& p_param) override; // vtable+0xdc void Exit() override; // vtable+0xe4 + virtual MxLong HandleEndAction(MxEndActionNotificationParam& p_param); // vtable+0xf0 void CreateState(); void FUN_1004dab0(); @@ -106,7 +109,7 @@ class TowTrack : public IslePathActor { undefined4 m_unk0x160; // 0x160 TowTrackMissionState* m_state; // 0x164 MxS16 m_unk0x168; // 0x168 - MxS16 m_unk0x16a; // 0x16a + MxS16 m_actorId; // 0x16a MxS16 m_unk0x16c; // 0x16c MxS16 m_unk0x16e; // 0x16e MxS32 m_unk0x170; // 0x170 diff --git a/LEGO1/lego/legoomni/src/actors/towtrack.cpp b/LEGO1/lego/legoomni/src/actors/towtrack.cpp index dd9a0d9b..357a51cb 100644 --- a/LEGO1/lego/legoomni/src/actors/towtrack.cpp +++ b/LEGO1/lego/legoomni/src/actors/towtrack.cpp @@ -1,14 +1,17 @@ #include "towtrack.h" +#include "isle.h" #include "isle_actions.h" #include "legocontrolmanager.h" #include "legogamestate.h" #include "legonavcontroller.h" +#include "legoutils.h" #include "legovariables.h" #include "legoworld.h" #include "misc.h" #include "mxmisc.h" #include "mxtimer.h" +#include "mxtransitionmanager.h" #include "mxvariabletable.h" DECOMP_SIZE_ASSERT(TowTrack, 0x180) @@ -18,7 +21,7 @@ DECOMP_SIZE_ASSERT(TowTrackMissionState, 0x28) TowTrack::TowTrack() { m_unk0x168 = 0; - m_unk0x16a = -1; + m_actorId = -1; m_state = NULL; m_unk0x16c = 0; m_unk0x170 = -1; @@ -100,15 +103,43 @@ void TowTrack::CreateState() } } -// STUB: LEGO1 0x1004cc80 +// FUNCTION: LEGO1 0x1004cc80 MxLong TowTrack::Notify(MxParam& p_param) { - // TODO - return 0; + MxLong result = 0; + + switch (((MxNotificationParam&) p_param).GetNotification()) { + case c_notificationType0: + result = HandleNotification0(); + break; + case c_notificationEndAction: + result = HandleEndAction((MxEndActionNotificationParam&) p_param); + break; + case c_notificationClick: + result = HandleClick(); + break; + case c_notificationControl: + result = HandleControl((LegoControlManagerNotificationParam&) p_param); + break; + case c_notificationEndAnim: + result = HandleEndAnim((LegoEndAnimNotificationParam&) p_param); + break; + case c_notificationPathStruct: + result = HandlePathStruct((LegoPathStructNotificationParam&) p_param); + break; + } + + return result; } -// STUB: LEGO1 0x1004cd30 +// FUNCTION: LEGO1 0x1004cd30 MxLong TowTrack::HandleEndAnim(LegoEndAnimNotificationParam& p_param) +{ + return 1; +} + +// STUB: LEGO1 0x1004cd40 +MxLong TowTrack::HandleEndAction(MxEndActionNotificationParam& p_param) { // TODO return 0; @@ -121,11 +152,56 @@ MxLong TowTrack::HandlePathStruct(LegoPathStructNotificationParam& p_param) return 0; } -// STUB: LEGO1 0x1004d690 +// FUNCTION: LEGO1 0x1004d690 MxLong TowTrack::HandleClick() { - // TODO - return 0; + if (((Act1State*) GameState()->GetState("Act1State"))->m_unk0x018 != 8) { + return 1; + } + + if (m_state->m_unk0x08 == 3) { + return 1; + } + + FUN_10015820(TRUE, 0); + ((Isle*) CurrentWorld())->SetDestLocation(LegoGameState::e_towtrack); + TransitionManager()->StartTransition(MxTransitionManager::e_mosaic, 50, FALSE, FALSE); + + if (UserActor()->GetActorId() != GameState()->GetActorId()) { + ((IslePathActor*) UserActor())->Exit(); + } + + m_time = Timer()->GetTime(); + m_actorId = UserActor()->GetActorId(); + + Enter(); + InvokeAction(Extra::e_start, *g_isleScript, IsleScript::c_TowTrackDashboard, NULL); + ControlManager()->Register(this); + + if (m_state->m_unk0x08 == 0) { + return 1; + } + + if (m_state->m_unk0x08 == 2) { + SpawnPlayer(LegoGameState::e_unk52, TRUE, 0); + FindROI("rcred")->SetVisibility(FALSE); + } + else { + SpawnPlayer(LegoGameState::e_unk28, TRUE, 0); + m_unk0x170 = -1; + m_unk0x174 = -1; + m_state->m_unk0x0c = Timer()->GetTime(); + m_state->m_unk0x10 = FALSE; + InvokeAction(Extra::e_start, *g_isleScript, IsleScript::c_wns057rd_RunAnim, NULL); + InvokeAction(Extra::e_start, *g_isleScript, IsleScript::c_wns048p1_RunAnim, NULL); + InvokeAction(Extra::e_start, *g_isleScript, IsleScript::c_wns049p1_RunAnim, NULL); + InvokeAction(Extra::e_start, *g_isleScript, IsleScript::c_wns051bd_RunAnim, NULL); + InvokeAction(Extra::e_start, *g_isleScript, IsleScript::c_wns053pr_RunAnim, NULL); + InvokeAction(Extra::e_start, *g_isleScript, IsleScript::c_wns045di_RunAnim, NULL); + InvokeAction(Extra::e_start, *g_isleScript, IsleScript::c_pns123pr_RunAnim, NULL); + } + + return 1; } // STUB: LEGO1 0x1004d8f0 From a1c6196bbcb05605f0dda0e32c704bbf8929bb8d Mon Sep 17 00:00:00 2001 From: Christian Semmler Date: Sat, 3 Aug 2024 11:07:08 -0700 Subject: [PATCH 05/10] Implement/match TowTrack::Leave and related (#1074) --- LEGO1/lego/legoomni/include/towtrack.h | 3 +- LEGO1/lego/legoomni/src/actors/towtrack.cpp | 37 ++++++++++++++++++--- LEGO1/lego/legoomni/src/worlds/isle.cpp | 4 +-- 3 files changed, 36 insertions(+), 8 deletions(-) diff --git a/LEGO1/lego/legoomni/include/towtrack.h b/LEGO1/lego/legoomni/include/towtrack.h index c20d4d36..b9d4ab32 100644 --- a/LEGO1/lego/legoomni/include/towtrack.h +++ b/LEGO1/lego/legoomni/include/towtrack.h @@ -97,13 +97,14 @@ class TowTrack : public IslePathActor { void CreateState(); void FUN_1004dab0(); void FUN_1004dad0(); - void FUN_1004db10(); + void StopActions(); void FUN_1004dbe0(); // SYNTHETIC: LEGO1 0x1004c950 // TowTrack::`scalar deleting destructor' private: + void Leave(); void FUN_1004dcf0(IsleScript::Script); undefined4 m_unk0x160; // 0x160 diff --git a/LEGO1/lego/legoomni/src/actors/towtrack.cpp b/LEGO1/lego/legoomni/src/actors/towtrack.cpp index 357a51cb..67321333 100644 --- a/LEGO1/lego/legoomni/src/actors/towtrack.cpp +++ b/LEGO1/lego/legoomni/src/actors/towtrack.cpp @@ -204,10 +204,29 @@ MxLong TowTrack::HandleClick() return 1; } -// STUB: LEGO1 0x1004d8f0 +// FUNCTION: LEGO1 0x1004d8f0 void TowTrack::Exit() { - // TODO + GameState()->m_currentArea = LegoGameState::e_garageExterior; + StopActions(); + FUN_1004dbe0(); + Leave(); +} + +// FUNCTION: LEGO1 0x1004d920 +void TowTrack::Leave() +{ + IslePathActor::Exit(); + CurrentWorld()->RemoveActor(this); + m_roi->SetVisibility(FALSE); + RemoveFromCurrentWorld(*g_isleScript, IsleScript::c_TowTrackDashboard_Bitmap); + RemoveFromCurrentWorld(*g_isleScript, IsleScript::c_TowTrackArms_Ctl); + RemoveFromCurrentWorld(*g_isleScript, IsleScript::c_TowHorn_Ctl); + RemoveFromCurrentWorld(*g_isleScript, IsleScript::c_TowHorn_Sound); + RemoveFromCurrentWorld(*g_isleScript, IsleScript::c_TowInfo_Ctl); + RemoveFromCurrentWorld(*g_isleScript, IsleScript::c_TowSpeedMeter); + RemoveFromCurrentWorld(*g_isleScript, IsleScript::c_TowFuelMeter); + ControlManager()->Unregister(this); } // STUB: LEGO1 0x1004d9e0 @@ -229,10 +248,18 @@ void TowTrack::FUN_1004dad0() // TODO } -// STUB: LEGO1 0x1004db10 -void TowTrack::FUN_1004db10() +// FUNCTION: LEGO1 0x1004db10 +void TowTrack::StopActions() { - // TODO + InvokeAction(Extra::e_stop, *g_isleScript, IsleScript::c_wns050p1_RunAnim, NULL); + InvokeAction(Extra::e_stop, *g_isleScript, IsleScript::c_wns046mg_RunAnim, NULL); + InvokeAction(Extra::e_stop, *g_isleScript, IsleScript::c_wns057rd_RunAnim, NULL); + InvokeAction(Extra::e_stop, *g_isleScript, IsleScript::c_wns048p1_RunAnim, NULL); + InvokeAction(Extra::e_stop, *g_isleScript, IsleScript::c_wns049p1_RunAnim, NULL); + InvokeAction(Extra::e_stop, *g_isleScript, IsleScript::c_wns051bd_RunAnim, NULL); + InvokeAction(Extra::e_stop, *g_isleScript, IsleScript::c_wns053pr_RunAnim, NULL); + InvokeAction(Extra::e_stop, *g_isleScript, IsleScript::c_wns045di_RunAnim, NULL); + InvokeAction(Extra::e_stop, *g_isleScript, IsleScript::c_pns123pr_RunAnim, NULL); } // STUB: LEGO1 0x1004dbe0 diff --git a/LEGO1/lego/legoomni/src/worlds/isle.cpp b/LEGO1/lego/legoomni/src/worlds/isle.cpp index fa9f9c5f..20c7d28a 100644 --- a/LEGO1/lego/legoomni/src/worlds/isle.cpp +++ b/LEGO1/lego/legoomni/src/worlds/isle.cpp @@ -1196,7 +1196,7 @@ MxBool Isle::Escape() break; case 8: if (UserActor() != NULL && !UserActor()->IsA("TowTrack")) { - m_towtrack->FUN_1004db10(); + m_towtrack->StopActions(); m_towtrack->FUN_1004dbe0(); } break; @@ -1250,7 +1250,7 @@ void Isle::FUN_10033350() if (m_act1state->m_unk0x018 == 8) { if (UserActor() != NULL && !UserActor()->IsA("TowTrack")) { - m_towtrack->FUN_1004db10(); + m_towtrack->StopActions(); m_towtrack->FUN_1004dbe0(); } } From 63586f88b214c900d72e46fd4a74200298779052 Mon Sep 17 00:00:00 2001 From: Christian Semmler Date: Sat, 3 Aug 2024 11:17:17 -0700 Subject: [PATCH 06/10] Implement/match TowTrack::ActivateSceneActions (#1075) * Implement/match TowTrack::ActivateSceneActions * Add PlayAction --- LEGO1/lego/legoomni/include/towtrack.h | 24 ++++---- LEGO1/lego/legoomni/src/actors/towtrack.cpp | 68 ++++++++++++++++----- LEGO1/lego/legoomni/src/worlds/isle.cpp | 2 +- 3 files changed, 67 insertions(+), 27 deletions(-) diff --git a/LEGO1/lego/legoomni/include/towtrack.h b/LEGO1/lego/legoomni/include/towtrack.h index b9d4ab32..da5ca2aa 100644 --- a/LEGO1/lego/legoomni/include/towtrack.h +++ b/LEGO1/lego/legoomni/include/towtrack.h @@ -96,7 +96,7 @@ class TowTrack : public IslePathActor { void CreateState(); void FUN_1004dab0(); - void FUN_1004dad0(); + void ActivateSceneActions(); void StopActions(); void FUN_1004dbe0(); @@ -105,18 +105,18 @@ class TowTrack : public IslePathActor { private: void Leave(); - void FUN_1004dcf0(IsleScript::Script); + void PlayAction(IsleScript::Script p_objectId); - undefined4 m_unk0x160; // 0x160 - TowTrackMissionState* m_state; // 0x164 - MxS16 m_unk0x168; // 0x168 - MxS16 m_actorId; // 0x16a - MxS16 m_unk0x16c; // 0x16c - MxS16 m_unk0x16e; // 0x16e - MxS32 m_unk0x170; // 0x170 - MxS32 m_unk0x174; // 0x174 - MxFloat m_fuel; // 0x178 - MxFloat m_time; // 0x17c + undefined4 m_unk0x160; // 0x160 + TowTrackMissionState* m_state; // 0x164 + MxS16 m_unk0x168; // 0x168 + MxS16 m_actorId; // 0x16a + MxS16 m_unk0x16c; // 0x16c + MxS16 m_unk0x16e; // 0x16e + IsleScript::Script m_lastAction; // 0x170 + MxS32 m_unk0x174; // 0x174 + MxFloat m_fuel; // 0x178 + MxFloat m_time; // 0x17c }; #endif // TOWTRACK_H diff --git a/LEGO1/lego/legoomni/src/actors/towtrack.cpp b/LEGO1/lego/legoomni/src/actors/towtrack.cpp index 67321333..a6fc0455 100644 --- a/LEGO1/lego/legoomni/src/actors/towtrack.cpp +++ b/LEGO1/lego/legoomni/src/actors/towtrack.cpp @@ -2,6 +2,7 @@ #include "isle.h" #include "isle_actions.h" +#include "jukebox_actions.h" #include "legocontrolmanager.h" #include "legogamestate.h" #include "legonavcontroller.h" @@ -9,7 +10,9 @@ #include "legovariables.h" #include "legoworld.h" #include "misc.h" +#include "mxbackgroundaudiomanager.h" #include "mxmisc.h" +#include "mxsoundpresenter.h" #include "mxtimer.h" #include "mxtransitionmanager.h" #include "mxvariabletable.h" @@ -24,7 +27,7 @@ TowTrack::TowTrack() m_actorId = -1; m_state = NULL; m_unk0x16c = 0; - m_unk0x170 = -1; + m_lastAction = IsleScript::c_noneIsle; m_unk0x16e = 0; m_unk0x174 = -1; m_maxLinearVel = 40.0; @@ -88,7 +91,7 @@ void TowTrack::VTable0x70(float p_time) VariableTable()->SetVariable(g_varTOWFUEL, buf); if (p_time - m_state->m_unk0x0c > 100000.0f && m_state->m_unk0x08 == 1 && !m_state->m_unk0x10) { - FUN_1004dcf0(IsleScript::c_Avo909In_PlayWav); + PlayAction(IsleScript::c_Avo909In_PlayWav); m_state->m_unk0x10 = TRUE; } } @@ -188,7 +191,7 @@ MxLong TowTrack::HandleClick() } else { SpawnPlayer(LegoGameState::e_unk28, TRUE, 0); - m_unk0x170 = -1; + m_lastAction = IsleScript::c_noneIsle; m_unk0x174 = -1; m_state->m_unk0x0c = Timer()->GetTime(); m_state->m_unk0x10 = FALSE; @@ -229,23 +232,55 @@ void TowTrack::Leave() ControlManager()->Unregister(this); } -// STUB: LEGO1 0x1004d9e0 +// FUNCTION: LEGO1 0x1004d9e0 MxLong TowTrack::HandleControl(LegoControlManagerNotificationParam& p_param) { - // TODO - return 0; + MxLong result = 0; + + if (p_param.GetUnknown0x28() == 1) { + switch (p_param.GetClickedObjectId()) { + case IsleScript::c_TowTrackArms_Ctl: + Exit(); + GameState()->m_currentArea = LegoGameState::e_unk66; + result = 1; + break; + case IsleScript::c_TowInfo_Ctl: + ((Isle*) CurrentWorld())->SetDestLocation(LegoGameState::e_infomain); + TransitionManager()->StartTransition(MxTransitionManager::e_mosaic, 50, FALSE, FALSE); + Exit(); + GameState()->m_currentArea = LegoGameState::e_unk66; + result = 1; + break; + case IsleScript::c_TowHorn_Ctl: + MxSoundPresenter* presenter = (MxSoundPresenter*) CurrentWorld()->Find("MxSoundPresenter", "TowHorn_Sound"); + presenter->Enable(p_param.GetUnknown0x28()); + break; + } + } + + return result; } -// STUB: LEGO1 0x1004dab0 +// FUNCTION: LEGO1 0x1004dab0 void TowTrack::FUN_1004dab0() { - // TODO + m_state->m_unk0x08 = 1; + HandleClick(); } -// STUB: LEGO1 0x1004dad0 -void TowTrack::FUN_1004dad0() +// FUNCTION: LEGO1 0x1004dad0 +void TowTrack::ActivateSceneActions() { - // TODO + PlayMusic(JukeboxScript::c_JBMusic2); + + if (m_state->m_unk0x08 != 0) { + if (m_state->m_unk0x08 == 2) { + PlayAction(IsleScript::c_wrt082na_PlayWav); + } + else { + PlayAction(IsleScript::c_wgs032nu_PlayWav); + } + } } // FUNCTION: LEGO1 0x1004db10 @@ -268,10 +303,15 @@ void TowTrack::FUN_1004dbe0() // TODO } -// STUB: LEGO1 0x1004dcf0 -void TowTrack::FUN_1004dcf0(IsleScript::Script) +// FUNCTION: LEGO1 0x1004dcf0 +void TowTrack::PlayAction(IsleScript::Script p_objectId) { - // TODO + if (p_objectId != -1) { + InvokeAction(Extra::e_start, *g_isleScript, p_objectId, NULL); + } + + m_lastAction = p_objectId; + BackgroundAudioManager()->LowerVolume(); } // FUNCTION: LEGO1 0x1004dd30 diff --git a/LEGO1/lego/legoomni/src/worlds/isle.cpp b/LEGO1/lego/legoomni/src/worlds/isle.cpp index 20c7d28a..cc759ea8 100644 --- a/LEGO1/lego/legoomni/src/worlds/isle.cpp +++ b/LEGO1/lego/legoomni/src/worlds/isle.cpp @@ -1036,7 +1036,7 @@ MxLong Isle::HandleTransitionEnd() FUN_10032d30(IsleScript::c_TowFuelMeter, JukeboxScript::c_MusicTheme1, NULL, TRUE); if (!m_act1state->m_unk0x01f) { - m_towtrack->FUN_1004dad0(); + m_towtrack->ActivateSceneActions(); } break; case LegoGameState::e_jetski: From fe1b66938d628253e7ff180851cc96183ec17d6f Mon Sep 17 00:00:00 2001 From: Christian Semmler Date: Sat, 3 Aug 2024 11:31:49 -0700 Subject: [PATCH 07/10] Implement/match TowTrack::FUN_1004dbe0 (#1076) --- LEGO1/lego/legoomni/src/actors/towtrack.cpp | 21 +++++++++++++++++++-- 1 file changed, 19 insertions(+), 2 deletions(-) diff --git a/LEGO1/lego/legoomni/src/actors/towtrack.cpp b/LEGO1/lego/legoomni/src/actors/towtrack.cpp index a6fc0455..9acea958 100644 --- a/LEGO1/lego/legoomni/src/actors/towtrack.cpp +++ b/LEGO1/lego/legoomni/src/actors/towtrack.cpp @@ -3,6 +3,7 @@ #include "isle.h" #include "isle_actions.h" #include "jukebox_actions.h" +#include "legoanimationmanager.h" #include "legocontrolmanager.h" #include "legogamestate.h" #include "legonavcontroller.h" @@ -20,6 +21,9 @@ DECOMP_SIZE_ASSERT(TowTrack, 0x180) DECOMP_SIZE_ASSERT(TowTrackMissionState, 0x28) +// Flags used in isle.cpp +extern MxU32 g_isleFlags; + // FUNCTION: LEGO1 0x1004c720 TowTrack::TowTrack() { @@ -297,10 +301,23 @@ void TowTrack::StopActions() InvokeAction(Extra::e_stop, *g_isleScript, IsleScript::c_pns123pr_RunAnim, NULL); } -// STUB: LEGO1 0x1004dbe0 +// FUNCTION: LEGO1 0x1004dbe0 void TowTrack::FUN_1004dbe0() { - // TODO + if (m_lastAction != -1) { + InvokeAction(Extra::e_stop, *g_isleScript, m_lastAction, NULL); + } + + ((Act1State*) GameState()->GetState("Act1State"))->m_unk0x018 = 0; + m_state->m_unk0x08 = 0; + g_isleFlags |= Isle::c_playMusic; + AnimationManager()->EnableCamAnims(TRUE); + AnimationManager()->FUN_1005f6d0(TRUE); + m_state->m_unk0x0c = INT_MIN; + m_state->m_unk0x10 = FALSE; + m_state = NULL; + m_unk0x16c = 0; + m_unk0x16e = 0; } // FUNCTION: LEGO1 0x1004dcf0 From e09acfcddb6fe07328389567a35b2d95edaaa5b5 Mon Sep 17 00:00:00 2001 From: jonschz <17198703+jonschz@users.noreply.github.com> Date: Sun, 4 Aug 2024 21:13:12 +0200 Subject: [PATCH 08/10] Implement/match `LegoCarRaceActor::VTable0x1c` (#1078) * Implement/match `LegoCarRaceActor::VTable0x1c` * Fix formatting * Fix LegoEdge::CWVertex() * Fix more CI issues * Trz to fix lvalue compile issue * Fix formatting --------- Co-authored-by: jonschz --- CMakeLists.txt | 4 +- .../legoomni/include/legojetskiraceactor.h | 8 +- .../lego/legoomni/include/legonavcontroller.h | 4 + LEGO1/lego/legoomni/include/legopathactor.h | 4 +- .../legoomni/include/legopathcontroller.h | 21 +- LEGO1/lego/legoomni/include/legoracers.h | 2 +- .../{legocarraceactor.h => legoracespecial.h} | 4 +- .../legoomni/src/common/legoobjectfactory.cpp | 2 +- LEGO1/lego/legoomni/src/common/misc.cpp | 2 + .../legoomni/src/entity/legocarraceactor.cpp | 124 ---------- .../src/entity/legojetskiraceactor.cpp | 3 +- .../lego/legoomni/src/paths/legopathactor.cpp | 2 +- .../legoomni/src/paths/legopathcontroller.cpp | 16 +- LEGO1/lego/legoomni/src/race/legoracers.cpp | 8 +- .../legoomni/src/race/legoracespecial.cpp | 228 ++++++++++++++++++ LEGO1/lego/sources/geom/legoedge.cpp | 19 +- LEGO1/lego/sources/geom/legoweedge.cpp | 6 +- LEGO1/lego/sources/geom/legoweedge.h | 11 +- LEGO1/lego/sources/geom/legowegedge.h | 3 + LEGO1/lego/sources/misc/legounknown.cpp | 2 +- LEGO1/lego/sources/misc/legounknown.h | 2 +- LEGO1/mxgeometry/mxgeometry3d.h | 1 + LEGO1/realtime/vector.h | 13 +- 23 files changed, 328 insertions(+), 161 deletions(-) rename LEGO1/lego/legoomni/include/{legocarraceactor.h => legoracespecial.h} (95%) delete mode 100644 LEGO1/lego/legoomni/src/entity/legocarraceactor.cpp create mode 100644 LEGO1/lego/legoomni/src/race/legoracespecial.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index a775dc2e..54d4b76b 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -350,7 +350,6 @@ add_library(lego1 SHARED LEGO1/lego/legoomni/src/entity/legoactor.cpp LEGO1/lego/legoomni/src/entity/legoactorpresenter.cpp LEGO1/lego/legoomni/src/entity/legocameracontroller.cpp - LEGO1/lego/legoomni/src/entity/legocarraceactor.cpp LEGO1/lego/legoomni/src/entity/legoentity.cpp LEGO1/lego/legoomni/src/entity/legoentitypresenter.cpp LEGO1/lego/legoomni/src/entity/legojetski.cpp @@ -375,8 +374,9 @@ add_library(lego1 SHARED LEGO1/lego/legoomni/src/race/jetskirace.cpp LEGO1/lego/legoomni/src/race/legorace.cpp LEGO1/lego/legoomni/src/race/legoraceactor.cpp - LEGO1/lego/legoomni/src/race/legoracers.cpp LEGO1/lego/legoomni/src/race/legoracemap.cpp + LEGO1/lego/legoomni/src/race/legoracers.cpp + LEGO1/lego/legoomni/src/race/legoracespecial.cpp LEGO1/lego/legoomni/src/race/raceskel.cpp LEGO1/lego/legoomni/src/video/legoanimpresenter.cpp LEGO1/lego/legoomni/src/video/legoflctexturepresenter.cpp diff --git a/LEGO1/lego/legoomni/include/legojetskiraceactor.h b/LEGO1/lego/legoomni/include/legojetskiraceactor.h index c844a186..219ed51f 100644 --- a/LEGO1/lego/legoomni/include/legojetskiraceactor.h +++ b/LEGO1/lego/legoomni/include/legojetskiraceactor.h @@ -1,7 +1,7 @@ #ifndef LEGOJETSKIRACEACTOR_H #define LEGOJETSKIRACEACTOR_H -#include "legocarraceactor.h" +#include "legoracespecial.h" // VTABLE: LEGO1 0x100da208 LegoCarRaceActor // VTABLE: LEGO1 0x100da228 LegoRaceActor @@ -32,9 +32,9 @@ class LegoJetskiRaceActor : public virtual LegoCarRaceActor { float p_f1, float p_f2, Vector3& p_v3 - ) override; // vtable+0x6c - void VTable0x70(float p_float) override; // vtable+0x70 - void VTable0x1c() override; // vtable+0x1c + ) override; // vtable+0x6c + void VTable0x70(float p_float) override; // vtable+0x70 + MxS32 VTable0x1c(undefined4 p_param1, LegoEdge* p_edge) override; // vtable+0x1c // SYNTHETIC: LEGO1 0x10081d50 // LegoJetskiRaceActor::`scalar deleting destructor' diff --git a/LEGO1/lego/legoomni/include/legonavcontroller.h b/LEGO1/lego/legoomni/include/legonavcontroller.h index 8cd891dc..216b7b89 100644 --- a/LEGO1/lego/legoomni/include/legonavcontroller.h +++ b/LEGO1/lego/legoomni/include/legonavcontroller.h @@ -13,6 +13,7 @@ class Vector3; // LegoMouseController // VTABLE: LEGO1 0x100d85b8 +// VTABLE: BETA10 0x101bcc80 // SIZE 0x70 class LegoNavController : public MxCore { public: @@ -22,6 +23,7 @@ class LegoNavController : public MxCore { MxLong Notify(MxParam& p_param) override; // vtable+0x04 // FUNCTION: LEGO1 0x10054b80 + // FUNCTION: BETA10 0x1009e5f0 const char* ClassName() const override // vtable+0x0c { // STRING: LEGO1 0x100f66d8 @@ -77,7 +79,9 @@ class LegoNavController : public MxCore { static MxS32 GetNumLocations(); static LegoLocation* GetLocation(MxU32 p_location); + // FUNCTION: BETA10 0x100b0f40 void SetLinearVel(MxFloat p_linearVel) { m_linearVel = p_linearVel; } + MxFloat GetLinearVel() { return m_linearVel; } MxFloat GetRotationalVel() { return m_rotationalVel; } MxFloat GetMaxLinearVel() { return m_maxLinearVel; } diff --git a/LEGO1/lego/legoomni/include/legopathactor.h b/LEGO1/lego/legoomni/include/legopathactor.h index 5110c8ee..242d46b6 100644 --- a/LEGO1/lego/legoomni/include/legopathactor.h +++ b/LEGO1/lego/legoomni/include/legopathactor.h @@ -59,7 +59,7 @@ class LegoPathActor : public LegoActor { virtual MxBool GetUserNavFlag() { return m_userNavFlag; } // vtable+0x7c virtual MxResult VTable0x80( - Vector3& p_point1, + const Vector3& p_point1, Vector3& p_point2, Vector3& p_point3, Vector3& p_point4 @@ -165,7 +165,7 @@ class LegoPathActor : public LegoActor { LegoUnknown100db7f4* m_destEdge; // 0xe0 MxFloat m_unk0xe4; // 0xe4 MxBool m_collideBox; // 0xe8 - undefined m_unk0xe9; // 0xe9 + MxBool m_unk0xe9; // 0xe9 MxBool m_userNavFlag; // 0xea MxMatrix m_unk0xec; // 0xec LegoPathEdgeContainer* m_grec; // 0x134 diff --git a/LEGO1/lego/legoomni/include/legopathcontroller.h b/LEGO1/lego/legoomni/include/legopathcontroller.h index 3722df2b..ad0b5791 100644 --- a/LEGO1/lego/legoomni/include/legopathcontroller.h +++ b/LEGO1/lego/legoomni/include/legopathcontroller.h @@ -60,7 +60,7 @@ class LegoPathController : public MxCore { } LegoPathController* m_controller; // 0x00 - LegoEdge* m_edge; // 0x04 + LegoUnknown100db7f4* m_edge; // 0x04 }; LegoPathController(); @@ -121,6 +121,16 @@ class LegoPathController : public MxCore { static MxResult Init(); static MxResult Reset(); + // FUNCTION: BETA10 0x100cf580 + static LegoUnknown100db7f4* GetControlEdgeA(MxS32 p_index) { return g_ctrlEdgesA[p_index].m_edge; } + + // FUNCTION: BETA10 0x100cf5b0 + static LegoPathBoundary* GetControlBoundaryA(MxS32 p_index) { return g_ctrlBoundariesA[p_index].m_boundary; } + + // These two are an educated guess because BETA10 does not have the g_ctrl.*B globals + static LegoUnknown100db7f4* GetControlEdgeB(MxS32 p_index) { return g_ctrlEdgesB[p_index].m_edge; } + static LegoPathBoundary* GetControlBoundaryB(MxS32 p_index) { return g_ctrlBoundariesB[p_index].m_boundary; } + private: void FUN_10046970(); MxResult Read(LegoStorage* p_storage); @@ -140,6 +150,15 @@ class LegoPathController : public MxCore { MxU16 m_numT; // 0x1e LegoPathCtrlEdgeSet m_pfsE; // 0x20 LegoPathActorSet m_actors; // 0x30 + + // Names verified by BETA10 + static CtrlBoundary* g_ctrlBoundariesA; + static CtrlEdge* g_ctrlEdgesA; + + static const char* g_unk0x100f42f0[]; + static const char* g_unk0x100f4330[]; + static CtrlBoundary* g_ctrlBoundariesB; + static CtrlEdge* g_ctrlEdgesB; }; // clang-format off diff --git a/LEGO1/lego/legoomni/include/legoracers.h b/LEGO1/lego/legoomni/include/legoracers.h index d20de8a7..7e207ef8 100644 --- a/LEGO1/lego/legoomni/include/legoracers.h +++ b/LEGO1/lego/legoomni/include/legoracers.h @@ -1,8 +1,8 @@ #ifndef LEGORACERS_H #define LEGORACERS_H -#include "legocarraceactor.h" #include "legoracemap.h" +#include "legoracespecial.h" #define LEGORACECAR_UNKNOWN_0 0 #define LEGORACECAR_UNKNOWN_1 1 diff --git a/LEGO1/lego/legoomni/include/legocarraceactor.h b/LEGO1/lego/legoomni/include/legoracespecial.h similarity index 95% rename from LEGO1/lego/legoomni/include/legocarraceactor.h rename to LEGO1/lego/legoomni/include/legoracespecial.h index e1d41dce..3db9838f 100644 --- a/LEGO1/lego/legoomni/include/legocarraceactor.h +++ b/LEGO1/lego/legoomni/include/legoracespecial.h @@ -64,7 +64,7 @@ class LegoCarRaceActor : public virtual LegoRaceActor { // FUNCTION: LEGO1 0x10012c00 virtual float FUN_10012c00() { return m_unk0x18; } - virtual void VTable0x1c(); // vtable+0x1c + virtual MxS32 VTable0x1c(undefined4 p_param1, LegoEdge* p_edge); // vtable+0x1c // SYNTHETIC: LEGO1 0x10012c30 // LegoCarRaceActor::`vbase destructor' @@ -86,6 +86,8 @@ class LegoCarRaceActor : public virtual LegoRaceActor { // Could be the current timestamp for time-based movement MxFloat m_unk0x1c; // 0x1c + + static MxFloat g_unk0x100f7aec; }; // GLOBAL: LEGO1 0x100da0b0 diff --git a/LEGO1/lego/legoomni/src/common/legoobjectfactory.cpp b/LEGO1/lego/legoomni/src/common/legoobjectfactory.cpp index 6bbae1fa..4eebe624 100644 --- a/LEGO1/lego/legoomni/src/common/legoobjectfactory.cpp +++ b/LEGO1/lego/legoomni/src/common/legoobjectfactory.cpp @@ -40,7 +40,6 @@ #include "legoanimpresenter.h" #include "legocarbuild.h" #include "legocarbuildpresenter.h" -#include "legocarraceactor.h" #include "legoentity.h" #include "legoentitypresenter.h" #include "legoflctexturepresenter.h" @@ -58,6 +57,7 @@ #include "legopathpresenter.h" #include "legophonemepresenter.h" #include "legoracers.h" +#include "legoracespecial.h" #include "legotexturepresenter.h" #include "legoworld.h" #include "legoworldpresenter.h" diff --git a/LEGO1/lego/legoomni/src/common/misc.cpp b/LEGO1/lego/legoomni/src/common/misc.cpp index 901810d0..05dcf678 100644 --- a/LEGO1/lego/legoomni/src/common/misc.cpp +++ b/LEGO1/lego/legoomni/src/common/misc.cpp @@ -61,8 +61,10 @@ LegoAnimationManager* AnimationManager() } // FUNCTION: LEGO1 0x10015780 +// FUNCTION: BETA10 0x100e49b8 LegoNavController* NavController() { + assert(LegoOmni::GetInstance()); return LegoOmni::GetInstance()->GetNavController(); } diff --git a/LEGO1/lego/legoomni/src/entity/legocarraceactor.cpp b/LEGO1/lego/legoomni/src/entity/legocarraceactor.cpp deleted file mode 100644 index 3dd009da..00000000 --- a/LEGO1/lego/legoomni/src/entity/legocarraceactor.cpp +++ /dev/null @@ -1,124 +0,0 @@ -#include "legocarraceactor.h" - -#include "geom/legounkown100db7f4.h" -#include "legopathboundary.h" -#include "misc.h" -#include "mxmisc.h" -#include "mxvariabletable.h" - -DECOMP_SIZE_ASSERT(LegoCarRaceActor, 0x1a0) - -// GLOBAL: LEGO1 0x100f7af0 -// STRING: LEGO1 0x100f7ae4 -const char* g_fuel = "FUEL"; - -// FUNCTION: LEGO1 0x10080350 -// FUNCTION: BETA10 0x100cd6b0 -LegoCarRaceActor::LegoCarRaceActor() -{ - m_unk0x08 = 1.0f; - m_unk0x70 = 0.0f; - m_unk0x0c = 0; - m_maxLinearVel = 0.0f; - m_frequencyFactor = 1.0f; - m_unk0x1c = 0; - m_unk0x10 = 0.65f; - m_unk0x14 = 0.03f; - m_unk0x18 = 0.6f; - m_unk0x140 = 0.1f; - m_unk0x150 = -5.0f; - m_unk0x148 = 1; - VariableTable()->SetVariable(g_fuel, "0.8"); -} - -// FUNCTION: LEGO1 0x10080590 -// FUNCTION: BETA10 0x100cd8cf -void LegoCarRaceActor::FUN_10080590(float p_float) -{ - MxFloat maxSpeed = m_maxLinearVel; - Mx3DPointFloat destEdgeUnknownVector; - Mx3DPointFloat worldDirection = Mx3DPointFloat(m_roi->GetWorldDirection()); - - m_destEdge->FUN_1002ddc0(*m_boundary, destEdgeUnknownVector); - - if (abs(destEdgeUnknownVector.Dot(destEdgeUnknownVector.GetData(), worldDirection.GetData())) > 0.5) { - maxSpeed *= m_unk0x10; - } - - MxS32 deltaUnk0x70; - LegoPathActor* userActor = UserActor(); - - if (userActor) { - // All known implementations of LegoPathActor->VTable0x5c() return LegoPathActor::m_unk0x70 - deltaUnk0x70 = m_unk0x70 - userActor->VTable0x5c(); - } - else { - deltaUnk0x70 = 0; - } - - if (deltaUnk0x70 > 1) { - if (deltaUnk0x70 > 3) { - deltaUnk0x70 = 3; - } - - maxSpeed *= (m_unk0x18 * (--deltaUnk0x70) * -0.25f + 1.0f); - } - else if (deltaUnk0x70 < -1) { - maxSpeed *= 1.3; - } - - MxFloat deltaSpeed = maxSpeed - m_worldSpeed; - MxFloat changeInSpeed = (p_float - m_unk0x1c) * m_unk0x14; - m_unk0x1c = p_float; - - if (deltaSpeed < 0.0f) { - changeInSpeed = -changeInSpeed; - } - - MxFloat newWorldSpeed = changeInSpeed + m_worldSpeed; - - if (newWorldSpeed > maxSpeed) { - newWorldSpeed = maxSpeed; - } - - SetWorldSpeed(newWorldSpeed); -} - -// STUB: LEGO1 0x10080740 -void LegoCarRaceActor::VTable0x1c() -{ -} - -// FUNCTION: LEGO1 0x10080b40 -// FUNCTION: BETA10 0x100cdb3c -void LegoCarRaceActor::SwitchBoundary(LegoPathBoundary*& p_boundary, LegoUnknown100db7f4*& p_edge, float& p_unk0xe4) -{ - LegoPathActor::SwitchBoundary(m_boundary, m_destEdge, m_unk0xe4); -} - -// STUB: LEGO1 0x10080b70 -void LegoCarRaceActor::VTable0x70(float p_float) -{ - // TODO -} - -// STUB: LEGO1 0x10080be0 -MxResult LegoCarRaceActor::VTable0x9c() -{ - // TODO - return SUCCESS; -} - -// STUB: LEGO1 0x10081840 -MxU32 LegoCarRaceActor::VTable0x6c( - LegoPathBoundary* p_boundary, - Vector3& p_v1, - Vector3& p_v2, - float p_f1, - float p_f2, - Vector3& p_v3 -) -{ - // TODO - return 0; -} diff --git a/LEGO1/lego/legoomni/src/entity/legojetskiraceactor.cpp b/LEGO1/lego/legoomni/src/entity/legojetskiraceactor.cpp index 18a4f3d9..6ebf975e 100644 --- a/LEGO1/lego/legoomni/src/entity/legojetskiraceactor.cpp +++ b/LEGO1/lego/legoomni/src/entity/legojetskiraceactor.cpp @@ -12,9 +12,10 @@ LegoJetskiRaceActor::LegoJetskiRaceActor() } // STUB: LEGO1 0x10081120 -void LegoJetskiRaceActor::VTable0x1c() +MxS32 LegoJetskiRaceActor::VTable0x1c(undefined4 p_param1, LegoEdge* p_edge) { // TODO + return 0; } // STUB: LEGO1 0x10081550 diff --git a/LEGO1/lego/legoomni/src/paths/legopathactor.cpp b/LEGO1/lego/legoomni/src/paths/legopathactor.cpp index 239f704f..9aa817c3 100644 --- a/LEGO1/lego/legoomni/src/paths/legopathactor.cpp +++ b/LEGO1/lego/legoomni/src/paths/legopathactor.cpp @@ -64,7 +64,7 @@ LegoPathActor::~LegoPathActor() } // FUNCTION: LEGO1 0x1002d8d0 -MxResult LegoPathActor::VTable0x80(Vector3& p_point1, Vector3& p_point2, Vector3& p_point3, Vector3& p_point4) +MxResult LegoPathActor::VTable0x80(const Vector3& p_point1, Vector3& p_point2, Vector3& p_point3, Vector3& p_point4) { Mx3DPointFloat p1, p2, p3; diff --git a/LEGO1/lego/legoomni/src/paths/legopathcontroller.cpp b/LEGO1/lego/legoomni/src/paths/legopathcontroller.cpp index ddfe66ec..480b43ec 100644 --- a/LEGO1/lego/legoomni/src/paths/legopathcontroller.cpp +++ b/LEGO1/lego/legoomni/src/paths/legopathcontroller.cpp @@ -19,13 +19,15 @@ MxU32 g_unk0x100d7cc8[] = {2, 2, 2, 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 0, 2, 0}; MxU32 g_unk0x100d7d08[] = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0}; // GLOBAL: LEGO1 0x100f42e8 -LegoPathController::CtrlBoundary* g_ctrlBoundariesA = NULL; +// GLOBAL: BETA10 0x101f25f0 +LegoPathController::CtrlBoundary* LegoPathController::g_ctrlBoundariesA = NULL; // GLOBAL: LEGO1 0x100f42ec -LegoPathController::CtrlEdge* g_ctrlEdgesA = NULL; +// GLOBAL: BETA10 0x101f25f4 +LegoPathController::CtrlEdge* LegoPathController::g_ctrlEdgesA = NULL; // GLOBAL: LEGO1 0x100f42f0 -const char* g_unk0x100f42f0[] = { +const char* LegoPathController::g_unk0x100f42f0[] = { "edg03_21", "edg03_23", "edg03_30", @@ -45,7 +47,7 @@ const char* g_unk0x100f42f0[] = { }; // GLOBAL: LEGO1 0x100f4330 -const char* g_unk0x100f4330[] = { +const char* LegoPathController::g_unk0x100f4330[] = { "edg03_06", "edg03_21", "edg03_30", @@ -59,10 +61,10 @@ const char* g_unk0x100f4330[] = { }; // GLOBAL: LEGO1 0x100f4358 -LegoPathController::CtrlBoundary* g_ctrlBoundariesB = NULL; +LegoPathController::CtrlBoundary* LegoPathController::g_ctrlBoundariesB = NULL; // GLOBAL: LEGO1 0x100f435c -LegoPathController::CtrlEdge* g_ctrlEdgesB = NULL; +LegoPathController::CtrlEdge* LegoPathController::g_ctrlEdgesB = NULL; // FUNCTION: LEGO1 0x10044f40 // FUNCTION: BETA10 0x100b6860 @@ -620,7 +622,7 @@ MxResult LegoPathController::ReadBoundaries(LegoStorage* p_storage) boundary.m_edgeNormals = new Mx4DPointFloat[numE]; - LegoEdge** edges = new LegoEdge*[numE]; + LegoUnknown100db7f4** edges = new LegoUnknown100db7f4*[numE]; boundary.SetEdges(edges, numE); for (j = 0; j < numE; j++) { diff --git a/LEGO1/lego/legoomni/src/race/legoracers.cpp b/LEGO1/lego/legoomni/src/race/legoracers.cpp index 16963723..d9f15050 100644 --- a/LEGO1/lego/legoomni/src/race/legoracers.cpp +++ b/LEGO1/lego/legoomni/src/race/legoracers.cpp @@ -266,13 +266,13 @@ void LegoRaceCar::FUN_10012ff0(float p_param) if (a->GetDuration() <= deltaTime || deltaTime < 0.0) { if (m_userState == LEGORACECAR_KICK1) { - LegoEdge** edges = m_kick1B->GetEdges(); - m_destEdge = (LegoUnknown100db7f4*) (edges[2]); + LegoUnknown100db7f4** edges = m_kick1B->GetEdges(); + m_destEdge = edges[2]; m_boundary = m_kick1B; } else { - LegoEdge** edges = m_kick1B->GetEdges(); - m_destEdge = (LegoUnknown100db7f4*) (edges[1]); + LegoUnknown100db7f4** edges = m_kick1B->GetEdges(); + m_destEdge = edges[1]; m_boundary = m_kick2B; } diff --git a/LEGO1/lego/legoomni/src/race/legoracespecial.cpp b/LEGO1/lego/legoomni/src/race/legoracespecial.cpp new file mode 100644 index 00000000..5e38bbaa --- /dev/null +++ b/LEGO1/lego/legoomni/src/race/legoracespecial.cpp @@ -0,0 +1,228 @@ +#include "legoracespecial.h" + +#include "geom/legounkown100db7f4.h" +#include "legonavcontroller.h" +#include "legopathboundary.h" +#include "legopathcontroller.h" +#include "misc.h" +#include "mxmisc.h" +#include "mxvariabletable.h" + +// File name verified by BETA10 0x100cedf7 + +DECOMP_SIZE_ASSERT(LegoCarRaceActor, 0x1a0) + +// GLOBAL: LEGO1 0x100f7af0 +// STRING: LEGO1 0x100f7ae4 +const char* g_fuel = "FUEL"; + +// GLOBAL: LEGO1 0x100f7aec +MxFloat LegoCarRaceActor::g_unk0x100f7aec = 8.0f; + +// FUNCTION: LEGO1 0x10080350 +// FUNCTION: BETA10 0x100cd6b0 +LegoCarRaceActor::LegoCarRaceActor() +{ + m_unk0x08 = 1.0f; + m_unk0x70 = 0.0f; + m_unk0x0c = 0; + m_maxLinearVel = 0.0f; + m_frequencyFactor = 1.0f; + m_unk0x1c = 0; + m_unk0x10 = 0.65f; + m_unk0x14 = 0.03f; + m_unk0x18 = 0.6f; + m_unk0x140 = 0.1f; + m_unk0x150 = -5.0f; + m_unk0x148 = 1; + VariableTable()->SetVariable(g_fuel, "0.8"); +} + +// FUNCTION: LEGO1 0x10080590 +// FUNCTION: BETA10 0x100cd8cf +void LegoCarRaceActor::FUN_10080590(float p_float) +{ + MxFloat maxSpeed = m_maxLinearVel; + Mx3DPointFloat destEdgeUnknownVector; + Mx3DPointFloat worldDirection = Mx3DPointFloat(m_roi->GetWorldDirection()); + + m_destEdge->FUN_1002ddc0(*m_boundary, destEdgeUnknownVector); + + if (abs(destEdgeUnknownVector.Dot(destEdgeUnknownVector.GetData(), worldDirection.GetData())) > 0.5) { + maxSpeed *= m_unk0x10; + } + + MxS32 deltaUnk0x70; + LegoPathActor* userActor = UserActor(); + + if (userActor) { + // All known implementations of LegoPathActor->VTable0x5c() return LegoPathActor::m_unk0x70 + deltaUnk0x70 = m_unk0x70 - userActor->VTable0x5c(); + } + else { + deltaUnk0x70 = 0; + } + + if (deltaUnk0x70 > 1) { + if (deltaUnk0x70 > 3) { + deltaUnk0x70 = 3; + } + + maxSpeed *= (m_unk0x18 * (--deltaUnk0x70) * -0.25f + 1.0f); + } + else if (deltaUnk0x70 < -1) { + maxSpeed *= 1.3; + } + + MxFloat deltaSpeed = maxSpeed - m_worldSpeed; + MxFloat changeInSpeed = (p_float - m_unk0x1c) * m_unk0x14; + m_unk0x1c = p_float; + + if (deltaSpeed < 0.0f) { + changeInSpeed = -changeInSpeed; + } + + MxFloat newWorldSpeed = changeInSpeed + m_worldSpeed; + + if (newWorldSpeed > maxSpeed) { + newWorldSpeed = maxSpeed; + } + + SetWorldSpeed(newWorldSpeed); +} + +// FUNCTION: LEGO1 0x10080740 +// FUNCTION: BETA10 0x100cece0 +MxS32 LegoCarRaceActor::VTable0x1c(undefined4 p_param1, LegoEdge* p_edge) +{ + Mx3DPointFloat pointUnknown; + Mx3DPointFloat destEdgeUnknownVector; + Mx3DPointFloat crossProduct; + + if (m_state == 1) { + m_boundary = NULL; + + // Not sure where the upper bound of 11 comes from, the underlying array has a size of 16 + for (MxS32 i = 0; i < 11; i += 2) { + if (LegoPathController::GetControlEdgeA(i + 1) == m_destEdge) { + m_boundary = LegoPathController::GetControlBoundaryA(i + 1); + break; + } + } + + assert(m_boundary); + + m_state = 0; + m_unk0x7c = 0; + + if (m_userNavFlag) { + NavController()->SetLinearVel(m_worldSpeed); + return 0; + } + else { + return 1; + } + } + else { + for (MxS32 i = 0; i < 11; i += 2) { + if (LegoPathController::GetControlEdgeA(i) == p_edge) { + m_state = 1; + + if (m_worldSpeed < g_unk0x100f7aec) { + m_worldSpeed = g_unk0x100f7aec; + } + + m_destEdge = LegoPathController::GetControlEdgeA(i + 1); + m_boundary = LegoPathController::GetControlBoundaryA(i + 1); + break; + } + } + + if (m_state == 1) { + if (m_userNavFlag) { + m_unk0xe4 = 0.5f; + } + + // variable names verified by BETA10 + Vector3* v1 = m_destEdge->CCWVertex(*m_boundary); + Vector3* v2 = m_destEdge->CWVertex(*m_boundary); + assert(v1 && v2); + + pointUnknown[0] = (*v1)[0] + ((*v2)[0] - (*v1)[0]) * m_unk0xe4; + pointUnknown[1] = (*v1)[1] + ((*v2)[1] - (*v1)[1]) * m_unk0xe4; + pointUnknown[2] = (*v1)[2] + ((*v2)[2] - (*v1)[2]) * m_unk0xe4; + + m_destEdge->FUN_1002ddc0(*m_boundary, destEdgeUnknownVector); + + crossProduct.EqualsCross(m_boundary->GetUnknown0x14(), &destEdgeUnknownVector); + crossProduct.Unitize(); + + Mx3DPointFloat worldDirection(Vector3(m_roi->GetWorldDirection())); + + if (!m_userNavFlag) { + ((Vector3*) &worldDirection)->Mul(-1.0f); + } + + ((Vector3*) &worldDirection)->Mul(5.0f); + ((Vector3*) &crossProduct)->Mul(5.0f); + + MxResult callResult = + VTable0x80(Vector3(m_roi->GetWorldPosition()), worldDirection, pointUnknown, crossProduct); + + if (callResult) { + m_unk0x7c = 0; + return 0; + } + else { + m_unk0x7c = 0; + assert(0); + return 0; // BETA10 returns -1 here + } + } + else { + // This `for` loop does not exist in BETA10 + for (MxS32 i = 0; i < 10; i++) { + if (LegoPathController::GetControlEdgeB(i) == p_edge && + LegoPathController::GetControlBoundaryB(i) == m_boundary) { + return 0; + } + } + + return 1; + } + } +} + +// FUNCTION: LEGO1 0x10080b40 +// FUNCTION: BETA10 0x100cdb3c +void LegoCarRaceActor::SwitchBoundary(LegoPathBoundary*& p_boundary, LegoUnknown100db7f4*& p_edge, float& p_unk0xe4) +{ + LegoPathActor::SwitchBoundary(m_boundary, m_destEdge, m_unk0xe4); +} + +// STUB: LEGO1 0x10080b70 +void LegoCarRaceActor::VTable0x70(float p_float) +{ + // TODO +} + +// STUB: LEGO1 0x10080be0 +MxResult LegoCarRaceActor::VTable0x9c() +{ + // TODO + return SUCCESS; +} + +// STUB: LEGO1 0x10081840 +MxU32 LegoCarRaceActor::VTable0x6c( + LegoPathBoundary* p_boundary, + Vector3& p_v1, + Vector3& p_v2, + float p_f1, + float p_f2, + Vector3& p_v3 +) +{ + // TODO + return 0; +} diff --git a/LEGO1/lego/sources/geom/legoedge.cpp b/LEGO1/lego/sources/geom/legoedge.cpp index 7606d6a1..d1a810d6 100644 --- a/LEGO1/lego/sources/geom/legoedge.cpp +++ b/LEGO1/lego/sources/geom/legoedge.cpp @@ -1,5 +1,6 @@ #include "legoedge.h" +#include "assert.h" #include "decomp.h" DECOMP_SIZE_ASSERT(LegoEdge, 0x24) @@ -51,13 +52,27 @@ LegoEdge* LegoEdge::GetCounterclockwiseEdge(LegoWEEdge& p_face) } // FUNCTION: LEGO1 0x1009a510 +// FUNCTION: BETA10 0x10182433 Vector3* LegoEdge::CWVertex(LegoWEEdge& p_face) { - return &p_face == m_faceA ? m_pointB : m_pointA; + if (m_faceA == &p_face) { + return m_pointB; + } + else { + assert(m_faceB == &p_face); + return m_pointA; + } } // FUNCTION: LEGO1 0x1009a530 +// FUNCTION: BETA10 0x10182498 Vector3* LegoEdge::CCWVertex(LegoWEEdge& p_face) { - return &p_face == m_faceB ? m_pointB : m_pointA; + if (m_faceB == &p_face) { + return m_pointB; + } + else { + assert(m_faceA == &p_face); + return m_pointA; + } } diff --git a/LEGO1/lego/sources/geom/legoweedge.cpp b/LEGO1/lego/sources/geom/legoweedge.cpp index c03a5e3f..a87072e9 100644 --- a/LEGO1/lego/sources/geom/legoweedge.cpp +++ b/LEGO1/lego/sources/geom/legoweedge.cpp @@ -1,5 +1,7 @@ #include "legoweedge.h" +#include "legounkown100db7f4.h" + DECOMP_SIZE_ASSERT(LegoWEEdge, 0x0c) // FUNCTION: LEGO1 0x1009a550 @@ -21,8 +23,8 @@ LegoWEEdge::~LegoWEEdge() LegoResult LegoWEEdge::VTable0x04() { for (LegoS32 i = 0; i < m_numEdges; i++) { - LegoEdge* e1 = m_edges[i]; - LegoEdge* e2 = (m_numEdges - i) == 1 ? m_edges[0] : m_edges[i + 1]; + LegoUnknown100db7f4* e1 = m_edges[i]; + LegoUnknown100db7f4* e2 = (m_numEdges - i) == 1 ? m_edges[0] : m_edges[i + 1]; if (e2->m_pointA == e1->m_pointA) { e1->m_faceA = this; diff --git a/LEGO1/lego/sources/geom/legoweedge.h b/LEGO1/lego/sources/geom/legoweedge.h index 6e5f4548..a63eb50e 100644 --- a/LEGO1/lego/sources/geom/legoweedge.h +++ b/LEGO1/lego/sources/geom/legoweedge.h @@ -2,9 +2,10 @@ #define __LEGOWEEDGE_H #include "decomp.h" -#include "legoedge.h" #include "misc/legotypes.h" +struct LegoUnknown100db7f4; + // might be a struct with public members // VTABLE: LEGO1 0x100db7c0 // SIZE 0x0c @@ -18,13 +19,13 @@ class LegoWEEdge { LegoU8 GetNumEdges() { return m_numEdges; } // FUNCTION: BETA10 0x1001cc30 - LegoEdge** GetEdges() { return m_edges; } + LegoUnknown100db7f4** GetEdges() { return m_edges; } // TODO: The assertion at BETA10 0x10037352 suggests that this function might take a pointer instead of a reference // FUNCTION: BETA10 0x100373f0 LegoU32 IsEqual(LegoWEEdge& p_other) { return this == &p_other; } - void SetEdges(LegoEdge** p_edges, LegoU8 p_numEdges) + void SetEdges(LegoUnknown100db7f4** p_edges, LegoU8 p_numEdges) { m_edges = p_edges; m_numEdges = p_numEdges; @@ -34,8 +35,8 @@ class LegoWEEdge { // LegoWEEdge::`scalar deleting destructor' protected: - LegoU8 m_numEdges; // 0x04 - LegoEdge** m_edges; // 0x08 + LegoU8 m_numEdges; // 0x04 + LegoUnknown100db7f4** m_edges; // 0x08 }; #endif // __LEGOWEEDGE_H diff --git a/LEGO1/lego/sources/geom/legowegedge.h b/LEGO1/lego/sources/geom/legowegedge.h index 23bdf340..1e774abf 100644 --- a/LEGO1/lego/sources/geom/legowegedge.h +++ b/LEGO1/lego/sources/geom/legowegedge.h @@ -41,7 +41,10 @@ class LegoWEGEdge : public LegoWEEdge { LegoResult VTable0x04() override; // vtable+0x04 LegoU32 GetFlag0x10() { return m_flags & c_bit5 ? FALSE : TRUE; } + + // FUNCTION: BETA10 0x1001ff80 Mx4DPointFloat* GetUnknown0x14() { return &m_unk0x14; } + Mx4DPointFloat* GetEdgeNormal(int index) { return &m_edgeNormals[index]; } // FUNCTION: BETA10 0x1001c9b0 diff --git a/LEGO1/lego/sources/misc/legounknown.cpp b/LEGO1/lego/sources/misc/legounknown.cpp index b3976b51..67e1423c 100644 --- a/LEGO1/lego/sources/misc/legounknown.cpp +++ b/LEGO1/lego/sources/misc/legounknown.cpp @@ -16,7 +16,7 @@ LegoUnknown::~LegoUnknown() } // FUNCTION: LEGO1 0x1009a140 -void LegoUnknown::FUN_1009a140(Vector3& p_point1, Vector3& p_point2, Vector3& p_point3, Vector3& p_point4) +void LegoUnknown::FUN_1009a140(const Vector3& p_point1, Vector3& p_point2, Vector3& p_point3, Vector3& p_point4) { m_unk0x00[0] = p_point1; m_unk0x00[1] = p_point2; diff --git a/LEGO1/lego/sources/misc/legounknown.h b/LEGO1/lego/sources/misc/legounknown.h index 1d0d1aef..cc7a467b 100644 --- a/LEGO1/lego/sources/misc/legounknown.h +++ b/LEGO1/lego/sources/misc/legounknown.h @@ -12,7 +12,7 @@ class LegoUnknown { LegoUnknown(); ~LegoUnknown(); - void FUN_1009a140(Vector3& p_point1, Vector3& p_point2, Vector3& p_point3, Vector3& p_point4); + void FUN_1009a140(const Vector3& p_point1, Vector3& p_point2, Vector3& p_point3, Vector3& p_point4); LegoResult FUN_1009a1e0(float p_f1, Matrix4& p_mat, Vector3& p_v, LegoU32 p_und); private: diff --git a/LEGO1/mxgeometry/mxgeometry3d.h b/LEGO1/mxgeometry/mxgeometry3d.h index 20946865..f675a350 100644 --- a/LEGO1/mxgeometry/mxgeometry3d.h +++ b/LEGO1/mxgeometry/mxgeometry3d.h @@ -18,6 +18,7 @@ class Mx3DPointFloat : public Vector3 { } // FUNCTION: LEGO1 0x100343a0 + // FUNCTION: BETA10 0x10011600 Mx3DPointFloat(const Mx3DPointFloat& p_other) : Vector3(m_elements) { EqualsImpl(p_other.m_data); } Mx3DPointFloat(const Vector3& p_other) : Vector3(m_elements) { EqualsImpl(p_other.m_data); } diff --git a/LEGO1/realtime/vector.h b/LEGO1/realtime/vector.h index 920b24b1..9c73f31f 100644 --- a/LEGO1/realtime/vector.h +++ b/LEGO1/realtime/vector.h @@ -10,10 +10,12 @@ // The class needs to undergo a very careful refactoring to fix that (no matches should break). // VTABLE: LEGO1 0x100d4288 +// VTABLE: BETA10 0x101b8440 // SIZE 0x08 class Vector2 { public: // FUNCTION: LEGO1 0x1000c0f0 + // FUNCTION: BETA10 0x100116a0 Vector2(float* p_data) { SetData(p_data); } // Note: virtual function overloads appear in the virtual table @@ -68,6 +70,7 @@ class Vector2 { virtual float DotImpl(float* p_a, float* p_b) const { return p_b[0] * p_a[0] + p_b[1] * p_a[1]; } // vtable+0x18 // FUNCTION: LEGO1 0x10002060 + // FUNCTION: BETA10 0x10010c90 virtual void SetData(float* p_data) { m_data = p_data; } // vtable+0x1c // FUNCTION: LEGO1 0x10002070 @@ -86,6 +89,7 @@ class Vector2 { virtual float Dot(float* p_a, float* p_b) const { return DotImpl(p_a, p_b); } // vtable+0x3c // FUNCTION: LEGO1 0x100020f0 + // FUNCTION: BETA10 0x100028f6 virtual float Dot(Vector2* p_a, Vector2* p_b) const { return DotImpl(p_a->m_data, p_b->m_data); } // vtable+0x38 // FUNCTION: LEGO1 0x10002110 @@ -98,6 +102,7 @@ class Vector2 { virtual float LenSquared() const { return m_data[0] * m_data[0] + m_data[1] * m_data[1]; } // vtable+0x40 // FUNCTION: LEGO1 0x10002160 + // FUNCTION: BETA10 0x10010900 virtual int Unitize() { float sq = LenSquared(); @@ -154,7 +159,11 @@ class Vector2 { Vector2::SetVector(&p_other); return *this; } + + // FUNCTION: BETA10 0x10013460 float& operator[](int idx) { return m_data[idx]; } + + // FUNCTION: BETA10 0x1001d140 const float& operator[](int idx) const { return m_data[idx]; } protected: @@ -188,6 +197,7 @@ class Vector3 : public Vector2 { } // vtable+0x74 // FUNCTION: LEGO1 0x100022c0 + // FUNCTION: BETA10 0x10011430 virtual void EqualsCross(Vector3* p_a, Vector3* p_b) { EqualsCrossImpl(p_a->m_data, p_b->m_data); } // vtable+0x80 // FUNCTION: LEGO1 0x100022e0 @@ -261,10 +271,11 @@ class Vector3 : public Vector2 { } // vtable+0x18 // FUNCTION: LEGO1 0x10003ba0 + // FUNCTION: BETA10 0x100113f0 void EqualsImpl(float* p_data) override { memcpy(m_data, p_data, sizeof(float) * 3); } // vtable+0x20 // FUNCTION: LEGO1 0x10003bc0 - // FUNCTION: BETA10 0x101b84fc + // FUNCTION: BETA10 0x1000132a void Clear() override { memset(m_data, 0, sizeof(float) * 3); } // vtable+0x2c // FUNCTION: LEGO1 0x10003bd0 From 51d4dead1a041e411a4f15d6ef7c54560c2e34d5 Mon Sep 17 00:00:00 2001 From: MS Date: Wed, 7 Aug 2024 23:19:12 -0400 Subject: [PATCH 09/10] MxSmack::LoadHeader (#1080) --- LEGO1/library_smack.h | 5 + LEGO1/omni/include/mxbitmap.h | 2 +- LEGO1/omni/src/video/mxsmack.cpp | 225 ++++++++++++++++--------------- 3 files changed, 121 insertions(+), 111 deletions(-) diff --git a/LEGO1/library_smack.h b/LEGO1/library_smack.h index 2909ebd3..8571011f 100644 --- a/LEGO1/library_smack.h +++ b/LEGO1/library_smack.h @@ -1,18 +1,23 @@ #ifdef 0 // LIBRARY: LEGO1 0x100cd782 +// LIBRARY: BETA10 0x1015fb82 // _SmackGetSizeTables // LIBRARY: LEGO1 0x100cd7e8 +// LIBRARY: BETA10 0x1015fbe8 // _SmackDoTables // LIBRARY: LEGO1 0x100cda83 +// LIBRARY: BETA10 0x1015fe83 // _SmackDoFrameToBuffer // LIBRARY: LEGO1 0x100d052c +// LIBRARY: BETA10 0x1016292c // _SmackGetSizeDeltas // LIBRARY: LEGO1 0x100d0543 +// LIBRARY: BETA10 0x10162943 // _SmackGetRect #endif diff --git a/LEGO1/omni/include/mxbitmap.h b/LEGO1/omni/include/mxbitmap.h index 10ce5b11..5df4c8ba 100644 --- a/LEGO1/omni/include/mxbitmap.h +++ b/LEGO1/omni/include/mxbitmap.h @@ -90,7 +90,7 @@ class MxBitmap : public MxCore { // DECOMP: This could be a free function. It is static here because it has no // reference to "this". In the beta it is called in two places: // 1. GetBmiHeightAbs - // 2. at 0x101523b9, in reference to BITMAPINFOHEADER.biHeight + // 2. MxSmack::LoadFrame // FUNCTION: BETA10 0x1002c690 static MxLong HeightAbs(MxLong p_value) { return p_value > 0 ? p_value : -p_value; } diff --git a/LEGO1/omni/src/video/mxsmack.cpp b/LEGO1/omni/src/video/mxsmack.cpp index fd08e8a9..d0a9480c 100644 --- a/LEGO1/omni/src/video/mxsmack.cpp +++ b/LEGO1/omni/src/video/mxsmack.cpp @@ -8,127 +8,136 @@ DECOMP_SIZE_ASSERT(SmackTag, 0x390); DECOMP_SIZE_ASSERT(MxSmack, 0x6b8); // FUNCTION: LEGO1 0x100c5a90 +// FUNCTION: BETA10 0x10151e70 MxResult MxSmack::LoadHeader(MxU8* p_data, MxSmack* p_mxSmack) { // Macros for readability -#define FRAME_COUNT(mxSmack) (p_mxSmack->m_smackTag.Frames + (p_mxSmack->m_smackTag.SmackerType & 1)) +// If bit0 of SmackerType is set, there is an extra frame ("ring frame") +// at the end. It is a duplicate of the first frame to simplify looping. +#define FRAME_COUNT(_tag) (_tag->Frames + (_tag->SmackerType & 1)) MxResult result = SUCCESS; + MxU32* frameSizes = NULL; MxU8* frameTypes = NULL; MxU8* huffmanTrees = NULL; + MxU32 sizetables = 0; + + // Forced to declare here because of the gotos. + MxU32 i; + MxU32 treeSize; + MxU32* data; + MxU32 size; + MxS32 width; if (!p_data || !p_mxSmack) { + return FAILURE; + } + + SmackTag* smackTag = &p_mxSmack->m_smackTag; + p_mxSmack->m_frameTypes = NULL; + p_mxSmack->m_frameSizes = NULL; + p_mxSmack->m_huffmanTrees = NULL; + p_mxSmack->m_huffmanTables = NULL; + + memcpy(smackTag, p_data, SmackHeaderSize(smackTag)); + p_data += SmackHeaderSize(smackTag); + + frameSizes = new MxU32[FRAME_COUNT(smackTag)]; + + if (!frameSizes) { result = FAILURE; - } - else { - p_mxSmack->m_frameTypes = NULL; - p_mxSmack->m_frameSizes = NULL; - p_mxSmack->m_huffmanTrees = NULL; - p_mxSmack->m_huffmanTables = NULL; - - memcpy(&p_mxSmack->m_smackTag, p_data, SmackHeaderSize(&p_mxSmack->m_smackTag)); - p_data += SmackHeaderSize(&p_mxSmack->m_smackTag); - - MxU32* frameSizes = new MxU32[FRAME_COUNT(p_mxSmack)]; - - if (!frameSizes) { - result = FAILURE; - } - else { - memcpy(frameSizes, p_data, FRAME_COUNT(p_mxSmack) * sizeof(MxU32)); - - p_data += FRAME_COUNT(p_mxSmack) * sizeof(MxU32); - p_mxSmack->m_maxFrameSize = 0; - - // TODO - for (MxU32 i = 0; i < FRAME_COUNT(p_mxSmack); i++) { - if (p_mxSmack->m_maxFrameSize < frameSizes[i]) { - p_mxSmack->m_maxFrameSize = frameSizes[i]; - } - } - - frameTypes = new MxU8[FRAME_COUNT(p_mxSmack)]; - - if (!frameTypes) { - result = FAILURE; - } - else { - memcpy(frameTypes, p_data, FRAME_COUNT(p_mxSmack)); - p_data += FRAME_COUNT(p_mxSmack); - - MxU32 treeSize = p_mxSmack->m_smackTag.tablesize + 0x1000; - if (treeSize <= 0x2000) { - treeSize = 0x2000; - } - - huffmanTrees = new MxU8[treeSize]; - - if (!huffmanTrees) { - result = FAILURE; - } - else { - memcpy(huffmanTrees + 0x1000, p_data, p_mxSmack->m_smackTag.tablesize); - - p_mxSmack->m_huffmanTables = new MxU8 - [p_mxSmack->m_smackTag.codesize + p_mxSmack->m_smackTag.absize + - p_mxSmack->m_smackTag.detailsize + p_mxSmack->m_smackTag.typesize + SmackGetSizeTables()]; - - if (!p_mxSmack->m_huffmanTables) { - result = FAILURE; - } - else { - SmackDoTables( - huffmanTrees, - p_mxSmack->m_huffmanTables, - p_mxSmack->m_smackTag.codesize, - p_mxSmack->m_smackTag.absize, - p_mxSmack->m_smackTag.detailsize, - p_mxSmack->m_smackTag.typesize - ); - - MxU32 size = SmackGetSizeDeltas(p_mxSmack->m_smackTag.Width, p_mxSmack->m_smackTag.Height) + 32; - p_mxSmack->m_unk0x6b4 = new MxU8[size]; - memset(p_mxSmack->m_unk0x6b4, 0, size); - - MxS32 width = p_mxSmack->m_smackTag.Width; - MxU32* data = (MxU32*) p_mxSmack->m_unk0x6b4; - - *data = 1; - data++; - *data = NULL; // MxU8* bitmapData - data++; - *data = p_mxSmack->m_smackTag.Width / 4; - data++; - *data = p_mxSmack->m_smackTag.Height / 4; - data++; - *data = width - 4; - data++; - *data = width * 3; - data++; - *data = width; - data++; - *data = width * 4 - p_mxSmack->m_smackTag.Width; - data++; - data++; - *data = p_mxSmack->m_smackTag.Width; - data++; - *data = p_mxSmack->m_smackTag.Height; - } - } - } - } - - p_mxSmack->m_frameTypes = frameTypes; - p_mxSmack->m_frameSizes = frameSizes; - p_mxSmack->m_huffmanTrees = huffmanTrees; + goto done; } + memcpy(frameSizes, p_data, FRAME_COUNT(smackTag) * sizeof(MxU32)); + + p_data += FRAME_COUNT(smackTag) * sizeof(MxU32); + p_mxSmack->m_maxFrameSize = 0; + + for (i = 0; i < FRAME_COUNT(smackTag); i++) { + if (p_mxSmack->m_maxFrameSize < frameSizes[i]) { + p_mxSmack->m_maxFrameSize = frameSizes[i]; + } + } + + frameTypes = new MxU8[FRAME_COUNT(smackTag)]; + + if (!frameTypes) { + result = FAILURE; + goto done; + } + + memcpy(frameTypes, p_data, FRAME_COUNT(smackTag)); + p_data += FRAME_COUNT(smackTag); + + treeSize = smackTag->tablesize + 0x1000; + huffmanTrees = new MxU8[treeSize <= 0x2000 ? 0x2000 : treeSize]; + + if (!huffmanTrees) { + result = FAILURE; + goto done; + } + + memcpy(huffmanTrees + 0x1000, p_data, smackTag->tablesize); + p_data += smackTag->tablesize; + + sizetables = SmackGetSizeTables(); + p_mxSmack->m_huffmanTables = + new MxU8[smackTag->codesize + smackTag->detailsize + smackTag->typesize + smackTag->absize + sizetables]; + + if (!p_mxSmack->m_huffmanTables) { + result = FAILURE; + goto done; + } + + SmackDoTables( + huffmanTrees, + p_mxSmack->m_huffmanTables, + smackTag->codesize, + smackTag->absize, + smackTag->detailsize, + smackTag->typesize + ); + + size = SmackGetSizeDeltas(smackTag->Width, smackTag->Height) + 32; + p_mxSmack->m_unk0x6b4 = new MxU8[size]; + memset(p_mxSmack->m_unk0x6b4, 0, size); + + width = p_mxSmack->m_smackTag.Width; + data = (MxU32*) p_mxSmack->m_unk0x6b4; + + *data = 1; + data++; + *data = NULL; // MxU8* bitmapData + data++; + *data = smackTag->Width / 4; + data++; + *data = smackTag->Height / 4; + data++; + *data = width - 4; + data++; + *data = width * 3; + data++; + *data = width; + data++; + *data = width * 3 + (width - smackTag->Width); + data++; + data++; + *data = smackTag->Width; + data++; + *data = smackTag->Height; + +done: + p_mxSmack->m_frameTypes = frameTypes; + p_mxSmack->m_frameSizes = frameSizes; + p_mxSmack->m_huffmanTrees = huffmanTrees; return result; #undef FRAME_COUNT } // FUNCTION: LEGO1 0x100c5d40 +// FUNCTION: BETA10 0x10152298 void MxSmack::Destroy(MxSmack* p_mxSmack) { if (p_mxSmack->m_frameSizes) { @@ -148,13 +157,8 @@ void MxSmack::Destroy(MxSmack* p_mxSmack) } } -// This should be refactored to somewhere else -inline MxLong AbsFlipped(MxLong p_value) -{ - return p_value > 0 ? p_value : -p_value; -} - // FUNCTION: LEGO1 0x100c5db0 +// FUNCTION: BETA10 0x10152391 MxResult MxSmack::LoadFrame( MxBITMAPINFO* p_bitmapInfo, MxU8* p_bitmapData, @@ -164,7 +168,7 @@ MxResult MxSmack::LoadFrame( MxRectList* p_list ) { - p_bitmapInfo->m_bmiHeader.biHeight = -AbsFlipped(p_bitmapInfo->m_bmiHeader.biHeight); + p_bitmapInfo->m_bmiHeader.biHeight = -MxBitmap::HeightAbs(p_bitmapInfo->m_bmiHeader.biHeight); *(MxU8**) (p_mxSmack->m_unk0x6b4 + 4) = p_bitmapData; // Reference: https://wiki.multimedia.cx/index.php/Smacker#Palette_Chunk @@ -228,6 +232,7 @@ MxResult MxSmack::LoadFrame( } // FUNCTION: LEGO1 0x100c6050 +// FUNCTION: BETA10 0x10152739 MxBool MxSmack::GetRect(MxU8* p_unk0x6b4, MxU16* p_und, u32* p_smackRect, MxRect32* p_rect) { u32 left, bottom, top, right; From a6a241b09dc05f742340164c97abbb36e2d32190 Mon Sep 17 00:00:00 2001 From: jonschz <17198703+jonschz@users.noreply.github.com> Date: Thu, 8 Aug 2024 21:55:01 +0200 Subject: [PATCH 10/10] Implement `LegoCarRaceActor::VTable0x70` and `VTable0x1c` (#1079) * Implement/match `LegoCarRaceActor::VTable0x70` * Add BETA10 annotations * Start with VTable0x9c, fix data type * basic BETA10 code completion * small fix * small fix * cleanup * Fix formatting * Address review comments * review comment --------- Co-authored-by: jonschz --- LEGO1/lego/legoomni/include/carrace.h | 6 ++ .../legoomni/include/legojetskiraceactor.h | 6 +- LEGO1/lego/legoomni/include/legoracespecial.h | 2 +- .../src/entity/legojetskiraceactor.cpp | 2 +- .../legoomni/src/race/legoracespecial.cpp | 89 +++++++++++++++++-- LEGO1/realtime/orientableroi.h | 1 + 6 files changed, 93 insertions(+), 13 deletions(-) diff --git a/LEGO1/lego/legoomni/include/carrace.h b/LEGO1/lego/legoomni/include/carrace.h index 5a43f420..954c1326 100644 --- a/LEGO1/lego/legoomni/include/carrace.h +++ b/LEGO1/lego/legoomni/include/carrace.h @@ -5,10 +5,12 @@ #include "legorace.h" // VTABLE: LEGO1 0x100d4b70 +// VTABLE: BETA10 0x101bd5f0 // SIZE 0x2c class CarRaceState : public RaceState { public: // FUNCTION: LEGO1 0x1000dd30 + // FUNCTION: BETA10 0x100a9100 const char* ClassName() const override // vtable+0x0c { // STRING: LEGO1 0x100f009c @@ -16,6 +18,7 @@ class CarRaceState : public RaceState { } // FUNCTION: LEGO1 0x1000dd40 + // FUNCTION: BETA10 0x100a9130 MxBool IsA(const char* p_name) const override // vtable+0x10 { return !strcmp(p_name, CarRaceState::ClassName()) || RaceState::IsA(p_name); @@ -26,12 +29,14 @@ class CarRaceState : public RaceState { }; // VTABLE: LEGO1 0x100d5e50 +// VTABLE: BETA10 0x101be290 // SIZE 0x154 class CarRace : public LegoRace { public: CarRace(); // FUNCTION: LEGO1 0x10016b20 + // FUNCTION: BETA10 0x100c9870 const char* ClassName() const override // vtable+0x0c { // STRING: LEGO1 0x100f0528 @@ -39,6 +44,7 @@ class CarRace : public LegoRace { } // FUNCTION: LEGO1 0x10016b30 + // FUNCTION: BETA10 0x100c98a0 MxBool IsA(const char* p_name) const override // vtable+0x10 { return !strcmp(p_name, CarRace::ClassName()) || LegoRace::IsA(p_name); diff --git a/LEGO1/lego/legoomni/include/legojetskiraceactor.h b/LEGO1/lego/legoomni/include/legojetskiraceactor.h index 219ed51f..2f5b3d19 100644 --- a/LEGO1/lego/legoomni/include/legojetskiraceactor.h +++ b/LEGO1/lego/legoomni/include/legojetskiraceactor.h @@ -32,9 +32,9 @@ class LegoJetskiRaceActor : public virtual LegoCarRaceActor { float p_f1, float p_f2, Vector3& p_v3 - ) override; // vtable+0x6c - void VTable0x70(float p_float) override; // vtable+0x70 - MxS32 VTable0x1c(undefined4 p_param1, LegoEdge* p_edge) override; // vtable+0x1c + ) override; // vtable+0x6c + void VTable0x70(float p_float) override; // vtable+0x70 + MxS32 VTable0x1c(LegoPathBoundary* p_boundary, LegoEdge* p_edge) override; // vtable+0x1c // SYNTHETIC: LEGO1 0x10081d50 // LegoJetskiRaceActor::`scalar deleting destructor' diff --git a/LEGO1/lego/legoomni/include/legoracespecial.h b/LEGO1/lego/legoomni/include/legoracespecial.h index 3db9838f..a85a967f 100644 --- a/LEGO1/lego/legoomni/include/legoracespecial.h +++ b/LEGO1/lego/legoomni/include/legoracespecial.h @@ -64,7 +64,7 @@ class LegoCarRaceActor : public virtual LegoRaceActor { // FUNCTION: LEGO1 0x10012c00 virtual float FUN_10012c00() { return m_unk0x18; } - virtual MxS32 VTable0x1c(undefined4 p_param1, LegoEdge* p_edge); // vtable+0x1c + virtual MxS32 VTable0x1c(LegoPathBoundary* p_boundary, LegoEdge* p_edge); // vtable+0x1c // SYNTHETIC: LEGO1 0x10012c30 // LegoCarRaceActor::`vbase destructor' diff --git a/LEGO1/lego/legoomni/src/entity/legojetskiraceactor.cpp b/LEGO1/lego/legoomni/src/entity/legojetskiraceactor.cpp index 6ebf975e..ae020f15 100644 --- a/LEGO1/lego/legoomni/src/entity/legojetskiraceactor.cpp +++ b/LEGO1/lego/legoomni/src/entity/legojetskiraceactor.cpp @@ -12,7 +12,7 @@ LegoJetskiRaceActor::LegoJetskiRaceActor() } // STUB: LEGO1 0x10081120 -MxS32 LegoJetskiRaceActor::VTable0x1c(undefined4 p_param1, LegoEdge* p_edge) +MxS32 LegoJetskiRaceActor::VTable0x1c(LegoPathBoundary* p_boundary, LegoEdge* p_edge) { // TODO return 0; diff --git a/LEGO1/lego/legoomni/src/race/legoracespecial.cpp b/LEGO1/lego/legoomni/src/race/legoracespecial.cpp index 5e38bbaa..bd6e4330 100644 --- a/LEGO1/lego/legoomni/src/race/legoracespecial.cpp +++ b/LEGO1/lego/legoomni/src/race/legoracespecial.cpp @@ -8,14 +8,24 @@ #include "mxmisc.h" #include "mxvariabletable.h" +#include + // File name verified by BETA10 0x100cedf7 DECOMP_SIZE_ASSERT(LegoCarRaceActor, 0x1a0) +// GLOBAL: LEGO1 0x100f0c68 +// STRING: LEGO1 0x100f0c5c +const char* g_raceState = "RACE_STATE"; + // GLOBAL: LEGO1 0x100f7af0 // STRING: LEGO1 0x100f7ae4 const char* g_fuel = "FUEL"; +// GLOBAL: LEGO1 0x100f0c6c +// STRING: LEGO1 0x100f0c54 +const char* g_racing = "RACING"; + // GLOBAL: LEGO1 0x100f7aec MxFloat LegoCarRaceActor::g_unk0x100f7aec = 8.0f; @@ -93,7 +103,7 @@ void LegoCarRaceActor::FUN_10080590(float p_float) // FUNCTION: LEGO1 0x10080740 // FUNCTION: BETA10 0x100cece0 -MxS32 LegoCarRaceActor::VTable0x1c(undefined4 p_param1, LegoEdge* p_edge) +MxS32 LegoCarRaceActor::VTable0x1c(LegoPathBoundary* p_boundary, LegoEdge* p_edge) { Mx3DPointFloat pointUnknown; Mx3DPointFloat destEdgeUnknownVector; @@ -148,9 +158,7 @@ MxS32 LegoCarRaceActor::VTable0x1c(undefined4 p_param1, LegoEdge* p_edge) Vector3* v2 = m_destEdge->CWVertex(*m_boundary); assert(v1 && v2); - pointUnknown[0] = (*v1)[0] + ((*v2)[0] - (*v1)[0]) * m_unk0xe4; - pointUnknown[1] = (*v1)[1] + ((*v2)[1] - (*v1)[1]) * m_unk0xe4; - pointUnknown[2] = (*v1)[2] + ((*v2)[2] - (*v1)[2]) * m_unk0xe4; + LERP3(pointUnknown, *v1, *v2, m_unk0xe4); m_destEdge->FUN_1002ddc0(*m_boundary, destEdgeUnknownVector); @@ -200,20 +208,80 @@ void LegoCarRaceActor::SwitchBoundary(LegoPathBoundary*& p_boundary, LegoUnknown LegoPathActor::SwitchBoundary(m_boundary, m_destEdge, m_unk0xe4); } -// STUB: LEGO1 0x10080b70 +// FUNCTION: LEGO1 0x10080b70 +// FUNCTION: BETA10 0x1000366b void LegoCarRaceActor::VTable0x70(float p_float) { - // TODO + // m_unk0x0c is not an MxBool, there are places where it is set to 2 or higher + if (m_unk0x0c == 0) { + const char* value = VariableTable()->GetVariable(g_raceState); + + if (strcmpi(value, g_racing) == 0) { + m_unk0x0c = 1; + m_lastTime = p_float - 1.0f; + m_unk0x1c = p_float; + } + } + + if (m_unk0x0c == 1) { + LegoAnimActor::VTable0x70(p_float); + } } -// STUB: LEGO1 0x10080be0 +// FUNCTION: LEGO1 0x10080be0 +// FUNCTION: BETA10 0x100cdc54 MxResult LegoCarRaceActor::VTable0x9c() { - // TODO + LegoUnknown100db7f4* d = m_destEdge; + + if (VTable0x1c(m_boundary, m_destEdge)) { + LegoPathBoundary* b = m_boundary; + + SwitchBoundary(m_boundary, m_destEdge, m_unk0xe4); + assert(m_boundary && m_destEdge); + + // variable names verified by BETA10 + Vector3* v1 = m_destEdge->CWVertex(*m_boundary); + Vector3* v2 = m_destEdge->CCWVertex(*m_boundary); + assert(v1 && v2); + + Mx3DPointFloat point1; + LERP3(point1, *v1, *v2, m_unk0xe4); + + Mx3DPointFloat point2; + Mx3DPointFloat point3; + Mx3DPointFloat point4; + Mx3DPointFloat point5; + + d->FUN_1002ddc0(*b, point2); + m_destEdge->FUN_1002ddc0(*m_boundary, point3); + + point4.EqualsCross(&point2, m_boundary->GetUnknown0x14()); + point5.EqualsCross(m_boundary->GetUnknown0x14(), &point3); + + point4.Unitize(); + point5.Unitize(); + + ((Vector3*) &point4)->Mul(5.0f); + ((Vector3*) &point5)->Mul(5.0f); + + MxResult res = VTable0x80(m_roi->GetWorldPosition(), point4, point1, point5); + +#ifndef NDEBUG // BETA10 only + if (res) { + assert(0); + return -1; + } +#endif + + m_unk0x7c = 0; + } + return SUCCESS; } // STUB: LEGO1 0x10081840 +// FUNCTION: BETA10 0x100cf680 MxU32 LegoCarRaceActor::VTable0x6c( LegoPathBoundary* p_boundary, Vector3& p_v1, @@ -223,6 +291,11 @@ MxU32 LegoCarRaceActor::VTable0x6c( Vector3& p_v3 ) { + // LegoAnimPresenterSet& presenters = p_boundary->GetPresenters(); + + // Significant overlap with parent function -> Try to copy-paste LegoPathActor::VTable0x6c here + // and see by how much we diverge + // TODO return 0; } diff --git a/LEGO1/realtime/orientableroi.h b/LEGO1/realtime/orientableroi.h index ad84d9ed..61b61645 100644 --- a/LEGO1/realtime/orientableroi.h +++ b/LEGO1/realtime/orientableroi.h @@ -42,6 +42,7 @@ class OrientableROI : public ROI { // FUNCTION: BETA10 0x1000fbf0 const Matrix4& GetLocal2World() const { return m_local2world; } + // FUNCTION: BETA10 0x10011750 const float* GetWorldPosition() const { return m_local2world[3]; } // FUNCTION: BETA10 0x10011780