From 01f07a323c5c1f1733ba831b9b613528a7941cfe Mon Sep 17 00:00:00 2001 From: Christian Semmler Date: Sun, 3 Mar 2024 15:35:56 -0500 Subject: [PATCH] Implement ModelDb (WDB reader/parser) (#619) * WIP Read WDB * Fixes * WIP * WIP * WIP * WIP * Match * Match * Fix Compare * Rename member --- CMakeLists.txt | 1 + .../lego/legoomni/include/legopartpresenter.h | 10 + .../legoomni/include/legotexturepresenter.h | 7 + .../legoomni/include/legoworldpresenter.h | 8 +- .../src/entity/legoworldpresenter.cpp | 177 +++++++++++++++++- .../legoomni/src/video/legopartpresenter.cpp | 17 +- .../src/video/legotexturepresenter.cpp | 15 ++ LEGO1/library_msvc.h | 3 + LEGO1/modeldb/modeldb.cpp | 83 ++++++++ LEGO1/modeldb/modeldb.h | 112 +++++++++++ 10 files changed, 427 insertions(+), 6 deletions(-) create mode 100644 LEGO1/modeldb/modeldb.cpp create mode 100644 LEGO1/modeldb/modeldb.h diff --git a/CMakeLists.txt b/CMakeLists.txt index 9c49f5c2..3b82c241 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -387,6 +387,7 @@ add_library(lego1 SHARED LEGO1/lego/legoomni/src/video/legovideomanager.cpp LEGO1/lego/legoomni/src/video/mxtransitionmanager.cpp LEGO1/main.cpp + LEGO1/modeldb/modeldb.cpp ) register_lego1_target(lego1) diff --git a/LEGO1/lego/legoomni/include/legopartpresenter.h b/LEGO1/lego/legoomni/include/legopartpresenter.h index 2ff6faa1..90cc2ddc 100644 --- a/LEGO1/lego/legoomni/include/legopartpresenter.h +++ b/LEGO1/lego/legoomni/include/legopartpresenter.h @@ -7,6 +7,9 @@ // SIZE 0x54 (from inlined construction at 0x10009fac) class LegoPartPresenter : public MxMediaPresenter { public: + LegoPartPresenter() { Reset(); } + + // FUNCTION: LEGO1 0x10067300 ~LegoPartPresenter() override { Destroy(TRUE); } // FUNCTION: LEGO1 0x1000cf70 @@ -31,8 +34,15 @@ public: // SYNTHETIC: LEGO1 0x1000d060 // LegoPartPresenter::`scalar deleting destructor' + inline void Reset() { m_partData = NULL; } + + MxResult ParsePart(MxDSChunk& p_chunk); + void FUN_1007df20(); + private: void Destroy(MxBool p_fromDestructor); + + MxDSChunk* m_partData; // 0x54 }; #endif // LEGOPARTPRESENTER_H diff --git a/LEGO1/lego/legoomni/include/legotexturepresenter.h b/LEGO1/lego/legoomni/include/legotexturepresenter.h index 472c5828..657d3a52 100644 --- a/LEGO1/lego/legoomni/include/legotexturepresenter.h +++ b/LEGO1/lego/legoomni/include/legotexturepresenter.h @@ -7,6 +7,7 @@ // SIZE 0x54 (from inlined construction at 0x10009bb5) class LegoTexturePresenter : public MxMediaPresenter { public: + LegoTexturePresenter() : m_textureData(NULL) {} ~LegoTexturePresenter() override; // FUNCTION: LEGO1 0x1000ce50 @@ -28,6 +29,12 @@ public: // SYNTHETIC: LEGO1 0x1000cf40 // LegoTexturePresenter::`scalar deleting destructor' + + MxResult ParseTexture(MxDSChunk& p_chunk); + void FUN_1004f290(); + +private: + MxDSChunk* m_textureData; // 0x54 }; #endif // LEGOTEXTUREPRESENTER_H diff --git a/LEGO1/lego/legoomni/include/legoworldpresenter.h b/LEGO1/lego/legoomni/include/legoworldpresenter.h index ed0b87e2..e101acfb 100644 --- a/LEGO1/lego/legoomni/include/legoworldpresenter.h +++ b/LEGO1/lego/legoomni/include/legoworldpresenter.h @@ -3,7 +3,11 @@ #include "legoentitypresenter.h" +#include + class LegoWorld; +struct ModelDbPart; +struct ModelDbModel; // VTABLE: LEGO1 0x100d8ee0 // SIZE 0x54 @@ -33,7 +37,9 @@ public: MxResult StartAction(MxStreamController* p_controller, MxDSAction* p_action) override; // vtable+0x3c void VTable0x60(MxPresenter* p_presenter) override; // vtable+0x60 - void LoadWorld(char* p_worldName, LegoWorld* p_world); + MxResult FUN_10067360(ModelDbPart& p_part, FILE* p_wdbFile); + MxResult FUN_100674b0(ModelDbModel& p_model, FILE* p_wdbFile, LegoWorld* p_world); + MxResult LoadWorld(char* p_worldName, LegoWorld* p_world); // SYNTHETIC: LEGO1 0x10066750 // LegoWorldPresenter::`scalar deleting destructor' diff --git a/LEGO1/lego/legoomni/src/entity/legoworldpresenter.cpp b/LEGO1/lego/legoomni/src/entity/legoworldpresenter.cpp index be761f30..0b96ed04 100644 --- a/LEGO1/lego/legoomni/src/entity/legoworldpresenter.cpp +++ b/LEGO1/lego/legoomni/src/entity/legoworldpresenter.cpp @@ -5,12 +5,16 @@ #include "legobuildingmanager.h" #include "legoentity.h" #include "legoomni.h" +#include "legopartpresenter.h" #include "legoplantmanager.h" +#include "legotexturepresenter.h" #include "legovideomanager.h" #include "legoworld.h" +#include "modeldb/modeldb.h" #include "mxactionnotificationparam.h" #include "mxautolocker.h" #include "mxdsactionlist.h" +#include "mxdschunk.h" #include "mxdsmediaaction.h" #include "mxdsmultiaction.h" #include "mxnotificationmanager.h" @@ -19,8 +23,13 @@ #include "mxstl/stlcompat.h" #include "mxutil.h" +#include + // GLOBAL: LEGO1 0x100f75d4 -undefined4 g_legoWorldPresenterQuality = 1; +MxS32 g_legoWorldPresenterQuality = 1; + +// GLOBAL: LEGO1 0x100f75d8 +long g_wdbOffset = 0; // FUNCTION: LEGO1 0x100665b0 void LegoWorldPresenter::configureLegoWorldPresenter(MxS32 p_legoWorldPresenterQuality) @@ -152,9 +161,171 @@ void LegoWorldPresenter::StartingTickle() ProgressTickleState(e_streaming); } -// STUB: LEGO1 0x10066b40 -void LegoWorldPresenter::LoadWorld(char* p_worldName, LegoWorld* p_world) +// FUNCTION: LEGO1 0x10066b40 +MxResult LegoWorldPresenter::LoadWorld(char* p_worldName, LegoWorld* p_world) { + char wdbPath[512]; + sprintf(wdbPath, "%s", MxOmni::GetHD()); + + if (wdbPath[strlen(wdbPath) - 1] != '\\') { + strcat(wdbPath, "\\"); + } + + strcat(wdbPath, "lego\\data\\world.wdb"); + + if (access(wdbPath, 4) != 0) { + sprintf(wdbPath, "%s", MxOmni::GetCD()); + + if (wdbPath[strlen(wdbPath) - 1] != '\\') { + strcat(wdbPath, "\\"); + } + + strcat(wdbPath, "lego\\data\\world.wdb"); + + if (access(wdbPath, 4) != 0) { + return FAILURE; + } + } + + ModelDbWorld* worlds = NULL; + MxS32 numWorlds, i, j; + MxU32 size; + MxU8* buff; + FILE* wdbFile = fopen(wdbPath, "rb"); + + if (wdbFile == NULL) { + return FAILURE; + } + + ReadModelDbWorlds(wdbFile, worlds, numWorlds); + + for (i = 0; i < numWorlds; i++) { + if (!strcmpi(worlds[i].m_worldName, p_worldName)) { + break; + } + } + + if (i == numWorlds) { + return FAILURE; + } + + if (g_wdbOffset == 0) { + if (fread(&size, sizeof(size), 1, wdbFile) != 1) { + return FAILURE; + } + + buff = new MxU8[size]; + if (fread(buff, size, 1, wdbFile) != 1) { + return FAILURE; + } + + MxDSChunk chunk; + chunk.SetLength(size); + chunk.SetData(buff); + + LegoTexturePresenter texturePresenter; + if (texturePresenter.ParseTexture(chunk) == SUCCESS) { + texturePresenter.FUN_1004f290(); + } + + delete[] buff; + + if (fread(&size, sizeof(size), 1, wdbFile) != 1) { + return FAILURE; + } + + buff = new MxU8[size]; + if (fread(buff, size, 1, wdbFile) != 1) { + return FAILURE; + } + + chunk.SetLength(size); + chunk.SetData(buff); + + LegoPartPresenter partPresenter; + if (partPresenter.ParsePart(chunk) == SUCCESS) { + partPresenter.FUN_1007df20(); + } + + delete[] buff; + + g_wdbOffset = ftell(wdbFile); + } + else { + if (fseek(wdbFile, g_wdbOffset, SEEK_SET) != 0) { + return FAILURE; + } + } + + ModelDbPartListCursor cursor(worlds[i].m_partList); + ModelDbPart* part; + + while (cursor.Next(part)) { + if (GetViewLODListManager()->Lookup(part->m_roiName.GetData()) == NULL && + FUN_10067360(*part, wdbFile) != SUCCESS) { + return FAILURE; + } + } + + for (j = 0; j < worlds[i].m_numModels; j++) { + if (!strnicmp(worlds[i].m_models[j].m_modelName, "isle", 4)) { + switch (g_legoWorldPresenterQuality) { + case 0: + if (strcmpi(worlds[i].m_models[j].m_modelName, "isle_lo")) { + continue; + } + break; + case 1: + if (strcmpi(worlds[i].m_models[j].m_modelName, "isle")) { + continue; + } + break; + case 2: + if (strcmpi(worlds[i].m_models[j].m_modelName, "isle_hi")) { + continue; + } + } + } + else if (g_legoWorldPresenterQuality <= 1 && !strnicmp(worlds[i].m_models[j].m_modelName, "haus", 4)) { + if (worlds[i].m_models[j].m_modelName[4] == '3') { + if (FUN_100674b0(worlds[i].m_models[j], wdbFile, p_world) != SUCCESS) { + return FAILURE; + } + + if (FUN_100674b0(worlds[i].m_models[j - 2], wdbFile, p_world) != SUCCESS) { + return FAILURE; + } + + if (FUN_100674b0(worlds[i].m_models[j - 1], wdbFile, p_world) != SUCCESS) { + return FAILURE; + } + } + + continue; + } + + if (FUN_100674b0(worlds[i].m_models[j], wdbFile, p_world) != SUCCESS) { + return FAILURE; + } + } + + FreeModelDbWorlds(worlds, numWorlds); + fclose(wdbFile); + return SUCCESS; +} + +// STUB: LEGO1 0x10067360 +MxResult LegoWorldPresenter::FUN_10067360(ModelDbPart& p_part, FILE* p_wdbFile) +{ + // TODO + return SUCCESS; +} + +// STUB: LEGO1 0x100674b0 +MxResult LegoWorldPresenter::FUN_100674b0(ModelDbModel& p_model, FILE* p_wdbFile, LegoWorld* p_world) +{ + // TODO + return SUCCESS; } // FUNCTION: LEGO1 0x10067a70 diff --git a/LEGO1/lego/legoomni/src/video/legopartpresenter.cpp b/LEGO1/lego/legoomni/src/video/legopartpresenter.cpp index 08cfe878..b792f202 100644 --- a/LEGO1/lego/legoomni/src/video/legopartpresenter.cpp +++ b/LEGO1/lego/legoomni/src/video/legopartpresenter.cpp @@ -4,10 +4,10 @@ #include "legovideomanager.h" // GLOBAL: LEGO1 0x100f7aa0 -int g_partPresenterConfig1 = 1; +MxS32 g_partPresenterConfig1 = 1; // GLOBAL: LEGO1 0x100f7aa4 -int g_partPresenterConfig2 = 100; +MxS32 g_partPresenterConfig2 = 100; // FUNCTION: LEGO1 0x1000cf60 void LegoPartPresenter::Destroy() @@ -35,8 +35,21 @@ void LegoPartPresenter::Destroy(MxBool p_fromDestructor) // TODO } +// STUB: LEGO1 0x1007ca30 +MxResult LegoPartPresenter::ParsePart(MxDSChunk& p_chunk) +{ + // TODO + return SUCCESS; +} + // STUB: LEGO1 0x1007deb0 void LegoPartPresenter::ReadyTickle() { // TODO } + +// STUB: LEGO1 0x1007df20 +void LegoPartPresenter::FUN_1007df20() +{ + // TODO +} diff --git a/LEGO1/lego/legoomni/src/video/legotexturepresenter.cpp b/LEGO1/lego/legoomni/src/video/legotexturepresenter.cpp index e376966f..b101b2c9 100644 --- a/LEGO1/lego/legoomni/src/video/legotexturepresenter.cpp +++ b/LEGO1/lego/legoomni/src/video/legotexturepresenter.cpp @@ -4,6 +4,8 @@ #include "legovideomanager.h" #include "mxcompositepresenter.h" +DECOMP_SIZE_ASSERT(LegoTexturePresenter, 0x54) + // FUNCTION: LEGO1 0x1004eb40 LegoTexturePresenter::~LegoTexturePresenter() { @@ -17,6 +19,19 @@ MxResult LegoTexturePresenter::AddToManager() return SUCCESS; } +// STUB: LEGO1 0x1004ebd0 +MxResult LegoTexturePresenter::ParseTexture(MxDSChunk& p_chunk) +{ + // TODO + return SUCCESS; +} + +// STUB: LEGO1 0x1004f290 +void LegoTexturePresenter::FUN_1004f290() +{ + // TODO +} + // STUB: LEGO1 0x1004fc60 MxResult LegoTexturePresenter::PutData() { diff --git a/LEGO1/library_msvc.h b/LEGO1/library_msvc.h index f08a4876..e819f995 100644 --- a/LEGO1/library_msvc.h +++ b/LEGO1/library_msvc.h @@ -78,6 +78,9 @@ // LIBRARY: LEGO1 0x1008c410 // _strlwr +// LIBRARY: LEGO1 0x1008c570 +// _access + // LIBRARY: LEGO1 0x1008c5c0 // _fseek diff --git a/LEGO1/modeldb/modeldb.cpp b/LEGO1/modeldb/modeldb.cpp new file mode 100644 index 00000000..e472e7e7 --- /dev/null +++ b/LEGO1/modeldb/modeldb.cpp @@ -0,0 +1,83 @@ +#include "modeldb.h" + +DECOMP_SIZE_ASSERT(ModelDbWorld, 0x18) +DECOMP_SIZE_ASSERT(ModelDbPart, 0x18) +DECOMP_SIZE_ASSERT(ModelDbModel, 0x38) +DECOMP_SIZE_ASSERT(ModelDbPartList, 0x1c) +DECOMP_SIZE_ASSERT(ModelDbPartListCursor, 0x10) + +// STUB: LEGO1 0x100276b0 +MxResult ModelDbModel::Read(FILE* p_file) +{ + return SUCCESS; +} + +// STUB: LEGO1 0x10027850 +MxResult ModelDbPart::Read(FILE* p_file) +{ + return SUCCESS; +} + +// FUNCTION: LEGO1 0x10027910 +MxResult ReadModelDbWorlds(FILE* p_file, ModelDbWorld*& p_worlds, MxS32& p_numWorlds) +{ + p_worlds = NULL; + p_numWorlds = 0; + + MxS32 numWorlds; + if (fread(&numWorlds, sizeof(numWorlds), 1, p_file) != 1) { + return FAILURE; + } + + ModelDbWorld* worlds = new ModelDbWorld[numWorlds]; + MxS32 worldNameLen, numParts, i, j; + + for (i = 0; i < numWorlds; i++) { + if (fread(&worldNameLen, sizeof(worldNameLen), 1, p_file) != 1) { + return FAILURE; + } + + worlds[i].m_worldName = new char[worldNameLen]; + if (fread(worlds[i].m_worldName, worldNameLen, 1, p_file) != 1) { + return FAILURE; + } + + if (fread(&numParts, sizeof(numParts), 1, p_file) != 1) { + return FAILURE; + } + + worlds[i].m_partList = new ModelDbPartList(); + + for (j = 0; j < numParts; j++) { + ModelDbPart* part = new ModelDbPart(); + + if (part->Read(p_file) != SUCCESS) { + return FAILURE; + } + + worlds[i].m_partList->Append(part); + } + + if (fread(&worlds[i].m_numModels, sizeof(worlds[i].m_numModels), 1, p_file) != 1) { + return FAILURE; + } + + worlds[i].m_models = new ModelDbModel[worlds[i].m_numModels]; + + for (j = 0; j < worlds[i].m_numModels; j++) { + if (worlds[i].m_models[j].Read(p_file) != SUCCESS) { + return FAILURE; + } + } + } + + p_worlds = worlds; + p_numWorlds = numWorlds; + return SUCCESS; +} + +// STUB: LEGO1 0x10028080 +void FreeModelDbWorlds(ModelDbWorld*& p_worlds, MxS32 p_numWorlds) +{ + // TODO +} diff --git a/LEGO1/modeldb/modeldb.h b/LEGO1/modeldb/modeldb.h new file mode 100644 index 00000000..ad60e677 --- /dev/null +++ b/LEGO1/modeldb/modeldb.h @@ -0,0 +1,112 @@ +#ifndef MODELDB_H +#define MODELDB_H + +#include "decomp.h" +#include "mxlist.h" +#include "mxstring.h" +#include "mxtypes.h" + +#include + +// SIZE 0x18 +struct ModelDbPart { + MxResult Read(FILE* p_file); + + MxString m_roiName; // 0x00 + undefined4 m_unk0x10; // 0x10 + undefined4 m_unk0x14; // 0x14 +}; + +// VTABLE: LEGO1 0x100d6888 +// class MxCollection + +// VTABLE: LEGO1 0x100d68a0 +// class MxList + +// VTABLE: LEGO1 0x100d68b8 +// SIZE 0x1c +class ModelDbPartList : public MxList { +public: + ModelDbPartList() { m_unk0x18 = 1; } + + // FUNCTION: LEGO1 0x10027c40 + MxS8 Compare(ModelDbPart* p_a, ModelDbPart* p_b) override + { + MxS32 compare = strcmpi(p_a->m_roiName.GetData(), p_b->m_roiName.GetData()); + + if (compare == 0) { + p_b->m_unk0x10 = p_a->m_unk0x10; + p_b->m_unk0x14 = p_a->m_unk0x14; + } + + return compare; + } // vtable+0x14 + + // SYNTHETIC: LEGO1 0x10027d70 + // ModelDbPartList::`scalar deleting destructor' + +private: + undefined m_unk0x18; +}; + +// VTABLE: LEGO1 0x100d68d0 +// class MxListCursor + +// VTABLE: LEGO1 0x100d68e8 +// SIZE 0x10 +class ModelDbPartListCursor : public MxListCursor { +public: + ModelDbPartListCursor(ModelDbPartList* p_list) : MxListCursor(p_list) {} +}; + +// TEMPLATE: LEGO1 0x10027c70 +// MxCollection::Compare + +// TEMPLATE: LEGO1 0x10027c80 +// MxCollection::~MxCollection + +// TEMPLATE: LEGO1 0x10027cd0 +// MxCollection::Destroy + +// TEMPLATE: LEGO1 0x10027ce0 +// MxList::~MxList + +// SYNTHETIC: LEGO1 0x10027de0 +// MxCollection::`scalar deleting destructor' + +// SYNTHETIC: LEGO1 0x10027e50 +// MxList::`scalar deleting destructor' + +// SYNTHETIC: LEGO1 0x10027f00 +// ModelDbPartListCursor::`scalar deleting destructor' + +// TEMPLATE: LEGO1 0x10027f70 +// MxListCursor::~MxListCursor + +// SYNTHETIC: LEGO1 0x10027fc0 +// MxListCursor::`scalar deleting destructor' + +// TEMPLATE: LEGO1 0x10028030 +// ModelDbPartListCursor::~ModelDbPartListCursor + +// SIZE 0x38 +struct ModelDbModel { + MxResult Read(FILE* p_file); + + char* m_modelName; // 0x00 + undefined m_unk0x04[0x34]; // 0x04 +}; + +// SIZE 0x18 +struct ModelDbWorld { + char* m_worldName; // 0x00 + ModelDbPartList* m_partList; // 0x04 + ModelDbModel* m_models; // 0x08 + MxS32 m_numModels; // 0x0c + undefined m_unk0x10[0x08]; // 0x10 +}; + +MxResult ReadModelDbWorlds(FILE* p_file, ModelDbWorld*& p_worlds, MxS32& p_numWorlds); +void FreeModelDbWorlds(ModelDbWorld*& p_worlds, MxS32 p_numWorlds); + +#endif // MODELDB_H