From 8498c69af1b7428828ef5a03f120df851f87dde3 Mon Sep 17 00:00:00 2001 From: Fabian Neundorf Date: Tue, 24 Jun 2025 22:38:08 +0200 Subject: [PATCH 001/188] Clear unknowns in `LegoPlantManager` (#1587) --- LEGO1/lego/legoomni/include/legoplantmanager.h | 12 ++++++------ .../lego/legoomni/src/common/legoplantmanager.cpp | 14 +++++++------- 2 files changed, 13 insertions(+), 13 deletions(-) diff --git a/LEGO1/lego/legoomni/include/legoplantmanager.h b/LEGO1/lego/legoomni/include/legoplantmanager.h index 8a8c7902..91fe49c8 100644 --- a/LEGO1/lego/legoomni/include/legoplantmanager.h +++ b/LEGO1/lego/legoomni/include/legoplantmanager.h @@ -54,7 +54,7 @@ class LegoPlantManager : public MxCore { LegoEntity* CreatePlant(MxS32 p_index, LegoWorld* p_world, LegoOmni::World p_worldId); MxBool DecrementCounter(LegoEntity* p_entity); void ScheduleAnimation(LegoEntity* p_entity, MxLong p_length); - MxResult FUN_10026410(); + MxResult DetermineBoundaries(); void ClearCounters(); void SetInitialCounters(); @@ -77,11 +77,11 @@ class LegoPlantManager : public MxCore { static MxS32 g_maxMove[4]; static MxU32 g_maxSound; - LegoOmni::World m_worldId; // 0x08 - undefined m_unk0x0c; // 0x0c - AnimEntry* m_entries[5]; // 0x10 - MxS8 m_numEntries; // 0x24 - LegoWorld* m_world; // 0x28 + LegoOmni::World m_worldId; // 0x08 + MxBool m_boundariesDetermined; // 0x0c + AnimEntry* m_entries[5]; // 0x10 + MxS8 m_numEntries; // 0x24 + LegoWorld* m_world; // 0x28 }; #endif // LEGOPLANTMANAGER_H diff --git a/LEGO1/lego/legoomni/src/common/legoplantmanager.cpp b/LEGO1/lego/legoomni/src/common/legoplantmanager.cpp index 734806fc..766edf79 100644 --- a/LEGO1/lego/legoomni/src/common/legoplantmanager.cpp +++ b/LEGO1/lego/legoomni/src/common/legoplantmanager.cpp @@ -83,7 +83,7 @@ void LegoPlantManager::Init() } m_worldId = LegoOmni::e_undefined; - m_unk0x0c = 0; + m_boundariesDetermined = FALSE; m_numEntries = 0; } @@ -98,7 +98,7 @@ void LegoPlantManager::LoadWorldInfo(LegoOmni::World p_worldId) CreatePlant(i, world, p_worldId); } - m_unk0x0c = 0; + m_boundariesDetermined = FALSE; } // FUNCTION: LEGO1 0x100263a0 @@ -119,12 +119,12 @@ void LegoPlantManager::Reset(LegoOmni::World p_worldId) } m_worldId = LegoOmni::e_undefined; - m_unk0x0c = 0; + m_boundariesDetermined = FALSE; } // FUNCTION: LEGO1 0x10026410 // FUNCTION: BETA10 0x100c50e9 -MxResult LegoPlantManager::FUN_10026410() +MxResult LegoPlantManager::DetermineBoundaries() { // similar to LegoBuildingManager::FUN_10030630() @@ -192,7 +192,7 @@ MxResult LegoPlantManager::FUN_10026410() } } - m_unk0x0c = TRUE; + m_boundariesDetermined = TRUE; return SUCCESS; } @@ -200,8 +200,8 @@ MxResult LegoPlantManager::FUN_10026410() // FUNCTION: BETA10 0x100c55e0 LegoPlantInfo* LegoPlantManager::GetInfoArray(MxS32& p_length) { - if (!m_unk0x0c) { - FUN_10026410(); + if (!m_boundariesDetermined) { + DetermineBoundaries(); } p_length = sizeOfArray(g_plantInfo); From 73bab247218fee0f6e5e555a08c6a88a33f105e5 Mon Sep 17 00:00:00 2001 From: VoxelTek <53562267+VoxelTek@users.noreply.github.com> Date: Fri, 27 Jun 2025 00:21:11 +1000 Subject: [PATCH 002/188] Add Launch Game button to config tool, rename executable to isle-config (#435) * Add Launch Game option to config tool * Rename executable from "config" to "isle-config" * Add error popup if unable to find game executable * Use one QMessageBox for both Win and *nix Create one QMessageBox object to use for both Windows and non-Windows platforms. Additionally, set all relevant text during creation of QMessageBox, and show Warning icon as part of message box. * Add tooltips to save, launch, and exit buttons * Change "Launch Game" to "Save and Launch" * Remove unnecessary Windows-specific code --- CMakeLists.txt | 38 +++++++++++++++++++------------------- CONFIG/MainDlg.cpp | 27 +++++++++++++++++++++++++++ CONFIG/MainDlg.h | 1 + CONFIG/res/maindialog.ui | 22 +++++++++++++++++++--- 4 files changed, 66 insertions(+), 22 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 334580c0..1f7e0595 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -525,7 +525,7 @@ endif() if (ISLE_BUILD_CONFIG) find_package(Qt6 REQUIRED COMPONENTS Core Widgets) qt_standard_project_setup() - qt_add_executable(config WIN32 + qt_add_executable(isle-config WIN32 LEGO1/mxdirectx/mxdirectxinfo.cpp LEGO1/mxdirectx/legodxinfo.cpp CONFIG/config.cpp @@ -535,22 +535,22 @@ if (ISLE_BUILD_CONFIG) CONFIG/res/config.rc CONFIG/res/config.qrc ) - target_link_libraries(config PRIVATE Qt6::Core Qt6::Widgets) - set_property(TARGET config PROPERTY AUTOMOC ON) - set_property(TARGET config PROPERTY AUTORCC ON) - set_property(TARGET config PROPERTY AUTOUIC ON) - set_property(TARGET config PROPERTY AUTOUIC_SEARCH_PATHS "${CMAKE_CURRENT_SOURCE_DIR}/CONFIG/res") - list(APPEND isle_targets config) - target_compile_definitions(config PRIVATE _AFXDLL MXDIRECTX_FOR_CONFIG) - target_include_directories(config PRIVATE "${CMAKE_CURRENT_SOURCE_DIR}/LEGO1") - target_include_directories(config PUBLIC "$") + target_link_libraries(isle-config PRIVATE Qt6::Core Qt6::Widgets) + set_property(TARGET isle-config PROPERTY AUTOMOC ON) + set_property(TARGET isle-config PROPERTY AUTORCC ON) + set_property(TARGET isle-config PROPERTY AUTOUIC ON) + set_property(TARGET isle-config PROPERTY AUTOUIC_SEARCH_PATHS "${CMAKE_CURRENT_SOURCE_DIR}/CONFIG/res") + list(APPEND isle_targets isle-config) + target_compile_definitions(isle-config PRIVATE _AFXDLL MXDIRECTX_FOR_CONFIG) + target_include_directories(isle-config PRIVATE "${CMAKE_CURRENT_SOURCE_DIR}/LEGO1") + target_include_directories(isle-config PUBLIC "$") if (CMAKE_CXX_COMPILER_VERSION VERSION_LESS 14) - target_link_libraries(config PRIVATE DirectX5::DirectX5) + target_link_libraries(isle-config PRIVATE DirectX5::DirectX5) endif() - target_compile_definitions(config PRIVATE DIRECT3D_VERSION=0x500) - target_link_libraries(config PRIVATE SDL3::SDL3 Isle::iniparser) + target_compile_definitions(isle-config PRIVATE DIRECT3D_VERSION=0x500) + target_link_libraries(isle-config PRIVATE SDL3::SDL3 Isle::iniparser) if (NOT ISLE_MINIWIN) - target_link_libraries(config PRIVATE ddraw dxguid) + target_link_libraries(isle-config PRIVATE ddraw dxguid) endif() endif() @@ -564,8 +564,8 @@ if (MSVC) if (TARGET isle) target_compile_definitions(isle PRIVATE "_CRT_SECURE_NO_WARNINGS") endif() - if (TARGET config) - target_compile_definitions(config PRIVATE "_CRT_SECURE_NO_WARNINGS") + if (TARGET isle-config) + target_compile_definitions(isle-config PRIVATE "_CRT_SECURE_NO_WARNINGS") endif() endif() # Visual Studio 2017 version 15.7 needs "/Zc:__cplusplus" for __cplusplus @@ -574,8 +574,8 @@ if (MSVC) if (TARGET isle) target_compile_options(isle PRIVATE "-Zc:__cplusplus") endif() - if (TARGET config) - target_compile_options(config PRIVATE "-Zc:__cplusplus") + if (TARGET isle-config) + target_compile_options(isle-config PRIVATE "-Zc:__cplusplus") endif() endif() endif() @@ -633,7 +633,7 @@ install(TARGETS isle ${install_extra_targets} LIBRARY DESTINATION "${CMAKE_INSTALL_LIBDIR}" ) if (ISLE_BUILD_CONFIG) - install(TARGETS config + install(TARGETS isle-config RUNTIME DESTINATION "${CMAKE_INSTALL_BINDIR}" ) endif() diff --git a/CONFIG/MainDlg.cpp b/CONFIG/MainDlg.cpp index b90f1f66..e2c526f6 100644 --- a/CONFIG/MainDlg.cpp +++ b/CONFIG/MainDlg.cpp @@ -11,6 +11,8 @@ #include "res/resource.h" #include +#include +#include #include #include @@ -59,6 +61,7 @@ CMainDialog::CMainDialog(QWidget* pParent) : QDialog(pParent) connect(m_ui->fullscreenCheckBox, &QCheckBox::toggled, this, &CMainDialog::OnCheckboxFullscreen); connect(m_ui->okButton, &QPushButton::clicked, this, &CMainDialog::accept); connect(m_ui->cancelButton, &QPushButton::clicked, this, &CMainDialog::reject); + connect(m_ui->launchButton, &QPushButton::clicked, this, &CMainDialog::launch); connect(m_ui->dataPathOpen, &QPushButton::clicked, this, &CMainDialog::SelectDataPathDialog); connect(m_ui->savePathOpen, &QPushButton::clicked, this, &CMainDialog::SelectSavePathDialog); @@ -155,6 +158,30 @@ void CMainDialog::accept() QDialog::accept(); } +void CMainDialog::launch() +{ + if (m_modified) { + currentConfigApp->WriteRegisterSettings(); + } + + QDir::setCurrent(QCoreApplication::applicationDirPath()); + + QMessageBox msgBox = QMessageBox( + QMessageBox::Warning, + QString("Error!"), + QString("Unable to locate isle executable!"), + QMessageBox::Close + ); + + if (!QProcess::startDetached("./isle")) { // Check in isle-config directory + if (!QProcess::startDetached("isle")) { // Check in $PATH + msgBox.exec(); + } + } + + QDialog::accept(); +} + // FUNCTION: CONFIG 0x00404360 void CMainDialog::UpdateInterface() { diff --git a/CONFIG/MainDlg.h b/CONFIG/MainDlg.h index 65782037..372a1156 100644 --- a/CONFIG/MainDlg.h +++ b/CONFIG/MainDlg.h @@ -45,6 +45,7 @@ private slots: void OnCheckboxFullscreen(bool checked); void accept() override; void reject() override; + void launch(); void SelectDataPathDialog(); void SelectSavePathDialog(); void DataPathEdited(); diff --git a/CONFIG/res/maindialog.ui b/CONFIG/res/maindialog.ui index 78c5a174..c2760e7c 100644 --- a/CONFIG/res/maindialog.ui +++ b/CONFIG/res/maindialog.ui @@ -508,8 +508,11 @@ + + Save configuration and close the config tool. + - OK + Save and Exit true @@ -517,9 +520,22 @@ - + + + Save configuration and launch LEGO Island. + - Cancel + Save and Launch + + + + + + + Discard changed settings and close the config tool. + + + Exit without saving From a782c489809f7eeae99d8c4fd3509f3497dacdee Mon Sep 17 00:00:00 2001 From: Danct12 Date: Fri, 27 Jun 2025 00:41:46 +0700 Subject: [PATCH 003/188] miniwin: Use PRIu32 for printing uint32_t (#436) Some architecture uses different type for uint32_t. For example: x86_64 | aarch64 = unsigned int xtensa | riscv32 = long unsigned int arm = long unsigned int --- miniwin/src/ddraw/ddraw.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/miniwin/src/ddraw/ddraw.cpp b/miniwin/src/ddraw/ddraw.cpp index 2765a2a1..acf695eb 100644 --- a/miniwin/src/ddraw/ddraw.cpp +++ b/miniwin/src/ddraw/ddraw.cpp @@ -18,6 +18,7 @@ #include #include +#include #include #include #include @@ -75,7 +76,7 @@ HRESULT DirectDrawImpl::CreateSurface( if ((lpDDSurfaceDesc->dwFlags & DDSD_ZBUFFERBITDEPTH) != DDSD_ZBUFFERBITDEPTH) { return DDERR_INVALIDPARAMS; } - SDL_Log("Todo: Set %dbit Z-Buffer", lpDDSurfaceDesc->dwZBufferBitDepth); + SDL_Log("Todo: Set %" PRIu32 "bit Z-Buffer", lpDDSurfaceDesc->dwZBufferBitDepth); *lplpDDSurface = static_cast(new DummySurfaceImpl); return DD_OK; } From b66d1e2f64547d29854f36275ffa9a0fb584f23d Mon Sep 17 00:00:00 2001 From: Fabian Neundorf Date: Thu, 26 Jun 2025 20:31:30 +0200 Subject: [PATCH 004/188] Clear unknowns in `Ambulance` and `AmbulanceMissionState` (#1588) --- LEGO1/lego/legoomni/include/ambulance.h | 46 +++++++---- LEGO1/lego/legoomni/src/actors/ambulance.cpp | 84 ++++++++++---------- LEGO1/lego/legoomni/src/worlds/isle.cpp | 6 +- 3 files changed, 74 insertions(+), 62 deletions(-) diff --git a/LEGO1/lego/legoomni/include/ambulance.h b/LEGO1/lego/legoomni/include/ambulance.h index 97e6ba38..cb6a396a 100644 --- a/LEGO1/lego/legoomni/include/ambulance.h +++ b/LEGO1/lego/legoomni/include/ambulance.h @@ -11,6 +11,12 @@ class MxEndActionNotificationParam; // SIZE 0x24 class AmbulanceMissionState : public LegoState { public: + enum { + e_ready = 0, + e_enteredAmbulance = 1, + e_prepareAmbulance = 2, + }; + AmbulanceMissionState(); // FUNCTION: LEGO1 0x10037440 @@ -125,18 +131,18 @@ class AmbulanceMissionState : public LegoState { // SYNTHETIC: LEGO1 0x100376c0 // AmbulanceMissionState::`scalar deleting destructor' - undefined4 m_unk0x08; // 0x08 - MxLong m_startTime; // 0x0c - MxS16 m_peScore; // 0x10 - MxS16 m_maScore; // 0x12 - MxS16 m_paScore; // 0x14 - MxS16 m_niScore; // 0x16 - MxS16 m_laScore; // 0x18 - MxS16 m_peHighScore; // 0x1a - MxS16 m_maHighScore; // 0x1c - MxS16 m_paHighScore; // 0x1e - MxS16 m_niHighScore; // 0x20 - MxS16 m_laHighScore; // 0x22 + MxU32 m_state; // 0x08 + MxLong m_startTime; // 0x0c + MxS16 m_peScore; // 0x10 + MxS16 m_maScore; // 0x12 + MxS16 m_paScore; // 0x14 + MxS16 m_niScore; // 0x16 + MxS16 m_laScore; // 0x18 + MxS16 m_peHighScore; // 0x1a + MxS16 m_maHighScore; // 0x1c + MxS16 m_paHighScore; // 0x1e + MxS16 m_niHighScore; // 0x20 + MxS16 m_laHighScore; // 0x22 }; // VTABLE: LEGO1 0x100d71a8 @@ -177,15 +183,21 @@ class Ambulance : public IslePathActor { virtual MxLong HandleEndAction(MxEndActionNotificationParam& p_param); // vtable+0xf4 void CreateState(); - void FUN_10036e60(); + void Init(); void ActivateSceneActions(); void StopActions(); - void FUN_10037250(); + void Reset(); // SYNTHETIC: LEGO1 0x10036130 // Ambulance::`scalar deleting destructor' private: + enum { + e_none = 0, + e_waiting = 1, + e_finished = 3, + }; + void PlayAnimation(IsleScript::Script p_objectId); void PlayFinalAnimation(IsleScript::Script p_objectId); void StopAction(IsleScript::Script p_objectId); @@ -196,9 +208,9 @@ class Ambulance : public IslePathActor { AmbulanceMissionState* m_state; // 0x164 MxS16 m_unk0x168; // 0x168 MxS16 m_actorId; // 0x16a - MxS16 m_unk0x16c; // 0x16c - MxS16 m_unk0x16e; // 0x16e - MxS16 m_unk0x170; // 0x170 + MxS16 m_atPoliceTask; // 0x16c + MxS16 m_atBeachTask; // 0x16e + MxS16 m_taskState; // 0x170 MxS16 m_unk0x172; // 0x172 IsleScript::Script m_lastAction; // 0x174 IsleScript::Script m_lastAnimation; // 0x178 diff --git a/LEGO1/lego/legoomni/src/actors/ambulance.cpp b/LEGO1/lego/legoomni/src/actors/ambulance.cpp index 22c1e701..95ca6975 100644 --- a/LEGO1/lego/legoomni/src/actors/ambulance.cpp +++ b/LEGO1/lego/legoomni/src/actors/ambulance.cpp @@ -37,9 +37,9 @@ Ambulance::Ambulance() m_state = NULL; m_unk0x168 = 0; m_actorId = -1; - m_unk0x16c = 0; - m_unk0x16e = 0; - m_unk0x170 = 0; + m_atPoliceTask = 0; + m_atBeachTask = 0; + m_taskState = Ambulance::e_none; m_lastAction = IsleScript::c_noneIsle; m_unk0x172 = 0; m_lastAnimation = IsleScript::c_noneIsle; @@ -70,7 +70,7 @@ MxResult Ambulance::Create(MxDSAction& p_dsAction) m_state = (AmbulanceMissionState*) GameState()->GetState("AmbulanceMissionState"); if (!m_state) { m_state = new AmbulanceMissionState(); - m_state->m_unk0x08 = 0; + m_state->m_state = AmbulanceMissionState::e_ready; GameState()->RegisterState(m_state); } } @@ -170,25 +170,25 @@ MxLong Ambulance::HandleEndAction(MxEndActionNotificationParam& p_param) m_lastAction = IsleScript::c_noneIsle; } else if (objectId == IsleScript::c_hho027en_RunAnim) { - m_state->m_unk0x08 = 1; + m_state->m_state = AmbulanceMissionState::e_enteredAmbulance; CurrentWorld()->PlaceActor(UserActor()); HandleClick(); m_unk0x172 = 0; TickleManager()->RegisterClient(this, 40000); } else if (objectId == IsleScript::c_hpz047pe_RunAnim || objectId == IsleScript::c_hpz048pe_RunAnim || objectId == IsleScript::c_hpz049bd_RunAnim || objectId == IsleScript::c_hpz053pa_RunAnim) { - if (m_unk0x170 == 3) { + if (m_taskState == Ambulance::e_finished) { PlayAnimation(IsleScript::c_hpz055pa_RunAnim); - m_unk0x170 = 0; + m_taskState = Ambulance::e_none; } else { PlayAnimation(IsleScript::c_hpz053pa_RunAnim); } } else if (objectId == IsleScript::c_hpz050bd_RunAnim || objectId == IsleScript::c_hpz052ma_RunAnim) { - if (m_unk0x170 == 3) { + if (m_taskState == Ambulance::e_finished) { PlayAnimation(IsleScript::c_hpz057ma_RunAnim); - m_unk0x170 = 0; + m_taskState = Ambulance::e_none; } else { PlayAnimation(IsleScript::c_hpz052ma_RunAnim); @@ -201,18 +201,18 @@ MxLong Ambulance::HandleEndAction(MxEndActionNotificationParam& p_param) m_unk0x172 = 0; TickleManager()->RegisterClient(this, 40000); - if (m_unk0x16c != 0) { + if (m_atPoliceTask != 0) { StopActions(); } } else if (objectId == IsleScript::c_hps116bd_RunAnim || objectId == IsleScript::c_hps118re_RunAnim) { - if (objectId == IsleScript::c_hps116bd_RunAnim && m_unk0x170 != 3) { + if (objectId == IsleScript::c_hps116bd_RunAnim && m_taskState != Ambulance::e_finished) { PlayAction(IsleScript::c_Avo923In_PlayWav); } - if (m_unk0x170 == 3) { + if (m_taskState == Ambulance::e_finished) { PlayAnimation(IsleScript::c_hps117bd_RunAnim); - m_unk0x170 = 0; + m_taskState = Ambulance::e_none; } else { PlayAnimation(IsleScript::c_hps118re_RunAnim); @@ -225,12 +225,12 @@ MxLong Ambulance::HandleEndAction(MxEndActionNotificationParam& p_param) m_unk0x172 = 0; TickleManager()->RegisterClient(this, 40000); - if (m_unk0x16e != 0) { + if (m_atBeachTask != 0) { StopActions(); } } else if (objectId == IsleScript::c_hho142cl_RunAnim || objectId == IsleScript::c_hho143cl_RunAnim || objectId == IsleScript::c_hho144cl_RunAnim) { - FUN_10037250(); + Reset(); } } @@ -241,18 +241,18 @@ MxLong Ambulance::HandleEndAction(MxEndActionNotificationParam& p_param) // FUNCTION: BETA10 0x100230bf MxLong Ambulance::HandleButtonDown(LegoControlManagerNotificationParam& p_param) { - if (m_unk0x170 == 1) { + if (m_taskState == Ambulance::e_waiting) { LegoROI* roi = PickROI(p_param.GetX(), p_param.GetY()); if (roi != NULL && !strcmpi(roi->GetName(), "ps-gate")) { - m_unk0x170 = 3; + m_taskState = Ambulance::e_finished; return 1; } roi = PickRootROI(p_param.GetX(), p_param.GetY()); if (roi != NULL && !strcmpi(roi->GetName(), "gd")) { - m_unk0x170 = 3; + m_taskState = Ambulance::e_finished; return 1; } } @@ -270,9 +270,9 @@ MxLong Ambulance::HandlePathStruct(LegoPathStructNotificationParam& p_param) } if (p_param.GetTrigger() == LegoPathStruct::c_camAnim && p_param.GetData() == 0x0b) { - if (m_unk0x16e != 0) { - if (m_unk0x16c != 0) { - m_state->m_unk0x08 = 2; + if (m_atBeachTask != 0) { + if (m_atPoliceTask != 0) { + m_state->m_state = AmbulanceMissionState::e_prepareAmbulance; if (m_lastAction != IsleScript::c_noneIsle) { InvokeAction(Extra::e_stop, *g_isleScript, m_lastAction, NULL); @@ -297,7 +297,7 @@ MxLong Ambulance::HandlePathStruct(LegoPathStructNotificationParam& p_param) return 0; } - if (m_unk0x16e != 0) { + if (m_atBeachTask != 0) { if (m_lastAction != IsleScript::c_noneIsle) { InvokeAction(Extra::e_stop, *g_isleScript, m_lastAction, NULL); } @@ -307,7 +307,7 @@ MxLong Ambulance::HandlePathStruct(LegoPathStructNotificationParam& p_param) } } - if (m_unk0x16c != 0) { + if (m_atPoliceTask != 0) { if (m_lastAction != IsleScript::c_noneIsle) { InvokeAction(Extra::e_stop, *g_isleScript, m_lastAction, NULL); } @@ -315,9 +315,9 @@ MxLong Ambulance::HandlePathStruct(LegoPathStructNotificationParam& p_param) PlayAction(IsleScript::c_Avo915In_PlayWav); } } - else if (p_param.GetTrigger() == LegoPathStruct::c_s && p_param.GetData() == 0x131 && m_unk0x16e == 0) { - m_unk0x16e = 1; - m_unk0x170 = 1; + else if (p_param.GetTrigger() == LegoPathStruct::c_s && p_param.GetData() == 0x131 && m_atBeachTask == 0) { + m_atBeachTask = 1; + m_taskState = Ambulance::e_waiting; if (m_lastAction != IsleScript::c_noneIsle) { InvokeAction(Extra::e_stop, *g_isleScript, m_lastAction, NULL); @@ -345,9 +345,9 @@ MxLong Ambulance::HandlePathStruct(LegoPathStructNotificationParam& p_param) break; } } - else if (p_param.GetTrigger() == LegoPathStruct::c_camAnim && (p_param.GetData() == 0x22 || p_param.GetData() == 0x23 || p_param.GetData() == 0x24) && m_unk0x16c == 0) { - m_unk0x16c = 1; - m_unk0x170 = 1; + else if (p_param.GetTrigger() == LegoPathStruct::c_camAnim && (p_param.GetData() == 0x22 || p_param.GetData() == 0x23 || p_param.GetData() == 0x24) && m_atPoliceTask == 0) { + m_atPoliceTask = 1; + m_taskState = Ambulance::e_waiting; if (m_lastAction != IsleScript::c_noneIsle) { InvokeAction(Extra::e_stop, *g_isleScript, m_lastAction, NULL); @@ -368,7 +368,7 @@ MxLong Ambulance::HandleClick() return 1; } - if (m_state->m_unk0x08 == 2) { + if (m_state->m_state == AmbulanceMissionState::e_prepareAmbulance) { return 1; } @@ -387,7 +387,7 @@ MxLong Ambulance::HandleClick() InvokeAction(Extra::e_start, *g_isleScript, IsleScript::c_AmbulanceDashboard, NULL); ControlManager()->Register(this); - if (m_state->m_unk0x08 == 1) { + if (m_state->m_state == AmbulanceMissionState::e_enteredAmbulance) { SpawnPlayer(LegoGameState::e_hospitalExited, TRUE, 0); m_state->m_startTime = Timer()->GetTime(); InvokeAction(Extra::e_start, *g_isleScript, IsleScript::c_pns018rd_RunAnim, NULL); @@ -398,9 +398,9 @@ MxLong Ambulance::HandleClick() // FUNCTION: LEGO1 0x10036e60 // FUNCTION: BETA10 0x100236bb -void Ambulance::FUN_10036e60() +void Ambulance::Init() { - m_state->m_unk0x08 = 2; + m_state->m_state = AmbulanceMissionState::e_prepareAmbulance; PlayAnimation(IsleScript::c_hho027en_RunAnim); m_lastAction = IsleScript::c_noneIsle; m_lastAnimation = IsleScript::c_noneIsle; @@ -411,7 +411,7 @@ void Ambulance::Exit() { GameState()->m_currentArea = LegoGameState::e_hospitalExterior; StopActions(); - FUN_10037250(); + Reset(); Leave(); } @@ -467,11 +467,11 @@ void Ambulance::ActivateSceneActions() { PlayMusic(JukeboxScript::c_Hospital_Music); - if (m_state->m_unk0x08 == 1) { - m_state->m_unk0x08 = 0; + if (m_state->m_state == AmbulanceMissionState::e_enteredAmbulance) { + m_state->m_state = AmbulanceMissionState::e_ready; PlayAction(IsleScript::c_ham033cl_PlayWav); } - else if (m_unk0x16c != 0 && m_unk0x16e != 0) { + else if (m_atPoliceTask != 0 && m_atBeachTask != 0) { IsleScript::Script objectId; switch (rand() % 2) { @@ -571,14 +571,14 @@ void Ambulance::StopActions() } // FUNCTION: LEGO1 0x10037250 -void Ambulance::FUN_10037250() +void Ambulance::Reset() { StopAction(m_lastAction); BackgroundAudioManager()->RaiseVolume(); ((Act1State*) GameState()->GetState("Act1State"))->m_unk0x018 = 0; - m_state->m_unk0x08 = 0; - m_unk0x16e = 0; - m_unk0x16c = 0; + m_state->m_state = AmbulanceMissionState::e_ready; + m_atBeachTask = 0; + m_atPoliceTask = 0; g_isleFlags |= Isle::c_playMusic; AnimationManager()->EnableCamAnims(TRUE); AnimationManager()->FUN_1005f6d0(TRUE); @@ -626,7 +626,7 @@ void Ambulance::PlayAction(IsleScript::Script p_objectId) // FUNCTION: LEGO1 0x100373a0 AmbulanceMissionState::AmbulanceMissionState() { - m_unk0x08 = 0; + m_state = AmbulanceMissionState::e_ready; m_startTime = 0; m_peScore = 0; m_maScore = 0; diff --git a/LEGO1/lego/legoomni/src/worlds/isle.cpp b/LEGO1/lego/legoomni/src/worlds/isle.cpp index 050aae97..2eb47874 100644 --- a/LEGO1/lego/legoomni/src/worlds/isle.cpp +++ b/LEGO1/lego/legoomni/src/worlds/isle.cpp @@ -810,7 +810,7 @@ void Isle::Enable(MxBool p_enable) AnimationManager()->EnableCamAnims(FALSE); g_isleFlags &= ~c_playMusic; - m_ambulance->FUN_10036e60(); + m_ambulance->Init(); break; case 11: m_act1state->m_unk0x018 = 0; @@ -1209,7 +1209,7 @@ MxBool Isle::Escape() case 10: if (UserActor() != NULL && !UserActor()->IsA("Ambulance")) { m_ambulance->StopActions(); - m_ambulance->FUN_10037250(); + m_ambulance->Reset(); } break; } @@ -1250,7 +1250,7 @@ void Isle::FUN_10033350() if (m_act1state->m_unk0x018 == 10) { if (UserActor() != NULL && !UserActor()->IsA("Ambulance")) { m_ambulance->StopActions(); - m_ambulance->FUN_10037250(); + m_ambulance->Reset(); } } From 056064f9d4f4d9c69faae65012ff6de0c22f56e7 Mon Sep 17 00:00:00 2001 From: Fabian Neundorf Date: Thu, 26 Jun 2025 22:35:04 +0200 Subject: [PATCH 005/188] Clear unknown in `LegoLOD` (#1590) The function does mostly the same as `SetTextureInfo` but does not explicitly set the color as well. --- LEGO1/lego/legoomni/src/common/legocharactermanager.cpp | 2 +- LEGO1/lego/sources/roi/legolod.cpp | 2 +- LEGO1/lego/sources/roi/legolod.h | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/LEGO1/lego/legoomni/src/common/legocharactermanager.cpp b/LEGO1/lego/legoomni/src/common/legocharactermanager.cpp index 485d3511..80b69efc 100644 --- a/LEGO1/lego/legoomni/src/common/legocharactermanager.cpp +++ b/LEGO1/lego/legoomni/src/common/legocharactermanager.cpp @@ -634,7 +634,7 @@ MxBool LegoCharacterManager::SetHeadTexture(LegoROI* p_roi, LegoTextureInfo* p_t LegoLOD* clone = lod->Clone(renderer); if (p_texture != NULL) { - clone->FUN_100aad70(p_texture); + clone->UpdateTextureInfo(p_texture); } dupLodList->PushBack(clone); diff --git a/LEGO1/lego/sources/roi/legolod.cpp b/LEGO1/lego/sources/roi/legolod.cpp index 2291a3e7..bf157b56 100644 --- a/LEGO1/lego/sources/roi/legolod.cpp +++ b/LEGO1/lego/sources/roi/legolod.cpp @@ -351,7 +351,7 @@ LegoResult LegoLOD::SetTextureInfo(LegoTextureInfo* p_textureInfo) } // FUNCTION: LEGO1 0x100aad70 -LegoResult LegoLOD::FUN_100aad70(LegoTextureInfo* p_textureInfo) +LegoResult LegoLOD::UpdateTextureInfo(LegoTextureInfo* p_textureInfo) { for (LegoU32 i = m_meshOffset; i < m_numMeshes; i++) { if (m_melems[i].m_textured) { diff --git a/LEGO1/lego/sources/roi/legolod.h b/LEGO1/lego/sources/roi/legolod.h index 114e377b..e787d377 100644 --- a/LEGO1/lego/sources/roi/legolod.h +++ b/LEGO1/lego/sources/roi/legolod.h @@ -31,7 +31,7 @@ class LegoLOD : public ViewLOD { LegoLOD* Clone(Tgl::Renderer* p_renderer); LegoResult SetColor(LegoFloat p_red, LegoFloat p_green, LegoFloat p_blue, LegoFloat p_alpha); LegoResult SetTextureInfo(LegoTextureInfo* p_textureInfo); - LegoResult FUN_100aad70(LegoTextureInfo* p_textureInfo); + LegoResult UpdateTextureInfo(LegoTextureInfo* p_textureInfo); void ClearMeshOffset(); LegoResult GetTextureInfo(LegoTextureInfo*& p_textureInfo); From a987595e1eea009ffd1b078a2108d60fdf35f295 Mon Sep 17 00:00:00 2001 From: Christian Semmler Date: Thu, 26 Jun 2025 17:30:24 -0700 Subject: [PATCH 006/188] 1.0 compatibility (#434) * Make Infocenter compatible with 1.0 versions * Fix * Emscripten patch * Fix * Fix Emscripten patch * Window title * Naming * Don't exit car build automatically in 1.0 * Disable character selection by clicking icon in 1.0 * Remove obsolete includes --- ISLE/emscripten/libwasmfs_fetch.js.patch | 18 ++++- ISLE/isleapp.cpp | 29 ++++++++ ISLE/isleapp.h | 1 + LEGO1/lego/legoomni/include/legomain.h | 4 + .../lego/legoomni/src/build/legocarbuild.cpp | 2 +- LEGO1/lego/legoomni/src/main/legomain.cpp | 1 + LEGO1/lego/legoomni/src/worlds/infocenter.cpp | 74 +++++++++++-------- LEGO1/omni/include/mxstring.h | 8 +- 8 files changed, 100 insertions(+), 37 deletions(-) diff --git a/ISLE/emscripten/libwasmfs_fetch.js.patch b/ISLE/emscripten/libwasmfs_fetch.js.patch index eb16caf0..de8fab60 100644 --- a/ISLE/emscripten/libwasmfs_fetch.js.patch +++ b/ISLE/emscripten/libwasmfs_fetch.js.patch @@ -1,5 +1,5 @@ diff --git a/src/lib/libwasmfs_fetch.js b/src/lib/libwasmfs_fetch.js -index e8c9f7e21..1c0eea957 100644 +index e8c9f7e21..caf1971d2 100644 --- a/src/lib/libwasmfs_fetch.js +++ b/src/lib/libwasmfs_fetch.js @@ -38,36 +38,7 @@ addToLibrary({ @@ -89,7 +89,21 @@ index e8c9f7e21..1c0eea957 100644 return Promise.resolve(); } -@@ -164,6 +156,21 @@ addToLibrary({ +@@ -156,14 +148,31 @@ addToLibrary({ + return readLength; + }, + getSize: async (file) => { +- try { +- await getFileRange(file, 0, 0); +- } catch (failedResponse) { +- return 0; ++ if (!(file in wasmFS$JSMemoryRanges)) { ++ try { ++ await getFileRange(file, undefined, undefined); ++ } catch (failedResponse) { ++ return 0; ++ } + } return wasmFS$JSMemoryRanges[file].size; }, }; diff --git a/ISLE/isleapp.cpp b/ISLE/isleapp.cpp index 2ca33577..a5091ece 100644 --- a/ISLE/isleapp.cpp +++ b/ISLE/isleapp.cpp @@ -703,6 +703,7 @@ MxResult IsleApp::SetupWindow() return FAILURE; } + DetectGameVersion(); GameState()->SerializePlayersInfo(LegoStorage::c_read); GameState()->SerializeScoreHistory(LegoStorage::c_read); @@ -1098,6 +1099,34 @@ MxResult IsleApp::VerifyFilesystem() return SUCCESS; } +void IsleApp::DetectGameVersion() +{ + const char* file = "/lego/scripts/infocntr/infomain.si"; + SDL_PathInfo info; + bool success = false; + + MxString path = MxString(m_hdPath) + file; + path.MapPathToFilesystem(); + if (!(success = SDL_GetPathInfo(path.GetData(), &info))) { + path = MxString(m_cdPath) + file; + path.MapPathToFilesystem(); + success = SDL_GetPathInfo(path.GetData(), &info); + } + + assert(success); + + // File sizes of INFOMAIN.SI in English 1.0 and Japanese 1.0 + Lego()->SetVersion10(info.size == 58130432 || info.size == 57737216); + + if (Lego()->IsVersion10()) { + SDL_Log("Detected game version 1.0"); + SDL_SetWindowTitle(reinterpret_cast(m_windowHandle), "Lego Island"); + } + else { + SDL_Log("Detected game version 1.1"); + } +} + IDirect3DRMMiniwinDevice* GetD3DRMMiniwinDevice() { LegoVideoManager* videoManager = LegoOmni::GetInstance()->GetVideoManager(); diff --git a/ISLE/isleapp.h b/ISLE/isleapp.h index d76a291b..2766607f 100644 --- a/ISLE/isleapp.h +++ b/ISLE/isleapp.h @@ -56,6 +56,7 @@ class IsleApp { MxResult ParseArguments(int argc, char** argv); MxResult VerifyFilesystem(); + void DetectGameVersion(); private: char* m_hdPath; // 0x00 diff --git a/LEGO1/lego/legoomni/include/legomain.h b/LEGO1/lego/legoomni/include/legomain.h index fa9a91c5..c27be280 100644 --- a/LEGO1/lego/legoomni/include/legomain.h +++ b/LEGO1/lego/legoomni/include/legomain.h @@ -200,6 +200,9 @@ class LegoOmni : public MxOmni { SDL_PushEvent(&event); } + void SetVersion10(MxBool p_version10) { m_version10 = p_version10; } + MxBool IsVersion10() { return m_version10; } + // SYNTHETIC: LEGO1 0x10058b30 // LegoOmni::`scalar deleting destructor' @@ -221,6 +224,7 @@ class LegoOmni : public MxOmni { MxDSAction m_action; // 0xa0 MxBackgroundAudioManager* m_bkgAudioManager; // 0x134 MxTransitionManager* m_transitionManager; // 0x138 + MxBool m_version10; public: MxBool m_unk0x13c; // 0x13c diff --git a/LEGO1/lego/legoomni/src/build/legocarbuild.cpp b/LEGO1/lego/legoomni/src/build/legocarbuild.cpp index e69415f6..fa0be996 100644 --- a/LEGO1/lego/legoomni/src/build/legocarbuild.cpp +++ b/LEGO1/lego/legoomni/src/build/legocarbuild.cpp @@ -488,7 +488,7 @@ void LegoCarBuild::FUN_100236d0() m_unk0x110 = NULL; m_unk0x100 = 0; - if (m_animPresenter->AllPartsPlaced()) { + if (m_animPresenter->AllPartsPlaced() && !Lego()->IsVersion10()) { // Note the code duplication with LEGO1 0x10025ee0 switch (m_carId) { case 1: diff --git a/LEGO1/lego/legoomni/src/main/legomain.cpp b/LEGO1/lego/legoomni/src/main/legomain.cpp index 3b99f0b5..1695e1c3 100644 --- a/LEGO1/lego/legoomni/src/main/legomain.cpp +++ b/LEGO1/lego/legoomni/src/main/legomain.cpp @@ -76,6 +76,7 @@ void LegoOmni::Init() m_bkgAudioManager = NULL; m_unk0x13c = TRUE; m_transitionManager = NULL; + m_version10 = FALSE; } // FUNCTION: LEGO1 0x10058c30 diff --git a/LEGO1/lego/legoomni/src/worlds/infocenter.cpp b/LEGO1/lego/legoomni/src/worlds/infocenter.cpp index f9bfb4eb..c007dc96 100644 --- a/LEGO1/lego/legoomni/src/worlds/infocenter.cpp +++ b/LEGO1/lego/legoomni/src/worlds/infocenter.cpp @@ -306,29 +306,32 @@ MxLong Infocenter::HandleEndAction(MxEndActionNotificationParam& p_param) if (!m_unk0x1d4) { PlayMusic(JukeboxScript::c_InformationCenter_Music); - GameState()->SetActor(m_selectedCharacter); - switch (m_selectedCharacter) { - case e_pepper: - PlayAction(InfomainScript::c_avo901in_RunAnim); - break; - case e_mama: - PlayAction(InfomainScript::c_avo902in_RunAnim); - break; - case e_papa: - PlayAction(InfomainScript::c_avo903in_RunAnim); - break; - case e_nick: - PlayAction(InfomainScript::c_avo904in_RunAnim); - break; - case e_laura: - PlayAction(InfomainScript::c_avo905in_RunAnim); - break; - default: - break; + if (!Lego()->IsVersion10()) { + GameState()->SetActor(m_selectedCharacter); + + switch (m_selectedCharacter) { + case e_pepper: + PlayAction(InfomainScript::c_avo901in_RunAnim); + break; + case e_mama: + PlayAction(InfomainScript::c_avo902in_RunAnim); + break; + case e_papa: + PlayAction(InfomainScript::c_avo903in_RunAnim); + break; + case e_nick: + PlayAction(InfomainScript::c_avo904in_RunAnim); + break; + case e_laura: + PlayAction(InfomainScript::c_avo905in_RunAnim); + break; + default: + break; + } + + UpdateFrameHot(TRUE); } - - UpdateFrameHot(TRUE); } } @@ -338,7 +341,7 @@ MxLong Infocenter::HandleEndAction(MxEndActionNotificationParam& p_param) return result; } - if (action->GetObjectId() == InfomainScript::c_iicx26in_RunAnim) { + if (action->GetObjectId() == InfomainScript::c_iicx26in_RunAnim - Lego()->IsVersion10()) { ControlManager()->FUN_100293c0(InfomainScript::c_BigInfo_Ctl, action->GetAtomId().GetInternal(), 0); m_unk0x1d6 = 0; } @@ -478,7 +481,7 @@ void Infocenter::ReadyWorld() InfomainScript::Script script = m_infocenterState->GetNextReturnDialogue(); PlayAction(script); - if (script == InfomainScript::c_iicx26in_RunAnim) { + if (script == InfomainScript::c_iicx26in_RunAnim - Lego()->IsVersion10()) { m_unk0x1d6 = 1; } @@ -1186,13 +1189,13 @@ MxLong Infocenter::HandleNotification0(MxNotificationParam& p_param) m_currentInfomainScript == InfomainScript::c_Pepper_All_Movie || m_currentInfomainScript == InfomainScript::c_Nick_All_Movie || m_currentInfomainScript == InfomainScript::c_Laura_All_Movie || - m_currentInfomainScript == InfomainScript::c_iic007ra_PlayWav || - m_currentInfomainScript == InfomainScript::c_ijs002ra_PlayWav || - m_currentInfomainScript == InfomainScript::c_irt001ra_PlayWav || - m_currentInfomainScript == InfomainScript::c_ipz006ra_PlayWav || - m_currentInfomainScript == InfomainScript::c_igs004ra_PlayWav || - m_currentInfomainScript == InfomainScript::c_iho003ra_PlayWav || - m_currentInfomainScript == InfomainScript::c_ips005ra_PlayWav) { + m_currentInfomainScript == InfomainScript::c_iic007ra_PlayWav - Lego()->IsVersion10() || + m_currentInfomainScript == InfomainScript::c_ijs002ra_PlayWav - Lego()->IsVersion10() || + m_currentInfomainScript == InfomainScript::c_irt001ra_PlayWav - Lego()->IsVersion10() || + m_currentInfomainScript == InfomainScript::c_ipz006ra_PlayWav - Lego()->IsVersion10() || + m_currentInfomainScript == InfomainScript::c_igs004ra_PlayWav - Lego()->IsVersion10() || + m_currentInfomainScript == InfomainScript::c_iho003ra_PlayWav - Lego()->IsVersion10() || + m_currentInfomainScript == InfomainScript::c_ips005ra_PlayWav - Lego()->IsVersion10()) { StopCurrentAction(); } } @@ -1506,6 +1509,17 @@ void Infocenter::StopCredits() // FUNCTION: BETA10 0x1002ee8c void Infocenter::PlayAction(InfomainScript::Script p_script) { + if (Lego()->IsVersion10()) { + if (p_script == InfomainScript::c_iicx18in_RunAnim) { + // Alternative dialogue after signing in (1.0 version) + p_script = InfomainScript::c_iic016in_RunAnim; + } + else if (p_script > InfomainScript::c_iicx18in_RunAnim) { + // Shift all other actions by 1 + p_script = (InfomainScript::Script)((int) p_script - 1); + } + } + MxDSAction action; action.SetObjectId(p_script); action.SetAtomId(*g_infomainScript); diff --git a/LEGO1/omni/include/mxstring.h b/LEGO1/omni/include/mxstring.h index eaa58181..b4b25262 100644 --- a/LEGO1/omni/include/mxstring.h +++ b/LEGO1/omni/include/mxstring.h @@ -20,10 +20,10 @@ class MxString : public MxCore { void ToLowerCase(); void MapPathToFilesystem() { MapPathToFilesystem(m_data); } - MxString& operator=(const MxString& p_str); - const MxString& operator=(const char* p_str); - MxString operator+(const MxString& p_str) const; - MxString operator+(const char* p_str) const; + LEGO1_EXPORT MxString& operator=(const MxString& p_str); + LEGO1_EXPORT const MxString& operator=(const char* p_str); + LEGO1_EXPORT MxString operator+(const MxString& p_str) const; + LEGO1_EXPORT MxString operator+(const char* p_str) const; LEGO1_EXPORT MxString& operator+=(const char* p_str); static void CharSwap(char* p_a, char* p_b); From 16a94c725caf3fc1d9aca50c685390ab78fb2b9a Mon Sep 17 00:00:00 2001 From: Korbo Date: Fri, 27 Jun 2025 18:08:45 -0500 Subject: [PATCH 007/188] Names for race related or adjacent functions and variables (#1592) * Names for race related or adjacent functions and variables * fix formatting * fix formatting --- LEGO1/lego/legoomni/include/legopathactor.h | 2 +- LEGO1/lego/legoomni/include/legoracers.h | 6 ++-- LEGO1/lego/legoomni/include/legoracespecial.h | 9 +++-- .../lego/legoomni/src/paths/legopathactor.cpp | 6 ++-- LEGO1/lego/legoomni/src/race/carrace.cpp | 8 ++--- LEGO1/lego/legoomni/src/race/jetskirace.cpp | 8 ++--- .../lego/legoomni/src/race/legoraceactor.cpp | 4 +-- LEGO1/lego/legoomni/src/race/legoracers.cpp | 34 +++++++++---------- .../legoomni/src/race/legoracespecial.cpp | 16 ++++----- 9 files changed, 48 insertions(+), 45 deletions(-) diff --git a/LEGO1/lego/legoomni/include/legopathactor.h b/LEGO1/lego/legoomni/include/legopathactor.h index 6566c6fb..8df0e9db 100644 --- a/LEGO1/lego/legoomni/include/legopathactor.h +++ b/LEGO1/lego/legoomni/include/legopathactor.h @@ -13,7 +13,7 @@ struct LegoPathEdgeContainer; struct LegoOrientedEdge; class LegoWEEdge; -extern MxLong g_unk0x100f3308; +extern MxLong g_timeLastHitSoundPlayed; extern const char* g_strHIT_WALL_SOUND; // VTABLE: LEGO1 0x100d6e28 diff --git a/LEGO1/lego/legoomni/include/legoracers.h b/LEGO1/lego/legoomni/include/legoracers.h index 76d1071d..3c8ffe07 100644 --- a/LEGO1/lego/legoomni/include/legoracers.h +++ b/LEGO1/lego/legoomni/include/legoracers.h @@ -144,10 +144,10 @@ class LegoRaceCar : public LegoCarRaceActor, public LegoRaceMap { MxResult VTable0x9c() override; // vtable+0x9c virtual void SetMaxLinearVelocity(float p_maxLinearVelocity); - virtual void FUN_10012ff0(float p_param); + virtual void KickCamera(float p_param); virtual MxU32 HandleSkeletonKicks(float p_param1); - static void FUN_10012de0(); + static void InitYouCantStopSound(); static void InitSoundIndices(); // SYNTHETIC: LEGO1 0x10014240 @@ -155,7 +155,7 @@ class LegoRaceCar : public LegoCarRaceActor, public LegoRaceMap { private: undefined m_userState; // 0x54 - float m_unk0x58; // 0x58 + float m_kickStart; // 0x58 Mx3DPointFloat m_unk0x5c; // 0x5c // Names verified by BETA10 0x100cb4a9 diff --git a/LEGO1/lego/legoomni/include/legoracespecial.h b/LEGO1/lego/legoomni/include/legoracespecial.h index 97fa7d9f..bfa38632 100644 --- a/LEGO1/lego/legoomni/include/legoracespecial.h +++ b/LEGO1/lego/legoomni/include/legoracespecial.h @@ -44,8 +44,11 @@ class LegoCarRaceActor : public virtual LegoRaceActor { Vector3& p_v3 ) override; // vtable+0x6c void Animate(float p_time) override; // vtable+0x70 - void SwitchBoundary(LegoPathBoundary*& p_boundary, LegoOrientedEdge*& p_edge, float& p_unk0xe4) - override; // vtable+0x98 + void SwitchBoundary( + LegoPathBoundary*& p_boundary, + LegoOrientedEdge*& p_edge, + float& p_unk0xe4 + ) override; // vtable+0x98 MxResult VTable0x9c() override; // vtable+0x9c // LegoCarRaceActor vtable @@ -83,7 +86,7 @@ class LegoCarRaceActor : public virtual LegoRaceActor { protected: MxFloat m_unk0x08; // 0x08 - MxU8 m_unk0x0c; // 0x0c + MxU8 m_animState; // 0x0c // Could be a multiplier for the maximum speed when going straight MxFloat m_unk0x10; // 0x10 diff --git a/LEGO1/lego/legoomni/src/paths/legopathactor.cpp b/LEGO1/lego/legoomni/src/paths/legopathactor.cpp index 292d8214..0a8f6878 100644 --- a/LEGO1/lego/legoomni/src/paths/legopathactor.cpp +++ b/LEGO1/lego/legoomni/src/paths/legopathactor.cpp @@ -36,7 +36,7 @@ const char* g_strHIT_WALL_SOUND = "HIT_WALL_SOUND"; // GLOBAL: LEGO1 0x100f3308 // GLOBAL: BETA10 0x101f1e1c -MxLong g_unk0x100f3308 = 0; +MxLong g_timeLastHitSoundPlayed = 0; // FUNCTION: LEGO1 0x1002d700 // FUNCTION: BETA10 0x100ae6e0 @@ -291,8 +291,8 @@ MxS32 LegoPathActor::VTable0x8c(float p_time, Matrix4& p_transform) if (m_boundary == oldBoundary) { MxLong time = Timer()->GetTime(); - if (time - g_unk0x100f3308 > 1000) { - g_unk0x100f3308 = time; + if (time - g_timeLastHitSoundPlayed > 1000) { + g_timeLastHitSoundPlayed = time; const char* var = VariableTable()->GetVariable(g_strHIT_WALL_SOUND); if (var && var[0] != 0) { diff --git a/LEGO1/lego/legoomni/src/race/carrace.cpp b/LEGO1/lego/legoomni/src/race/carrace.cpp index e775a3be..86c50b4a 100644 --- a/LEGO1/lego/legoomni/src/race/carrace.cpp +++ b/LEGO1/lego/legoomni/src/race/carrace.cpp @@ -244,7 +244,7 @@ MxLong CarRace::HandlePathStruct(LegoPathStructNotificationParam& p_param) VariableTable()->SetVariable(g_strHIT_WALL_SOUND, ""); NavController()->SetDeadZone(NavController()->GetDefaultDeadZone()); NavController()->SetTrackDefault(1); - LegoRaceCar::FUN_10012de0(); + LegoRaceCar::InitYouCantStopSound(); m_raceState->m_unk0x28 = 2; RaceState::Entry* raceState = m_raceState->GetState(GameState()->GetActorId()); @@ -346,7 +346,7 @@ MxLong CarRace::HandleClick(LegoEventNotificationParam& p_param) VariableTable()->SetVariable(g_strHIT_WALL_SOUND, ""); NavController()->SetDeadZone(NavController()->GetDefaultDeadZone()); NavController()->SetTrackDefault(1); - LegoRaceCar::FUN_10012de0(); + LegoRaceCar::InitYouCantStopSound(); m_destLocation = LegoGameState::e_infomain; TransitionManager()->StartTransition(MxTransitionManager::e_mosaic, 50, FALSE, FALSE); GameState()->GetBackgroundColor()->SetValue("reset"); @@ -358,7 +358,7 @@ MxLong CarRace::HandleClick(LegoEventNotificationParam& p_param) VariableTable()->SetVariable(g_strHIT_WALL_SOUND, ""); NavController()->SetDeadZone(NavController()->GetDefaultDeadZone()); NavController()->SetTrackDefault(1); - LegoRaceCar::FUN_10012de0(); + LegoRaceCar::InitYouCantStopSound(); m_destLocation = LegoGameState::e_carraceExterior; TransitionManager()->StartTransition(MxTransitionManager::e_mosaic, 50, FALSE, FALSE); GameState()->GetBackgroundColor()->SetValue("reset"); @@ -422,7 +422,7 @@ MxBool CarRace::Escape() NavController()->SetDeadZone(NavController()->GetDefaultDeadZone()); NavController()->SetTrackDefault(1); - LegoRaceCar::FUN_10012de0(); + LegoRaceCar::InitYouCantStopSound(); GameState()->GetBackgroundColor()->SetValue("reset"); m_destLocation = LegoGameState::e_infomain; diff --git a/LEGO1/lego/legoomni/src/race/jetskirace.cpp b/LEGO1/lego/legoomni/src/race/jetskirace.cpp index d1de13e9..54ad806c 100644 --- a/LEGO1/lego/legoomni/src/race/jetskirace.cpp +++ b/LEGO1/lego/legoomni/src/race/jetskirace.cpp @@ -130,7 +130,7 @@ MxLong JetskiRace::HandleClick(LegoEventNotificationParam& p_param) m_act1State->m_unk0x018 = 0; VariableTable()->SetVariable(g_raceState, ""); VariableTable()->SetVariable(g_strHIT_WALL_SOUND, ""); - LegoRaceCar::FUN_10012de0(); + LegoRaceCar::InitYouCantStopSound(); m_destLocation = LegoGameState::e_jetraceExterior; TransitionManager()->StartTransition(MxTransitionManager::e_mosaic, 50, FALSE, FALSE); break; @@ -138,7 +138,7 @@ MxLong JetskiRace::HandleClick(LegoEventNotificationParam& p_param) m_act1State->m_unk0x018 = 0; VariableTable()->SetVariable(g_raceState, ""); VariableTable()->SetVariable(g_strHIT_WALL_SOUND, ""); - LegoRaceCar::FUN_10012de0(); + LegoRaceCar::InitYouCantStopSound(); m_destLocation = LegoGameState::e_infomain; result = 1; TransitionManager()->StartTransition(MxTransitionManager::e_mosaic, 50, FALSE, FALSE); @@ -191,7 +191,7 @@ MxLong JetskiRace::HandlePathStruct(LegoPathStructNotificationParam& p_param) VariableTable()->SetVariable(g_raceState, ""); VariableTable()->SetVariable(g_strHIT_WALL_SOUND, ""); - LegoRaceCar::FUN_10012de0(); + LegoRaceCar::InitYouCantStopSound(); m_raceState->m_unk0x28 = 2; RaceState::Entry* raceStateEntry = m_raceState->GetState(GameState()->GetActorId()); @@ -292,6 +292,6 @@ MxBool JetskiRace::Escape() VariableTable()->SetVariable(g_raceState, ""); VariableTable()->SetVariable(g_strHIT_WALL_SOUND, ""); m_destLocation = LegoGameState::e_infomain; - LegoRaceCar::FUN_10012de0(); + LegoRaceCar::InitYouCantStopSound(); return TRUE; } diff --git a/LEGO1/lego/legoomni/src/race/legoraceactor.cpp b/LEGO1/lego/legoomni/src/race/legoraceactor.cpp index 965ba326..f005d996 100644 --- a/LEGO1/lego/legoomni/src/race/legoraceactor.cpp +++ b/LEGO1/lego/legoomni/src/race/legoraceactor.cpp @@ -31,8 +31,8 @@ MxS32 LegoRaceActor::VTable0x68(Vector3& p_v1, Vector3& p_v2, Vector3& p_v3) if (m_userNavFlag && result) { MxLong time = Timer()->GetTime(); - if (time - g_unk0x100f3308 > 1000) { - g_unk0x100f3308 = time; + if (time - g_timeLastHitSoundPlayed > 1000) { + g_timeLastHitSoundPlayed = time; const char* soundKey = VariableTable()->GetVariable(g_strHIT_ACTOR_SOUND); if (soundKey && *soundKey) { diff --git a/LEGO1/lego/legoomni/src/race/legoracers.cpp b/LEGO1/lego/legoomni/src/race/legoracers.cpp index 39c0c0e6..0a9fa1e2 100644 --- a/LEGO1/lego/legoomni/src/race/legoracers.cpp +++ b/LEGO1/lego/legoomni/src/race/legoracers.cpp @@ -175,7 +175,7 @@ LegoRaceCar::LegoRaceCar() m_skelKick1Anim = 0; m_skelKick2Anim = 0; m_unk0x5c.Clear(); - m_unk0x58 = 0; + m_kickStart = 0; m_kick1B = 0; m_kick2B = 0; NotificationManager()->Register(this); @@ -198,10 +198,10 @@ MxLong LegoRaceCar::Notify(MxParam& p_param) // Initialized at LEGO1 0x10012db0 // GLOBAL: LEGO1 0x10102af0 // GLOBAL: BETA10 0x102114c0 -Mx3DPointFloat g_unk0x10102af0 = Mx3DPointFloat(0.0f, 2.0f, 0.0f); +Mx3DPointFloat g_hitOffset = Mx3DPointFloat(0.0f, 2.0f, 0.0f); // FUNCTION: LEGO1 0x10012de0 -void LegoRaceCar::FUN_10012de0() +void LegoRaceCar::InitYouCantStopSound() { // Init to TRUE so we don't play "you can't stop in the middle of the race!" before the player ever moves g_playedYouCantStopSound = TRUE; @@ -226,7 +226,7 @@ void LegoRaceCar::InitSoundIndices() void LegoRaceCar::SetWorldSpeed(MxFloat p_worldSpeed) { if (!m_userNavFlag) { - if (!LegoCarRaceActor::m_unk0x0c) { + if (!LegoCarRaceActor::m_animState) { m_maxLinearVel = p_worldSpeed; } LegoAnimActor::SetWorldSpeed(p_worldSpeed); @@ -241,7 +241,7 @@ void LegoRaceCar::SetWorldSpeed(MxFloat p_worldSpeed) void LegoRaceCar::SetMaxLinearVelocity(float p_maxLinearVelocity) { if (p_maxLinearVelocity < 0) { - LegoCarRaceActor::m_unk0x0c = 2; + LegoCarRaceActor::m_animState = 2; m_maxLinearVel = 0; SetWorldSpeed(0); } @@ -296,7 +296,7 @@ void LegoRaceCar::ParseAction(char* p_extra) // FUNCTION: LEGO1 0x10012ff0 // FUNCTION: BETA10 0x100cb60e -void LegoRaceCar::FUN_10012ff0(float p_param) +void LegoRaceCar::KickCamera(float p_param) { LegoAnimActorStruct* a; // called `a` in BETA10 float deltaTime; @@ -312,7 +312,7 @@ void LegoRaceCar::FUN_10012ff0(float p_param) assert(a && a->GetAnimTreePtr() && a->GetAnimTreePtr()->GetCamAnim()); if (a->GetAnimTreePtr()) { - deltaTime = p_param - m_unk0x58; + deltaTime = p_param - m_kickStart; if (a->GetDuration() <= deltaTime || deltaTime < 0.0) { if (m_userState == LEGORACECAR_KICK1) { @@ -387,7 +387,7 @@ MxU32 LegoRaceCar::HandleSkeletonKicks(float p_param1) return FALSE; } - m_unk0x58 = p_param1; + m_kickStart = p_param1; SoundManager()->GetCacheSoundManager()->Play(g_soundSkel3, NULL, FALSE); return TRUE; @@ -398,7 +398,7 @@ MxU32 LegoRaceCar::HandleSkeletonKicks(float p_param1) void LegoRaceCar::Animate(float p_time) { if (m_userNavFlag && (m_userState == LEGORACECAR_KICK1 || m_userState == LEGORACECAR_KICK2)) { - FUN_10012ff0(p_time); + KickCamera(p_time); return; } @@ -410,7 +410,7 @@ void LegoRaceCar::Animate(float p_time) } } - if (LegoCarRaceActor::m_unk0x0c == 1) { + if (LegoCarRaceActor::m_animState == 1) { FUN_1005d4b0(); if (!m_userNavFlag) { @@ -468,7 +468,7 @@ MxResult LegoRaceCar::HitActor(LegoPathActor* p_actor, MxBool p_bool) assert(roi); matr = roi->GetLocal2World(); - Vector3(matr[3]) += g_unk0x10102af0; + Vector3(matr[3]) += g_hitOffset; roi->SetLocal2World(matr); p_actor->SetActorState(c_two); @@ -513,7 +513,7 @@ MxResult LegoRaceCar::HitActor(LegoPathActor* p_actor, MxBool p_bool) if (soundKey) { SoundManager()->GetCacheSoundManager()->Play(soundKey, NULL, FALSE); - g_timeLastRaceCarSoundPlayed = g_unk0x100f3308 = time; + g_timeLastRaceCarSoundPlayed = g_timeLastHitSoundPlayed = time; } } @@ -579,7 +579,7 @@ void LegoJetski::InitSoundIndices() void LegoJetski::SetWorldSpeed(MxFloat p_worldSpeed) { if (!m_userNavFlag) { - if (!LegoCarRaceActor::m_unk0x0c) { + if (!LegoCarRaceActor::m_animState) { m_maxLinearVel = p_worldSpeed; } LegoAnimActor::SetWorldSpeed(p_worldSpeed); @@ -594,7 +594,7 @@ void LegoJetski::SetWorldSpeed(MxFloat p_worldSpeed) void LegoJetski::FUN_100136f0(float p_worldSpeed) { if (p_worldSpeed < 0) { - LegoCarRaceActor::m_unk0x0c = 2; + LegoCarRaceActor::m_animState = 2; m_maxLinearVel = 0; SetWorldSpeed(0); } @@ -609,7 +609,7 @@ void LegoJetski::Animate(float p_time) { LegoJetskiRaceActor::Animate(p_time); - if (LegoCarRaceActor::m_unk0x0c == 1) { + if (LegoCarRaceActor::m_animState == 1) { FUN_1005d4b0(); if (!m_userNavFlag) { @@ -682,7 +682,7 @@ MxResult LegoJetski::HitActor(LegoPathActor* p_actor, MxBool p_bool) LegoROI* roi = p_actor->GetROI(); matr = roi->GetLocal2World(); - Vector3(matr[3]) += g_unk0x10102af0; + Vector3(matr[3]) += g_hitOffset; roi->SetLocal2World(matr); p_actor->SetActorState(c_two); @@ -711,7 +711,7 @@ MxResult LegoJetski::HitActor(LegoPathActor* p_actor, MxBool p_bool) if (soundKey) { SoundManager()->GetCacheSoundManager()->Play(soundKey, NULL, FALSE); - g_timeLastJetskiSoundPlayed = g_unk0x100f3308 = time; + g_timeLastJetskiSoundPlayed = g_timeLastHitSoundPlayed = time; } } diff --git a/LEGO1/lego/legoomni/src/race/legoracespecial.cpp b/LEGO1/lego/legoomni/src/race/legoracespecial.cpp index 81f9255f..e599c633 100644 --- a/LEGO1/lego/legoomni/src/race/legoracespecial.cpp +++ b/LEGO1/lego/legoomni/src/race/legoracespecial.cpp @@ -44,7 +44,7 @@ LegoCarRaceActor::LegoCarRaceActor() { m_unk0x08 = 1.0f; m_unk0x70 = 0.0f; - m_unk0x0c = 0; + m_animState = 0; m_maxLinearVel = 0.0f; m_frequencyFactor = 1.0f; m_unk0x1c = 0; @@ -223,18 +223,18 @@ void LegoCarRaceActor::SwitchBoundary(LegoPathBoundary*& p_boundary, LegoOriente // FUNCTION: BETA10 0x100cdbae void LegoCarRaceActor::Animate(float p_time) { - // m_unk0x0c is not an MxBool, there are places where it is set to 2 or higher - if (m_unk0x0c == 0) { + // m_animState is not an MxBool, there are places where it is set to 2 or higher + if (m_animState == 0) { const char* value = VariableTable()->GetVariable(g_raceState); if (strcmpi(value, g_racing) == 0) { - m_unk0x0c = 1; + m_animState = 1; m_lastTime = p_time - 1.0f; m_unk0x1c = p_time; } } - if (m_unk0x0c == 1) { + if (m_animState == 1) { LegoAnimActor::Animate(p_time); } } @@ -398,10 +398,10 @@ MxS32 LegoJetskiRaceActor::VTable0x1c(LegoPathBoundary* p_boundary, LegoEdge* p_ // FUNCTION: LEGO1 0x10081550 void LegoJetskiRaceActor::Animate(float p_time) { - if (m_unk0x0c == 0) { + if (m_animState == 0) { const LegoChar* raceState = VariableTable()->GetVariable(g_raceState); if (!stricmp(raceState, g_racing)) { - m_unk0x0c = 1; + m_animState = 1; m_lastTime = p_time - 1.0f; m_unk0x1c = p_time; } @@ -410,7 +410,7 @@ void LegoJetskiRaceActor::Animate(float p_time) } } - if (m_unk0x0c == 1) { + if (m_animState == 1) { LegoAnimActor::Animate(p_time); } } From 02dd261ca91467688a6e0ed1fbffa50e99300338 Mon Sep 17 00:00:00 2001 From: Christian Semmler Date: Fri, 27 Jun 2025 16:15:08 -0700 Subject: [PATCH 008/188] Fix isle/master merge --- LEGO1/lego/legoomni/src/race/legoracespecial.cpp | 5 ----- 1 file changed, 5 deletions(-) diff --git a/LEGO1/lego/legoomni/src/race/legoracespecial.cpp b/LEGO1/lego/legoomni/src/race/legoracespecial.cpp index 6fa64058..7fe58c71 100644 --- a/LEGO1/lego/legoomni/src/race/legoracespecial.cpp +++ b/LEGO1/lego/legoomni/src/race/legoracespecial.cpp @@ -401,13 +401,8 @@ void LegoJetskiRaceActor::Animate(float p_time) { if (m_animState == 0) { const LegoChar* raceState = VariableTable()->GetVariable(g_raceState); -<<<<<<< HEAD if (!SDL_strcasecmp(raceState, g_racing)) { - m_unk0x0c = 1; -======= - if (!stricmp(raceState, g_racing)) { m_animState = 1; ->>>>>>> isle/master m_lastTime = p_time - 1.0f; m_unk0x1c = p_time; } From 89539a64f17cbd0c21c65aa66cf7af63ffaed924 Mon Sep 17 00:00:00 2001 From: Fabian Neundorf Date: Sat, 28 Jun 2025 16:28:13 +0200 Subject: [PATCH 009/188] Clear unknown 0x10 in `LegoEntity` (#1593) The naming is a bit weird, as only one bit is used so there are no other usages of this value. --- LEGO1/lego/legoomni/include/legoanimpresenter.h | 2 +- LEGO1/lego/legoomni/include/legoentity.h | 10 +++++----- LEGO1/lego/legoomni/src/entity/legoactor.cpp | 2 +- LEGO1/lego/legoomni/src/entity/legoentity.cpp | 8 ++++---- LEGO1/lego/legoomni/src/entity/legonavcontroller.cpp | 2 +- LEGO1/lego/legoomni/src/video/legoanimpresenter.cpp | 12 ++++++------ 6 files changed, 18 insertions(+), 18 deletions(-) diff --git a/LEGO1/lego/legoomni/include/legoanimpresenter.h b/LEGO1/lego/legoomni/include/legoanimpresenter.h index 85917cbc..a3d6cd60 100644 --- a/LEGO1/lego/legoomni/include/legoanimpresenter.h +++ b/LEGO1/lego/legoomni/include/legoanimpresenter.h @@ -121,7 +121,7 @@ class LegoAnimPresenter : public MxVideoPresenter { void SubstituteVariables(); void FUN_1006b900(LegoAnim* p_anim, MxLong p_time, Matrix4* p_matrix); void FUN_1006b9a0(LegoAnim* p_anim, MxLong p_time, Matrix4* p_matrix); - void FUN_1006c8a0(MxBool p_bool); + void SetDisabled(MxBool p_disabled); LegoAnim* m_anim; // 0x64 LegoROI** m_roiMap; // 0x68 diff --git a/LEGO1/lego/legoomni/include/legoentity.h b/LEGO1/lego/legoomni/include/legoentity.h index eecf6867..2859b6ac 100644 --- a/LEGO1/lego/legoomni/include/legoentity.h +++ b/LEGO1/lego/legoomni/include/legoentity.h @@ -28,7 +28,7 @@ class LegoEntity : public MxEntity { }; enum { - c_altBit1 = 0x01 + c_disabled = 0x01 }; LegoEntity() { Init(); } @@ -83,7 +83,7 @@ class LegoEntity : public MxEntity { Mx3DPointFloat GetWorldUp(); Mx3DPointFloat GetWorldPosition(); - MxBool GetUnknown0x10IsSet(MxU8 p_flag) { return m_unk0x10 & p_flag; } + MxBool IsInteraction(MxU8 p_flag) { return m_interaction & p_flag; } MxBool GetFlagsIsSet(MxU8 p_flag) { return m_flags & p_flag; } MxU8 GetFlags() { return m_flags; } @@ -101,14 +101,14 @@ class LegoEntity : public MxEntity { void SetFlags(MxU8 p_flags) { m_flags = p_flags; } void SetFlag(MxU8 p_flag) { m_flags |= p_flag; } void ClearFlag(MxU8 p_flag) { m_flags &= ~p_flag; } - void SetUnknown0x10Flag(MxU8 p_flag) { m_unk0x10 |= p_flag; } - void ClearUnknown0x10Flag(MxU8 p_flag) { m_unk0x10 &= ~p_flag; } + void SetInteractionFlag(MxU8 p_flag) { m_interaction |= p_flag; } + void ClearInteractionFlag(MxU8 p_flag) { m_interaction &= ~p_flag; } protected: void Init(); void SetWorld(); - MxU8 m_unk0x10; // 0x10 + MxU8 m_interaction; // 0x10 MxU8 m_flags; // 0x11 Mx3DPointFloat m_worldLocation; // 0x14 Mx3DPointFloat m_worldDirection; // 0x28 diff --git a/LEGO1/lego/legoomni/src/entity/legoactor.cpp b/LEGO1/lego/legoomni/src/entity/legoactor.cpp index c1ce3f74..4b0428b8 100644 --- a/LEGO1/lego/legoomni/src/entity/legoactor.cpp +++ b/LEGO1/lego/legoomni/src/entity/legoactor.cpp @@ -18,7 +18,7 @@ LegoActor::LegoActor() m_frequencyFactor = 0.0f; m_sound = NULL; m_unk0x70 = 0.0f; - m_unk0x10 = 0; + m_interaction = 0; m_actorId = 0; } diff --git a/LEGO1/lego/legoomni/src/entity/legoentity.cpp b/LEGO1/lego/legoomni/src/entity/legoentity.cpp index 467d65d8..b76da503 100644 --- a/LEGO1/lego/legoomni/src/entity/legoentity.cpp +++ b/LEGO1/lego/legoomni/src/entity/legoentity.cpp @@ -29,7 +29,7 @@ void LegoEntity::Init() m_roi = NULL; m_cameraFlag = FALSE; m_siFile = NULL; - m_unk0x10 = 0; + m_interaction = 0; m_flags = 0; m_actionType = Extra::ActionType::e_unknown; m_targetEntityId = -1; @@ -265,7 +265,7 @@ void LegoEntity::ParseAction(char* p_extra) // FUNCTION: BETA10 0x1007ee87 void LegoEntity::ClickSound(MxBool p_und) { - if (!GetUnknown0x10IsSet(c_altBit1)) { + if (!IsInteraction(c_disabled)) { MxU32 objectId = 0; const char* name = m_roi->GetName(); @@ -297,7 +297,7 @@ void LegoEntity::ClickSound(MxBool p_und) // FUNCTION: BETA10 0x1007f062 void LegoEntity::ClickAnimation() { - if (!GetUnknown0x10IsSet(c_altBit1)) { + if (!IsInteraction(c_disabled)) { MxU32 objectId = 0; MxDSAction action; const char* name = m_roi->GetName(); @@ -329,7 +329,7 @@ void LegoEntity::ClickAnimation() action.SetObjectId(objectId); action.AppendExtra(strlen(extra) + 1, extra); LegoOmni::GetInstance()->GetAnimationManager()->StartEntityAction(action, this); - m_unk0x10 |= c_altBit1; + m_interaction |= c_disabled; } } } diff --git a/LEGO1/lego/legoomni/src/entity/legonavcontroller.cpp b/LEGO1/lego/legoomni/src/entity/legonavcontroller.cpp index 1c306c1b..fa8158da 100644 --- a/LEGO1/lego/legoomni/src/entity/legonavcontroller.cpp +++ b/LEGO1/lego/legoomni/src/entity/legonavcontroller.cpp @@ -691,7 +691,7 @@ MxLong LegoNavController::Notify(MxParam& p_param) for (MxS32 i = 0; i < numPlants; i++) { LegoEntity* entity = plantMgr->CreatePlant(i, NULL, LegoOmni::e_act1); - if (entity != NULL && !entity->GetUnknown0x10IsSet(LegoEntity::c_altBit1)) { + if (entity != NULL && !entity->IsInteraction(LegoEntity::c_disabled)) { LegoROI* roi = entity->GetROI(); if (roi != NULL && roi->GetVisibility()) { diff --git a/LEGO1/lego/legoomni/src/video/legoanimpresenter.cpp b/LEGO1/lego/legoomni/src/video/legoanimpresenter.cpp index 852263fc..73918606 100644 --- a/LEGO1/lego/legoomni/src/video/legoanimpresenter.cpp +++ b/LEGO1/lego/legoomni/src/video/legoanimpresenter.cpp @@ -790,7 +790,7 @@ void LegoAnimPresenter::StartingTickle() } FUN_10069b10(); - FUN_1006c8a0(TRUE); + SetDisabled(TRUE); if (m_unk0x78 == NULL) { if (fabs(m_action->GetDirection()[0]) >= 0.00000047683716F || @@ -1090,7 +1090,7 @@ void LegoAnimPresenter::EndAction() } } - FUN_1006c8a0(FALSE); + SetDisabled(FALSE); FUN_1006ab70(); VTable0x90(); @@ -1151,18 +1151,18 @@ void LegoAnimPresenter::VTable0x90() } // FUNCTION: LEGO1 0x1006c8a0 -void LegoAnimPresenter::FUN_1006c8a0(MxBool p_bool) +void LegoAnimPresenter::SetDisabled(MxBool p_disabled) { if (m_roiMapSize != 0 && m_roiMap != NULL) { for (MxU32 i = 1; i <= m_roiMapSize; i++) { LegoEntity* entity = m_roiMap[i]->GetEntity(); if (entity != NULL) { - if (p_bool) { - entity->SetUnknown0x10Flag(LegoEntity::c_altBit1); + if (p_disabled) { + entity->SetInteractionFlag(LegoEntity::c_disabled); } else { - entity->ClearUnknown0x10Flag(LegoEntity::c_altBit1); + entity->ClearInteractionFlag(LegoEntity::c_disabled); } } } From 9dcc701fcb18eb84e7d57ae3454a9391decdba6d Mon Sep 17 00:00:00 2001 From: Fabian Neundorf Date: Sat, 28 Jun 2025 20:32:09 +0200 Subject: [PATCH 010/188] Clear unknowns in GetSoundId (#1594) --- LEGO1/lego/legoomni/include/legobuildingmanager.h | 2 +- LEGO1/lego/legoomni/include/legocharactermanager.h | 2 +- LEGO1/lego/legoomni/include/legoentity.h | 14 +++++++------- LEGO1/lego/legoomni/include/legoplantmanager.h | 2 +- .../legoomni/src/common/legobuildingmanager.cpp | 12 ++++++------ .../legoomni/src/common/legocharactermanager.cpp | 12 ++++++------ .../lego/legoomni/src/common/legoplantmanager.cpp | 12 ++++++------ LEGO1/lego/legoomni/src/entity/legoentity.cpp | 8 ++++---- 8 files changed, 32 insertions(+), 32 deletions(-) diff --git a/LEGO1/lego/legoomni/include/legobuildingmanager.h b/LEGO1/lego/legoomni/include/legobuildingmanager.h index 3d251c98..c5a69309 100644 --- a/LEGO1/lego/legoomni/include/legobuildingmanager.h +++ b/LEGO1/lego/legoomni/include/legobuildingmanager.h @@ -78,7 +78,7 @@ class LegoBuildingManager : public MxCore { MxBool SwitchMove(LegoEntity* p_entity); MxBool SwitchMood(LegoEntity* p_entity); MxU32 GetAnimationId(LegoEntity* p_entity); - MxU32 GetSoundId(LegoEntity* p_entity, MxBool p_state); + MxU32 GetSoundId(LegoEntity* p_entity, MxBool p_basedOnMood); MxBool DecrementCounter(LegoEntity* p_entity); MxBool DecrementCounter(MxS32 p_index); MxBool DecrementCounter(LegoBuildingInfo* p_data); diff --git a/LEGO1/lego/legoomni/include/legocharactermanager.h b/LEGO1/lego/legoomni/include/legocharactermanager.h index 174db01e..c5a0c242 100644 --- a/LEGO1/lego/legoomni/include/legocharactermanager.h +++ b/LEGO1/lego/legoomni/include/legocharactermanager.h @@ -86,7 +86,7 @@ class LegoCharacterManager { MxBool SwitchMove(LegoROI* p_roi); MxBool SwitchMood(LegoROI* p_roi); MxU32 GetAnimationId(LegoROI* p_roi); - MxU32 GetSoundId(LegoROI* p_roi, MxBool p_und); + MxU32 GetSoundId(LegoROI* p_roi, MxBool p_basedOnMood); MxU8 GetMood(LegoROI* p_roi); LegoROI* CreateAutoROI(const char* p_name, const char* p_lodName, MxBool p_createEntity); MxResult UpdateBoundingSphereAndBox(LegoROI* p_roi); diff --git a/LEGO1/lego/legoomni/include/legoentity.h b/LEGO1/lego/legoomni/include/legoentity.h index 2859b6ac..a6d9d9ee 100644 --- a/LEGO1/lego/legoomni/include/legoentity.h +++ b/LEGO1/lego/legoomni/include/legoentity.h @@ -68,13 +68,13 @@ class LegoEntity : public MxEntity { // FUNCTION: BETA10 0x10013260 virtual void SetWorldSpeed(MxFloat p_worldSpeed) { m_worldSpeed = p_worldSpeed; } // vtable+0x30 - virtual void ClickSound(MxBool p_und); // vtable+0x34 - virtual void ClickAnimation(); // vtable+0x38 - virtual void SwitchVariant(); // vtable+0x3c - virtual void SwitchSound(); // vtable+0x40 - virtual void SwitchMove(); // vtable+0x44 - virtual void SwitchColor(LegoROI* p_roi); // vtable+0x48 - virtual void SwitchMood(); // vtable+0x4c + virtual void ClickSound(MxBool p_basedOnMood); // vtable+0x34 + virtual void ClickAnimation(); // vtable+0x38 + virtual void SwitchVariant(); // vtable+0x3c + virtual void SwitchSound(); // vtable+0x40 + virtual void SwitchMove(); // vtable+0x44 + virtual void SwitchColor(LegoROI* p_roi); // vtable+0x48 + virtual void SwitchMood(); // vtable+0x4c void FUN_10010c30(); void SetType(MxU8 p_type); diff --git a/LEGO1/lego/legoomni/include/legoplantmanager.h b/LEGO1/lego/legoomni/include/legoplantmanager.h index 91fe49c8..c3b2c4e1 100644 --- a/LEGO1/lego/legoomni/include/legoplantmanager.h +++ b/LEGO1/lego/legoomni/include/legoplantmanager.h @@ -49,7 +49,7 @@ class LegoPlantManager : public MxCore { MxBool SwitchMove(LegoEntity* p_entity); MxBool SwitchMood(LegoEntity* p_entity); MxU32 GetAnimationId(LegoEntity* p_entity); - MxU32 GetSoundId(LegoEntity* p_entity, MxBool p_state); + MxU32 GetSoundId(LegoEntity* p_entity, MxBool p_basedOnMood); LegoPlantInfo* GetInfoArray(MxS32& p_length); LegoEntity* CreatePlant(MxS32 p_index, LegoWorld* p_world, LegoOmni::World p_worldId); MxBool DecrementCounter(LegoEntity* p_entity); diff --git a/LEGO1/lego/legoomni/src/common/legobuildingmanager.cpp b/LEGO1/lego/legoomni/src/common/legobuildingmanager.cpp index d46b8a49..1e4a8b82 100644 --- a/LEGO1/lego/legoomni/src/common/legobuildingmanager.cpp +++ b/LEGO1/lego/legoomni/src/common/legobuildingmanager.cpp @@ -199,10 +199,10 @@ LegoBuildingInfo g_buildingInfoInit[16] = { MxU32 LegoBuildingManager::g_maxSound = 6; // GLOBAL: LEGO1 0x100f373c -MxU32 g_unk0x100f373c = 0x3c; +MxU32 g_buildingSoundIdOffset = 0x3c; // GLOBAL: LEGO1 0x100f3740 -MxU32 g_unk0x100f3740 = 0x42; +MxU32 g_buildingSoundIdMoodOffset = 0x42; // clang-format off // GLOBAL: LEGO1 0x100f3788 @@ -548,7 +548,7 @@ MxU32 LegoBuildingManager::GetAnimationId(LegoEntity* p_entity) // FUNCTION: LEGO1 0x1002ff40 // FUNCTION: BETA10 0x10064398 -MxU32 LegoBuildingManager::GetSoundId(LegoEntity* p_entity, MxBool p_state) +MxU32 LegoBuildingManager::GetSoundId(LegoEntity* p_entity, MxBool p_basedOnMood) { LegoBuildingInfo* info = GetInfo(p_entity); @@ -556,12 +556,12 @@ MxU32 LegoBuildingManager::GetSoundId(LegoEntity* p_entity, MxBool p_state) return 0; } - if (p_state) { - return info->m_mood + g_unk0x100f3740; + if (p_basedOnMood) { + return info->m_mood + g_buildingSoundIdMoodOffset; } if (info != NULL) { - return info->m_sound + g_unk0x100f373c; + return info->m_sound + g_buildingSoundIdOffset; } return 0; diff --git a/LEGO1/lego/legoomni/src/common/legocharactermanager.cpp b/LEGO1/lego/legoomni/src/common/legocharactermanager.cpp index 80b69efc..ff7cf23b 100644 --- a/LEGO1/lego/legoomni/src/common/legocharactermanager.cpp +++ b/LEGO1/lego/legoomni/src/common/legocharactermanager.cpp @@ -37,10 +37,10 @@ MxU32 g_characterAnimationId = 10; char* LegoCharacterManager::g_customizeAnimFile = NULL; // GLOBAL: LEGO1 0x100fc4d8 -MxU32 g_soundIdOffset = 50; +MxU32 g_characterSoundIdOffset = 50; // GLOBAL: LEGO1 0x100fc4dc -MxU32 g_soundIdMoodOffset = 66; +MxU32 g_characterSoundIdMoodOffset = 66; // GLOBAL: LEGO1 0x100fc4e8 MxU32 g_headTextureCounter = 0; @@ -931,16 +931,16 @@ MxU32 LegoCharacterManager::GetAnimationId(LegoROI* p_roi) // FUNCTION: LEGO1 0x10085140 // FUNCTION: BETA10 0x10076855 -MxU32 LegoCharacterManager::GetSoundId(LegoROI* p_roi, MxBool p_und) +MxU32 LegoCharacterManager::GetSoundId(LegoROI* p_roi, MxBool p_basedOnMood) { LegoActorInfo* info = GetActorInfo(p_roi); - if (p_und) { - return info->m_mood + g_soundIdMoodOffset; + if (p_basedOnMood) { + return info->m_mood + g_characterSoundIdMoodOffset; } if (info != NULL) { - return info->m_sound + g_soundIdOffset; + return info->m_sound + g_characterSoundIdOffset; } else { return 0; diff --git a/LEGO1/lego/legoomni/src/common/legoplantmanager.cpp b/LEGO1/lego/legoomni/src/common/legoplantmanager.cpp index 766edf79..4e8e2626 100644 --- a/LEGO1/lego/legoomni/src/common/legoplantmanager.cpp +++ b/LEGO1/lego/legoomni/src/common/legoplantmanager.cpp @@ -40,10 +40,10 @@ MxU8 g_counters[] = {1, 2, 2, 3}; MxU32 LegoPlantManager::g_maxSound = 8; // GLOBAL: LEGO1 0x100f3160 -MxU32 g_unk0x100f3160 = 56; +MxU32 g_plantSoundIdOffset = 56; // GLOBAL: LEGO1 0x100f3164 -MxU32 g_unk0x100f3164 = 66; +MxU32 g_plantSoundIdMoodOffset = 66; // GLOBAL: LEGO1 0x100f3168 MxS32 LegoPlantManager::g_maxMove[4] = {3, 3, 3, 3}; @@ -513,16 +513,16 @@ MxU32 LegoPlantManager::GetAnimationId(LegoEntity* p_entity) // FUNCTION: LEGO1 0x10026ba0 // FUNCTION: BETA10 0x100c61ba -MxU32 LegoPlantManager::GetSoundId(LegoEntity* p_entity, MxBool p_state) +MxU32 LegoPlantManager::GetSoundId(LegoEntity* p_entity, MxBool p_basedOnMood) { LegoPlantInfo* info = GetInfo(p_entity); - if (p_state) { - return (info->m_mood & 1) + g_unk0x100f3164; + if (p_basedOnMood) { + return (info->m_mood & 1) + g_plantSoundIdMoodOffset; } if (info != NULL) { - return info->m_sound + g_unk0x100f3160; + return info->m_sound + g_plantSoundIdOffset; } return 0; diff --git a/LEGO1/lego/legoomni/src/entity/legoentity.cpp b/LEGO1/lego/legoomni/src/entity/legoentity.cpp index b76da503..9ac9aad6 100644 --- a/LEGO1/lego/legoomni/src/entity/legoentity.cpp +++ b/LEGO1/lego/legoomni/src/entity/legoentity.cpp @@ -263,7 +263,7 @@ void LegoEntity::ParseAction(char* p_extra) // FUNCTION: LEGO1 0x10010f10 // FUNCTION: BETA10 0x1007ee87 -void LegoEntity::ClickSound(MxBool p_und) +void LegoEntity::ClickSound(MxBool p_basedOnMood) { if (!IsInteraction(c_disabled)) { MxU32 objectId = 0; @@ -271,15 +271,15 @@ void LegoEntity::ClickSound(MxBool p_und) switch (m_type) { case e_actor: - objectId = CharacterManager()->GetSoundId(m_roi, p_und); + objectId = CharacterManager()->GetSoundId(m_roi, p_basedOnMood); break; case e_unk1: break; case e_plant: - objectId = PlantManager()->GetSoundId(this, p_und); + objectId = PlantManager()->GetSoundId(this, p_basedOnMood); break; case e_building: - objectId = BuildingManager()->GetSoundId(this, p_und); + objectId = BuildingManager()->GetSoundId(this, p_basedOnMood); break; } From 0982038453babc6233710fea5318b1437516a188 Mon Sep 17 00:00:00 2001 From: Fabian Neundorf Date: Sun, 29 Jun 2025 00:38:48 +0200 Subject: [PATCH 011/188] Clear unknown in `Hospital` (#1595) --- LEGO1/lego/legoomni/include/hospital.h | 2 +- LEGO1/lego/legoomni/src/worlds/hospital.cpp | 22 ++++++++++----------- 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/LEGO1/lego/legoomni/include/hospital.h b/LEGO1/lego/legoomni/include/hospital.h index 4a136568..2a90764b 100644 --- a/LEGO1/lego/legoomni/include/hospital.h +++ b/LEGO1/lego/legoomni/include/hospital.h @@ -123,7 +123,7 @@ class Hospital : public LegoWorld { MxLong m_copLedAnimTimer; // 0x11c MxLong m_pizzaLedAnimTimer; // 0x120 MxLong m_time; // 0x124 - undefined m_unk0x128; // 0x128 + MxBool m_exited; // 0x128 }; #endif // HOSPITAL_H diff --git a/LEGO1/lego/legoomni/src/worlds/hospital.cpp b/LEGO1/lego/legoomni/src/worlds/hospital.cpp index 4aa4b5a8..dbb1c00c 100644 --- a/LEGO1/lego/legoomni/src/worlds/hospital.cpp +++ b/LEGO1/lego/legoomni/src/worlds/hospital.cpp @@ -46,7 +46,7 @@ Hospital::Hospital() m_flashingLeds = 0; m_copLedAnimTimer = 0; m_pizzaLedAnimTimer = 0; - m_unk0x128 = 0; + m_exited = FALSE; NotificationManager()->Register(this); } @@ -367,8 +367,8 @@ MxLong Hospital::HandleEndAction(MxEndActionNotificationParam& p_param) act1State = (Act1State*) GameState()->GetState("Act1State"); act1State->SetUnknown18(9); case HospitalState::e_exitToFront: - if (m_unk0x128 == 0) { - m_unk0x128 = 1; + if (m_exited == FALSE) { + m_exited = TRUE; m_destLocation = LegoGameState::e_hospitalExited; DeleteObjects(&m_atomId, HospitalScript::c_hho002cl_RunAnim, HospitalScript::c_hho006cl_RunAnim); @@ -376,8 +376,8 @@ MxLong Hospital::HandleEndAction(MxEndActionNotificationParam& p_param) } break; case HospitalState::e_exitToInfocenter: - if (m_unk0x128 == 0) { - m_unk0x128 = 1; + if (m_exited == FALSE) { + m_exited = TRUE; m_destLocation = LegoGameState::e_infomain; DeleteObjects(&m_atomId, HospitalScript::c_hho002cl_RunAnim, HospitalScript::c_hho006cl_RunAnim); @@ -410,8 +410,8 @@ MxLong Hospital::HandleButtonDown(LegoControlManagerNotificationParam& p_param) m_interactionMode = 3; if (m_hospitalState->m_state == HospitalState::e_explainQuestShort) { - if (m_unk0x128 == 0) { - m_unk0x128 = 1; + if (m_exited == FALSE) { + m_exited = TRUE; TickleManager()->UnregisterClient(this); @@ -566,8 +566,8 @@ MxBool Hospital::HandleControl(LegoControlManagerNotificationParam& p_param) m_currentAction = HospitalScript::c_hho016cl_RunAnim; m_setWithCurrentAction = 1; } - else if (m_unk0x128 == 0) { - m_unk0x128 = 1; + else if (m_exited == FALSE) { + m_exited = TRUE; m_hospitalState->m_state = HospitalState::e_exitImmediately; m_destLocation = LegoGameState::e_infomain; @@ -587,8 +587,8 @@ MxBool Hospital::HandleControl(LegoControlManagerNotificationParam& p_param) m_currentAction = HospitalScript::c_hho016cl_RunAnim; m_setWithCurrentAction = 1; } - else if (m_unk0x128 == 0) { - m_unk0x128 = 1; + else if (m_exited == FALSE) { + m_exited = TRUE; m_hospitalState->m_state = HospitalState::e_exitImmediately; m_destLocation = LegoGameState::e_hospitalExited; From 020969c4831819dc766398140cbd9867e8b90a7b Mon Sep 17 00:00:00 2001 From: Christian Semmler Date: Sat, 28 Jun 2025 17:49:46 -0700 Subject: [PATCH 012/188] Add transition type to ini (#441) --- ISLE/isleapp.cpp | 15 +++++++++++---- ISLE/isleapp.h | 2 ++ LEGO1/lego/legoomni/include/mxtransitionmanager.h | 2 ++ .../legoomni/src/common/mxtransitionmanager.cpp | 9 ++++++++- tools/ncc/skip.yml | 1 + 5 files changed, 24 insertions(+), 5 deletions(-) diff --git a/ISLE/isleapp.cpp b/ISLE/isleapp.cpp index a5091ece..4ee1980f 100644 --- a/ISLE/isleapp.cpp +++ b/ISLE/isleapp.cpp @@ -96,11 +96,7 @@ IsleApp::IsleApp() m_cdPath = NULL; m_deviceId = NULL; m_savePath = NULL; -#ifdef __EMSCRIPTEN__ - m_fullScreen = FALSE; -#else m_fullScreen = TRUE; -#endif m_flipSurfaces = FALSE; m_backBuffersInVram = TRUE; m_using8bit = FALSE; @@ -140,6 +136,7 @@ IsleApp::IsleApp() m_iniPath = NULL; m_maxLod = RealtimeView::GetUserMaxLOD(); m_maxAllowedExtras = m_islandQuality <= 1 ? 10 : 20; + m_transitionType = MxTransitionManager::e_mosaic; } // FUNCTION: ISLE 0x4011a0 @@ -726,6 +723,7 @@ MxResult IsleApp::SetupWindow() LegoBuildingManager::configureLegoBuildingManager(m_islandQuality); LegoROI::configureLegoROI(iVar10); LegoAnimationManager::configureLegoAnimationManager(m_maxAllowedExtras); + MxTransitionManager::configureMxTransitionManager(m_transitionType); RealtimeView::SetUserMaxLOD(m_maxLod); if (LegoOmni::GetInstance()) { if (LegoOmni::GetInstance()->GetInputManager()) { @@ -824,6 +822,7 @@ bool IsleApp::LoadConfig() SDL_snprintf(buf, sizeof(buf), "%f", m_maxLod); iniparser_set(dict, "isle:Max LOD", buf); iniparser_set(dict, "isle:Max Allowed Extras", SDL_itoa(m_maxAllowedExtras, buf, 10)); + iniparser_set(dict, "isle:Transition Type", SDL_itoa(m_transitionType, buf, 10)); iniparser_dump_ini(dict, iniFP); SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION, "New config written at '%s'", iniConfig); @@ -853,7 +852,13 @@ bool IsleApp::LoadConfig() strcpy(m_mediaPath, mediaPath); m_flipSurfaces = iniparser_getboolean(dict, "isle:Flip Surfaces", m_flipSurfaces); + +#ifdef __EMSCRIPTEN__ + m_fullScreen = FALSE; +#else m_fullScreen = iniparser_getboolean(dict, "isle:Full Screen", m_fullScreen); +#endif + m_wideViewAngle = iniparser_getboolean(dict, "isle:Wide View Angle", m_wideViewAngle); m_use3dSound = iniparser_getboolean(dict, "isle:3DSound", m_use3dSound); m_useMusic = iniparser_getboolean(dict, "isle:Music", m_useMusic); @@ -880,6 +885,8 @@ bool IsleApp::LoadConfig() m_islandTexture = iniparser_getint(dict, "isle:Island Texture", m_islandTexture); m_maxLod = iniparser_getdouble(dict, "isle:Max LOD", m_maxLod); m_maxAllowedExtras = iniparser_getint(dict, "isle:Max Allowed Extras", m_maxAllowedExtras); + m_transitionType = + (MxTransitionManager::TransitionType) iniparser_getint(dict, "isle:Transition Type", m_transitionType); const char* deviceId = iniparser_getstring(dict, "isle:3D Device ID", NULL); if (deviceId != NULL) { diff --git a/ISLE/isleapp.h b/ISLE/isleapp.h index 2766607f..dd7bfdb1 100644 --- a/ISLE/isleapp.h +++ b/ISLE/isleapp.h @@ -3,6 +3,7 @@ #include "lego1_export.h" #include "legoutils.h" +#include "mxtransitionmanager.h" #include "mxtypes.h" #include "mxvideoparam.h" @@ -91,6 +92,7 @@ class IsleApp { char* m_iniPath; MxFloat m_maxLod; MxU32 m_maxAllowedExtras; + MxTransitionManager::TransitionType m_transitionType; }; extern IsleApp* g_isle; diff --git a/LEGO1/lego/legoomni/include/mxtransitionmanager.h b/LEGO1/lego/legoomni/include/mxtransitionmanager.h index 50b80055..dd9dc9ed 100644 --- a/LEGO1/lego/legoomni/include/mxtransitionmanager.h +++ b/LEGO1/lego/legoomni/include/mxtransitionmanager.h @@ -55,6 +55,8 @@ class MxTransitionManager : public MxCore { TransitionType GetTransitionType() { return m_mode; } + LEGO1_EXPORT static void configureMxTransitionManager(TransitionType p_transitionManagerConfig); + // SYNTHETIC: LEGO1 0x1004b9e0 // MxTransitionManager::`scalar deleting destructor' diff --git a/LEGO1/lego/legoomni/src/common/mxtransitionmanager.cpp b/LEGO1/lego/legoomni/src/common/mxtransitionmanager.cpp index b251c1c0..d54563ab 100644 --- a/LEGO1/lego/legoomni/src/common/mxtransitionmanager.cpp +++ b/LEGO1/lego/legoomni/src/common/mxtransitionmanager.cpp @@ -16,6 +16,8 @@ DECOMP_SIZE_ASSERT(MxTransitionManager, 0x900) +MxTransitionManager::TransitionType g_transitionManagerConfig = MxTransitionManager::e_mosaic; + // GLOBAL: LEGO1 0x100f4378 RECT g_fullScreenRect = {0, 0, 640, 480}; @@ -105,7 +107,7 @@ MxResult MxTransitionManager::StartTransition( backgroundAudioManager->Stop(); } - m_mode = p_animationType; + m_mode = g_transitionManagerConfig; m_copyFlags.m_bit0 = p_doCopy; @@ -632,3 +634,8 @@ void MxTransitionManager::SetupCopyRect(LPDDSURFACEDESC p_ddsc) ); } } + +void MxTransitionManager::configureMxTransitionManager(TransitionType p_transitionManagerConfig) +{ + g_transitionManagerConfig = p_transitionManagerConfig; +} diff --git a/tools/ncc/skip.yml b/tools/ncc/skip.yml index 65de1ce7..c5ceef72 100644 --- a/tools/ncc/skip.yml +++ b/tools/ncc/skip.yml @@ -4,6 +4,7 @@ configureLegoModelPresenter(MxS32): 'DLL exported function' configureLegoPartPresenter(MxS32, MxS32): 'DLL exported function' configureLegoROI(int): 'DLL exported function' configureLegoWorldPresenter(MxS32): 'DLL exported function' +configureMxTransitionManager(TransitionType): 'DLL exported function' GetNoCD_SourceName(): 'DLL exported function' m_3dView: 'Allow this variable name' m_3dManager: 'Allow this variable name' From e87184b50227f8fb15957a573da7ca9a2b840fd6 Mon Sep 17 00:00:00 2001 From: David Gow Date: Sun, 29 Jun 2025 23:47:09 +0800 Subject: [PATCH 013/188] Fix the OpenGL backends on non-glx Linux platforms (and remove GLEW dependency) (#446) * Work around issues with depth-buffer size on EGL-based platforms The OpenGL 1.1 and OpenGL ES 2.0 backends can break on EGL-based platforms, such as Wayland, or X11 with SDL_VIDEO_FORCE_EGL=1. One of the reasons for this (the other being glew on the GL1.1 backend) is that SDL/egl get very confused by the way we set OpenGL attributes, particularly SDL_GL_DEPTH_SIZE, resulting in SDL_GL_CreateContext() failing with EGL_BAD_MATCH. The exact cause of this is unknown, but it seems to be a combination of: - SDL_GL_SetAttribute() is supposed to be called _before_ the window is created, and we're calling it afterward. - Creating several test windows during the enumeration process, mixing and matching between OpenGL and OpenGL ES profiles. The "most correct" solution is probably to delay creating the game window until the backend creation process, rather than before the enumeration occurs. But that's a real refactor, which could cause other issues. Instead, set the 24-bit bit depth (which we've hardcoded anyway) before creating the window, and use SDL_GL_ResetAttributes() when creating backends. This seems to work here in all of the cases I was able to try (modulo the GLEW dependency, which is removed in the next patch). * miniwin: Remove GLEW dependency for OpenGL 1.1 GLEW normally backs directly onto glXGetProcAddress on Linux, which is broken on non-GLX setups, such as Wayland (but also X11 with EGL, and presumably KMSDRM). Replace it with manual calls to SDL_GL_GetProcAddress() for the VBO path. Note, however, that SDL_opengl.h includes "windows.h", so conflicts with the miniwin implementation, which breaks builds on windows. In order to work around this, we do what the Direct3D9 implementation does and push all of the OpenGL calls to a separate file, actual.cpp. Going forward, it may make sense to load _all_ OpenGL entry points via SDL, which would allow us to avoid linking directly with libGL/libOpenGL, and therefore eliminate the separate build dependency altogether, as well as allowing more runtime configurability as to the OpenGL library to load. (But that's definitely a bit uglier, and also useful very rarely.) --- .github/workflows/ci.yml | 2 +- .github/workflows/release.yml | 2 +- ISLE/isleapp.cpp | 1 + miniwin/CMakeLists.txt | 14 +- miniwin/src/d3drm/backends/opengl1/actual.cpp | 354 ++++++++++++++++++ miniwin/src/d3drm/backends/opengl1/actual.h | 88 +++++ .../src/d3drm/backends/opengl1/renderer.cpp | 298 ++------------- .../src/d3drm/backends/opengles2/renderer.cpp | 8 +- miniwin/src/internal/d3drmrenderer_opengl1.h | 32 +- 9 files changed, 494 insertions(+), 305 deletions(-) create mode 100644 miniwin/src/d3drm/backends/opengl1/actual.cpp create mode 100644 miniwin/src/d3drm/backends/opengl1/actual.h diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 7d688877..64c447e5 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -66,7 +66,7 @@ jobs: sudo apt-get update sudo apt-get install -y \ libx11-dev libxext-dev libxrandr-dev libxrender-dev libxfixes-dev libxi-dev libxinerama-dev \ - libxcursor-dev libwayland-dev libxkbcommon-dev wayland-protocols libgl1-mesa-dev libglew-dev qt6-base-dev \ + libxcursor-dev libwayland-dev libxkbcommon-dev wayland-protocols libgl1-mesa-dev qt6-base-dev \ libasound2-dev - name: Install macOS dependencies (brew) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 0e4c6cc9..ef8fdae2 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -45,7 +45,7 @@ jobs: sudo apt-get update sudo apt-get install -y \ libx11-dev libxext-dev libxrandr-dev libxrender-dev libxfixes-dev libxi-dev libxinerama-dev \ - libxcursor-dev libwayland-dev libxkbcommon-dev wayland-protocols libgl1-mesa-dev libglew-dev qt6-base-dev \ + libxcursor-dev libwayland-dev libxkbcommon-dev wayland-protocols libgl1-mesa-dev qt6-base-dev \ libasound2-dev - name: Install macOS dependencies (brew) diff --git a/ISLE/isleapp.cpp b/ISLE/isleapp.cpp index 4ee1980f..e700d5b2 100644 --- a/ISLE/isleapp.cpp +++ b/ISLE/isleapp.cpp @@ -657,6 +657,7 @@ MxResult IsleApp::SetupWindow() #ifdef MINIWIN SDL_SetBooleanProperty(props, SDL_PROP_WINDOW_CREATE_OPENGL_BOOLEAN, true); SDL_GL_SetAttribute(SDL_GL_DOUBLEBUFFER, 1); + SDL_GL_SetAttribute(SDL_GL_DEPTH_SIZE, 24); #endif window = SDL_CreateWindowWithProperties(props); diff --git a/miniwin/CMakeLists.txt b/miniwin/CMakeLists.txt index a1939441..508e685d 100644 --- a/miniwin/CMakeLists.txt +++ b/miniwin/CMakeLists.txt @@ -32,14 +32,16 @@ target_compile_definitions(miniwin PRIVATE ) find_package(OpenGL) -find_package(GLEW) -if(OpenGL_FOUND AND GLEW_FOUND) - message(STATUS "Found OpenGL and GLEW: enabling OpenGL 1.x renderer") - target_sources(miniwin PRIVATE src/d3drm/backends/opengl1/renderer.cpp) +if(OpenGL_FOUND) + message(STATUS "Found OpenGL: enabling OpenGL 1.x renderer") + target_sources(miniwin PRIVATE + src/d3drm/backends/opengl1/actual.cpp + src/d3drm/backends/opengl1/renderer.cpp + ) target_compile_definitions(miniwin PRIVATE USE_OPENGL1) - target_link_libraries(miniwin PRIVATE OpenGL::GL GLEW::GLEW) + target_link_libraries(miniwin PRIVATE OpenGL::GL) else() - message(STATUS "🧩 OpenGL 1.x support not enabled — needs OpenGL and GLEW") + message(STATUS "🧩 OpenGL 1.x support not enabled — needs OpenGL") endif() find_library(OPENGL_ES2_LIBRARY NAMES GLESv2) diff --git a/miniwin/src/d3drm/backends/opengl1/actual.cpp b/miniwin/src/d3drm/backends/opengl1/actual.cpp new file mode 100644 index 00000000..e1c56636 --- /dev/null +++ b/miniwin/src/d3drm/backends/opengl1/actual.cpp @@ -0,0 +1,354 @@ +// This file cannot include any minwin headers. + +#include "actual.h" + +#include "structs.h" + +#include +#include +#include +#include +#include + +// GL extension API functions. +bool g_useVBOs; +PFNGLGENBUFFERSPROC mwglGenBuffers; +PFNGLBINDBUFFERPROC mwglBindBuffer; +PFNGLBUFFERDATAPROC mwglBufferData; +PFNGLDELETEBUFFERSPROC mwglDeleteBuffers; + +void GL11_InitState() +{ + glEnable(GL_CULL_FACE); + glCullFace(GL_BACK); + glFrontFace(GL_CW); +} + +void GL11_LoadExtensions() +{ + g_useVBOs = SDL_GL_ExtensionSupported("GL_ARB_vertex_buffer_object"); + + if (g_useVBOs) { + // Load the required GL function pointers. + mwglGenBuffers = (PFNGLGENBUFFERSPROC) SDL_GL_GetProcAddress("glGenBuffersARB"); + mwglBindBuffer = (PFNGLBINDBUFFERPROC) SDL_GL_GetProcAddress("glBindBufferARB"); + mwglBufferData = (PFNGLBUFFERDATAPROC) SDL_GL_GetProcAddress("glBufferDataARB"); + mwglDeleteBuffers = (PFNGLDELETEBUFFERSPROC) SDL_GL_GetProcAddress("glDeleteBuffersARB"); + } +} + +void GL11_DestroyTexture(GLuint texId) +{ + glDeleteTextures(1, &texId); +} + +GLuint GL11_UploadTextureData(void* pixels, int width, int height) +{ + GLuint texId; + glGenTextures(1, &texId); + glBindTexture(GL_TEXTURE_2D, texId); + glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, pixels); + return texId; +} + +void GL11_UploadMesh(GLMeshCacheEntry& cache, bool hasTexture) +{ + if (g_useVBOs) { + mwglGenBuffers(1, &cache.vboPositions); + mwglBindBuffer(GL_ARRAY_BUFFER_ARB, cache.vboPositions); + mwglBufferData( + GL_ARRAY_BUFFER_ARB, + cache.positions.size() * sizeof(GL11_BridgeVector), + cache.positions.data(), + GL_STATIC_DRAW_ARB + ); + + mwglGenBuffers(1, &cache.vboNormals); + mwglBindBuffer(GL_ARRAY_BUFFER_ARB, cache.vboNormals); + mwglBufferData( + GL_ARRAY_BUFFER_ARB, + cache.normals.size() * sizeof(GL11_BridgeVector), + cache.normals.data(), + GL_STATIC_DRAW_ARB + ); + + if (hasTexture) { + mwglGenBuffers(1, &cache.vboTexcoords); + mwglBindBuffer(GL_ARRAY_BUFFER_ARB, cache.vboTexcoords); + mwglBufferData( + GL_ARRAY_BUFFER_ARB, + cache.texcoords.size() * sizeof(GL11_BridgeTexCoord), + cache.texcoords.data(), + GL_STATIC_DRAW_ARB + ); + } + + mwglGenBuffers(1, &cache.ibo); + mwglBindBuffer(GL_ELEMENT_ARRAY_BUFFER_ARB, cache.ibo); + mwglBufferData( + GL_ELEMENT_ARRAY_BUFFER_ARB, + cache.indices.size() * sizeof(cache.indices[0]), + cache.indices.data(), + GL_STATIC_DRAW_ARB + ); + } +} + +void GL11_DestroyMesh(GLMeshCacheEntry& cache) +{ + if (g_useVBOs) { + mwglDeleteBuffers(1, &cache.vboPositions); + mwglDeleteBuffers(1, &cache.vboNormals); + mwglDeleteBuffers(1, &cache.vboTexcoords); + mwglDeleteBuffers(1, &cache.ibo); + } +} + +void GL11_BeginFrame(const Matrix4x4* projection) +{ + glDisable(GL_BLEND); + glEnable(GL_DEPTH_TEST); + glDepthMask(GL_TRUE); + glEnable(GL_LIGHTING); + glEnable(GL_COLOR_MATERIAL); + glColorMaterial(GL_FRONT, GL_AMBIENT_AND_DIFFUSE); + + // Disable all lights and reset global ambient + for (int i = 0; i < 8; ++i) { + glDisable(GL_LIGHT0 + i); + } + const GLfloat zeroAmbient[4] = {0.f, 0.f, 0.f, 1.f}; + glLightModelfv(GL_LIGHT_MODEL_AMBIENT, zeroAmbient); + glLightModeli(GL_LIGHT_MODEL_LOCAL_VIEWER, GL_TRUE); + + // Projection and view + glMatrixMode(GL_PROJECTION); + glLoadMatrixf((const GLfloat*) projection); + glMatrixMode(GL_MODELVIEW); + glLoadIdentity(); +} + +void GL11_UploadLight(int lightIdx, GL11_BridgeSceneLight* l) +{ + // Setup light + glMatrixMode(GL_MODELVIEW); + glPushMatrix(); + glLoadIdentity(); + GLenum lightId = GL_LIGHT0 + lightIdx++; + const FColor& c = l->color; + GLfloat col[4] = {c.r, c.g, c.b, c.a}; + const GLfloat zeroAmbient[4] = {0.f, 0.f, 0.f, 1.f}; + + if (l->positional == 0.f && l->directional == 0.f) { + // Ambient light only + glLightfv(lightId, GL_AMBIENT, col); + const GLfloat black[4] = {0.f, 0.f, 0.f, 1.f}; + glLightfv(lightId, GL_DIFFUSE, black); + glLightfv(lightId, GL_SPECULAR, black); + const GLfloat dummyPos[4] = {0.f, 0.f, 1.f, 0.f}; + glLightfv(lightId, GL_POSITION, dummyPos); + } + else { + glLightfv(lightId, GL_AMBIENT, zeroAmbient); + glLightfv(lightId, GL_DIFFUSE, col); + if (l->directional == 1.0f) { + glLightfv(lightId, GL_SPECULAR, col); + } + else { + const GLfloat black[4] = {0.f, 0.f, 0.f, 1.f}; + glLightfv(lightId, GL_SPECULAR, black); + } + + GLfloat pos[4]; + if (l->directional == 1.f) { + pos[0] = -l->direction.x; + pos[1] = -l->direction.y; + pos[2] = -l->direction.z; + pos[3] = 0.f; + } + else { + pos[0] = l->position.x; + pos[1] = l->position.y; + pos[2] = l->position.z; + pos[3] = 1.f; + } + glLightfv(lightId, GL_POSITION, pos); + } + glEnable(lightId); + + glPopMatrix(); +} + +void GL11_EnableTransparency() +{ + glEnable(GL_BLEND); + glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); + glDepthMask(GL_FALSE); +} + +#define NO_TEXTURE_ID 0xffffffff + +void GL11_SubmitDraw( + GLMeshCacheEntry& mesh, + const Matrix4x4& modelViewMatrix, + const Appearance& appearance, + GLuint texId +) +{ + glLoadMatrixf(&modelViewMatrix[0][0]); + glEnable(GL_NORMALIZE); + + glColor4ub(appearance.color.r, appearance.color.g, appearance.color.b, appearance.color.a); + + if (appearance.shininess != 0.0f) { + GLfloat whiteSpec[] = {1.f, 1.f, 1.f, 1.f}; + glMaterialfv(GL_FRONT, GL_SPECULAR, whiteSpec); + glMaterialf(GL_FRONT, GL_SHININESS, appearance.shininess); + } + else { + GLfloat noSpec[] = {0.0f, 0.0f, 0.0f, 0.0f}; + glMaterialfv(GL_FRONT, GL_SPECULAR, noSpec); + glMaterialf(GL_FRONT, GL_SHININESS, 0.0f); + } + + if (mesh.flat) { + glShadeModel(GL_FLAT); + } + else { + glShadeModel(GL_SMOOTH); + } + + // Bind texture if present + if (appearance.textureId != NO_TEXTURE_ID) { + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); + glEnable(GL_TEXTURE_2D); + glBindTexture(GL_TEXTURE_2D, texId); + glEnableClientState(GL_TEXTURE_COORD_ARRAY); + } + else { + glDisable(GL_TEXTURE_2D); + glDisableClientState(GL_TEXTURE_COORD_ARRAY); + } + + glEnableClientState(GL_VERTEX_ARRAY); + glEnableClientState(GL_NORMAL_ARRAY); + + if (g_useVBOs) { + mwglBindBuffer(GL_ARRAY_BUFFER_ARB, mesh.vboPositions); + glVertexPointer(3, GL_FLOAT, 0, nullptr); + + mwglBindBuffer(GL_ARRAY_BUFFER_ARB, mesh.vboNormals); + glNormalPointer(GL_FLOAT, 0, nullptr); + + if (appearance.textureId != NO_TEXTURE_ID) { + mwglBindBuffer(GL_ARRAY_BUFFER_ARB, mesh.vboTexcoords); + glTexCoordPointer(2, GL_FLOAT, 0, nullptr); + } + + mwglBindBuffer(GL_ELEMENT_ARRAY_BUFFER_ARB, mesh.ibo); + glDrawElements(GL_TRIANGLES, static_cast(mesh.indices.size()), GL_UNSIGNED_SHORT, nullptr); + + mwglBindBuffer(GL_ARRAY_BUFFER_ARB, 0); + mwglBindBuffer(GL_ELEMENT_ARRAY_BUFFER_ARB, 0); + } + else { + glVertexPointer(3, GL_FLOAT, 0, mesh.positions.data()); + glNormalPointer(GL_FLOAT, 0, mesh.normals.data()); + if (appearance.textureId != NO_TEXTURE_ID) { + glTexCoordPointer(2, GL_FLOAT, 0, mesh.texcoords.data()); + } + + glDrawElements(GL_TRIANGLES, static_cast(mesh.indices.size()), GL_UNSIGNED_SHORT, mesh.indices.data()); + } + + glPopMatrix(); +} + +void GL11_Resize(int width, int height) +{ + glViewport(0, 0, width, height); +} + +void GL11_Clear(float r, float g, float b) +{ + glEnable(GL_DEPTH_TEST); + glDepthMask(GL_TRUE); + glClearColor(r, g, b, 1.0f); + glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); +} + +void GL11_Draw2DImage( + GLuint texId, + const SDL_Rect& srcRect, + const SDL_Rect& dstRect, + float left, + float right, + float bottom, + float top +) +{ + glDisable(GL_DEPTH_TEST); + glDepthMask(GL_FALSE); + + glMatrixMode(GL_PROJECTION); + glPushMatrix(); + glLoadIdentity(); + + glOrtho(left, right, bottom, top, -1, 1); + + glMatrixMode(GL_MODELVIEW); + glPushMatrix(); + glLoadIdentity(); + + glDisable(GL_LIGHTING); + glColor4f(1.0f, 1.0f, 1.0f, 1.0f); + + glEnable(GL_TEXTURE_2D); + glBindTexture(GL_TEXTURE_2D, texId); + glEnable(GL_BLEND); + glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); + + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); + + GLint boundTexture = 0; + glGetIntegerv(GL_TEXTURE_BINDING_2D, &boundTexture); + + GLfloat texW, texH; + glGetTexLevelParameterfv(GL_TEXTURE_2D, 0, GL_TEXTURE_WIDTH, &texW); + glGetTexLevelParameterfv(GL_TEXTURE_2D, 0, GL_TEXTURE_HEIGHT, &texH); + + float u1 = srcRect.x / texW; + float v1 = srcRect.y / texH; + float u2 = (srcRect.x + srcRect.w) / texW; + float v2 = (srcRect.y + srcRect.h) / texH; + + float x1 = (float) dstRect.x; + float y1 = (float) dstRect.y; + float x2 = x1 + dstRect.w; + float y2 = y1 + dstRect.h; + + glBegin(GL_QUADS); + glTexCoord2f(u1, v1); + glVertex2f(x1, y1); + glTexCoord2f(u2, v1); + glVertex2f(x2, y1); + glTexCoord2f(u2, v2); + glVertex2f(x2, y2); + glTexCoord2f(u1, v2); + glVertex2f(x1, y2); + glEnd(); + + // Restore state + glMatrixMode(GL_MODELVIEW); + glPopMatrix(); + glMatrixMode(GL_PROJECTION); + glPopMatrix(); +} + +void GL11_Download(SDL_Surface* target) +{ + glFinish(); + glReadPixels(0, 0, target->w, target->h, GL_RGBA, GL_UNSIGNED_BYTE, target->pixels); +} diff --git a/miniwin/src/d3drm/backends/opengl1/actual.h b/miniwin/src/d3drm/backends/opengl1/actual.h new file mode 100644 index 00000000..8a4fb8e8 --- /dev/null +++ b/miniwin/src/d3drm/backends/opengl1/actual.h @@ -0,0 +1,88 @@ +#pragma once + +#include "structs.h" + +#include +#include +#include + +// We don't want to transitively include windows.h, but we need GLuint +typedef unsigned int GLuint; +struct IDirect3DRMTexture; +struct MeshGroup; + +typedef float Matrix4x4[4][4]; + +struct GL11_BridgeVector { + float x, y, z; +}; + +struct GL11_BridgeTexCoord { + float u, v; +}; + +struct GL11_BridgeSceneLight { + FColor color; + GL11_BridgeVector position; + float positional; + GL11_BridgeVector direction; + float directional; +}; + +struct GL11_BridgeSceneVertex { + GL11_BridgeVector position; + GL11_BridgeVector normal; + float tu, tv; +}; + +struct GLTextureCacheEntry { + IDirect3DRMTexture* texture; + Uint32 version; + GLuint glTextureId; +}; + +struct GLMeshCacheEntry { + const MeshGroup* meshGroup; + int version; + bool flat; + + // non-VBO cache + std::vector positions; + std::vector normals; + std::vector texcoords; + std::vector indices; + + // VBO cache + GLuint vboPositions; + GLuint vboNormals; + GLuint vboTexcoords; + GLuint ibo; +}; + +void GL11_InitState(); +void GL11_LoadExtensions(); +void GL11_DestroyTexture(GLuint texId); +GLuint GL11_UploadTextureData(void* pixels, int width, int height); +void GL11_UploadMesh(GLMeshCacheEntry& cache, bool hasTexture); +void GL11_DestroyMesh(GLMeshCacheEntry& cache); +void GL11_BeginFrame(const Matrix4x4* projection); +void GL11_UploadLight(int lightIdx, GL11_BridgeSceneLight* l); +void GL11_EnableTransparency(); +void GL11_SubmitDraw( + GLMeshCacheEntry& mesh, + const Matrix4x4& modelViewMatrix, + const Appearance& appearance, + GLuint texId +); +void GL11_Resize(int width, int height); +void GL11_Clear(float r, float g, float b); +void GL11_Draw2DImage( + GLuint texId, + const SDL_Rect& srcRect, + const SDL_Rect& dstRect, + float left, + float right, + float bottom, + float top +); +void GL11_Download(SDL_Surface* target); diff --git a/miniwin/src/d3drm/backends/opengl1/renderer.cpp b/miniwin/src/d3drm/backends/opengl1/renderer.cpp index cc415de8..a0b84a80 100644 --- a/miniwin/src/d3drm/backends/opengl1/renderer.cpp +++ b/miniwin/src/d3drm/backends/opengl1/renderer.cpp @@ -1,5 +1,4 @@ -#include -// must come after GLEW +#include "actual.h" #include "d3drmrenderer_opengl1.h" #include "ddraw_impl.h" #include "ddsurface_impl.h" @@ -11,8 +10,20 @@ #include #include +static_assert(sizeof(Matrix4x4) == sizeof(D3DRMMATRIX4D), "Matrix4x4 is wrong size"); +static_assert(sizeof(GL11_BridgeVector) == sizeof(D3DVECTOR), "GL11_BridgeVector is wrong size"); +static_assert(sizeof(GL11_BridgeTexCoord) == sizeof(TexCoord), "GL11_BridgeTexCoord is wrong size"); +static_assert(sizeof(GL11_BridgeSceneLight) == sizeof(SceneLight), "GL11_BridgeSceneLight is wrong size"); +static_assert(sizeof(GL11_BridgeSceneVertex) == sizeof(D3DRMVERTEX), "GL11_BridgeSceneVertex is wrong size"); + Direct3DRMRenderer* OpenGL1Renderer::Create(DWORD width, DWORD height) { + // We have to reset the attributes here after having enumerated the + // OpenGL ES 2.0 renderer, or else SDL gets very confused by SDL_GL_DEPTH_SIZE + // call below when on an EGL-based backend, and crashes with EGL_BAD_MATCH. + SDL_GL_ResetAttributes(); + // But ResetAttributes resets it to 16. + SDL_GL_SetAttribute(SDL_GL_DEPTH_SIZE, 24); SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_COMPATIBILITY); SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 1); SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 1); @@ -28,8 +39,6 @@ Direct3DRMRenderer* OpenGL1Renderer::Create(DWORD width, DWORD height) testWindow = true; } - SDL_GL_SetAttribute(SDL_GL_DEPTH_SIZE, 24); - SDL_GLContext context = SDL_GL_CreateContext(window); if (!context) { SDL_Log("SDL_GL_CreateContext: %s", SDL_GetError()); @@ -47,18 +56,7 @@ Direct3DRMRenderer* OpenGL1Renderer::Create(DWORD width, DWORD height) return nullptr; } - GLenum err = glewInit(); - if (err != GLEW_OK) { - SDL_Log("glewInit: %s", glewGetErrorString(err)); - if (testWindow) { - SDL_DestroyWindow(window); - } - return nullptr; - } - - glEnable(GL_CULL_FACE); - glCullFace(GL_BACK); - glFrontFace(GL_CW); + GL11_InitState(); if (testWindow) { SDL_DestroyWindow(window); @@ -74,7 +72,7 @@ OpenGL1Renderer::OpenGL1Renderer(DWORD width, DWORD height, SDL_GLContext contex m_virtualWidth = width; m_virtualHeight = height; m_renderedImage = SDL_CreateSurface(m_width, m_height, SDL_PIXELFORMAT_RGBA32); - m_useVBOs = GLEW_ARB_vertex_buffer_object; + GL11_LoadExtensions(); } OpenGL1Renderer::~OpenGL1Renderer() @@ -114,7 +112,7 @@ void OpenGL1Renderer::AddTextureDestroyCallback(Uint32 id, IDirect3DRMTexture* t auto* ctx = static_cast(arg); auto& cache = ctx->renderer->m_textures[ctx->textureId]; if (cache.glTextureId != 0) { - glDeleteTextures(1, &cache.glTextureId); + GL11_DestroyTexture(cache.glTextureId); cache.glTextureId = 0; cache.texture = nullptr; } @@ -133,15 +131,13 @@ Uint32 OpenGL1Renderer::GetTextureId(IDirect3DRMTexture* iTexture) auto& tex = m_textures[i]; if (tex.texture == texture) { if (tex.version != texture->m_version) { - glDeleteTextures(1, &tex.glTextureId); - glGenTextures(1, &tex.glTextureId); - glBindTexture(GL_TEXTURE_2D, tex.glTextureId); + GL11_DestroyTexture(tex.glTextureId); SDL_Surface* surf = SDL_ConvertSurface(surface->m_surface, SDL_PIXELFORMAT_RGBA32); if (!surf) { return NO_TEXTURE_ID; } - glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, surf->w, surf->h, 0, GL_RGBA, GL_UNSIGNED_BYTE, surf->pixels); + tex.glTextureId = GL11_UploadTextureData(surf->pixels, surf->w, surf->h); SDL_DestroySurface(surf); tex.version = texture->m_version; @@ -151,14 +147,12 @@ Uint32 OpenGL1Renderer::GetTextureId(IDirect3DRMTexture* iTexture) } GLuint texId; - glGenTextures(1, &texId); - glBindTexture(GL_TEXTURE_2D, texId); SDL_Surface* surf = SDL_ConvertSurface(surface->m_surface, SDL_PIXELFORMAT_RGBA32); if (!surf) { return NO_TEXTURE_ID; } - glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, surf->w, surf->h, 0, GL_RGBA, GL_UNSIGNED_BYTE, surf->pixels); + texId = GL11_UploadTextureData(surf->pixels, surf->w, surf->h); SDL_DestroySurface(surf); for (Uint32 i = 0; i < m_textures.size(); ++i) { @@ -206,52 +200,19 @@ GLMeshCacheEntry GLUploadMesh(const MeshGroup& meshGroup, bool useVBOs) if (meshGroup.texture) { cache.texcoords.resize(vertices.size()); std::transform(vertices.begin(), vertices.end(), cache.texcoords.begin(), [](const D3DRMVERTEX& v) { - return v.texCoord; + return GL11_BridgeTexCoord{v.texCoord.u, v.texCoord.v}; }); } cache.positions.resize(vertices.size()); std::transform(vertices.begin(), vertices.end(), cache.positions.begin(), [](const D3DRMVERTEX& v) { - return v.position; + return GL11_BridgeVector{v.position.x, v.position.y, v.position.z}; }); cache.normals.resize(vertices.size()); std::transform(vertices.begin(), vertices.end(), cache.normals.begin(), [](const D3DRMVERTEX& v) { - return v.normal; + return GL11_BridgeVector{v.normal.x, v.normal.y, v.normal.z}; }); - if (useVBOs) { - glGenBuffers(1, &cache.vboPositions); - glBindBuffer(GL_ARRAY_BUFFER, cache.vboPositions); - glBufferData( - GL_ARRAY_BUFFER, - cache.positions.size() * sizeof(D3DVECTOR), - cache.positions.data(), - GL_STATIC_DRAW - ); - - glGenBuffers(1, &cache.vboNormals); - glBindBuffer(GL_ARRAY_BUFFER, cache.vboNormals); - glBufferData(GL_ARRAY_BUFFER, cache.normals.size() * sizeof(D3DVECTOR), cache.normals.data(), GL_STATIC_DRAW); - - if (meshGroup.texture) { - glGenBuffers(1, &cache.vboTexcoords); - glBindBuffer(GL_ARRAY_BUFFER, cache.vboTexcoords); - glBufferData( - GL_ARRAY_BUFFER, - cache.texcoords.size() * sizeof(TexCoord), - cache.texcoords.data(), - GL_STATIC_DRAW - ); - } - - glGenBuffers(1, &cache.ibo); - glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, cache.ibo); - glBufferData( - GL_ELEMENT_ARRAY_BUFFER, - cache.indices.size() * sizeof(cache.indices[0]), - cache.indices.data(), - GL_STATIC_DRAW - ); - } + GL11_UploadMesh(cache, meshGroup.texture != nullptr); return cache; } @@ -269,12 +230,7 @@ void OpenGL1Renderer::AddMeshDestroyCallback(Uint32 id, IDirect3DRMMesh* mesh) auto* ctx = static_cast(arg); auto& cache = ctx->renderer->m_meshs[ctx->id]; cache.meshGroup = nullptr; - if (ctx->renderer->m_useVBOs) { - glDeleteBuffers(1, &cache.vboPositions); - glDeleteBuffers(1, &cache.vboNormals); - glDeleteBuffers(1, &cache.vboTexcoords); - glDeleteBuffers(1, &cache.ibo); - } + GL11_DestroyMesh(cache); delete ctx; }, ctx @@ -329,90 +285,23 @@ const char* OpenGL1Renderer::GetName() HRESULT OpenGL1Renderer::BeginFrame() { - m_dirty = true; - - glDisable(GL_BLEND); - glEnable(GL_DEPTH_TEST); - glDepthMask(GL_TRUE); - glEnable(GL_LIGHTING); - glEnable(GL_COLOR_MATERIAL); - glColorMaterial(GL_FRONT, GL_AMBIENT_AND_DIFFUSE); - - // Disable all lights and reset global ambient - for (int i = 0; i < 8; ++i) { - glDisable(GL_LIGHT0 + i); - } - const GLfloat zeroAmbient[4] = {0.f, 0.f, 0.f, 1.f}; - glLightModelfv(GL_LIGHT_MODEL_AMBIENT, zeroAmbient); - glLightModeli(GL_LIGHT_MODEL_LOCAL_VIEWER, GL_TRUE); - - // Setup lights - glMatrixMode(GL_MODELVIEW); - glPushMatrix(); - glLoadIdentity(); + GL11_BeginFrame((Matrix4x4*) &m_projection[0][0]); int lightIdx = 0; for (const auto& l : m_lights) { if (lightIdx > 7) { break; } - GLenum lightId = GL_LIGHT0 + lightIdx++; - const FColor& c = l.color; - GLfloat col[4] = {c.r, c.g, c.b, c.a}; + GL11_UploadLight(lightIdx, (GL11_BridgeSceneLight*) &l); - if (l.positional == 0.f && l.directional == 0.f) { - // Ambient light only - glLightfv(lightId, GL_AMBIENT, col); - const GLfloat black[4] = {0.f, 0.f, 0.f, 1.f}; - glLightfv(lightId, GL_DIFFUSE, black); - glLightfv(lightId, GL_SPECULAR, black); - const GLfloat dummyPos[4] = {0.f, 0.f, 1.f, 0.f}; - glLightfv(lightId, GL_POSITION, dummyPos); - } - else { - glLightfv(lightId, GL_AMBIENT, zeroAmbient); - glLightfv(lightId, GL_DIFFUSE, col); - if (l.directional == 1.0f) { - glLightfv(lightId, GL_SPECULAR, col); - } - else { - const GLfloat black[4] = {0.f, 0.f, 0.f, 1.f}; - glLightfv(lightId, GL_SPECULAR, black); - } - - GLfloat pos[4]; - if (l.directional == 1.f) { - pos[0] = -l.direction.x; - pos[1] = -l.direction.y; - pos[2] = -l.direction.z; - pos[3] = 0.f; - } - else { - pos[0] = l.position.x; - pos[1] = l.position.y; - pos[2] = l.position.z; - pos[3] = 1.f; - } - glLightfv(lightId, GL_POSITION, pos); - } - glEnable(lightId); + lightIdx++; } - glPopMatrix(); - - // Projection and view - glMatrixMode(GL_PROJECTION); - glLoadMatrixf(&m_projection[0][0]); - glMatrixMode(GL_MODELVIEW); - glLoadIdentity(); - return DD_OK; } void OpenGL1Renderer::EnableTransparency() { - glEnable(GL_BLEND); - glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); - glDepthMask(GL_FALSE); + GL11_EnableTransparency(); } void OpenGL1Renderer::SubmitDraw( @@ -426,75 +315,14 @@ void OpenGL1Renderer::SubmitDraw( { auto& mesh = m_meshs[meshId]; - glLoadMatrixf(&modelViewMatrix[0][0]); - glEnable(GL_NORMALIZE); - - glColor4ub(appearance.color.r, appearance.color.g, appearance.color.b, appearance.color.a); - - if (appearance.shininess != 0.0f) { - GLfloat whiteSpec[] = {1.f, 1.f, 1.f, 1.f}; - glMaterialfv(GL_FRONT, GL_SPECULAR, whiteSpec); - glMaterialf(GL_FRONT, GL_SHININESS, appearance.shininess); - } - else { - GLfloat noSpec[] = {0.0f, 0.0f, 0.0f, 0.0f}; - glMaterialfv(GL_FRONT, GL_SPECULAR, noSpec); - glMaterialf(GL_FRONT, GL_SHININESS, 0.0f); - } - - if (mesh.flat) { - glShadeModel(GL_FLAT); - } - else { - glShadeModel(GL_SMOOTH); - } - // Bind texture if present if (appearance.textureId != NO_TEXTURE_ID) { - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); auto& tex = m_textures[appearance.textureId]; - glEnable(GL_TEXTURE_2D); - glBindTexture(GL_TEXTURE_2D, tex.glTextureId); - glEnableClientState(GL_TEXTURE_COORD_ARRAY); + GL11_SubmitDraw(mesh, modelViewMatrix, appearance, tex.glTextureId); } else { - glDisable(GL_TEXTURE_2D); - glDisableClientState(GL_TEXTURE_COORD_ARRAY); + GL11_SubmitDraw(mesh, modelViewMatrix, appearance, 0); } - - glEnableClientState(GL_VERTEX_ARRAY); - glEnableClientState(GL_NORMAL_ARRAY); - - if (m_useVBOs) { - glBindBuffer(GL_ARRAY_BUFFER, mesh.vboPositions); - glVertexPointer(3, GL_FLOAT, 0, nullptr); - - glBindBuffer(GL_ARRAY_BUFFER, mesh.vboNormals); - glNormalPointer(GL_FLOAT, 0, nullptr); - - if (appearance.textureId != NO_TEXTURE_ID) { - glBindBuffer(GL_ARRAY_BUFFER, mesh.vboTexcoords); - glTexCoordPointer(2, GL_FLOAT, 0, nullptr); - } - - glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, mesh.ibo); - glDrawElements(GL_TRIANGLES, static_cast(mesh.indices.size()), GL_UNSIGNED_SHORT, nullptr); - - glBindBuffer(GL_ARRAY_BUFFER, 0); - glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0); - } - else { - glVertexPointer(3, GL_FLOAT, 0, mesh.positions.data()); - glNormalPointer(GL_FLOAT, 0, mesh.normals.data()); - if (appearance.textureId != NO_TEXTURE_ID) { - glTexCoordPointer(2, GL_FLOAT, 0, mesh.texcoords.data()); - } - - glDrawElements(GL_TRIANGLES, static_cast(mesh.indices.size()), GL_UNSIGNED_SHORT, mesh.indices.data()); - } - - glPopMatrix(); } HRESULT OpenGL1Renderer::FinalizeFrame() @@ -509,16 +337,13 @@ void OpenGL1Renderer::Resize(int width, int height, const ViewportTransform& vie m_viewportTransform = viewportTransform; SDL_DestroySurface(m_renderedImage); m_renderedImage = SDL_CreateSurface(m_width, m_height, SDL_PIXELFORMAT_RGBA32); - glViewport(0, 0, m_width, m_height); + GL11_Resize(width, height); } void OpenGL1Renderer::Clear(float r, float g, float b) { m_dirty = true; - glEnable(GL_DEPTH_TEST); - glDepthMask(GL_TRUE); - glClearColor(r, g, b, 1.0f); - glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); + GL11_Clear(r, g, b); } void OpenGL1Renderer::Flip() @@ -532,73 +357,18 @@ void OpenGL1Renderer::Flip() void OpenGL1Renderer::Draw2DImage(Uint32 textureId, const SDL_Rect& srcRect, const SDL_Rect& dstRect) { m_dirty = true; - glDisable(GL_DEPTH_TEST); - glDepthMask(GL_FALSE); - - glMatrixMode(GL_PROJECTION); - glPushMatrix(); - glLoadIdentity(); float left = -m_viewportTransform.offsetX / m_viewportTransform.scale; float right = (m_width - m_viewportTransform.offsetX) / m_viewportTransform.scale; float top = -m_viewportTransform.offsetY / m_viewportTransform.scale; float bottom = (m_height - m_viewportTransform.offsetY) / m_viewportTransform.scale; - glOrtho(left, right, bottom, top, -1, 1); - glMatrixMode(GL_MODELVIEW); - glPushMatrix(); - glLoadIdentity(); - - glDisable(GL_LIGHTING); - glColor4f(1.0f, 1.0f, 1.0f, 1.0f); - - glEnable(GL_TEXTURE_2D); - glBindTexture(GL_TEXTURE_2D, m_textures[textureId].glTextureId); - glEnable(GL_BLEND); - glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); - - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); - - GLint boundTexture = 0; - glGetIntegerv(GL_TEXTURE_BINDING_2D, &boundTexture); - - GLfloat texW, texH; - glGetTexLevelParameterfv(GL_TEXTURE_2D, 0, GL_TEXTURE_WIDTH, &texW); - glGetTexLevelParameterfv(GL_TEXTURE_2D, 0, GL_TEXTURE_HEIGHT, &texH); - - float u1 = srcRect.x / texW; - float v1 = srcRect.y / texH; - float u2 = (srcRect.x + srcRect.w) / texW; - float v2 = (srcRect.y + srcRect.h) / texH; - - float x1 = (float) dstRect.x; - float y1 = (float) dstRect.y; - float x2 = x1 + dstRect.w; - float y2 = y1 + dstRect.h; - - glBegin(GL_QUADS); - glTexCoord2f(u1, v1); - glVertex2f(x1, y1); - glTexCoord2f(u2, v1); - glVertex2f(x2, y1); - glTexCoord2f(u2, v2); - glVertex2f(x2, y2); - glTexCoord2f(u1, v2); - glVertex2f(x1, y2); - glEnd(); - - // Restore state - glMatrixMode(GL_MODELVIEW); - glPopMatrix(); - glMatrixMode(GL_PROJECTION); - glPopMatrix(); + GL11_Draw2DImage(m_textures[textureId].glTextureId, srcRect, dstRect, left, right, bottom, top); } void OpenGL1Renderer::Download(SDL_Surface* target) { - glFinish(); - glReadPixels(0, 0, m_width, m_height, GL_RGBA, GL_UNSIGNED_BYTE, m_renderedImage->pixels); + GL11_Download(m_renderedImage); SDL_Rect srcRect = { static_cast(m_viewportTransform.offsetX), diff --git a/miniwin/src/d3drm/backends/opengles2/renderer.cpp b/miniwin/src/d3drm/backends/opengles2/renderer.cpp index 3dbabda2..9305510a 100644 --- a/miniwin/src/d3drm/backends/opengles2/renderer.cpp +++ b/miniwin/src/d3drm/backends/opengles2/renderer.cpp @@ -30,6 +30,12 @@ struct SceneLightGLES2 { Direct3DRMRenderer* OpenGLES2Renderer::Create(DWORD width, DWORD height) { + // We have to reset the attributes here after having enumerated the + // OpenGL ES 2.0 renderer, or else SDL gets very confused by SDL_GL_DEPTH_SIZE + // call below when on an EGL-based backend, and crashes with EGL_BAD_MATCH. + SDL_GL_ResetAttributes(); + // But ResetAttributes resets it to 16. + SDL_GL_SetAttribute(SDL_GL_DEPTH_SIZE, 24); SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_ES); SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 2); SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 0); @@ -41,8 +47,6 @@ Direct3DRMRenderer* OpenGLES2Renderer::Create(DWORD width, DWORD height) testWindow = true; } - SDL_GL_SetAttribute(SDL_GL_DEPTH_SIZE, 24); - SDL_GLContext context = SDL_GL_CreateContext(window); if (!context) { if (testWindow) { diff --git a/miniwin/src/internal/d3drmrenderer_opengl1.h b/miniwin/src/internal/d3drmrenderer_opengl1.h index 71c0ea82..d80621a5 100644 --- a/miniwin/src/internal/d3drmrenderer_opengl1.h +++ b/miniwin/src/internal/d3drmrenderer_opengl1.h @@ -1,44 +1,14 @@ #pragma once - +#include "../d3drm/backends/opengl1/actual.h" #include "d3drmrenderer.h" #include "d3drmtexture_impl.h" #include "ddraw_impl.h" -#ifdef __APPLE__ -#include -#else -#include -#endif - #include #include DEFINE_GUID(OpenGL1_GUID, 0x682656F3, 0x0000, 0x0000, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03); -struct GLTextureCacheEntry { - IDirect3DRMTexture* texture; - Uint32 version; - GLuint glTextureId; -}; - -struct GLMeshCacheEntry { - const MeshGroup* meshGroup; - int version; - bool flat; - - // non-VBO cache - std::vector positions; - std::vector normals; - std::vector texcoords; - std::vector indices; - - // VBO cache - GLuint vboPositions; - GLuint vboNormals; - GLuint vboTexcoords; - GLuint ibo; -}; - class OpenGL1Renderer : public Direct3DRMRenderer { public: static Direct3DRMRenderer* Create(DWORD width, DWORD height); From aa825aeecf44e61a02a240d72986bf49e26315d3 Mon Sep 17 00:00:00 2001 From: Christian Semmler Date: Sun, 29 Jun 2025 08:55:34 -0700 Subject: [PATCH 014/188] Add macro for switchable building index (#1596) --- LEGO1/lego/legoomni/src/common/legobuildingmanager.cpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/LEGO1/lego/legoomni/src/common/legobuildingmanager.cpp b/LEGO1/lego/legoomni/src/common/legobuildingmanager.cpp index 1e4a8b82..4ea96d2b 100644 --- a/LEGO1/lego/legoomni/src/common/legobuildingmanager.cpp +++ b/LEGO1/lego/legoomni/src/common/legobuildingmanager.cpp @@ -227,6 +227,8 @@ LegoBuildingInfo g_buildingInfo[16]; // GLOBAL: LEGO1 0x100f3748 MxS32 LegoBuildingManager::g_maxMove[16] = {0, 0, 0, 0, 0, 0, 0, 0, 0, 3, 3, 3, 3, 3, 3, 0}; +#define HAUS1_INDEX 12 + // FUNCTION: LEGO1 0x1002f8b0 void LegoBuildingManager::configureLegoBuildingManager(MxS32 p_buildingManagerConfig) { @@ -461,7 +463,7 @@ MxBool LegoBuildingManager::SwitchVariant(LegoEntity* p_entity) roi->SetVisibility(FALSE); info->m_variant = g_buildingInfoVariants[m_nextVariant]; - CreateBuilding(12, CurrentWorld()); + CreateBuilding(HAUS1_INDEX, CurrentWorld()); if (info->m_entity != NULL) { info->m_entity->GetROI()->SetVisibility(TRUE); From 77bbbfe2ca29c95bfc6f64cc9231b921c446c410 Mon Sep 17 00:00:00 2001 From: Christian Semmler Date: Sun, 29 Jun 2025 09:15:42 -0700 Subject: [PATCH 015/188] (Pepper) Fix building variant switch bug (#451) * (Pepper) Fix building variant switch bug * Add comment --- LEGO1/lego/legoomni/src/common/legobuildingmanager.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/LEGO1/lego/legoomni/src/common/legobuildingmanager.cpp b/LEGO1/lego/legoomni/src/common/legobuildingmanager.cpp index 4ea96d2b..8dae5d61 100644 --- a/LEGO1/lego/legoomni/src/common/legobuildingmanager.cpp +++ b/LEGO1/lego/legoomni/src/common/legobuildingmanager.cpp @@ -394,6 +394,9 @@ MxResult LegoBuildingManager::Read(LegoStorage* p_storage) m_nextVariant = 0; } + // Bugfix: allow Pepper to change variant building after save game load + g_buildingInfo[HAUS1_INDEX].m_variant = g_buildingInfoVariants[m_nextVariant]; + result = SUCCESS; done: From 6e7347621c535a3992be40692e950e21a9f6110f Mon Sep 17 00:00:00 2001 From: Brenden Davidson Date: Sun, 29 Jun 2025 14:04:08 -0500 Subject: [PATCH 016/188] Flatpak Build Support (#407) --- .editorconfig | 5 + .gitattributes | 4 + .github/workflows/release.yml | 34 +++- .gitignore | 6 + CMakeLists.txt | 2 + packaging/CMakeLists.txt | 19 +++ packaging/icons/isle.svg | 161 ++++++++++++++++++ packaging/linux/CMakeLists.txt | 7 + .../linux/flatpak/org.legoisland.Isle.json | 89 ++++++++++ packaging/linux/isledecomp.desktop.in | 37 ++++ packaging/linux/isledecomp.metainfo.xml.in | 91 ++++++++++ 11 files changed, 453 insertions(+), 2 deletions(-) create mode 100644 packaging/CMakeLists.txt create mode 100644 packaging/icons/isle.svg create mode 100644 packaging/linux/CMakeLists.txt create mode 100644 packaging/linux/flatpak/org.legoisland.Isle.json create mode 100644 packaging/linux/isledecomp.desktop.in create mode 100644 packaging/linux/isledecomp.metainfo.xml.in diff --git a/.editorconfig b/.editorconfig index c8d511ec..05d09a66 100644 --- a/.editorconfig +++ b/.editorconfig @@ -14,3 +14,8 @@ trim_trailing_whitespace = true [{CMakeLists.txt,*.cmake}] indent_size = 2 +insert_final_newline = true + +[*.{json,xml.in,desktop.in}] +indent_size = 2 +insert_final_newline = true diff --git a/.gitattributes b/.gitattributes index 232342c7..04926c6b 100644 --- a/.gitattributes +++ b/.gitattributes @@ -5,3 +5,7 @@ *.html text eol=lf diff=html *.mdp binary *.mak text eol=crlf +**/*.ico binary +**/*.png binary +**/*.svg text eol=lf +**/*.desktop text eol=lf diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index ef8fdae2..966ef3c6 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -91,15 +91,45 @@ jobs: path: | build/dist/isle-* + flatpak: + name: "Flatpak (${{ matrix.arch }})" + runs-on: ${{ matrix.os }} + + strategy: + fail-fast: false + matrix: + include: + - arch: x86_64 + os: ubuntu-latest + + - arch: aarch64 + os: ubuntu-22.04-arm + + container: + image: ghcr.io/flathub-infra/flatpak-github-actions:kde-6.8 + options: --privileged + + steps: + - uses: actions/checkout@v4 + + - name: Build Flatpak + uses: flatpak/flatpak-github-actions/flatpak-builder@v6 + with: + bundle: org.legoisland.Isle.${{ matrix.arch }}.flatpak + manifest-path: packaging/linux/flatpak/org.legoisland.Isle.json + arch: ${{ matrix.arch }} + release: name: 'Release' runs-on: ubuntu-latest - needs: build + needs: + - build + - flatpak steps: - name: Download All Artifacts uses: actions/download-artifact@main with: - pattern: Release-* + pattern: "{Release-*,*.flatpak}" path: Release merge-multiple: true diff --git a/.gitignore b/.gitignore index 662466db..1610ba3d 100644 --- a/.gitignore +++ b/.gitignore @@ -30,3 +30,9 @@ LEGO1.DLL # Kate - Text /.cache + +# Flatpak build cache +**/.flatpak-builder/ + +# Flatpak build dir +**/flatpak-build/ diff --git a/CMakeLists.txt b/CMakeLists.txt index 1f7e0595..d77ce732 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -643,6 +643,8 @@ if(EMSCRIPTEN) ) endif() +add_subdirectory(packaging) + set(CPACK_PACKAGE_DIRECTORY "dist") set(CPACK_PACKAGE_FILE_NAME "isle-${PROJECT_VERSION}-${ISLE_PACKAGE_NAME}-${CMAKE_SYSTEM_PROCESSOR}") if(MSVC) diff --git a/packaging/CMakeLists.txt b/packaging/CMakeLists.txt new file mode 100644 index 00000000..9994b0c0 --- /dev/null +++ b/packaging/CMakeLists.txt @@ -0,0 +1,19 @@ +set(APP_ID "org.legoisland.Isle") +set(APP_NAME "Isle Portable") +set(APP_SUMMARY "Portable version of the LEGO Island Decompilation Project") +set(APP_SPDX "LGPL-3.0-or-later") + +string(TIMESTAMP BUILD_DATE UTC) + +# The following will need to be refined if we wish to post actual releases to a repo such as Flathub +if(DEFINED ENV{GITHUB_ACTIONS} AND ENV{GITHUB_ACTIONS} EQUAL TRUE) + # Use the sequential run# of the current pipeline when running in GH Actions + set(SEMANTIC_VERSION "${PROJECT_VERSION}~build$ENV{GITHUB_RUN_NUMBER}") +else() + # Don't worry about the build number for local builds + set(SEMANTIC_VERSION "${PROJECT_VERSION}") +endif() + +if(LINUX) + add_subdirectory(linux) +endif() diff --git a/packaging/icons/isle.svg b/packaging/icons/isle.svg new file mode 100644 index 00000000..d3f6dbf1 --- /dev/null +++ b/packaging/icons/isle.svg @@ -0,0 +1,161 @@ + + + +LEGO Island IconLEGO Island Icon2025-06-22 diff --git a/packaging/linux/CMakeLists.txt b/packaging/linux/CMakeLists.txt new file mode 100644 index 00000000..2ece9db8 --- /dev/null +++ b/packaging/linux/CMakeLists.txt @@ -0,0 +1,7 @@ +# Injects the required variables into the Desktop and MetaInfo files +configure_file(isledecomp.desktop.in "${APP_ID}.desktop" @ONLY) +configure_file(isledecomp.metainfo.xml.in "${APP_ID}.metainfo.xml" @ONLY) + +install(FILES "../icons/isle.svg" RENAME "${APP_ID}.svg" DESTINATION "share/icons/hicolor/scalable/apps") +install(FILES "${CMAKE_CURRENT_BINARY_DIR}/${APP_ID}.desktop" DESTINATION "share/applications") +install(FILES "${CMAKE_CURRENT_BINARY_DIR}/${APP_ID}.metainfo.xml" DESTINATION "share/metainfo") diff --git a/packaging/linux/flatpak/org.legoisland.Isle.json b/packaging/linux/flatpak/org.legoisland.Isle.json new file mode 100644 index 00000000..e8cd3f8e --- /dev/null +++ b/packaging/linux/flatpak/org.legoisland.Isle.json @@ -0,0 +1,89 @@ +{ + "id": "org.legoisland.Isle", + + "runtime": "org.kde.Platform", + "sdk": "org.kde.Sdk", + "runtime-version": "6.8", + + "command": "isle", + + "finish-args": [ + "--share=ipc", + "--socket=wayland", + "--socket=fallback-x11", + "--socket=pulseaudio", + "--device=dri", + "--device=input", + "--filesystem=/run/media/:ro", + "--filesystem=/media/:ro", + "--filesystem=/mnt/:ro", + "--filesystem=home:ro" + ], + + "modules": [ + { + "name": "isle", + "buildsystem": "cmake-ninja", + "config-opts": [ + "-DCMAKE_BUILD_TYPE=RelWithDebInfo", + "-DISLE_DEBUG=OFF" + ], + "sources": [ + { + "type": "dir", + "path": "../../../3rdparty", + "dest": "3rdparty/" + }, + { + "type": "dir", + "path": "../../../cmake", + "dest": "cmake/" + }, + { + "type": "dir", + "path": "../../../CMake", + "dest": "CMake/" + }, + { + "type": "dir", + "path": "../../../CONFIG", + "dest": "CONFIG/" + }, + { + "type": "dir", + "path": "../../../ISLE", + "dest": "ISLE/" + }, + { + "type": "dir", + "path": "../../../LEGO1", + "dest": "LEGO1/" + }, + { + "type": "dir", + "path": "../../../miniwin", + "dest": "miniwin/" + }, + { + "type": "dir", + "path": "../../../packaging", + "dest": "packaging/" + }, + { + "type": "dir", + "path": "../../../util", + "dest": "util/" + }, + { + "type": "file", + "path": "../../../CMakeLists.txt" + } + ], + "build-options": { + "build-args": [ + "--share=network" + ] + } + } + ] +} diff --git a/packaging/linux/isledecomp.desktop.in b/packaging/linux/isledecomp.desktop.in new file mode 100644 index 00000000..a40c4a2a --- /dev/null +++ b/packaging/linux/isledecomp.desktop.in @@ -0,0 +1,37 @@ +[Desktop Entry] +Version=1.5 + +Name=@APP_NAME@ +Comment=@APP_SUMMARY@ + +Icon=@APP_ID@ +Type=Application +Categories=Game;KidsGame;AdventureGame;Qt + +Keywords=LEGO;lego;LEGO Island +Keywords[da]=LEGO;lego;Panik på LEGO Øen +Keywords[de]=LEGO;lego;Abenteuer auf der LEGO Insel +Keywords[es]=LEGO;lego;La Isla LEGO +Keywords[fr]=LEGO;lego;Aventures sur L'île LEGO +Keywords[it]=LEGO;lego;Isola LEGO +Keywords[ja]=LEGO;lego;レゴアイランドの大冒険 +Keywords[ko]=LEGO;lego;레고 아일랜드 +Keywords[pt]=LEGO;lego;A Ilha LEGO +Keywords[ru]=LEGO;lego;Остров LEGO + +SingleMainWindow=true + +TryExec=isle +Exec=isle + +Actions=play;configure + +[Desktop Action play] +Name=Play Game +Icon=currenttrack_play +Exec=isle + +[Desktop Action configure] +Name=Configure Settings +Icon=settings +Exec=isle-config diff --git a/packaging/linux/isledecomp.metainfo.xml.in b/packaging/linux/isledecomp.metainfo.xml.in new file mode 100644 index 00000000..b8faecc1 --- /dev/null +++ b/packaging/linux/isledecomp.metainfo.xml.in @@ -0,0 +1,91 @@ + + + + + @APP_ID@ + + @APP_NAME@ + @APP_SUMMARY@ + + @APP_ID@.desktop + + + #e3000b + + + + Isle Decomp Team + + + https://github.com/isledecomp/isle-portable + https://github.com/isledecomp/isle-portable/blob/master/CONTRIBUTING.md + https://github.com/isledecomp/isle-portable/tree/master + https://github.com/isledecomp/isle-portable/issues + + MIT + @APP_SPDX@ + + + 640 + offline-only + + + + 128 + + + + pointing + keyboard + gamepad + + + +

This initiative is a portable version of LEGO Island (Version 1.1, English) + based on the decompilation project. Our primary goal is to transform the codebase to achieve + platform independence, thereby enhancing compatibility across various systems while preserving + the original game's experience as faithfully as possible. +

+ +

+ Please note: this project is dedicated to achieving platform independence without altering the + core gameplay, adding new features, enhancing visual quality, or rewriting code for + improvement's sake. While those are worthwhile objectives, they are not within the scope + of this project. +

+ +
+ + + mild + mild + + + + Game + KidsGame + AdventureGame + Qt + + + + LEGO + lego + + LEGO Island + + Panik på LEGO Øen + Abenteuer auf der LEGO Insel + La Isla LEGO + Aventures sur L'île LEGO + Isola LEGO + レゴアイランドの大冒険 + 레고 아일랜드 + A Ilha LEGO + Остров LEGO + + + + + +
From 54694a4611ecf78392e71373687bde450186d934 Mon Sep 17 00:00:00 2001 From: Damglador <52221087+Damglador@users.noreply.github.com> Date: Sun, 29 Jun 2025 22:53:54 +0200 Subject: [PATCH 017/188] AppImage packaging (#439) * AppImage packaging * Add flags to specify location of required local files `--build=path` specifies where on the system is the directory with pre-build game binaries (must have binaries `isle` and `isle-config` in `path/bin` and game-specific libraries in `path/lib`) `--apprun=path` specifies where the apprun is `--desktop-file=path` same for the desktop file * Move to packaging/linux * Move building to appimage/build and ignore it in git * Use local icon. Option to specify location for it * Cleaning * Attempt at Github automation * Update CMakeLists.txt * Fix build * I guess it doesn't need quotes * Update CMakeLists.txt * Update release.yml * Work around for liblego1.so loading, fix arguments * Create testing.yml * Update testing.yml * I should pay more attention to what docs say * Fix copy-pasting mistake * Add AppImage packaging to the Release workflow * Try fixing filepicker * Delete testing.yml * Fix releases Can't specify where linuxdeploy leaves the file without specifying the name of the file, which I don't want to do, so just move the file in `dist` after packaging. * Remove unnecessary changes * Add qt6-xdgdesktopportal-platformtheme as deps Needed to call the xdg filepicker, basically desktop-specific filepicker. Hopefully this will allow to use it in AppImage * Get back flatpak in release * Update release.yml * Remove libglew-dev from apt install * Fix duplicate upload artifact * Update release.yml Co-authored-by: Christian Semmler * Remove *.AppImage pattern in Download All Artifacts --------- Co-authored-by: Christian Semmler --- .github/workflows/release.yml | 28 +++++++- packaging/CMakeLists.txt | 3 + packaging/linux/appimage/AppRun | 25 +++++++ packaging/linux/appimage/Build | 100 ++++++++++++++++++++++++++ packaging/linux/isledecomp.desktop.in | 1 + 5 files changed, 155 insertions(+), 2 deletions(-) create mode 100755 packaging/linux/appimage/AppRun create mode 100755 packaging/linux/appimage/Build diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 966ef3c6..e5d42a8b 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -46,7 +46,7 @@ jobs: sudo apt-get install -y \ libx11-dev libxext-dev libxrandr-dev libxrender-dev libxfixes-dev libxi-dev libxinerama-dev \ libxcursor-dev libwayland-dev libxkbcommon-dev wayland-protocols libgl1-mesa-dev qt6-base-dev \ - libasound2-dev + libasound2-dev qt6-xdgdesktopportal-platformtheme - name: Install macOS dependencies (brew) if: ${{ matrix.brew }} @@ -84,12 +84,36 @@ jobs: cd build cpack . + - name: Install linuxdeploy + if: ${{ matrix.linux }} + id: install-linuxdeploy + uses: miurahr/install-linuxdeploy-action@v1.8.0 + with: + plugins: qt appimage + + - name: Package (AppImage) + if: ${{ matrix.linux }} + run: | + cd build && \ + export LD_LIBRARY_PATH=".:$LD_LIBRARY_PATH" && \ + NO_STRIP=1 ${{ steps.install-linuxdeploy.outputs.linuxdeploy }} \ + -p qt \ + -e isle \ + -e isle-config \ + -d packaging/linux/org.legoisland.Isle.desktop \ + -i icons/org.legoisland.Isle.svg \ + --custom-apprun=../packaging/linux/appimage/AppRun \ + --appdir packaging/linux/appimage/AppDir \ + --output appimage && \ + mv *.AppImage dist/ + - name: Upload Artifact uses: actions/upload-artifact@main with: name: Release-${{ matrix.name }} path: | build/dist/isle-* + build/dist/*.AppImage flatpak: name: "Flatpak (${{ matrix.arch }})" @@ -118,7 +142,7 @@ jobs: bundle: org.legoisland.Isle.${{ matrix.arch }}.flatpak manifest-path: packaging/linux/flatpak/org.legoisland.Isle.json arch: ${{ matrix.arch }} - + release: name: 'Release' runs-on: ubuntu-latest diff --git a/packaging/CMakeLists.txt b/packaging/CMakeLists.txt index 9994b0c0..d6fc19f0 100644 --- a/packaging/CMakeLists.txt +++ b/packaging/CMakeLists.txt @@ -5,6 +5,9 @@ set(APP_SPDX "LGPL-3.0-or-later") string(TIMESTAMP BUILD_DATE UTC) +file(MAKE_DIRECTORY ${CMAKE_BINARY_DIR}/icons) +file(COPY_FILE icons/isle.svg ${CMAKE_BINARY_DIR}/icons/${APP_ID}.svg) + # The following will need to be refined if we wish to post actual releases to a repo such as Flathub if(DEFINED ENV{GITHUB_ACTIONS} AND ENV{GITHUB_ACTIONS} EQUAL TRUE) # Use the sequential run# of the current pipeline when running in GH Actions diff --git a/packaging/linux/appimage/AppRun b/packaging/linux/appimage/AppRun new file mode 100755 index 00000000..0da15db1 --- /dev/null +++ b/packaging/linux/appimage/AppRun @@ -0,0 +1,25 @@ +#!/bin/sh + +HERE="$(dirname "$(readlink -f "${0}")")" + +MAIN=$(grep -r "^Exec=.*" "$HERE"/*.desktop | head -n 1 | cut -d "=" -f 2 | cut -d " " -f 1) + +# MAIN_BIN=$(find "$HERE/usr/bin" -name "$MAIN" | head -n 1) +MAIN_BIN="$HERE/usr/bin/isle-config" + +export PATH="${HERE}/usr/bin/":$PATH # Prefer bundled binaries + +export QT_QPA_PLATFORMTHEME=xdgdesktopportal # Use XDG filepicker for forward compatability +[ -z "$QT_PLUGIN_PATH" ] && export QT_PLUGIN_PATH=/usr/lib/qt6/plugins:/usr/lib64/qt6/plugins # Use system Qt theme, will fallback to the default one if unavailable + + +if [ ! -z $APPIMAGE ]; then + BINARY_NAME=$(basename "$ARGV0") + if [ -e "$HERE/usr/bin/$BINARY_NAME" ]; then + exec "$HERE/usr/bin/$BINARY_NAME" "$@" + else + exec "${MAIN_BIN}" "$@" + fi +else + exec "${MAIN_BIN}" "$@" +fi diff --git a/packaging/linux/appimage/Build b/packaging/linux/appimage/Build new file mode 100755 index 00000000..9cf0325c --- /dev/null +++ b/packaging/linux/appimage/Build @@ -0,0 +1,100 @@ +#!/usr/bin/env bash +set -e + +export LD_LIBRARY_PATH="build/source/lib:$LD_LIBRARY_PATH" +[ -z "$QMAKE" ] && export QMAKE=/usr/lib/qt6/bin/qmake + +# Sets a directory that has to have a following structure: +# build +# ├── bin +# │   ├── isle +# │   └── isle-config +# └── lib +# ├── liblego1.so +# ├── libSDL3.so -> libSDL3.so.0 # Not important if available on the system +# ├── libSDL3.so.0 -> libSDL3.so.0.3.0 # Not important if available on the system +# └── libSDL3.so.0.3.0 # Not important if available on the system +# Can also be defined using --build=path +BUILD_SOURCE=source + +# Sets where AppRun for AppImage is, can also be defined using --apprun=path +APPRUN_SOURCE=AppRun + +# Sets where desktop file for AppImage is, can also be defined using --desktop-file=path +DESKTOP_FILE_SOURCE=isledecomp.desktop + +# You know the drill +ICON_SOURCE=../../icons/isle.svg + +cd $(dirname $0) + +clean(){ + echo "Deleting build directory" + rm -rf build +} + +download(){ + if [ ! -e "$1" ]; then + curl -Lo "$1" "$2" + fi +} + +prepare(){ + mkdir -p build/tools + mkdir -p build/assets + + download build/tools/appimagetool.AppImage https://github.com/AppImage/appimagetool/releases/download/continuous/appimagetool-$(uname -m).AppImage + chmod u+x build/tools/appimagetool.AppImage + + download build/tools/linuxdeploy.AppImage https://github.com/linuxdeploy/linuxdeploy/releases/latest/download/linuxdeploy-$(uname -m).AppImage + chmod u+x build/tools/linuxdeploy.AppImage + + download build/tools/linuxdeploy-plugin-qt.AppImage https://github.com/linuxdeploy/linuxdeploy-plugin-qt/releases/latest/download/linuxdeploy-plugin-qt-$(uname -m).AppImage + chmod u+x build/tools/linuxdeploy-plugin-qt.AppImage + + if [ ! -f "build/assets/isledecomp.desktop" ]; then + cp $DESKTOP_FILE_SOURCE build/assets/isledecomp.desktop + cp $APPRUN_SOURCE build/assets/AppRun + cp ../../icons/isle.svg build/assets/isle.svg + fi + + if [ ! -d "build/source" ]; then + cp -r $BUILD_SOURCE build/source + fi +} + +compile(){ + NO_STRIP=1 build/tools/linuxdeploy.AppImage \ + --plugin qt \ + -e build/source/bin/isle \ + -e build/source/bin/isle-config \ + -d build/assets/isledecomp.desktop \ + -i build/assets/isle.svg \ + --custom-apprun=AppRun \ + --appdir=build/AppDir +} + +package(){ + build/tools/appimagetool.AppImage build/AppDir build/"LEGO_Island-$(uname -m).AppImage" +} + +stop(){ # Can be used to do `Build clean stop` to just clean the directory + exit +} + +for arg in "$@"; do + case "$arg" in + --build=*) BUILD_SOURCE="${arg#--build=}";; + --apprun=*) APPRUN_SOURCE="${arg#--apprun=}";; + --desktop-file=*) DESKTOP_FILE_SOURCE="${arg#--desktop-file=}";; + --icon=*) ICON_SOURCE="${arg#--icon=}";; + *) "$arg" + esac +done + +prepare +compile +package +# Symlinks named as binaries in appimage can call these binaries specifically +# ln -s "LEGO_Island-$(uname -m).AppImage" isle-config +# ln -s "LEGO_Island-$(uname -m).AppImage" isle \ No newline at end of file diff --git a/packaging/linux/isledecomp.desktop.in b/packaging/linux/isledecomp.desktop.in index a40c4a2a..28309205 100644 --- a/packaging/linux/isledecomp.desktop.in +++ b/packaging/linux/isledecomp.desktop.in @@ -18,6 +18,7 @@ Keywords[ja]=LEGO;lego;レゴアイランドの大冒険 Keywords[ko]=LEGO;lego;레고 아일랜드 Keywords[pt]=LEGO;lego;A Ilha LEGO Keywords[ru]=LEGO;lego;Остров LEGO +Keywords[uk_UA]=LEGO;lego;LEGO острів SingleMainWindow=true From daa0bd1a32e5f92845817d0e52170dfa9c2da074 Mon Sep 17 00:00:00 2001 From: Anonymous Maarten Date: Sun, 29 Jun 2025 22:50:28 +0000 Subject: [PATCH 018/188] ci: combine ci and release workflow (#453) --- .github/workflows/ci.yml | 105 ++++++++++++++++++--- .github/workflows/release.yml | 170 ---------------------------------- 2 files changed, 93 insertions(+), 182 deletions(-) delete mode 100644 .github/workflows/release.yml diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 64c447e5..43248276 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -33,14 +33,14 @@ jobs: fail-fast: false matrix: include: - - { name: 'Linux', os: 'ubuntu-latest', dx5: false, config: true, build-type: 'Debug', linux: true, werror: true, clang-tidy: true } - - { name: 'MSVC (x86)', os: 'windows-latest', dx5: true, config: false, build-type: 'Debug', msvc: true, werror: false, clang-tidy: false, vc-arch: 'amd64_x86' } - - { name: 'MSVC (x64)', os: 'windows-latest', dx5: false, config: false, build-type: 'Debug', msvc: true, werror: false, clang-tidy: false, vc-arch: 'amd64' } - - { name: 'MSVC (arm64)', os: 'windows-latest', dx5: false, config: false, build-type: 'Debug', msvc: true, werror: false, clang-tidy: false, vc-arch: 'amd64_arm64' } - - { name: 'msys2 mingw32', os: 'windows-latest', dx5: false, config: false, build-type: 'Debug', mingw: true, werror: true, clang-tidy: true, msystem: 'mingw32', msys-env: 'mingw-w64-i686', shell: 'msys2 {0}' } - - { name: 'msys2 mingw64', os: 'windows-latest', dx5: false, config: true, build-type: 'Debug', mingw: true, werror: true, clang-tidy: true, msystem: 'mingw64', msys-env: 'mingw-w64-x86_64', shell: 'msys2 {0}' } - - { name: 'macOS', os: 'macos-latest', dx5: false, config: true, build-type: 'Debug', brew: true, werror: true, clang-tidy: false } - - { name: 'Emscripten', os: 'ubuntu-latest', dx5: false, config: false, build-type: 'Debug', emsdk: true, werror: true, clang-tidy: false, cmake-wrapper: 'emcmake' } + - { name: 'Linux', os: 'ubuntu-latest', dx5: false, config: true, linux: true, werror: true, clang-tidy: true } + - { name: 'MSVC (x86)', os: 'windows-latest', dx5: true, config: false, msvc: true, werror: false, clang-tidy: false, vc-arch: 'amd64_x86' } + - { name: 'MSVC (x64)', os: 'windows-latest', dx5: false, config: false, msvc: true, werror: false, clang-tidy: false, vc-arch: 'amd64' } + - { name: 'MSVC (arm64)', os: 'windows-latest', dx5: false, config: false, msvc: true, werror: false, clang-tidy: false, vc-arch: 'amd64_arm64' } + - { name: 'msys2 mingw32', os: 'windows-latest', dx5: false, config: false, mingw: true, werror: true, clang-tidy: true, msystem: 'mingw32', msys-env: 'mingw-w64-i686', shell: 'msys2 {0}' } + - { name: 'msys2 mingw64', os: 'windows-latest', dx5: false, config: true, mingw: true, werror: true, clang-tidy: true, msystem: 'mingw64', msys-env: 'mingw-w64-x86_64', shell: 'msys2 {0}' } + - { name: 'macOS', os: 'macos-latest', dx5: false, config: true, brew: true, werror: true, clang-tidy: false } + - { name: 'Emscripten', os: 'ubuntu-latest', dx5: false, config: false, emsdk: true, werror: true, clang-tidy: false, cmake-wrapper: 'emcmake' } steps: - name: Setup vcvars if: ${{ !!matrix.msvc }} @@ -67,7 +67,7 @@ jobs: sudo apt-get install -y \ libx11-dev libxext-dev libxrandr-dev libxrender-dev libxfixes-dev libxi-dev libxinerama-dev \ libxcursor-dev libwayland-dev libxkbcommon-dev wayland-protocols libgl1-mesa-dev qt6-base-dev \ - libasound2-dev + libasound2-dev qt6-xdgdesktopportal-platformtheme - name: Install macOS dependencies (brew) if: ${{ matrix.brew }} @@ -89,11 +89,12 @@ jobs: - name: Configure (CMake) run: | ${{ matrix.cmake-wrapper || '' }} cmake -S . -B build -GNinja \ - -DCMAKE_BUILD_TYPE=${{ matrix.build-type }} \ + -DCMAKE_BUILD_TYPE=Release \ -DISLE_USE_DX5=${{ !!matrix.dx5 }} \ -DISLE_BUILD_CONFIG=${{ matrix.config }} \ -DENABLE_CLANG_TIDY=${{ !!matrix.clang-tidy }} \ -DISLE_WERROR=${{ !!matrix.werror }} \ + -DISLE_DEBUG=OFF \ -Werror=dev - name: Build (CMake) @@ -104,11 +105,64 @@ jobs: cd build cpack . + - name: Install linuxdeploy + if: ${{ matrix.linux }} + id: install-linuxdeploy + uses: miurahr/install-linuxdeploy-action@v1.8.0 + with: + plugins: qt appimage + + - name: Package (AppImage) + if: ${{ matrix.linux }} + run: | + cd build && \ + export LD_LIBRARY_PATH=".:$LD_LIBRARY_PATH" && \ + NO_STRIP=1 ${{ steps.install-linuxdeploy.outputs.linuxdeploy }} \ + -p qt \ + -e isle \ + -e isle-config \ + -d packaging/linux/org.legoisland.Isle.desktop \ + -i icons/org.legoisland.Isle.svg \ + --custom-apprun=../packaging/linux/appimage/AppRun \ + --appdir packaging/linux/appimage/AppDir \ + --output appimage && \ + mv *.AppImage dist/ + - name: Upload Build Artifacts uses: actions/upload-artifact@v4 with: - name: '${{ matrix.name }} ${{ matrix.build-type }}' - path: build/dist/isle-* + name: '${{ matrix.name }}' + path: | + build/dist/isle-* + build/dist/*.AppImage + + flatpak: + name: "Flatpak (${{ matrix.arch }})" + runs-on: ${{ matrix.os }} + + strategy: + fail-fast: false + matrix: + include: + - arch: x86_64 + os: ubuntu-latest + + - arch: aarch64 + os: ubuntu-22.04-arm + + container: + image: ghcr.io/flathub-infra/flatpak-github-actions:kde-6.8 + options: --privileged + + steps: + - uses: actions/checkout@v4 + + - name: Build Flatpak + uses: flatpak/flatpak-github-actions/flatpak-builder@v6 + with: + bundle: org.legoisland.Isle.${{ matrix.arch }}.flatpak + manifest-path: packaging/linux/flatpak/org.legoisland.Isle.json + arch: ${{ matrix.arch }} ncc: name: 'C++' @@ -148,3 +202,30 @@ jobs: LEGO1/omni/src/video/flic.cpp \ $action_headers \ --path LEGO1/omni LEGO1/lego/legoomni + + release: + name: 'Release' + if: ${{ github.event_name == 'push' && github.ref_name == 'master' }} + runs-on: ubuntu-latest + needs: + - build + - flatpak + steps: + - name: Download All Artifacts + uses: actions/download-artifact@main + with: + pattern: "{Release-*,*.flatpak}" + path: Release + merge-multiple: true + + - name: Checkout uploadtool + uses: actions/checkout@v4 + with: + repository: 'probonopd/uploadtool' + path: 'uploadtool' + + - name: Upload Continuous Release + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: | + ./uploadtool/upload.sh Release/* diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml deleted file mode 100644 index e5d42a8b..00000000 --- a/.github/workflows/release.yml +++ /dev/null @@ -1,170 +0,0 @@ -name: Release - -on: - push: - branches: - - master - -jobs: - build: - name: ${{ matrix.name }} - runs-on: ${{ matrix.os }} - defaults: - run: - shell: ${{ matrix.shell || 'sh' }} - - strategy: - fail-fast: false - matrix: - include: - - { name: 'Linux', os: 'ubuntu-latest', dx5: false, config: true, build-type: 'Release', linux: true, werror: true, clang-tidy: false } - - { name: 'Windows', os: 'windows-latest', dx5: false, config: false, build-type: 'Release', msvc: true, werror: false, clang-tidy: false, vc-arch: 'amd64' } - - { name: 'macOS', os: 'macos-latest', dx5: false, config: true, build-type: 'Release', brew: true, werror: true, clang-tidy: false } - steps: - - name: Setup vcvars - if: ${{ !!matrix.msvc }} - uses: ilammy/msvc-dev-cmd@v1 - with: - arch: ${{ matrix.vc-arch }} - - - name: Set up MSYS2 - if: ${{ !!matrix.msystem }} - uses: msys2/setup-msys2@v2 - with: - msystem: ${{ matrix.msystem }} - install: >- - ${{ matrix.msys-env }}-cc - ${{ matrix.msys-env }}-cmake - ${{ matrix.msys-env }}-ninja - ${{ matrix.msys-env }}-clang-tools-extra - ${{ (matrix.config && format('{0}-qt6-base', matrix.msys-env)) || '' }} - - - name: Install Linux dependencies (apt-get) - if: ${{ matrix.linux }} - run: | - sudo apt-get update - sudo apt-get install -y \ - libx11-dev libxext-dev libxrandr-dev libxrender-dev libxfixes-dev libxi-dev libxinerama-dev \ - libxcursor-dev libwayland-dev libxkbcommon-dev wayland-protocols libgl1-mesa-dev qt6-base-dev \ - libasound2-dev qt6-xdgdesktopportal-platformtheme - - - name: Install macOS dependencies (brew) - if: ${{ matrix.brew }} - run: | - brew update - brew install cmake ninja llvm qt6 - echo "LLVM_ROOT=$(brew --prefix llvm)/bin" >> $GITHUB_ENV - - - name: Setup Emscripten - uses: mymindstorm/setup-emsdk@master - if: ${{ matrix.emsdk }} - - - name: Setup ninja - if: ${{ matrix.msvc }} - uses: ashutoshvarma/setup-ninja@master - - - uses: actions/checkout@v4 - - - name: Configure (CMake) - run: | - ${{ matrix.cmake-wrapper || '' }} cmake -S . -B build -GNinja \ - -DCMAKE_BUILD_TYPE=${{ matrix.build-type }} \ - -DISLE_USE_DX5=${{ !!matrix.dx5 }} \ - -DISLE_BUILD_CONFIG=${{ matrix.config }} \ - -DENABLE_CLANG_TIDY=${{ !!matrix.clang-tidy }} \ - -DISLE_WERROR=${{ !!matrix.werror }} \ - -DISLE_DEBUG=OFF \ - -Werror=dev - - - name: Build (CMake) - run: cmake --build build --verbose - - - name: Package (CPack) - run: | - cd build - cpack . - - - name: Install linuxdeploy - if: ${{ matrix.linux }} - id: install-linuxdeploy - uses: miurahr/install-linuxdeploy-action@v1.8.0 - with: - plugins: qt appimage - - - name: Package (AppImage) - if: ${{ matrix.linux }} - run: | - cd build && \ - export LD_LIBRARY_PATH=".:$LD_LIBRARY_PATH" && \ - NO_STRIP=1 ${{ steps.install-linuxdeploy.outputs.linuxdeploy }} \ - -p qt \ - -e isle \ - -e isle-config \ - -d packaging/linux/org.legoisland.Isle.desktop \ - -i icons/org.legoisland.Isle.svg \ - --custom-apprun=../packaging/linux/appimage/AppRun \ - --appdir packaging/linux/appimage/AppDir \ - --output appimage && \ - mv *.AppImage dist/ - - - name: Upload Artifact - uses: actions/upload-artifact@main - with: - name: Release-${{ matrix.name }} - path: | - build/dist/isle-* - build/dist/*.AppImage - - flatpak: - name: "Flatpak (${{ matrix.arch }})" - runs-on: ${{ matrix.os }} - - strategy: - fail-fast: false - matrix: - include: - - arch: x86_64 - os: ubuntu-latest - - - arch: aarch64 - os: ubuntu-22.04-arm - - container: - image: ghcr.io/flathub-infra/flatpak-github-actions:kde-6.8 - options: --privileged - - steps: - - uses: actions/checkout@v4 - - - name: Build Flatpak - uses: flatpak/flatpak-github-actions/flatpak-builder@v6 - with: - bundle: org.legoisland.Isle.${{ matrix.arch }}.flatpak - manifest-path: packaging/linux/flatpak/org.legoisland.Isle.json - arch: ${{ matrix.arch }} - - release: - name: 'Release' - runs-on: ubuntu-latest - needs: - - build - - flatpak - steps: - - name: Download All Artifacts - uses: actions/download-artifact@main - with: - pattern: "{Release-*,*.flatpak}" - path: Release - merge-multiple: true - - - name: Checkout uploadtool - uses: actions/checkout@v4 - with: - repository: 'probonopd/uploadtool' - path: 'uploadtool' - - - name: Upload Continuous Release - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - run: | - ./uploadtool/upload.sh Release/* From 79462824e85deb81848ef5242e7a6b7948c43632 Mon Sep 17 00:00:00 2001 From: Anonymous Maarten Date: Sun, 29 Jun 2025 23:35:24 +0000 Subject: [PATCH 019/188] Fix continuous release (#454) --- .github/workflows/ci.yml | 4 +- CMakeLists.txt | 8 +- cmake/detectcpu.cmake | 156 +++++++++++++++++++++++++++++++++++++++ 3 files changed, 164 insertions(+), 4 deletions(-) create mode 100644 cmake/detectcpu.cmake diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 43248276..c6124f37 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -91,7 +91,7 @@ jobs: ${{ matrix.cmake-wrapper || '' }} cmake -S . -B build -GNinja \ -DCMAKE_BUILD_TYPE=Release \ -DISLE_USE_DX5=${{ !!matrix.dx5 }} \ - -DISLE_BUILD_CONFIG=${{ matrix.config }} \ + -DISLE_BUILD_CONFIG=${{ !!matrix.config }} \ -DENABLE_CLANG_TIDY=${{ !!matrix.clang-tidy }} \ -DISLE_WERROR=${{ !!matrix.werror }} \ -DISLE_DEBUG=OFF \ @@ -214,7 +214,7 @@ jobs: - name: Download All Artifacts uses: actions/download-artifact@main with: - pattern: "{Release-*,*.flatpak}" + pattern: "*" path: Release merge-multiple: true diff --git a/CMakeLists.txt b/CMakeLists.txt index d77ce732..f221d342 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -13,6 +13,9 @@ list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_LIST_DIR}/cmake") include(CheckCXXSourceCompiles) include(CMakeDependentOption) include(CMakePushCheckState) +include(cmake/detectcpu.cmake) + +DetectTargetCPUArchitectures(ISLE_CPUS) set(CMAKE_CXX_STANDARD 17) set(CMAKE_CXX_STANDARD_REQUIRED ON) @@ -624,7 +627,8 @@ else() include(GNUInstallDirs) endif() -set(ISLE_PACKAGE_NAME "${CMAKE_SYSTEM_NAME}-${CMAKE_SYSTEM_PROCESSOR}" CACHE STRING "Platform name of the package") +string(REPLACE ";" "-" ISLE_CPUS_STRING "${ISLE_CPU}") +set(ISLE_PACKAGE_NAME "${CMAKE_SYSTEM_NAME}-${ISLE_CPUS_STRING}" CACHE STRING "Platform name of the package") if(BUILD_SHARED_LIBS) list(APPEND install_extra_targets lego1) endif() @@ -646,7 +650,7 @@ endif() add_subdirectory(packaging) set(CPACK_PACKAGE_DIRECTORY "dist") -set(CPACK_PACKAGE_FILE_NAME "isle-${PROJECT_VERSION}-${ISLE_PACKAGE_NAME}-${CMAKE_SYSTEM_PROCESSOR}") +set(CPACK_PACKAGE_FILE_NAME "isle-${PROJECT_VERSION}-${ISLE_PACKAGE_NAME}") if(MSVC) set(CPACK_GENERATOR ZIP) else() diff --git a/cmake/detectcpu.cmake b/cmake/detectcpu.cmake new file mode 100644 index 00000000..e0b6feae --- /dev/null +++ b/cmake/detectcpu.cmake @@ -0,0 +1,156 @@ +function(DetectTargetCPUArchitectures DETECTED_ARCHS) + + set(known_archs EMSCRIPTEN ARM32 ARM64 ARM64EC LOONGARCH64 POWERPC32 POWERPC64 X86 X64) + + if(APPLE AND CMAKE_OSX_ARCHITECTURES) + foreach(known_arch IN LISTS known_archs) + set(CPU_${known_arch} "0" PARENT_SCOPE) + endforeach() + set(detected_archs) + foreach(osx_arch IN LISTS CMAKE_OSX_ARCHITECTURES) + if(osx_arch STREQUAL "x86_64") + set(CPU_X64 "1" PARENT_SCOPE) + list(APPEND detected_archs "X64") + elseif(osx_arch STREQUAL "arm64") + set(CPU_ARM64 "1" PARENT_SCOPE) + list(APPEND detected_archs "ARM64") + endif() + endforeach() + set("${DETECTED_ARCHS}" "${detected_archs}" PARENT_SCOPE) + return() + endif() + + set(detected_archs) + foreach(known_arch IN LISTS known_archs) + if(CPU_${known_arch}) + list(APPEND detected_archs "${known_arch}") + endif() + endforeach() + + if(detected_archs) + set("${DETECTED_ARCHS}" "${detected_archs}" PARENT_SCOPE) + return() + endif() + + set(arch_check_ARM32 "defined(__arm__) || defined(_M_ARM)") + set(arch_check_ARM64 "defined(__aarch64__) || defined(_M_ARM64)") + set(arch_check_ARM64EC "defined(_M_ARM64EC)") + set(arch_check_EMSCRIPTEN "defined(__EMSCRIPTEN__)") + set(arch_check_LOONGARCH64 "defined(__loongarch64)") + set(arch_check_POWERPC32 "(defined(__PPC__) || defined(__powerpc__)) && !defined(__powerpc64__)") + set(arch_check_POWERPC64 "defined(__PPC64__) || defined(__powerpc64__)") + set(arch_check_X86 "defined(__i386__) || defined(__i486__) || defined(__i586__) || defined(__i686__) ||defined( __i386) || defined(_M_IX86)") + set(arch_check_X64 "(defined(__amd64__) || defined(__amd64) || defined(__x86_64__) || defined(__x86_64) || defined(_M_X64) || defined(_M_AMD64)) && !defined(_M_ARM64EC)") + + set(src_vars "") + set(src_main "") + foreach(known_arch IN LISTS known_archs) + set(detected_${known_arch} "0") + + string(APPEND src_vars " +#if ${arch_check_${known_arch}} +#define ARCH_${known_arch} \"1\" +#else +#define ARCH_${known_arch} \"0\" +#endif +const char *arch_${known_arch} = \"INFO<${known_arch}=\" ARCH_${known_arch} \">\"; +") + string(APPEND src_main " + result += arch_${known_arch}[argc];") + endforeach() + + set(src_arch_detect "${src_vars} +int main(int argc, char *argv[]) { + int result = 0; + (void)argv; +${src_main} + return result; +}") + + if(CMAKE_C_COMPILER) + set(ext ".c") + elseif(CMAKE_CXX_COMPILER) + set(ext ".cpp") + else() + enable_language(C) + set(ext ".c") + endif() + set(path_src_arch_detect "${CMAKE_CURRENT_BINARY_DIR}/CMakeFiles/CMakeTmp/detect_arch${ext}") + file(WRITE "${path_src_arch_detect}" "${src_arch_detect}") + set(path_dir_arch_detect "${CMAKE_CURRENT_BINARY_DIR}/CMakeFiles/CMakeTmp/detect_arch") + set(path_bin_arch_detect "${path_dir_arch_detect}/bin") + + set(detected_archs) + + set(msg "Detecting Target CPU Architecture") + message(STATUS "${msg}") + + include(CMakePushCheckState) + + set(CMAKE_TRY_COMPILE_TARGET_TYPE "STATIC_LIBRARY") + + cmake_push_check_state(RESET) + try_compile(CPU_CHECK_ALL + "${CMAKE_CURRENT_BINARY_DIR}/CMakeFiles/CMakeTmp/detect_arch" + SOURCES "${path_src_arch_detect}" + COPY_FILE "${path_bin_arch_detect}" + ) + cmake_pop_check_state() + if(NOT CPU_CHECK_ALL) + message(STATUS "${msg} - ") + message(WARNING "Failed to compile source detecting the target CPU architecture") + else() + set(re "INFO<([A-Z0-9]+)=([01])>") + file(STRINGS "${path_bin_arch_detect}" infos REGEX "${re}") + + foreach(info_arch_01 IN LISTS infos) + string(REGEX MATCH "${re}" A "${info_arch_01}") + if(NOT "${CMAKE_MATCH_1}" IN_LIST known_archs) + message(WARNING "Unknown architecture: \"${CMAKE_MATCH_1}\"") + continue() + endif() + set(arch "${CMAKE_MATCH_1}") + set(arch_01 "${CMAKE_MATCH_2}") + set(detected_${arch} "${arch_01}") + endforeach() + + foreach(known_arch IN LISTS known_archs) + if(detected_${known_arch}) + list(APPEND detected_archs ${known_arch}) + endif() + endforeach() + endif() + + if(detected_archs) + foreach(known_arch IN LISTS known_archs) + set("CPU_${known_arch}" "${detected_${known_arch}}" CACHE BOOL "Detected architecture ${known_arch}") + endforeach() + message(STATUS "${msg} - ${detected_archs}") + else() + include(CheckCSourceCompiles) + cmake_push_check_state(RESET) + foreach(known_arch IN LISTS known_archs) + if(NOT detected_archs) + set(cache_variable "CPU_${known_arch}") + set(test_src " + int main(int argc, char *argv[]) { + #if ${arch_check_${known_arch}} + return 0; + #else + choke + #endif + } + ") + check_c_source_compiles("${test_src}" "${cache_variable}") + if(${cache_variable}) + set(CPU_${known_arch} "1" CACHE BOOL "Detected architecture ${known_arch}") + set(detected_archs ${known_arch}) + else() + set(CPU_${known_arch} "0" CACHE BOOL "Detected architecture ${known_arch}") + endif() + endif() + endforeach() + cmake_pop_check_state() + endif() + set("${DETECTED_ARCHS}" "${detected_archs}" PARENT_SCOPE) +endfunction() From a258a89b1f7265efc8240ca0ee2551e3c6d747f8 Mon Sep 17 00:00:00 2001 From: Anonymous Maarten Date: Mon, 30 Jun 2025 00:06:21 +0000 Subject: [PATCH 020/188] cmake: fix typo of architecture used in binary artifact (#455) --- CMakeLists.txt | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index f221d342..ac3663fc 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -627,7 +627,8 @@ else() include(GNUInstallDirs) endif() -string(REPLACE ";" "-" ISLE_CPUS_STRING "${ISLE_CPU}") +string(REPLACE ";" "-" ISLE_CPUS_STRING "${ISLE_CPUS}") +string(TOLOWER "${ISLE_CPUS_STRING}" ISLE_CPUS_STRING) set(ISLE_PACKAGE_NAME "${CMAKE_SYSTEM_NAME}-${ISLE_CPUS_STRING}" CACHE STRING "Platform name of the package") if(BUILD_SHARED_LIBS) list(APPEND install_extra_targets lego1) From f0df3cd0ef1f339293f9d13fd1d2e0854b0e011d Mon Sep 17 00:00:00 2001 From: Korbo Date: Mon, 30 Jun 2025 14:20:53 -0500 Subject: [PATCH 021/188] Clear a few unknowns in act 3 (#1597) * Clear a few unknowns in act 3 * fix build error * fix other build error * requested changes --- LEGO1/lego/legoomni/include/act3.h | 14 ++-- LEGO1/lego/legoomni/src/actors/act3actors.cpp | 4 +- LEGO1/lego/legoomni/src/actors/act3ammo.cpp | 4 +- LEGO1/lego/legoomni/src/worlds/act3.cpp | 67 +++++++++---------- 4 files changed, 44 insertions(+), 45 deletions(-) diff --git a/LEGO1/lego/legoomni/include/act3.h b/LEGO1/lego/legoomni/include/act3.h index dc81160c..38e2876e 100644 --- a/LEGO1/lego/legoomni/include/act3.h +++ b/LEGO1/lego/legoomni/include/act3.h @@ -129,7 +129,7 @@ class Act3 : public LegoWorld { void RemoveDonut(Act3Ammo& p_p); MxResult ShootPizza(LegoPathController* p_controller, Vector3& p_location, Vector3& p_direction, Vector3& p_up); MxResult ShootDonut(LegoPathController* p_controller, Vector3& p_location, Vector3& p_direction, Vector3& p_up); - void FUN_10072ad0(undefined4 p_param1); + void TriggerHitSound(undefined4 p_param1); MxResult FUN_10073360(Act3Ammo& p_ammo, const Vector3& p_param2); MxResult FUN_10073390(Act3Ammo& p_ammo, const Vector3& p_param2); void SetBrickster(Act3Brickster* p_brickster); @@ -168,12 +168,12 @@ class Act3 : public LegoWorld { Helicopter* m_copter; // 0x420c Act3Shark* m_shark; // 0x4210 MxFloat m_time; // 0x4214 - MxU8 m_unk0x4218; // 0x4218 - MxU8 m_unk0x4219; // 0x4219 - MxU8 m_unk0x421a; // 0x421a - MxU8 m_unk0x421b; // 0x421b - MxU8 m_unk0x421c; // 0x421c - MxU8 m_unk0x421d; // 0x421d + MxU8 m_pizzaHitSound; // 0x4218 + MxU8 m_pizzaMissSound; // 0x4219 + MxU8 m_copDonutSound; // 0x421a + MxU8 m_donutMissSound; // 0x421b + MxU8 m_islanderSound; // 0x421c + MxU8 m_bricksterDonutSound; // 0x421d undefined m_unk0x421e; // 0x421e Act3List m_unk0x4220; // 0x4220 MxPresenter* m_helicopterDots[15]; // 0x4230 diff --git a/LEGO1/lego/legoomni/src/actors/act3actors.cpp b/LEGO1/lego/legoomni/src/actors/act3actors.cpp index 1bd13278..c9750667 100644 --- a/LEGO1/lego/legoomni/src/actors/act3actors.cpp +++ b/LEGO1/lego/legoomni/src/actors/act3actors.cpp @@ -579,7 +579,7 @@ void Act3Brickster::Animate(float p_time) } if (m_unk0x54 < p_time) { - ((Act3*) m_world)->FUN_10072ad0(5); + ((Act3*) m_world)->TriggerHitSound(5); m_unk0x54 = p_time + 15000.0f; } @@ -595,7 +595,7 @@ void Act3Brickster::Animate(float p_time) assert(SoundManager()->GetCacheSoundManager()); if (m_unk0x58 >= 8) { - ((Act3*) m_world)->FUN_10072ad0(6); + ((Act3*) m_world)->TriggerHitSound(6); } else { SoundManager()->GetCacheSoundManager()->Play("eatpz", NULL, FALSE); diff --git a/LEGO1/lego/legoomni/src/actors/act3ammo.cpp b/LEGO1/lego/legoomni/src/actors/act3ammo.cpp index 04e0e93c..d88a7bee 100644 --- a/LEGO1/lego/legoomni/src/actors/act3ammo.cpp +++ b/LEGO1/lego/legoomni/src/actors/act3ammo.cpp @@ -378,11 +378,11 @@ void Act3Ammo::Animate(float p_time) if (IsBit4()) { if (IsPizza()) { m_world->RemovePizza(*this); - m_world->FUN_10072ad0(2); + m_world->TriggerHitSound(2); } else { m_world->RemoveDonut(*this); - m_world->FUN_10072ad0(4); + m_world->TriggerHitSound(4); } } else { diff --git a/LEGO1/lego/legoomni/src/worlds/act3.cpp b/LEGO1/lego/legoomni/src/worlds/act3.cpp index 64ed8d6c..10bf69cf 100644 --- a/LEGO1/lego/legoomni/src/worlds/act3.cpp +++ b/LEGO1/lego/legoomni/src/worlds/act3.cpp @@ -31,7 +31,7 @@ DECOMP_SIZE_ASSERT(Act3ListElement, 0x0c) DECOMP_SIZE_ASSERT(Act3List, 0x10) // GLOBAL: LEGO1 0x100d94f8 -Act3Script::Script g_unk0x100d94f8[] = { +Act3Script::Script g_pizzaHitSounds[] = { Act3Script::c_sns02xni_PlayWav, Act3Script::c_sns03xni_PlayWav, Act3Script::c_sns04xni_PlayWav, @@ -51,7 +51,7 @@ Act3Script::Script g_unk0x100d94f8[] = { }; // GLOBAL: LEGO1 0x100d9538 -Act3Script::Script g_unk0x100d9538[] = { +Act3Script::Script g_pizzaMissSounds[] = { Act3Script::c_sns19xni_PlayWav, Act3Script::c_sns20xni_PlayWav, Act3Script::c_sns22xni_PlayWav, @@ -61,7 +61,7 @@ Act3Script::Script g_unk0x100d9538[] = { }; // GLOBAL: LEGO1 0x100d9550 -Act3Script::Script g_unk0x100d9550[] = { +Act3Script::Script g_copDonutSounds[] = { Act3Script::c_sns25xni_PlayWav, Act3Script::c_sns26xni_PlayWav, Act3Script::c_sns27xni_PlayWav, @@ -73,7 +73,7 @@ Act3Script::Script g_unk0x100d9550[] = { }; // GLOBAL: LEGO1 0x100d9570 -Act3Script::Script g_unk0x100d9570[] = { +Act3Script::Script g_donutMissSounds[] = { Act3Script::c_sns30xni_PlayWav, Act3Script::c_sns31xni_PlayWav, Act3Script::c_sns32xni_PlayWav, @@ -83,7 +83,7 @@ Act3Script::Script g_unk0x100d9570[] = { }; // GLOBAL: LEGO1 0x100d9588 -Act3Script::Script g_unk0x100d9588[] = { +Act3Script::Script g_islanderSounds[] = { Act3Script::c_sns43xma_PlayWav, Act3Script::c_sns46xin_PlayWav, Act3Script::c_sns60xna_PlayWav, Act3Script::c_sns52xro_PlayWav, Act3Script::c_sns58xna_PlayWav, Act3Script::c_sns68xbu_PlayWav, Act3Script::c_sns59xna_PlayWav, Act3Script::c_sns51xin_PlayWav, Act3Script::c_sns61xva_PlayWav, @@ -94,7 +94,7 @@ Act3Script::Script g_unk0x100d9588[] = { }; // GLOBAL: LEGO1 0x100d95d8 -Act3Script::Script g_unk0x100d95d8[] = { +Act3Script::Script g_bricksterDonutSounds[] = { Act3Script::c_tns080br_PlayWav, Act3Script::c_tnsx07br_PlayWav, Act3Script::c_snsxx2br_PlayWav, @@ -403,58 +403,58 @@ MxResult Act3::ShootDonut(LegoPathController* p_controller, Vector3& p_location, // FUNCTION: LEGO1 0x10072ad0 // FUNCTION: BETA10 0x10015eec -void Act3::FUN_10072ad0(undefined4 p_param1) +void Act3::TriggerHitSound(undefined4 p_param1) { float time = Timer()->GetTime(); Act3Script::Script objectId; switch (p_param1) { case 1: { - if (m_unk0x4218 >= sizeOfArray(g_unk0x100d94f8)) { - m_unk0x4218 = 0; + if (m_pizzaHitSound >= sizeOfArray(g_pizzaHitSounds)) { + m_pizzaHitSound = 0; } - objectId = g_unk0x100d94f8[m_unk0x4218++]; + objectId = g_pizzaHitSounds[m_pizzaHitSound++]; break; } case 2: { - if (m_unk0x4219 >= sizeOfArray(g_unk0x100d9538) - 1) { - m_unk0x4219 = 0; + if (m_pizzaMissSound >= sizeOfArray(g_pizzaMissSounds) - 1) { + m_pizzaMissSound = 0; } - objectId = g_unk0x100d9538[m_unk0x4219++]; + objectId = g_pizzaMissSounds[m_pizzaMissSound++]; break; } case 3: { - if (m_unk0x421a >= sizeOfArray(g_unk0x100d9550)) { - m_unk0x421a = 0; + if (m_copDonutSound >= sizeOfArray(g_copDonutSounds)) { + m_copDonutSound = 0; } - objectId = g_unk0x100d9550[m_unk0x421a++]; + objectId = g_copDonutSounds[m_copDonutSound++]; break; } case 4: { - if (m_unk0x421b >= sizeOfArray(g_unk0x100d9570)) { - m_unk0x421b = 0; + if (m_donutMissSound >= sizeOfArray(g_donutMissSounds)) { + m_donutMissSound = 0; } - objectId = g_unk0x100d9570[m_unk0x421b++]; + objectId = g_donutMissSounds[m_donutMissSound++]; break; } case 5: { - if (m_unk0x421c >= sizeOfArray(g_unk0x100d9588)) { - m_unk0x421c = 0; + if (m_islanderSound >= sizeOfArray(g_islanderSounds)) { + m_islanderSound = 0; } - objectId = g_unk0x100d9588[m_unk0x421c++]; + objectId = g_islanderSounds[m_islanderSound++]; break; } case 6: { - if (m_unk0x421d >= sizeOfArray(g_unk0x100d95d8)) { - m_unk0x421d = 0; + if (m_bricksterDonutSound >= sizeOfArray(g_bricksterDonutSounds)) { + m_bricksterDonutSound = 0; } - m_unk0x4220.Insert(g_unk0x100d95d8[m_unk0x421d++], 1); + m_unk0x4220.Insert(g_bricksterDonutSounds[m_bricksterDonutSound++], 1); return; } default: @@ -575,13 +575,12 @@ MxLong Act3::Notify(MxParam& p_param) m_cop2->VTable0xa8(); m_brickster->VTable0xa8(); - - m_unk0x4218 = 0; - m_unk0x4219 = 0; - m_unk0x421a = 0; - m_unk0x421b = 0; - m_unk0x421c = 0; - m_unk0x421d = 0; + m_pizzaHitSound = 0; + m_pizzaMissSound = 0; + m_copDonutSound = 0; + m_donutMissSound = 0; + m_islanderSound = 0; + m_bricksterDonutSound = 0; MxS32 length; LegoBuildingInfo* info = BuildingManager()->GetInfoArray(length); @@ -701,7 +700,7 @@ MxResult Act3::FUN_10073360(Act3Ammo& p_ammo, const Vector3& p_param2) { assert(m_brickster); m_brickster->FUN_100417a0(p_ammo, p_param2); - FUN_10072ad0(1); + TriggerHitSound(1); return SUCCESS; } @@ -718,7 +717,7 @@ MxResult Act3::FUN_10073390(Act3Ammo& p_ammo, const Vector3& p_param2) m_cop2->FUN_10040350(p_ammo, p_param2); } - FUN_10072ad0(3); + TriggerHitSound(3); g_unk0x100f7814++; return SUCCESS; } From 225adda309deaba0d289fc8fa9543737e51dc953 Mon Sep 17 00:00:00 2001 From: Anders Jenbo Date: Mon, 30 Jun 2025 22:51:26 +0200 Subject: [PATCH 022/188] Rendering refactoring (#459) * Hint texture intent to renderer * improve transparancy for 32bit rendering * Align OpenGL ES 2.0 with SDL_GPU's 2D rendering --- LEGO1/omni/src/video/mxdisplaysurface.cpp | 20 +- .../src/d3drm/backends/directx9/renderer.cpp | 20 +- .../src/d3drm/backends/opengl1/renderer.cpp | 20 +- .../src/d3drm/backends/opengles2/renderer.cpp | 353 +++++++++--------- .../src/d3drm/backends/sdl3gpu/renderer.cpp | 53 +-- .../src/d3drm/backends/software/renderer.cpp | 20 +- miniwin/src/ddraw/ddraw.cpp | 16 +- miniwin/src/ddraw/framebuffer.cpp | 2 +- miniwin/src/internal/d3drmrenderer.h | 4 +- miniwin/src/internal/d3drmrenderer_directx9.h | 23 +- miniwin/src/internal/d3drmrenderer_opengl1.h | 23 +- .../src/internal/d3drmrenderer_opengles2.h | 38 +- miniwin/src/internal/d3drmrenderer_sdl3gpu.h | 23 +- miniwin/src/internal/d3drmrenderer_software.h | 20 +- miniwin/src/internal/ddraw_impl.h | 9 +- miniwin/src/internal/meshutils.cpp | 28 ++ miniwin/src/internal/meshutils.h | 10 + 17 files changed, 330 insertions(+), 352 deletions(-) diff --git a/LEGO1/omni/src/video/mxdisplaysurface.cpp b/LEGO1/omni/src/video/mxdisplaysurface.cpp index 6c93a0e7..c2886ebe 100644 --- a/LEGO1/omni/src/video/mxdisplaysurface.cpp +++ b/LEGO1/omni/src/video/mxdisplaysurface.cpp @@ -926,7 +926,7 @@ LPDIRECTDRAWSURFACE MxDisplaySurface::VTable0x44( transparentColor = RGB555_CREATE(0x1f, 0, 0x1f); break; default: - transparentColor = RGB8888_CREATE(0xff, 0, 0xff, 0); + transparentColor = RGB8888_CREATE(0, 0, 0, 0); break; } @@ -971,25 +971,11 @@ LPDIRECTDRAWSURFACE MxDisplaySurface::VTable0x44( surfacePtr += adjustedPitch; } - if (p_transparent && surface) { - DDCOLORKEY key; - key.dwColorSpaceLowValue = key.dwColorSpaceHighValue = transparentColor; - surface->SetColorKey(DDCKEY_SRCBLT, &key); - } - surface->Unlock(ddsd.lpSurface); - if (p_transparent && surface) { + if (p_transparent && surface && bytesPerPixel != 4) { DDCOLORKEY key; - if (bytesPerPixel == 1) { - key.dwColorSpaceLowValue = key.dwColorSpaceHighValue = 0; - } - else if (bytesPerPixel == 2) { - key.dwColorSpaceLowValue = key.dwColorSpaceHighValue = RGB555_CREATE(0x1f, 0, 0x1f); - } - else { - key.dwColorSpaceLowValue = key.dwColorSpaceHighValue = RGB8888_CREATE(0xff, 0, 0xff, 0); - } + key.dwColorSpaceLowValue = key.dwColorSpaceHighValue = transparentColor; surface->SetColorKey(DDCKEY_SRCBLT, &key); } } diff --git a/miniwin/src/d3drm/backends/directx9/renderer.cpp b/miniwin/src/d3drm/backends/directx9/renderer.cpp index 574939f5..5917cd07 100644 --- a/miniwin/src/d3drm/backends/directx9/renderer.cpp +++ b/miniwin/src/d3drm/backends/directx9/renderer.cpp @@ -76,7 +76,7 @@ void DirectX9Renderer::AddTextureDestroyCallback(Uint32 id, IDirect3DRMTexture* ); } -Uint32 DirectX9Renderer::GetTextureId(IDirect3DRMTexture* iTexture) +Uint32 DirectX9Renderer::GetTextureId(IDirect3DRMTexture* iTexture, bool isUi) { auto texture = static_cast(iTexture); auto surface = static_cast(texture->m_surface); @@ -216,24 +216,6 @@ Uint32 DirectX9Renderer::GetMeshId(IDirect3DRMMesh* mesh, const MeshGroup* meshG return static_cast(m_meshs.size() - 1); } -void DirectX9Renderer::GetDesc(D3DDEVICEDESC* halDesc, D3DDEVICEDESC* helDesc) -{ - halDesc->dcmColorModel = D3DCOLORMODEL::RGB; - halDesc->dwFlags = D3DDD_DEVICEZBUFFERBITDEPTH; - halDesc->dwDeviceZBufferBitDepth = DDBD_24; - helDesc->dwDeviceRenderBitDepth = DDBD_32; - halDesc->dpcTriCaps.dwTextureCaps = D3DPTEXTURECAPS_PERSPECTIVE; - halDesc->dpcTriCaps.dwShadeCaps = D3DPSHADECAPS_ALPHAFLATBLEND; - halDesc->dpcTriCaps.dwTextureFilterCaps = D3DPTFILTERCAPS_LINEAR; - - memset(helDesc, 0, sizeof(D3DDEVICEDESC)); -} - -const char* DirectX9Renderer::GetName() -{ - return "DirectX 9 HAL"; -} - HRESULT DirectX9Renderer::BeginFrame() { return Actual_BeginFrame(); diff --git a/miniwin/src/d3drm/backends/opengl1/renderer.cpp b/miniwin/src/d3drm/backends/opengl1/renderer.cpp index a0b84a80..66f08d23 100644 --- a/miniwin/src/d3drm/backends/opengl1/renderer.cpp +++ b/miniwin/src/d3drm/backends/opengl1/renderer.cpp @@ -122,7 +122,7 @@ void OpenGL1Renderer::AddTextureDestroyCallback(Uint32 id, IDirect3DRMTexture* t ); } -Uint32 OpenGL1Renderer::GetTextureId(IDirect3DRMTexture* iTexture) +Uint32 OpenGL1Renderer::GetTextureId(IDirect3DRMTexture* iTexture, bool isUi) { auto texture = static_cast(iTexture); auto surface = static_cast(texture->m_surface); @@ -265,24 +265,6 @@ Uint32 OpenGL1Renderer::GetMeshId(IDirect3DRMMesh* mesh, const MeshGroup* meshGr return (Uint32) (m_meshs.size() - 1); } -void OpenGL1Renderer::GetDesc(D3DDEVICEDESC* halDesc, D3DDEVICEDESC* helDesc) -{ - halDesc->dcmColorModel = D3DCOLORMODEL::RGB; - halDesc->dwFlags = D3DDD_DEVICEZBUFFERBITDEPTH; - halDesc->dwDeviceZBufferBitDepth = DDBD_24; - helDesc->dwDeviceRenderBitDepth = DDBD_32; - halDesc->dpcTriCaps.dwTextureCaps = D3DPTEXTURECAPS_PERSPECTIVE; - halDesc->dpcTriCaps.dwShadeCaps = D3DPSHADECAPS_ALPHAFLATBLEND; - halDesc->dpcTriCaps.dwTextureFilterCaps = D3DPTFILTERCAPS_LINEAR; - - memset(helDesc, 0, sizeof(D3DDEVICEDESC)); -} - -const char* OpenGL1Renderer::GetName() -{ - return "OpenGL 1.1 HAL"; -} - HRESULT OpenGL1Renderer::BeginFrame() { GL11_BeginFrame((Matrix4x4*) &m_projection[0][0]); diff --git a/miniwin/src/d3drm/backends/opengles2/renderer.cpp b/miniwin/src/d3drm/backends/opengles2/renderer.cpp index 9305510a..8c39d937 100644 --- a/miniwin/src/d3drm/backends/opengles2/renderer.cpp +++ b/miniwin/src/d3drm/backends/opengles2/renderer.cpp @@ -181,14 +181,88 @@ Direct3DRMRenderer* OpenGLES2Renderer::Create(DWORD width, DWORD height) return new OpenGLES2Renderer(width, height, context, shaderProgram); } +GLES2MeshCacheEntry GLES2UploadMesh(const MeshGroup& meshGroup, bool forceUV = false) +{ + GLES2MeshCacheEntry cache{&meshGroup, meshGroup.version}; + + cache.flat = meshGroup.quality == D3DRMRENDER_FLAT || meshGroup.quality == D3DRMRENDER_UNLITFLAT; + + std::vector vertices; + if (cache.flat) { + FlattenSurfaces( + meshGroup.vertices.data(), + meshGroup.vertices.size(), + meshGroup.indices.data(), + meshGroup.indices.size(), + meshGroup.texture != nullptr || forceUV, + vertices, + cache.indices + ); + } + else { + vertices = meshGroup.vertices; + cache.indices.resize(meshGroup.indices.size()); + std::transform(meshGroup.indices.begin(), meshGroup.indices.end(), cache.indices.begin(), [](DWORD index) { + return static_cast(index); + }); + } + + std::vector texcoords; + if (meshGroup.texture || forceUV) { + texcoords.resize(vertices.size()); + std::transform(vertices.begin(), vertices.end(), texcoords.begin(), [](const D3DRMVERTEX& v) { + return v.texCoord; + }); + } + std::vector positions(vertices.size()); + std::transform(vertices.begin(), vertices.end(), positions.begin(), [](const D3DRMVERTEX& v) { + return v.position; + }); + std::vector normals(vertices.size()); + std::transform(vertices.begin(), vertices.end(), normals.begin(), [](const D3DRMVERTEX& v) { return v.normal; }); + + glGenBuffers(1, &cache.vboPositions); + glBindBuffer(GL_ARRAY_BUFFER, cache.vboPositions); + glBufferData(GL_ARRAY_BUFFER, positions.size() * sizeof(D3DVECTOR), positions.data(), GL_STATIC_DRAW); + + glGenBuffers(1, &cache.vboNormals); + glBindBuffer(GL_ARRAY_BUFFER, cache.vboNormals); + glBufferData(GL_ARRAY_BUFFER, normals.size() * sizeof(D3DVECTOR), normals.data(), GL_STATIC_DRAW); + + if (meshGroup.texture || forceUV) { + glGenBuffers(1, &cache.vboTexcoords); + glBindBuffer(GL_ARRAY_BUFFER, cache.vboTexcoords); + glBufferData(GL_ARRAY_BUFFER, texcoords.size() * sizeof(TexCoord), texcoords.data(), GL_STATIC_DRAW); + } + + glGenBuffers(1, &cache.ibo); + glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, cache.ibo); + glBufferData( + GL_ELEMENT_ARRAY_BUFFER, + cache.indices.size() * sizeof(cache.indices[0]), + cache.indices.data(), + GL_STATIC_DRAW + ); + + return cache; +} + OpenGLES2Renderer::OpenGLES2Renderer(DWORD width, DWORD height, SDL_GLContext context, GLuint shaderProgram) : m_context(context), m_shaderProgram(shaderProgram) { - m_width = width; - m_height = height; m_virtualWidth = width; m_virtualHeight = height; - m_renderedImage = SDL_CreateSurface(m_width, m_height, SDL_PIXELFORMAT_RGBA32); + ViewportTransform viewportTransform = {1.0f, 0.0f, 0.0f}; + Resize(width, height, viewportTransform); + + m_uiMesh.vertices = { + {{0.0f, 0.0f, 0.0f}, {0, 0, -1}, {0.0f, 0.0f}}, + {{1.0f, 0.0f, 0.0f}, {0, 0, -1}, {1.0f, 0.0f}}, + {{1.0f, 1.0f, 0.0f}, {0, 0, -1}, {1.0f, 1.0f}}, + {{0.0f, 1.0f, 0.0f}, {0, 0, -1}, {0.0f, 1.0f}} + }; + m_uiMesh.indices = {0, 1, 2, 0, 2, 3}; + m_uiMeshCache = GLES2UploadMesh(m_uiMesh, true); } OpenGLES2Renderer::~OpenGLES2Renderer() @@ -239,7 +313,48 @@ void OpenGLES2Renderer::AddTextureDestroyCallback(Uint32 id, IDirect3DRMTexture* ); } -Uint32 OpenGLES2Renderer::GetTextureId(IDirect3DRMTexture* iTexture) +bool UploadTexture(SDL_Surface* source, GLuint& outTexId, bool isUi) +{ + SDL_Surface* surf = source; + if (source->format != SDL_PIXELFORMAT_RGBA32) { + surf = SDL_ConvertSurface(source, SDL_PIXELFORMAT_RGBA32); + if (!surf) { + return false; + } + } + + glGenTextures(1, &outTexId); + glBindTexture(GL_TEXTURE_2D, outTexId); + glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, surf->w, surf->h, 0, GL_RGBA, GL_UNSIGNED_BYTE, surf->pixels); + + if (isUi) { + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); + } + else { + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); + if (strstr((const char*) glGetString(GL_EXTENSIONS), "GL_EXT_texture_filter_anisotropic")) { + GLfloat maxAniso = 0.0f; + glGetFloatv(GL_MAX_TEXTURE_MAX_ANISOTROPY_EXT, &maxAniso); + GLfloat desiredAniso = fminf(8.0f, maxAniso); + glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAX_ANISOTROPY_EXT, desiredAniso); + } + glGenerateMipmap(GL_TEXTURE_2D); + } + + if (surf != source) { + SDL_DestroySurface(surf); + } + + return true; +} + +Uint32 OpenGLES2Renderer::GetTextureId(IDirect3DRMTexture* iTexture, bool isUi) { auto texture = static_cast(iTexture); auto surface = static_cast(texture->m_surface); @@ -249,31 +364,18 @@ Uint32 OpenGLES2Renderer::GetTextureId(IDirect3DRMTexture* iTexture) if (tex.texture == texture) { if (tex.version != texture->m_version) { glDeleteTextures(1, &tex.glTextureId); - glGenTextures(1, &tex.glTextureId); - glBindTexture(GL_TEXTURE_2D, tex.glTextureId); - - SDL_Surface* surf = SDL_ConvertSurface(surface->m_surface, SDL_PIXELFORMAT_RGBA32); - if (!surf) { - return NO_TEXTURE_ID; + if (UploadTexture(surface->m_surface, tex.glTextureId, isUi)) { + tex.version = texture->m_version; } - glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, surf->w, surf->h, 0, GL_RGBA, GL_UNSIGNED_BYTE, surf->pixels); - SDL_DestroySurface(surf); - - tex.version = texture->m_version; } return i; } } GLuint texId; - glGenTextures(1, &texId); - glBindTexture(GL_TEXTURE_2D, texId); - - SDL_Surface* surf = SDL_ConvertSurface(surface->m_surface, SDL_PIXELFORMAT_RGBA32); - if (!surf) { + if (!UploadTexture(surface->m_surface, texId, isUi)) { return NO_TEXTURE_ID; } - glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, surf->w, surf->h, 0, GL_RGBA, GL_UNSIGNED_BYTE, surf->pixels); for (Uint32 i = 0; i < m_textures.size(); ++i) { auto& tex = m_textures[i]; @@ -281,85 +383,20 @@ Uint32 OpenGLES2Renderer::GetTextureId(IDirect3DRMTexture* iTexture) tex.texture = texture; tex.version = texture->m_version; tex.glTextureId = texId; - tex.width = surf->w; - tex.height = surf->h; + tex.width = surface->m_surface->w; + tex.height = surface->m_surface->h; AddTextureDestroyCallback(i, texture); return i; } } - m_textures.push_back({texture, texture->m_version, texId, (uint16_t) surf->w, (uint16_t) surf->h}); - SDL_DestroySurface(surf); + m_textures.push_back( + {texture, texture->m_version, texId, (uint16_t) surface->m_surface->w, (uint16_t) surface->m_surface->h} + ); AddTextureDestroyCallback((Uint32) (m_textures.size() - 1), texture); return (Uint32) (m_textures.size() - 1); } -GLES2MeshCacheEntry GLES2UploadMesh(const MeshGroup& meshGroup) -{ - GLES2MeshCacheEntry cache{&meshGroup, meshGroup.version}; - - cache.flat = meshGroup.quality == D3DRMRENDER_FLAT || meshGroup.quality == D3DRMRENDER_UNLITFLAT; - - std::vector vertices; - if (cache.flat) { - FlattenSurfaces( - meshGroup.vertices.data(), - meshGroup.vertices.size(), - meshGroup.indices.data(), - meshGroup.indices.size(), - meshGroup.texture != nullptr, - vertices, - cache.indices - ); - } - else { - vertices = meshGroup.vertices; - cache.indices.resize(meshGroup.indices.size()); - std::transform(meshGroup.indices.begin(), meshGroup.indices.end(), cache.indices.begin(), [](DWORD index) { - return static_cast(index); - }); - } - - std::vector texcoords; - if (meshGroup.texture) { - texcoords.resize(vertices.size()); - std::transform(vertices.begin(), vertices.end(), texcoords.begin(), [](const D3DRMVERTEX& v) { - return v.texCoord; - }); - } - std::vector positions(vertices.size()); - std::transform(vertices.begin(), vertices.end(), positions.begin(), [](const D3DRMVERTEX& v) { - return v.position; - }); - std::vector normals(vertices.size()); - std::transform(vertices.begin(), vertices.end(), normals.begin(), [](const D3DRMVERTEX& v) { return v.normal; }); - - glGenBuffers(1, &cache.vboPositions); - glBindBuffer(GL_ARRAY_BUFFER, cache.vboPositions); - glBufferData(GL_ARRAY_BUFFER, positions.size() * sizeof(D3DVECTOR), positions.data(), GL_STATIC_DRAW); - - glGenBuffers(1, &cache.vboNormals); - glBindBuffer(GL_ARRAY_BUFFER, cache.vboNormals); - glBufferData(GL_ARRAY_BUFFER, normals.size() * sizeof(D3DVECTOR), normals.data(), GL_STATIC_DRAW); - - if (meshGroup.texture) { - glGenBuffers(1, &cache.vboTexcoords); - glBindBuffer(GL_ARRAY_BUFFER, cache.vboTexcoords); - glBufferData(GL_ARRAY_BUFFER, texcoords.size() * sizeof(TexCoord), texcoords.data(), GL_STATIC_DRAW); - } - - glGenBuffers(1, &cache.ibo); - glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, cache.ibo); - glBufferData( - GL_ELEMENT_ARRAY_BUFFER, - cache.indices.size() * sizeof(cache.indices[0]), - cache.indices.data(), - GL_STATIC_DRAW - ); - - return cache; -} - struct GLES2MeshDestroyContext { OpenGLES2Renderer* renderer; Uint32 id; @@ -411,37 +448,11 @@ Uint32 OpenGLES2Renderer::GetMeshId(IDirect3DRMMesh* mesh, const MeshGroup* mesh return (Uint32) (m_meshs.size() - 1); } -void OpenGLES2Renderer::GetDesc(D3DDEVICEDESC* halDesc, D3DDEVICEDESC* helDesc) -{ - halDesc->dcmColorModel = D3DCOLORMODEL::RGB; - halDesc->dwFlags = D3DDD_DEVICEZBUFFERBITDEPTH; - halDesc->dwDeviceZBufferBitDepth = DDBD_16; - const char* extensions = (const char*) glGetString(GL_EXTENSIONS); - if (extensions) { - if (strstr(extensions, "GL_OES_depth24")) { - halDesc->dwDeviceZBufferBitDepth |= DDBD_24; - } - if (strstr(extensions, "GL_OES_depth32")) { - halDesc->dwDeviceZBufferBitDepth |= DDBD_32; - } - } - helDesc->dwDeviceRenderBitDepth = DDBD_32; - halDesc->dpcTriCaps.dwTextureCaps = D3DPTEXTURECAPS_PERSPECTIVE; - halDesc->dpcTriCaps.dwShadeCaps = D3DPSHADECAPS_ALPHAFLATBLEND; - halDesc->dpcTriCaps.dwTextureFilterCaps = D3DPTFILTERCAPS_LINEAR; - - memset(helDesc, 0, sizeof(D3DDEVICEDESC)); -} - -const char* OpenGLES2Renderer::GetName() -{ - return "OpenGL ES 2.0 HAL"; -} - HRESULT OpenGLES2Renderer::BeginFrame() { m_dirty = true; + glEnable(GL_CULL_FACE); glDisable(GL_BLEND); glEnable(GL_DEPTH_TEST); glDepthMask(GL_TRUE); @@ -516,8 +527,6 @@ void OpenGLES2Renderer::SubmitDraw( glActiveTexture(GL_TEXTURE0); glBindTexture(GL_TEXTURE_2D, m_textures[appearance.textureId].glTextureId); glUniform1i(glGetUniformLocation(m_shaderProgram, "u_texture"), 0); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); } else { glUniform1i(glGetUniformLocation(m_shaderProgram, "u_useTexture"), 0); @@ -543,7 +552,6 @@ void OpenGLES2Renderer::SubmitDraw( glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, mesh.ibo); glDrawElements(GL_TRIANGLES, static_cast(mesh.indices.size()), GL_UNSIGNED_SHORT, nullptr); - glDisableVertexAttribArray(posLoc); glDisableVertexAttribArray(normLoc); glDisableVertexAttribArray(texLoc); } @@ -562,7 +570,9 @@ void OpenGLES2Renderer::Resize(int width, int height, const ViewportTransform& v m_width = width; m_height = height; m_viewportTransform = viewportTransform; - SDL_DestroySurface(m_renderedImage); + if (m_renderedImage) { + SDL_DestroySurface(m_renderedImage); + } m_renderedImage = SDL_CreateSurface(m_width, m_height, SDL_PIXELFORMAT_RGBA32); glViewport(0, 0, m_width, m_height); } @@ -584,35 +594,6 @@ void OpenGLES2Renderer::Flip() } } -void CreateOrthoMatrix(float left, float right, float bottom, float top, D3DRMMATRIX4D& outMatrix) -{ - float near = -1.0f; - float far = 1.0f; - float rl = right - left; - float tb = top - bottom; - float fn = far - near; - - outMatrix[0][0] = 2.0f / rl; - outMatrix[0][1] = 0.0f; - outMatrix[0][2] = 0.0f; - outMatrix[0][3] = 0.0f; - - outMatrix[1][0] = 0.0f; - outMatrix[1][1] = 2.0f / tb; - outMatrix[1][2] = 0.0f; - outMatrix[1][3] = 0.0f; - - outMatrix[2][0] = 0.0f; - outMatrix[2][1] = 0.0f; - outMatrix[2][2] = -2.0f / fn; - outMatrix[2][3] = 0.0f; - - outMatrix[3][0] = -(right + left) / rl; - outMatrix[3][1] = -(top + bottom) / tb; - outMatrix[3][2] = -(far + near) / fn; - outMatrix[3][3] = 1.0f; -} - void OpenGLES2Renderer::Draw2DImage(Uint32 textureId, const SDL_Rect& srcRect, const SDL_Rect& dstRect) { m_dirty = true; @@ -632,17 +613,29 @@ void OpenGLES2Renderer::Draw2DImage(Uint32 textureId, const SDL_Rect& srcRect, c glUniform4f(glGetUniformLocation(m_shaderProgram, "u_color"), 1.0f, 1.0f, 1.0f, 1.0f); glUniform1f(glGetUniformLocation(m_shaderProgram, "u_shininess"), 0.0f); - float left = -m_viewportTransform.offsetX / m_viewportTransform.scale; - float right = (m_width - m_viewportTransform.offsetX) / m_viewportTransform.scale; - float top = -m_viewportTransform.offsetY / m_viewportTransform.scale; - float bottom = (m_height - m_viewportTransform.offsetY) / m_viewportTransform.scale; + const GLES2TextureCacheEntry& texture = m_textures[textureId]; + float scaleX = static_cast(dstRect.w) / srcRect.w; + float scaleY = static_cast(dstRect.h) / srcRect.h; + SDL_Rect expandedDstRect = { + static_cast(std::round(dstRect.x - srcRect.x * scaleX)), + static_cast(std::round(dstRect.y - srcRect.y * scaleY)), + static_cast(std::round(texture.width * scaleX)), + static_cast(std::round(texture.height * scaleY)) + }; - D3DRMMATRIX4D projection; - CreateOrthoMatrix(left, right, bottom, top, projection); + D3DRMMATRIX4D modelView, projection; + Create2DTransformMatrix( + expandedDstRect, + m_viewportTransform.scale, + m_viewportTransform.offsetX, + m_viewportTransform.offsetY, + modelView + ); - D3DRMMATRIX4D identity = {{1.f, 0.f, 0.f, 0.f}, {0.f, 1.f, 0.f, 0.f}, {0.f, 0.f, 1.f, 0.f}, {0.f, 0.f, 0.f, 1.f}}; - glUniformMatrix4fv(glGetUniformLocation(m_shaderProgram, "u_modelViewMatrix"), 1, GL_FALSE, &identity[0][0]); + glUniformMatrix4fv(glGetUniformLocation(m_shaderProgram, "u_modelViewMatrix"), 1, GL_FALSE, &modelView[0][0]); + Matrix3x3 identity = {{1.f, 0.f, 0.f}, {0.f, 1.f, 0.f}, {0.f, 0.f, 1.f}}; glUniformMatrix3fv(glGetUniformLocation(m_shaderProgram, "u_normalMatrix"), 1, GL_FALSE, &identity[0][0]); + CreateOrthographicProjection((float) m_width, (float) m_height, projection); glUniformMatrix4fv(glGetUniformLocation(m_shaderProgram, "u_projectionMatrix"), 1, GL_FALSE, &projection[0][0]); glEnable(GL_BLEND); @@ -650,42 +643,34 @@ void OpenGLES2Renderer::Draw2DImage(Uint32 textureId, const SDL_Rect& srcRect, c glActiveTexture(GL_TEXTURE0); glUniform1i(glGetUniformLocation(m_shaderProgram, "u_useTexture"), 1); - const GLES2TextureCacheEntry& texture = m_textures[textureId]; glBindTexture(GL_TEXTURE_2D, texture.glTextureId); glUniform1i(glGetUniformLocation(m_shaderProgram, "u_texture"), 0); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); - float texW = texture.width; - float texH = texture.height; - float u1 = srcRect.x / texW; - float v1 = srcRect.y / texH; - float u2 = (srcRect.x + srcRect.w) / texW; - float v2 = (srcRect.y + srcRect.h) / texH; - - float x1 = static_cast(dstRect.x); - float y1 = static_cast(dstRect.y); - float x2 = x1 + dstRect.w; - float y2 = y1 + dstRect.h; - - GLfloat vertices[] = {x1, y1, u1, v1, x2, y1, u2, v1, x1, y2, u1, v2, x2, y2, u2, v2}; + glEnable(GL_SCISSOR_TEST); + glScissor( + static_cast(std::round(dstRect.x * m_viewportTransform.scale + m_viewportTransform.offsetX)), + m_height - static_cast( + std::round((dstRect.y + dstRect.h) * m_viewportTransform.scale + m_viewportTransform.offsetY) + ), + static_cast(std::round(dstRect.w * m_viewportTransform.scale)), + static_cast(std::round(dstRect.h * m_viewportTransform.scale)) + ); GLint posLoc = glGetAttribLocation(m_shaderProgram, "a_position"); - GLint texLoc = glGetAttribLocation(m_shaderProgram, "a_texCoord"); - + glBindBuffer(GL_ARRAY_BUFFER, m_uiMeshCache.vboPositions); glEnableVertexAttribArray(posLoc); + glVertexAttribPointer(posLoc, 3, GL_FLOAT, GL_FALSE, 0, nullptr); + + GLint texLoc = glGetAttribLocation(m_shaderProgram, "a_texCoord"); + glBindBuffer(GL_ARRAY_BUFFER, m_uiMeshCache.vboTexcoords); glEnableVertexAttribArray(texLoc); + glVertexAttribPointer(texLoc, 2, GL_FLOAT, GL_FALSE, 0, nullptr); - glVertexAttribPointer(posLoc, 2, GL_FLOAT, GL_FALSE, 4 * sizeof(GLfloat), vertices); - glVertexAttribPointer(texLoc, 2, GL_FLOAT, GL_FALSE, 4 * sizeof(GLfloat), vertices + 2); + glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, m_uiMeshCache.ibo); + glDrawElements(GL_TRIANGLES, static_cast(m_uiMeshCache.indices.size()), GL_UNSIGNED_SHORT, nullptr); - glDrawArrays(GL_TRIANGLE_STRIP, 0, 4); - - glDisableVertexAttribArray(posLoc); glDisableVertexAttribArray(texLoc); - - glBindTexture(GL_TEXTURE_2D, 0); - glUseProgram(0); + glDisable(GL_SCISSOR_TEST); } void OpenGLES2Renderer::Download(SDL_Surface* target) diff --git a/miniwin/src/d3drm/backends/sdl3gpu/renderer.cpp b/miniwin/src/d3drm/backends/sdl3gpu/renderer.cpp index 4ee5a35c..a499193c 100644 --- a/miniwin/src/d3drm/backends/sdl3gpu/renderer.cpp +++ b/miniwin/src/d3drm/backends/sdl3gpu/renderer.cpp @@ -533,7 +533,7 @@ SDL_GPUTexture* Direct3DRMSDL3GPURenderer::CreateTextureFromSurface(SDL_Surface* return texptr; } -Uint32 Direct3DRMSDL3GPURenderer::GetTextureId(IDirect3DRMTexture* iTexture) +Uint32 Direct3DRMSDL3GPURenderer::GetTextureId(IDirect3DRMTexture* iTexture, bool isUi) { auto texture = static_cast(iTexture); auto surface = static_cast(texture->m_surface); @@ -585,7 +585,7 @@ SDL3MeshCache Direct3DRMSDL3GPURenderer::UploadMesh(const MeshGroup& meshGroup) meshGroup.vertices.size(), meshGroup.indices.data(), meshGroup.indices.size(), - meshGroup.texture != nullptr, + true, finalVertices, newIndices ); @@ -712,24 +712,6 @@ Uint32 Direct3DRMSDL3GPURenderer::GetMeshId(IDirect3DRMMesh* mesh, const MeshGro return (Uint32) (m_meshs.size() - 1); } -void Direct3DRMSDL3GPURenderer::GetDesc(D3DDEVICEDESC* halDesc, D3DDEVICEDESC* helDesc) -{ - halDesc->dcmColorModel = D3DCOLORMODEL::RGB; - halDesc->dwFlags = D3DDD_DEVICEZBUFFERBITDEPTH; - halDesc->dwDeviceZBufferBitDepth = DDBD_32; // Todo add support for other depths - halDesc->dwDeviceRenderBitDepth = DDBD_32; - halDesc->dpcTriCaps.dwTextureCaps = D3DPTEXTURECAPS_PERSPECTIVE; - halDesc->dpcTriCaps.dwShadeCaps = D3DPSHADECAPS_ALPHAFLATBLEND; - halDesc->dpcTriCaps.dwTextureFilterCaps = D3DPTFILTERCAPS_LINEAR; - - memset(helDesc, 0, sizeof(D3DDEVICEDESC)); -} - -const char* Direct3DRMSDL3GPURenderer::GetName() -{ - return "SDL3 GPU HAL"; -} - void PackNormalMatrix(const Matrix3x3& normalMatrix3x3, D3DRMMATRIX4D& packedNormalMatrix4x4) { for (int row = 0; row < 3; ++row) { @@ -940,37 +922,6 @@ void Direct3DRMSDL3GPURenderer::Flip() m_cmdbuf = nullptr; } -// TODO use SDL_SetGPUScissor(SDL_GPURenderPass *render_pass, const SDL_Rect *scissor) when srcRect isn't 100% of -// texture - -void Create2DTransformMatrix( - const SDL_Rect& dstRect, - float scale, - float offsetX, - float offsetY, - D3DRMMATRIX4D& outMatrix -) -{ - float x = static_cast(dstRect.x) * scale + offsetX; - float y = static_cast(dstRect.y) * scale + offsetY; - float w = static_cast(dstRect.w) * scale; - float h = static_cast(dstRect.h) * scale; - - D3DVALUE tmp[4][4] = {{w, 0, 0, 0}, {0, h, 0, 0}, {0, 0, 1, 0}, {x, y, 0, 1}}; - memcpy(outMatrix, tmp, sizeof(tmp)); -} - -void CreateOrthographicProjection(float width, float height, D3DRMMATRIX4D& outProj) -{ - D3DVALUE tmp[4][4] = { - {2.0f / width, 0.0f, 0.0f, 0.0f}, - {0.0f, -2.0f / height, 0.0f, 0.0f}, - {0.0f, 0.0f, 1.0f, 0.0f}, - {-1.0f, 1.0f, 0.0f, 1.0f} - }; - memcpy(outProj, tmp, sizeof(tmp)); -} - void Direct3DRMSDL3GPURenderer::Draw2DImage(Uint32 textureId, const SDL_Rect& srcRect, const SDL_Rect& dstRect) { if (!m_renderPass) { diff --git a/miniwin/src/d3drm/backends/software/renderer.cpp b/miniwin/src/d3drm/backends/software/renderer.cpp index 64adc93b..6b969769 100644 --- a/miniwin/src/d3drm/backends/software/renderer.cpp +++ b/miniwin/src/d3drm/backends/software/renderer.cpp @@ -554,7 +554,7 @@ void Direct3DRMSoftwareRenderer::AddTextureDestroyCallback(Uint32 id, IDirect3DR ); } -Uint32 Direct3DRMSoftwareRenderer::GetTextureId(IDirect3DRMTexture* iTexture) +Uint32 Direct3DRMSoftwareRenderer::GetTextureId(IDirect3DRMTexture* iTexture, bool isUi) { auto texture = static_cast(iTexture); auto surface = static_cast(texture->m_surface); @@ -664,24 +664,6 @@ Uint32 Direct3DRMSoftwareRenderer::GetMeshId(IDirect3DRMMesh* mesh, const MeshGr return (Uint32) (m_meshs.size() - 1); } -void Direct3DRMSoftwareRenderer::GetDesc(D3DDEVICEDESC* halDesc, D3DDEVICEDESC* helDesc) -{ - memset(halDesc, 0, sizeof(D3DDEVICEDESC)); - - helDesc->dcmColorModel = D3DCOLORMODEL::RGB; - helDesc->dwFlags = D3DDD_DEVICEZBUFFERBITDEPTH; - helDesc->dwDeviceZBufferBitDepth = DDBD_32; - helDesc->dwDeviceRenderBitDepth = DDBD_32; - helDesc->dpcTriCaps.dwTextureCaps = D3DPTEXTURECAPS_PERSPECTIVE; - helDesc->dpcTriCaps.dwShadeCaps = D3DPSHADECAPS_ALPHAFLATBLEND; - helDesc->dpcTriCaps.dwTextureFilterCaps = D3DPTFILTERCAPS_LINEAR; -} - -const char* Direct3DRMSoftwareRenderer::GetName() -{ - return "Miniwin Emulation"; -} - HRESULT Direct3DRMSoftwareRenderer::BeginFrame() { if (!m_renderedImage || !SDL_LockSurface(m_renderedImage)) { diff --git a/miniwin/src/ddraw/ddraw.cpp b/miniwin/src/ddraw/ddraw.cpp index acf695eb..018fffd1 100644 --- a/miniwin/src/ddraw/ddraw.cpp +++ b/miniwin/src/ddraw/ddraw.cpp @@ -207,14 +207,18 @@ HRESULT DirectDrawImpl::GetCaps(LPDDCAPS lpDDDriverCaps, LPDDCAPS lpDDHELCaps) return S_OK; } -void EnumDevice(LPD3DENUMDEVICESCALLBACK cb, void* ctx, Direct3DRMRenderer* device, GUID deviceGuid) +void EnumDevice( + LPD3DENUMDEVICESCALLBACK cb, + void* ctx, + const char* name, + D3DDEVICEDESC* halDesc, + D3DDEVICEDESC* helDesc, + GUID deviceGuid +) { - D3DDEVICEDESC halDesc = {}; - D3DDEVICEDESC helDesc = {}; - device->GetDesc(&halDesc, &helDesc); - char* deviceNameDup = SDL_strdup(device->GetName()); + char* deviceNameDup = SDL_strdup(name); char* deviceDescDup = SDL_strdup("Miniwin driver"); - cb(&deviceGuid, deviceNameDup, deviceDescDup, &halDesc, &helDesc, ctx); + cb(&deviceGuid, deviceNameDup, deviceDescDup, halDesc, helDesc, ctx); SDL_free(deviceDescDup); SDL_free(deviceNameDup); } diff --git a/miniwin/src/ddraw/framebuffer.cpp b/miniwin/src/ddraw/framebuffer.cpp index eb21abef..9f4224fd 100644 --- a/miniwin/src/ddraw/framebuffer.cpp +++ b/miniwin/src/ddraw/framebuffer.cpp @@ -62,7 +62,7 @@ HRESULT FrameBufferImpl::Blt( if (!surface) { return DDERR_GENERIC; } - Uint32 textureId = DDRenderer->GetTextureId(surface->ToTexture()); + Uint32 textureId = DDRenderer->GetTextureId(surface->ToTexture(), true); SDL_Rect srcRect = lpSrcRect ? ConvertRect(lpSrcRect) : SDL_Rect{0, 0, surface->m_surface->w, surface->m_surface->h}; SDL_Rect dstRect = diff --git a/miniwin/src/internal/d3drmrenderer.h b/miniwin/src/internal/d3drmrenderer.h index e76f0759..259b20eb 100644 --- a/miniwin/src/internal/d3drmrenderer.h +++ b/miniwin/src/internal/d3drmrenderer.h @@ -31,14 +31,12 @@ class Direct3DRMRenderer : public IDirect3DDevice2 { virtual void PushLights(const SceneLight* vertices, size_t count) = 0; virtual void SetProjection(const D3DRMMATRIX4D& projection, D3DVALUE front, D3DVALUE back) = 0; virtual void SetFrustumPlanes(const Plane* frustumPlanes) = 0; - virtual Uint32 GetTextureId(IDirect3DRMTexture* texture) = 0; + virtual Uint32 GetTextureId(IDirect3DRMTexture* texture, bool isUi = false) = 0; virtual Uint32 GetMeshId(IDirect3DRMMesh* mesh, const MeshGroup* meshGroup) = 0; int GetWidth() { return m_width; } int GetHeight() { return m_height; } int GetVirtualWidth() { return m_virtualWidth; } int GetVirtualHeight() { return m_virtualHeight; } - virtual void GetDesc(D3DDEVICEDESC* halDesc, D3DDEVICEDESC* helDesc) = 0; - virtual const char* GetName() = 0; virtual HRESULT BeginFrame() = 0; virtual void EnableTransparency() = 0; virtual void SubmitDraw( diff --git a/miniwin/src/internal/d3drmrenderer_directx9.h b/miniwin/src/internal/d3drmrenderer_directx9.h index b7badb03..900bbe03 100644 --- a/miniwin/src/internal/d3drmrenderer_directx9.h +++ b/miniwin/src/internal/d3drmrenderer_directx9.h @@ -18,10 +18,8 @@ class DirectX9Renderer : public Direct3DRMRenderer { void PushLights(const SceneLight* lightsArray, size_t count) override; void SetProjection(const D3DRMMATRIX4D& projection, D3DVALUE front, D3DVALUE back) override; void SetFrustumPlanes(const Plane* frustumPlanes) override; - Uint32 GetTextureId(IDirect3DRMTexture* texture) override; + Uint32 GetTextureId(IDirect3DRMTexture* texture, bool isUi) override; Uint32 GetMeshId(IDirect3DRMMesh* mesh, const MeshGroup* meshGroup) override; - void GetDesc(D3DDEVICEDESC* halDesc, D3DDEVICEDESC* helDesc) override; - const char* GetName() override; HRESULT BeginFrame() override; void EnableTransparency() override; void SubmitDraw( @@ -52,8 +50,21 @@ class DirectX9Renderer : public Direct3DRMRenderer { inline static void DirectX9Renderer_EnumDevice(LPD3DENUMDEVICESCALLBACK cb, void* ctx) { Direct3DRMRenderer* device = DirectX9Renderer::Create(640, 480); - if (device) { - EnumDevice(cb, ctx, device, DirectX9_GUID); - delete device; + if (!device) { + return; } + delete device; + + D3DDEVICEDESC halDesc = {}; + halDesc.dcmColorModel = D3DCOLOR_RGB; + halDesc.dwFlags = D3DDD_DEVICEZBUFFERBITDEPTH; + halDesc.dwDeviceZBufferBitDepth = DDBD_24; + halDesc.dwDeviceRenderBitDepth = DDBD_32; + halDesc.dpcTriCaps.dwTextureCaps = D3DPTEXTURECAPS_PERSPECTIVE; + halDesc.dpcTriCaps.dwShadeCaps = D3DPSHADECAPS_ALPHAFLATBLEND; + halDesc.dpcTriCaps.dwTextureFilterCaps = D3DPTFILTERCAPS_LINEAR; + + D3DDEVICEDESC helDesc = {}; + + EnumDevice(cb, ctx, "DirectX 9 HAL", &halDesc, &helDesc, DirectX9_GUID); } diff --git a/miniwin/src/internal/d3drmrenderer_opengl1.h b/miniwin/src/internal/d3drmrenderer_opengl1.h index d80621a5..58406709 100644 --- a/miniwin/src/internal/d3drmrenderer_opengl1.h +++ b/miniwin/src/internal/d3drmrenderer_opengl1.h @@ -18,10 +18,8 @@ class OpenGL1Renderer : public Direct3DRMRenderer { void PushLights(const SceneLight* lightsArray, size_t count) override; void SetProjection(const D3DRMMATRIX4D& projection, D3DVALUE front, D3DVALUE back) override; void SetFrustumPlanes(const Plane* frustumPlanes) override; - Uint32 GetTextureId(IDirect3DRMTexture* texture) override; + Uint32 GetTextureId(IDirect3DRMTexture* texture, bool isUi) override; Uint32 GetMeshId(IDirect3DRMMesh* mesh, const MeshGroup* meshGroup) override; - void GetDesc(D3DDEVICEDESC* halDesc, D3DDEVICEDESC* helDesc) override; - const char* GetName() override; HRESULT BeginFrame() override; void EnableTransparency() override; void SubmitDraw( @@ -57,8 +55,21 @@ class OpenGL1Renderer : public Direct3DRMRenderer { inline static void OpenGL1Renderer_EnumDevice(LPD3DENUMDEVICESCALLBACK cb, void* ctx) { Direct3DRMRenderer* device = OpenGL1Renderer::Create(640, 480); - if (device) { - EnumDevice(cb, ctx, device, OpenGL1_GUID); - delete device; + if (!device) { + return; } + delete device; + + D3DDEVICEDESC halDesc = {}; + halDesc.dcmColorModel = D3DCOLORMODEL::RGB; + halDesc.dwFlags = D3DDD_DEVICEZBUFFERBITDEPTH; + halDesc.dwDeviceZBufferBitDepth = DDBD_24; + halDesc.dwDeviceRenderBitDepth = DDBD_32; + halDesc.dpcTriCaps.dwTextureCaps = D3DPTEXTURECAPS_PERSPECTIVE; + halDesc.dpcTriCaps.dwShadeCaps = D3DPSHADECAPS_ALPHAFLATBLEND; + halDesc.dpcTriCaps.dwTextureFilterCaps = D3DPTFILTERCAPS_LINEAR; + + D3DDEVICEDESC helDesc = {}; + + EnumDevice(cb, ctx, "OpenGL 1.1 HAL", &halDesc, &helDesc, OpenGL1_GUID); } diff --git a/miniwin/src/internal/d3drmrenderer_opengles2.h b/miniwin/src/internal/d3drmrenderer_opengles2.h index 7ea1ed9a..432f124e 100644 --- a/miniwin/src/internal/d3drmrenderer_opengles2.h +++ b/miniwin/src/internal/d3drmrenderer_opengles2.h @@ -39,10 +39,8 @@ class OpenGLES2Renderer : public Direct3DRMRenderer { void PushLights(const SceneLight* lightsArray, size_t count) override; void SetProjection(const D3DRMMATRIX4D& projection, D3DVALUE front, D3DVALUE back) override; void SetFrustumPlanes(const Plane* frustumPlanes) override; - Uint32 GetTextureId(IDirect3DRMTexture* texture) override; + Uint32 GetTextureId(IDirect3DRMTexture* texture, bool isUi) override; Uint32 GetMeshId(IDirect3DRMMesh* mesh, const MeshGroup* meshGroup) override; - void GetDesc(D3DDEVICEDESC* halDesc, D3DDEVICEDESC* helDesc) override; - const char* GetName() override; HRESULT BeginFrame() override; void EnableTransparency() override; void SubmitDraw( @@ -64,10 +62,12 @@ class OpenGLES2Renderer : public Direct3DRMRenderer { void AddTextureDestroyCallback(Uint32 id, IDirect3DRMTexture* texture); void AddMeshDestroyCallback(Uint32 id, IDirect3DRMMesh* mesh); + MeshGroup m_uiMesh; + GLES2MeshCacheEntry m_uiMeshCache; std::vector m_textures; std::vector m_meshs; D3DRMMATRIX4D m_projection; - SDL_Surface* m_renderedImage; + SDL_Surface* m_renderedImage = nullptr; bool m_dirty = false; std::vector m_lights; SDL_GLContext m_context; @@ -78,8 +78,32 @@ class OpenGLES2Renderer : public Direct3DRMRenderer { inline static void OpenGLES2Renderer_EnumDevice(LPD3DENUMDEVICESCALLBACK cb, void* ctx) { Direct3DRMRenderer* device = OpenGLES2Renderer::Create(640, 480); - if (device) { - EnumDevice(cb, ctx, device, OpenGLES2_GUID); - delete device; + if (!device) { + return; } + + D3DDEVICEDESC halDesc = {}; + halDesc.dcmColorModel = D3DCOLOR_RGB; + halDesc.dwFlags = D3DDD_DEVICEZBUFFERBITDEPTH; + halDesc.dwDeviceZBufferBitDepth = DDBD_16; + halDesc.dwDeviceRenderBitDepth = DDBD_32; + halDesc.dpcTriCaps.dwTextureCaps = D3DPTEXTURECAPS_PERSPECTIVE; + halDesc.dpcTriCaps.dwShadeCaps = D3DPSHADECAPS_ALPHAFLATBLEND; + halDesc.dpcTriCaps.dwTextureFilterCaps = D3DPTFILTERCAPS_LINEAR; + + const char* extensions = (const char*) glGetString(GL_EXTENSIONS); + if (extensions) { + if (strstr(extensions, "GL_OES_depth24")) { + halDesc.dwDeviceZBufferBitDepth |= DDBD_24; + } + if (strstr(extensions, "GL_OES_depth32")) { + halDesc.dwDeviceZBufferBitDepth |= DDBD_32; + } + } + + delete device; + + D3DDEVICEDESC helDesc = {}; + + EnumDevice(cb, ctx, "OpenGL ES 2.0 HAL", &halDesc, &helDesc, OpenGLES2_GUID); } diff --git a/miniwin/src/internal/d3drmrenderer_sdl3gpu.h b/miniwin/src/internal/d3drmrenderer_sdl3gpu.h index 25485b1f..02ec05d0 100644 --- a/miniwin/src/internal/d3drmrenderer_sdl3gpu.h +++ b/miniwin/src/internal/d3drmrenderer_sdl3gpu.h @@ -47,12 +47,10 @@ class Direct3DRMSDL3GPURenderer : public Direct3DRMRenderer { static Direct3DRMRenderer* Create(DWORD width, DWORD height); ~Direct3DRMSDL3GPURenderer() override; void PushLights(const SceneLight* vertices, size_t count) override; - Uint32 GetTextureId(IDirect3DRMTexture* texture) override; + Uint32 GetTextureId(IDirect3DRMTexture* texture, bool isUi) override; Uint32 GetMeshId(IDirect3DRMMesh* mesh, const MeshGroup* meshGroup) override; void SetProjection(const D3DRMMATRIX4D& projection, D3DVALUE front, D3DVALUE back) override; void SetFrustumPlanes(const Plane* frustumPlanes) override; - void GetDesc(D3DDEVICEDESC* halDesc, D3DDEVICEDESC* helDesc) override; - const char* GetName() override; HRESULT BeginFrame() override; void EnableTransparency() override; void SubmitDraw( @@ -122,8 +120,21 @@ class Direct3DRMSDL3GPURenderer : public Direct3DRMRenderer { inline static void Direct3DRMSDL3GPU_EnumDevice(LPD3DENUMDEVICESCALLBACK cb, void* ctx) { Direct3DRMRenderer* device = Direct3DRMSDL3GPURenderer::Create(640, 480); - if (device) { - EnumDevice(cb, ctx, device, SDL3_GPU_GUID); - delete device; + if (!device) { + return; } + delete device; + + D3DDEVICEDESC halDesc = {}; + halDesc.dcmColorModel = D3DCOLOR_RGB; + halDesc.dwFlags = D3DDD_DEVICEZBUFFERBITDEPTH; + halDesc.dwDeviceZBufferBitDepth = DDBD_32; + halDesc.dwDeviceRenderBitDepth = DDBD_32; + halDesc.dpcTriCaps.dwTextureCaps = D3DPTEXTURECAPS_PERSPECTIVE; + halDesc.dpcTriCaps.dwShadeCaps = D3DPSHADECAPS_ALPHAFLATBLEND; + halDesc.dpcTriCaps.dwTextureFilterCaps = D3DPTFILTERCAPS_LINEAR; + + D3DDEVICEDESC helDesc = {}; + + EnumDevice(cb, ctx, "SDL3 GPU HAL", &halDesc, &helDesc, SDL3_GPU_GUID); } diff --git a/miniwin/src/internal/d3drmrenderer_software.h b/miniwin/src/internal/d3drmrenderer_software.h index 444a80a3..87715999 100644 --- a/miniwin/src/internal/d3drmrenderer_software.h +++ b/miniwin/src/internal/d3drmrenderer_software.h @@ -29,12 +29,10 @@ class Direct3DRMSoftwareRenderer : public Direct3DRMRenderer { Direct3DRMSoftwareRenderer(DWORD width, DWORD height); ~Direct3DRMSoftwareRenderer() override; void PushLights(const SceneLight* vertices, size_t count) override; - Uint32 GetTextureId(IDirect3DRMTexture* texture) override; + Uint32 GetTextureId(IDirect3DRMTexture* texture, bool isUi) override; Uint32 GetMeshId(IDirect3DRMMesh* mesh, const MeshGroup* meshGroup) override; void SetProjection(const D3DRMMATRIX4D& projection, D3DVALUE front, D3DVALUE back) override; void SetFrustumPlanes(const Plane* frustumPlanes) override; - void GetDesc(D3DDEVICEDESC* halDesc, D3DDEVICEDESC* helDesc) override; - const char* GetName() override; HRESULT BeginFrame() override; void EnableTransparency() override; void SubmitDraw( @@ -87,8 +85,16 @@ class Direct3DRMSoftwareRenderer : public Direct3DRMRenderer { inline static void Direct3DRMSoftware_EnumDevice(LPD3DENUMDEVICESCALLBACK cb, void* ctx) { - Direct3DRMRenderer* device = nullptr; - device = new Direct3DRMSoftwareRenderer(640, 480); - EnumDevice(cb, ctx, device, SOFTWARE_GUID); - delete device; + D3DDEVICEDESC halDesc = {}; + + D3DDEVICEDESC helDesc = {}; + helDesc.dcmColorModel = D3DCOLOR_RGB; + helDesc.dwFlags = D3DDD_DEVICEZBUFFERBITDEPTH; + helDesc.dwDeviceZBufferBitDepth = DDBD_32; + helDesc.dwDeviceRenderBitDepth = DDBD_32; + helDesc.dpcTriCaps.dwTextureCaps = D3DPTEXTURECAPS_PERSPECTIVE; + helDesc.dpcTriCaps.dwShadeCaps = D3DPSHADECAPS_ALPHAFLATBLEND; + helDesc.dpcTriCaps.dwTextureFilterCaps = D3DPTFILTERCAPS_LINEAR; + + EnumDevice(cb, ctx, "Miniwin Emulation", &halDesc, &helDesc, SOFTWARE_GUID); } diff --git a/miniwin/src/internal/ddraw_impl.h b/miniwin/src/internal/ddraw_impl.h index f2604946..4adfb4a5 100644 --- a/miniwin/src/internal/ddraw_impl.h +++ b/miniwin/src/internal/ddraw_impl.h @@ -56,4 +56,11 @@ HRESULT DirectDrawEnumerate(LPDDENUMCALLBACKA cb, void* context); HRESULT DirectDrawCreate(LPGUID lpGuid, LPDIRECTDRAW* lplpDD, IUnknown* pUnkOuter); -void EnumDevice(LPD3DENUMDEVICESCALLBACK cb, void* ctx, Direct3DRMRenderer* device, GUID deviceGuid); +void EnumDevice( + LPD3DENUMDEVICESCALLBACK cb, + void* ctx, + const char* name, + D3DDEVICEDESC* halDesc, + D3DDEVICEDESC* helDesc, + GUID deviceGuid +); diff --git a/miniwin/src/internal/meshutils.cpp b/miniwin/src/internal/meshutils.cpp index 818ecd87..e0ce59e6 100644 --- a/miniwin/src/internal/meshutils.cpp +++ b/miniwin/src/internal/meshutils.cpp @@ -75,3 +75,31 @@ void FlattenSurfaces( } } } + +void Create2DTransformMatrix( + const SDL_Rect& dstRect, + float scale, + float offsetX, + float offsetY, + D3DRMMATRIX4D& outMatrix +) +{ + float x = static_cast(dstRect.x) * scale + offsetX; + float y = static_cast(dstRect.y) * scale + offsetY; + float w = static_cast(dstRect.w) * scale; + float h = static_cast(dstRect.h) * scale; + + D3DVALUE tmp[4][4] = {{w, 0, 0, 0}, {0, h, 0, 0}, {0, 0, 1, 0}, {x, y, 0, 1}}; + memcpy(outMatrix, tmp, sizeof(tmp)); +} + +void CreateOrthographicProjection(float width, float height, D3DRMMATRIX4D& outProj) +{ + D3DVALUE tmp[4][4] = { + {2.0f / width, 0.0f, 0.0f, 0.0f}, + {0.0f, -2.0f / height, 0.0f, 0.0f}, + {0.0f, 0.0f, 1.0f, 0.0f}, + {-1.0f, 1.0f, 0.0f, 1.0f} + }; + memcpy(outProj, tmp, sizeof(tmp)); +} diff --git a/miniwin/src/internal/meshutils.h b/miniwin/src/internal/meshutils.h index fb12a145..12c509ad 100644 --- a/miniwin/src/internal/meshutils.h +++ b/miniwin/src/internal/meshutils.h @@ -13,3 +13,13 @@ void FlattenSurfaces( std::vector& dedupedVertices, std::vector& newIndices ); + +void Create2DTransformMatrix( + const SDL_Rect& dstRect, + float scale, + float offsetX, + float offsetY, + D3DRMMATRIX4D& outMatrix +); + +void CreateOrthographicProjection(float width, float height, D3DRMMATRIX4D& outProj); From ab48ce60b0ccdedfb0053b1ad555e45e16a5a58c Mon Sep 17 00:00:00 2001 From: Anders Jenbo Date: Mon, 30 Jun 2025 23:38:44 +0200 Subject: [PATCH 023/188] OpenGL ES 2.0 Only lookup shader locations once (#460) --- .../src/d3drm/backends/opengles2/renderer.cpp | 95 +++++++++++-------- .../src/internal/d3drmrenderer_opengles2.h | 12 +++ 2 files changed, 65 insertions(+), 42 deletions(-) diff --git a/miniwin/src/d3drm/backends/opengles2/renderer.cpp b/miniwin/src/d3drm/backends/opengles2/renderer.cpp index 8c39d937..55719807 100644 --- a/miniwin/src/d3drm/backends/opengles2/renderer.cpp +++ b/miniwin/src/d3drm/backends/opengles2/renderer.cpp @@ -263,6 +263,23 @@ OpenGLES2Renderer::OpenGLES2Renderer(DWORD width, DWORD height, SDL_GLContext co }; m_uiMesh.indices = {0, 1, 2, 0, 2, 3}; m_uiMeshCache = GLES2UploadMesh(m_uiMesh, true); + m_posLoc = glGetAttribLocation(m_shaderProgram, "a_position"); + m_normLoc = glGetAttribLocation(m_shaderProgram, "a_normal"); + m_texLoc = glGetAttribLocation(m_shaderProgram, "a_texCoord"); + m_colorLoc = glGetUniformLocation(m_shaderProgram, "u_color"); + m_shinLoc = glGetUniformLocation(m_shaderProgram, "u_shininess"); + m_lightCountLoc = glGetUniformLocation(m_shaderProgram, "u_lightCount"); + m_useTextureLoc = glGetUniformLocation(m_shaderProgram, "u_useTexture"); + m_textureLoc = glGetUniformLocation(m_shaderProgram, "u_texture"); + for (int i = 0; i < 3; ++i) { + std::string base = "u_lights[" + std::to_string(i) + "]"; + u_lightLocs[i][0] = glGetUniformLocation(m_shaderProgram, (base + ".color").c_str()); + u_lightLocs[i][1] = glGetUniformLocation(m_shaderProgram, (base + ".position").c_str()); + u_lightLocs[i][2] = glGetUniformLocation(m_shaderProgram, (base + ".direction").c_str()); + } + m_modelViewMatrixLoc = glGetUniformLocation(m_shaderProgram, "u_modelViewMatrix"); + m_normalMatrixLoc = glGetUniformLocation(m_shaderProgram, "u_normalMatrix"); + m_projectionMatrixLoc = glGetUniformLocation(m_shaderProgram, "u_projectionMatrix"); } OpenGLES2Renderer::~OpenGLES2Renderer() @@ -481,12 +498,11 @@ HRESULT OpenGLES2Renderer::BeginFrame() } for (int i = 0; i < lightCount; ++i) { - std::string base = "u_lights[" + std::to_string(i) + "]"; - glUniform4fv(glGetUniformLocation(m_shaderProgram, (base + ".color").c_str()), 1, lightData[i].color); - glUniform4fv(glGetUniformLocation(m_shaderProgram, (base + ".position").c_str()), 1, lightData[i].position); - glUniform4fv(glGetUniformLocation(m_shaderProgram, (base + ".direction").c_str()), 1, lightData[i].direction); + glUniform4fv(u_lightLocs[i][0], 1, lightData[i].color); + glUniform4fv(u_lightLocs[i][1], 1, lightData[i].position); + glUniform4fv(u_lightLocs[i][2], 1, lightData[i].direction); } - glUniform1i(glGetUniformLocation(m_shaderProgram, "u_lightCount"), lightCount); + glUniform1i(m_lightCountLoc, lightCount); return DD_OK; } @@ -508,52 +524,49 @@ void OpenGLES2Renderer::SubmitDraw( { auto& mesh = m_meshs[meshId]; - glUniformMatrix4fv(glGetUniformLocation(m_shaderProgram, "u_modelViewMatrix"), 1, GL_FALSE, &modelViewMatrix[0][0]); - glUniformMatrix3fv(glGetUniformLocation(m_shaderProgram, "u_normalMatrix"), 1, GL_FALSE, &normalMatrix[0][0]); - glUniformMatrix4fv(glGetUniformLocation(m_shaderProgram, "u_projectionMatrix"), 1, GL_FALSE, &m_projection[0][0]); + glUniformMatrix4fv(m_modelViewMatrixLoc, 1, GL_FALSE, &modelViewMatrix[0][0]); + glUniformMatrix3fv(m_normalMatrixLoc, 1, GL_FALSE, &normalMatrix[0][0]); + glUniformMatrix4fv(m_projectionMatrixLoc, 1, GL_FALSE, &m_projection[0][0]); glUniform4f( - glGetUniformLocation(m_shaderProgram, "u_color"), + m_colorLoc, appearance.color.r / 255.0f, appearance.color.g / 255.0f, appearance.color.b / 255.0f, appearance.color.a / 255.0f ); - glUniform1f(glGetUniformLocation(m_shaderProgram, "u_shininess"), appearance.shininess); + glUniform1f(m_shinLoc, appearance.shininess); if (appearance.textureId != NO_TEXTURE_ID) { - glUniform1i(glGetUniformLocation(m_shaderProgram, "u_useTexture"), 1); + glUniform1i(m_useTextureLoc, 1); glActiveTexture(GL_TEXTURE0); glBindTexture(GL_TEXTURE_2D, m_textures[appearance.textureId].glTextureId); - glUniform1i(glGetUniformLocation(m_shaderProgram, "u_texture"), 0); + glUniform1i(m_textureLoc, 0); } else { - glUniform1i(glGetUniformLocation(m_shaderProgram, "u_useTexture"), 0); + glUniform1i(m_useTextureLoc, 0); } glBindBuffer(GL_ARRAY_BUFFER, mesh.vboPositions); - GLint posLoc = glGetAttribLocation(m_shaderProgram, "a_position"); - glEnableVertexAttribArray(posLoc); - glVertexAttribPointer(posLoc, 3, GL_FLOAT, GL_FALSE, 0, nullptr); + glEnableVertexAttribArray(m_posLoc); + glVertexAttribPointer(m_posLoc, 3, GL_FLOAT, GL_FALSE, 0, nullptr); glBindBuffer(GL_ARRAY_BUFFER, mesh.vboNormals); - GLint normLoc = glGetAttribLocation(m_shaderProgram, "a_normal"); - glEnableVertexAttribArray(normLoc); - glVertexAttribPointer(normLoc, 3, GL_FLOAT, GL_FALSE, 0, nullptr); + glEnableVertexAttribArray(m_normLoc); + glVertexAttribPointer(m_normLoc, 3, GL_FLOAT, GL_FALSE, 0, nullptr); - GLint texLoc = glGetAttribLocation(m_shaderProgram, "a_texCoord"); if (appearance.textureId != NO_TEXTURE_ID) { glBindBuffer(GL_ARRAY_BUFFER, mesh.vboTexcoords); - glEnableVertexAttribArray(texLoc); - glVertexAttribPointer(texLoc, 2, GL_FLOAT, GL_FALSE, 0, nullptr); + glEnableVertexAttribArray(m_texLoc); + glVertexAttribPointer(m_texLoc, 2, GL_FLOAT, GL_FALSE, 0, nullptr); } glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, mesh.ibo); glDrawElements(GL_TRIANGLES, static_cast(mesh.indices.size()), GL_UNSIGNED_SHORT, nullptr); - glDisableVertexAttribArray(normLoc); - glDisableVertexAttribArray(texLoc); + glDisableVertexAttribArray(m_normLoc); + glDisableVertexAttribArray(m_texLoc); } HRESULT OpenGLES2Renderer::FinalizeFrame() @@ -605,13 +618,13 @@ void OpenGLES2Renderer::Draw2DImage(Uint32 textureId, const SDL_Rect& srcRect, c float color[] = {1.0f, 1.0f, 1.0f, 1.0f}; float blank[] = {0.0f, 0.0f, 0.0f, 0.0f}; - glUniform4fv(glGetUniformLocation(m_shaderProgram, "u_lights[0].color"), 1, color); - glUniform4fv(glGetUniformLocation(m_shaderProgram, "u_lights[0].position"), 1, blank); - glUniform4fv(glGetUniformLocation(m_shaderProgram, "u_lights[0].direction"), 1, blank); - glUniform1i(glGetUniformLocation(m_shaderProgram, "u_lightCount"), 1); + glUniform4fv(u_lightLocs[0][0], 1, color); + glUniform4fv(u_lightLocs[0][1], 1, blank); + glUniform4fv(u_lightLocs[0][2], 1, blank); + glUniform1i(m_lightCountLoc, 1); - glUniform4f(glGetUniformLocation(m_shaderProgram, "u_color"), 1.0f, 1.0f, 1.0f, 1.0f); - glUniform1f(glGetUniformLocation(m_shaderProgram, "u_shininess"), 0.0f); + glUniform4f(m_colorLoc, 1.0f, 1.0f, 1.0f, 1.0f); + glUniform1f(m_shinLoc, 0.0f); const GLES2TextureCacheEntry& texture = m_textures[textureId]; float scaleX = static_cast(dstRect.w) / srcRect.w; @@ -632,19 +645,19 @@ void OpenGLES2Renderer::Draw2DImage(Uint32 textureId, const SDL_Rect& srcRect, c modelView ); - glUniformMatrix4fv(glGetUniformLocation(m_shaderProgram, "u_modelViewMatrix"), 1, GL_FALSE, &modelView[0][0]); + glUniformMatrix4fv(m_modelViewMatrixLoc, 1, GL_FALSE, &modelView[0][0]); Matrix3x3 identity = {{1.f, 0.f, 0.f}, {0.f, 1.f, 0.f}, {0.f, 0.f, 1.f}}; - glUniformMatrix3fv(glGetUniformLocation(m_shaderProgram, "u_normalMatrix"), 1, GL_FALSE, &identity[0][0]); + glUniformMatrix3fv(m_normalMatrixLoc, 1, GL_FALSE, &identity[0][0]); CreateOrthographicProjection((float) m_width, (float) m_height, projection); - glUniformMatrix4fv(glGetUniformLocation(m_shaderProgram, "u_projectionMatrix"), 1, GL_FALSE, &projection[0][0]); + glUniformMatrix4fv(m_projectionMatrixLoc, 1, GL_FALSE, &projection[0][0]); glEnable(GL_BLEND); glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); glActiveTexture(GL_TEXTURE0); - glUniform1i(glGetUniformLocation(m_shaderProgram, "u_useTexture"), 1); + glUniform1i(m_useTextureLoc, 1); glBindTexture(GL_TEXTURE_2D, texture.glTextureId); - glUniform1i(glGetUniformLocation(m_shaderProgram, "u_texture"), 0); + glUniform1i(m_textureLoc, 0); glEnable(GL_SCISSOR_TEST); glScissor( @@ -656,20 +669,18 @@ void OpenGLES2Renderer::Draw2DImage(Uint32 textureId, const SDL_Rect& srcRect, c static_cast(std::round(dstRect.h * m_viewportTransform.scale)) ); - GLint posLoc = glGetAttribLocation(m_shaderProgram, "a_position"); glBindBuffer(GL_ARRAY_BUFFER, m_uiMeshCache.vboPositions); - glEnableVertexAttribArray(posLoc); - glVertexAttribPointer(posLoc, 3, GL_FLOAT, GL_FALSE, 0, nullptr); + glEnableVertexAttribArray(m_posLoc); + glVertexAttribPointer(m_posLoc, 3, GL_FLOAT, GL_FALSE, 0, nullptr); - GLint texLoc = glGetAttribLocation(m_shaderProgram, "a_texCoord"); glBindBuffer(GL_ARRAY_BUFFER, m_uiMeshCache.vboTexcoords); - glEnableVertexAttribArray(texLoc); - glVertexAttribPointer(texLoc, 2, GL_FLOAT, GL_FALSE, 0, nullptr); + glEnableVertexAttribArray(m_texLoc); + glVertexAttribPointer(m_texLoc, 2, GL_FLOAT, GL_FALSE, 0, nullptr); glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, m_uiMeshCache.ibo); glDrawElements(GL_TRIANGLES, static_cast(m_uiMeshCache.indices.size()), GL_UNSIGNED_SHORT, nullptr); - glDisableVertexAttribArray(texLoc); + glDisableVertexAttribArray(m_texLoc); glDisable(GL_SCISSOR_TEST); } diff --git a/miniwin/src/internal/d3drmrenderer_opengles2.h b/miniwin/src/internal/d3drmrenderer_opengles2.h index 432f124e..f5d62579 100644 --- a/miniwin/src/internal/d3drmrenderer_opengles2.h +++ b/miniwin/src/internal/d3drmrenderer_opengles2.h @@ -72,6 +72,18 @@ class OpenGLES2Renderer : public Direct3DRMRenderer { std::vector m_lights; SDL_GLContext m_context; GLuint m_shaderProgram; + GLint m_posLoc; + GLint m_normLoc; + GLint m_texLoc; + GLint m_colorLoc; + GLint m_shinLoc; + GLint m_lightCountLoc; + GLint m_useTextureLoc; + GLint m_textureLoc; + GLint u_lightLocs[3][3]; + GLint m_modelViewMatrixLoc; + GLint m_normalMatrixLoc; + GLint m_projectionMatrixLoc; ViewportTransform m_viewportTransform; }; From f825b053ffa70e80d6cac7f8e8f2683c242a270f Mon Sep 17 00:00:00 2001 From: Fabian Neundorf Date: Tue, 1 Jul 2025 00:29:23 +0200 Subject: [PATCH 024/188] Clear unknown in `mxutilities.h` (#1598) Also change the parameter name to mask as it better represents the usage. --- LEGO1/lego/legoomni/src/common/legoutils.cpp | 4 ++-- LEGO1/lego/legoomni/src/common/mxcontrolpresenter.cpp | 2 +- LEGO1/lego/legoomni/src/entity/legoworld.cpp | 8 ++++---- LEGO1/lego/legoomni/src/worlds/infocenter.cpp | 4 ++-- LEGO1/omni/include/mxutilities.h | 2 +- LEGO1/omni/src/common/mxutilities.cpp | 8 ++++---- 6 files changed, 14 insertions(+), 14 deletions(-) diff --git a/LEGO1/lego/legoomni/src/common/legoutils.cpp b/LEGO1/lego/legoomni/src/common/legoutils.cpp index a4f8d022..a9ea98a7 100644 --- a/LEGO1/lego/legoomni/src/common/legoutils.cpp +++ b/LEGO1/lego/legoomni/src/common/legoutils.cpp @@ -507,7 +507,7 @@ MxBool RemoveFromCurrentWorld(const MxAtomId& p_atomId, MxS32 p_id) } else { if (((MxPresenter*) object)->GetAction()) { - FUN_100b7220(((MxPresenter*) object)->GetAction(), MxDSAction::c_world, FALSE); + ApplyMask(((MxPresenter*) object)->GetAction(), MxDSAction::c_world, FALSE); } ((MxPresenter*) object)->EndAction(); @@ -536,7 +536,7 @@ MxBool RemoveFromWorld(MxAtomId& p_entityAtom, MxS32 p_entityId, MxAtomId& p_wor } else { if (((MxPresenter*) object)->GetAction()) { - FUN_100b7220(((MxPresenter*) object)->GetAction(), MxDSAction::c_world, FALSE); + ApplyMask(((MxPresenter*) object)->GetAction(), MxDSAction::c_world, FALSE); } ((MxPresenter*) object)->EndAction(); diff --git a/LEGO1/lego/legoomni/src/common/mxcontrolpresenter.cpp b/LEGO1/lego/legoomni/src/common/mxcontrolpresenter.cpp index 0b397836..13ce2cee 100644 --- a/LEGO1/lego/legoomni/src/common/mxcontrolpresenter.cpp +++ b/LEGO1/lego/legoomni/src/common/mxcontrolpresenter.cpp @@ -44,7 +44,7 @@ MxResult MxControlPresenter::StartAction(MxStreamController* p_controller, MxDSA { MxResult result = MxCompositePresenter::StartAction(p_controller, p_action); - FUN_100b7220(m_action, MxDSAction::c_world | MxDSAction::c_looping, TRUE); + ApplyMask(m_action, MxDSAction::c_world | MxDSAction::c_looping, TRUE); ParseExtra(); MxS16 i = 0; diff --git a/LEGO1/lego/legoomni/src/entity/legoworld.cpp b/LEGO1/lego/legoomni/src/entity/legoworld.cpp index c0819883..c12d32e4 100644 --- a/LEGO1/lego/legoomni/src/entity/legoworld.cpp +++ b/LEGO1/lego/legoomni/src/entity/legoworld.cpp @@ -120,12 +120,12 @@ void LegoWorld::Destroy(MxBool p_fromDestructor) animPresenter->DecrementUnknown0xd4(); if (animPresenter->GetUnknown0xd4() == 0) { - FUN_100b7220(action, MxDSAction::c_world, FALSE); + ApplyMask(action, MxDSAction::c_world, FALSE); presenter->EndAction(); } } else { - FUN_100b7220(action, MxDSAction::c_world, FALSE); + ApplyMask(action, MxDSAction::c_world, FALSE); presenter->EndAction(); } } @@ -141,7 +141,7 @@ void LegoWorld::Destroy(MxBool p_fromDestructor) MxDSAction* action = presenter->GetAction(); if (action) { - FUN_100b7220(action, MxDSAction::c_world, FALSE); + ApplyMask(action, MxDSAction::c_world, FALSE); presenter->EndAction(); } } @@ -157,7 +157,7 @@ void LegoWorld::Destroy(MxBool p_fromDestructor) MxDSAction* action = presenter->GetAction(); if (action) { - FUN_100b7220(action, MxDSAction::c_world, FALSE); + ApplyMask(action, MxDSAction::c_world, FALSE); presenter->EndAction(); } } diff --git a/LEGO1/lego/legoomni/src/worlds/infocenter.cpp b/LEGO1/lego/legoomni/src/worlds/infocenter.cpp index 90078e6d..55171729 100644 --- a/LEGO1/lego/legoomni/src/worlds/infocenter.cpp +++ b/LEGO1/lego/legoomni/src/worlds/infocenter.cpp @@ -1450,7 +1450,7 @@ void Infocenter::StartCredits() MxDSAction* action = presenter->GetAction(); if (action) { - FUN_100b7220(action, MxDSAction::c_world, FALSE); + ApplyMask(action, MxDSAction::c_world, FALSE); presenter->EndAction(); } } @@ -1466,7 +1466,7 @@ void Infocenter::StartCredits() MxDSAction* action = presenter->GetAction(); if (action) { - FUN_100b7220(action, MxDSAction::c_world, FALSE); + ApplyMask(action, MxDSAction::c_world, FALSE); presenter->EndAction(); } } diff --git a/LEGO1/omni/include/mxutilities.h b/LEGO1/omni/include/mxutilities.h index 51b897b7..8a897e39 100644 --- a/LEGO1/omni/include/mxutilities.h +++ b/LEGO1/omni/include/mxutilities.h @@ -75,7 +75,7 @@ void MakeSourceName(char*, const char*); void OmniError(const char* p_message, MxS32 p_status); void SetOmniUserMessage(void (*p_omniUserMessage)(const char*, MxS32)); MxBool ContainsPresenter(MxCompositePresenterList& p_presenterList, MxPresenter* p_presenter); -void FUN_100b7220(MxDSAction* p_action, MxU32 p_newFlags, MxBool p_setFlags); +void ApplyMask(MxDSAction* p_action, MxU32 p_mask, MxBool p_setFlags); MxBool KeyValueStringParse(char*, const char*, const char*); // TEMPLATE: BETA10 0x1012dfd0 diff --git a/LEGO1/omni/src/common/mxutilities.cpp b/LEGO1/omni/src/common/mxutilities.cpp index 365d6b18..54714c4d 100644 --- a/LEGO1/omni/src/common/mxutilities.cpp +++ b/LEGO1/omni/src/common/mxutilities.cpp @@ -150,16 +150,16 @@ void SetOmniUserMessage(void (*p_omniUserMessage)(const char*, MxS32)) // FUNCTION: LEGO1 0x100b7220 // FUNCTION: BETA10 0x10136f37 -void FUN_100b7220(MxDSAction* p_action, MxU32 p_newFlags, MxBool p_setFlags) +void ApplyMask(MxDSAction* p_action, MxU32 p_mask, MxBool p_setFlags) { MxU32 oldFlags = p_action->GetFlags(); MxU32 newFlags; if (p_setFlags) { - newFlags = oldFlags | p_newFlags; + newFlags = oldFlags | p_mask; } else { - newFlags = oldFlags & ~p_newFlags; + newFlags = oldFlags & ~p_mask; } p_action->SetFlags(newFlags); @@ -169,7 +169,7 @@ void FUN_100b7220(MxDSAction* p_action, MxU32 p_newFlags, MxBool p_setFlags) MxDSAction* action; while (cursor.Next(action)) { - FUN_100b7220(action, p_newFlags, p_setFlags); + ApplyMask(action, p_mask, p_setFlags); } } } From e2ff65cf91fbf6142f439acea933f58f565ffd3e Mon Sep 17 00:00:00 2001 From: Anders Jenbo Date: Tue, 1 Jul 2025 02:23:13 +0200 Subject: [PATCH 025/188] Expose window during device probing (#462) --- CONFIG/config.cpp | 10 +++- .../legoomni/src/video/legovideomanager.cpp | 2 +- LEGO1/mxdirectx/mxdirectxinfo.cpp | 30 ++++++---- LEGO1/mxdirectx/mxdirectxinfo.h | 3 +- .../src/d3drm/backends/opengl1/renderer.cpp | 36 ++++------- .../src/d3drm/backends/opengles2/renderer.cpp | 30 ++++------ .../src/d3drm/backends/sdl3gpu/renderer.cpp | 60 ++++--------------- 7 files changed, 63 insertions(+), 108 deletions(-) diff --git a/CONFIG/config.cpp b/CONFIG/config.cpp index bd38e669..9030ae67 100644 --- a/CONFIG/config.cpp +++ b/CONFIG/config.cpp @@ -56,9 +56,17 @@ bool CConfigApp::InitInstance() return false; } m_device_enumerator = new LegoDeviceEnumerate; - if (m_device_enumerator->DoEnumerate()) { + SDL_Window* window = SDL_CreateWindow("Test window", 640, 480, SDL_WINDOW_HIDDEN | SDL_WINDOW_OPENGL); + HWND hWnd; +#ifdef MINIWIN + hWnd = reinterpret_cast(window); +#else + hWnd = (HWND) SDL_GetPointerProperty(SDL_GetWindowProperties(window), SDL_PROP_WINDOW_WIN32_HWND_POINTER, NULL); +#endif + if (m_device_enumerator->DoEnumerate(hWnd)) { return FALSE; } + SDL_DestroyWindow(window); m_driver = NULL; m_device = NULL; m_full_screen = TRUE; diff --git a/LEGO1/lego/legoomni/src/video/legovideomanager.cpp b/LEGO1/lego/legoomni/src/video/legovideomanager.cpp index a0173224..7e2f7e42 100644 --- a/LEGO1/lego/legoomni/src/video/legovideomanager.cpp +++ b/LEGO1/lego/legoomni/src/video/legovideomanager.cpp @@ -108,7 +108,7 @@ MxResult LegoVideoManager::Create(MxVideoParam& p_videoParam, MxU32 p_frequencyM goto done; } - if (deviceEnumerate.DoEnumerate() != SUCCESS) { + if (deviceEnumerate.DoEnumerate(hwnd) != SUCCESS) { SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "LegoDeviceEnumerate::DoEnumerate failed"); goto done; } diff --git a/LEGO1/mxdirectx/mxdirectxinfo.cpp b/LEGO1/mxdirectx/mxdirectxinfo.cpp index c5100031..639a2864 100644 --- a/LEGO1/mxdirectx/mxdirectxinfo.cpp +++ b/LEGO1/mxdirectx/mxdirectxinfo.cpp @@ -216,27 +216,33 @@ BOOL MxDeviceEnumerate::EnumDirectDrawCallback(LPGUID p_guid, LPSTR p_driverDesc BuildErrorString("DirectDraw Create failed: %s\n", EnumerateErrorToString(result)); } else { - newDevice.m_ddCaps.dwSize = sizeof(newDevice.m_ddCaps); - result = lpDD->GetCaps(&newDevice.m_ddCaps, NULL); - + result = lpDD->SetCooperativeLevel(m_hWnd, DDSCL_NORMAL); if (result != DD_OK) { - BuildErrorString("GetCaps failed: %s\n", EnumerateErrorToString(result)); + BuildErrorString("SetCooperativeLevel failed: %s\n", EnumerateErrorToString(result)); } else { - result = lpDD->QueryInterface(IID_IDirect3D2, (LPVOID*) &lpDirect3d2); + newDevice.m_ddCaps.dwSize = sizeof(newDevice.m_ddCaps); + result = lpDD->GetCaps(&newDevice.m_ddCaps, NULL); if (result != DD_OK) { - BuildErrorString("D3D creation failed: %s\n", EnumerateErrorToString(result)); + BuildErrorString("GetCaps failed: %s\n", EnumerateErrorToString(result)); } else { - result = lpDirect3d2->EnumDevices(DevicesEnumerateCallback, this); + result = lpDD->QueryInterface(IID_IDirect3D2, (LPVOID*) &lpDirect3d2); if (result != DD_OK) { - BuildErrorString("D3D enum devices failed: %s\n", EnumerateErrorToString(result)); + BuildErrorString("D3D creation failed: %s\n", EnumerateErrorToString(result)); } else { - if (!newDevice.m_devices.size()) { - m_list.pop_back(); + result = lpDirect3d2->EnumDevices(DevicesEnumerateCallback, this); + + if (result != DD_OK) { + BuildErrorString("D3D enum devices failed: %s\n", EnumerateErrorToString(result)); + } + else { + if (!newDevice.m_devices.size()) { + m_list.pop_back(); + } } } } @@ -306,12 +312,14 @@ HRESULT MxDeviceEnumerate::EnumDevicesCallback( // FUNCTION: CONFIG 0x00401dc0 // FUNCTION: LEGO1 0x1009c6c0 // FUNCTION: BETA10 0x1011e3fa -int MxDeviceEnumerate::DoEnumerate() +int MxDeviceEnumerate::DoEnumerate(HWND hWnd) { if (IsInitialized()) { return -1; } + m_hWnd = hWnd; + HRESULT ret = DirectDrawEnumerate(DirectDrawEnumerateCallback, this); if (ret != DD_OK) { BuildErrorString("DirectDrawEnumerate returned error %s\n", EnumerateErrorToString(ret)); diff --git a/LEGO1/mxdirectx/mxdirectxinfo.h b/LEGO1/mxdirectx/mxdirectxinfo.h index a4ebef91..9bc480de 100644 --- a/LEGO1/mxdirectx/mxdirectxinfo.h +++ b/LEGO1/mxdirectx/mxdirectxinfo.h @@ -194,7 +194,7 @@ class MxDeviceEnumerate { MxDeviceEnumerate(); ~MxDeviceEnumerate(); - virtual int DoEnumerate(); // vtable+0x00 + virtual int DoEnumerate(HWND hWnd); // vtable+0x00 BOOL EnumDirectDrawCallback(LPGUID p_guid, LPSTR p_driverDesc, LPSTR p_driverName); HRESULT EnumDevicesCallback( @@ -242,6 +242,7 @@ class MxDeviceEnumerate { protected: list m_list; // 0x04 unsigned char m_initialized; // 0x10 + HWND m_hWnd; }; // TEMPLATE: BETA10 0x1011c1b0 diff --git a/miniwin/src/d3drm/backends/opengl1/renderer.cpp b/miniwin/src/d3drm/backends/opengl1/renderer.cpp index 66f08d23..fb7fccd4 100644 --- a/miniwin/src/d3drm/backends/opengl1/renderer.cpp +++ b/miniwin/src/d3drm/backends/opengl1/renderer.cpp @@ -28,40 +28,25 @@ Direct3DRMRenderer* OpenGL1Renderer::Create(DWORD width, DWORD height) SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 1); SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 1); - SDL_Window* window = DDWindow; - bool testWindow = false; - if (!window) { - window = SDL_CreateWindow("OpenGL 1.1 test", width, height, SDL_WINDOW_HIDDEN | SDL_WINDOW_OPENGL); - if (!window) { - SDL_Log("SDL_CreateWindow: %s", SDL_GetError()); - return nullptr; - } - testWindow = true; - } - - SDL_GLContext context = SDL_GL_CreateContext(window); - if (!context) { - SDL_Log("SDL_GL_CreateContext: %s", SDL_GetError()); - if (testWindow) { - SDL_DestroyWindow(window); - } + if (!DDWindow) { + SDL_Log("No window handler"); return nullptr; } - if (!SDL_GL_MakeCurrent(window, context)) { + SDL_GLContext context = SDL_GL_CreateContext(DDWindow); + if (!context) { + SDL_Log("SDL_GL_CreateContext: %s", SDL_GetError()); + return nullptr; + } + + if (!SDL_GL_MakeCurrent(DDWindow, context)) { + SDL_GL_DestroyContext(context); SDL_Log("SDL_GL_MakeCurrent: %s", SDL_GetError()); - if (testWindow) { - SDL_DestroyWindow(window); - } return nullptr; } GL11_InitState(); - if (testWindow) { - SDL_DestroyWindow(window); - } - return new OpenGL1Renderer(width, height, context); } @@ -78,6 +63,7 @@ OpenGL1Renderer::OpenGL1Renderer(DWORD width, DWORD height, SDL_GLContext contex OpenGL1Renderer::~OpenGL1Renderer() { SDL_DestroySurface(m_renderedImage); + SDL_GL_DestroyContext(m_context); } void OpenGL1Renderer::PushLights(const SceneLight* lightsArray, size_t count) diff --git a/miniwin/src/d3drm/backends/opengles2/renderer.cpp b/miniwin/src/d3drm/backends/opengles2/renderer.cpp index 55719807..d3594298 100644 --- a/miniwin/src/d3drm/backends/opengles2/renderer.cpp +++ b/miniwin/src/d3drm/backends/opengles2/renderer.cpp @@ -40,25 +40,18 @@ Direct3DRMRenderer* OpenGLES2Renderer::Create(DWORD width, DWORD height) SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 2); SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 0); - SDL_Window* window = DDWindow; - bool testWindow = false; - if (!window) { - window = SDL_CreateWindow("OpenGL ES 2.0 test", width, height, SDL_WINDOW_HIDDEN | SDL_WINDOW_OPENGL); - testWindow = true; - } - - SDL_GLContext context = SDL_GL_CreateContext(window); - if (!context) { - if (testWindow) { - SDL_DestroyWindow(window); - } + if (!DDWindow) { + SDL_Log("No window handler"); return nullptr; } - if (!SDL_GL_MakeCurrent(window, context)) { - if (testWindow) { - SDL_DestroyWindow(window); - } + SDL_GLContext context = SDL_GL_CreateContext(DDWindow); + if (!context) { + return nullptr; + } + + if (!SDL_GL_MakeCurrent(DDWindow, context)) { + SDL_GL_DestroyContext(context); return nullptr; } @@ -174,10 +167,6 @@ Direct3DRMRenderer* OpenGLES2Renderer::Create(DWORD width, DWORD height) glDeleteShader(vs); glDeleteShader(fs); - if (testWindow) { - SDL_DestroyWindow(window); - } - return new OpenGLES2Renderer(width, height, context, shaderProgram); } @@ -285,6 +274,7 @@ OpenGLES2Renderer::OpenGLES2Renderer(DWORD width, DWORD height, SDL_GLContext co OpenGLES2Renderer::~OpenGLES2Renderer() { SDL_DestroySurface(m_renderedImage); + SDL_GL_DestroyContext(m_context); glDeleteProgram(m_shaderProgram); } diff --git a/miniwin/src/d3drm/backends/sdl3gpu/renderer.cpp b/miniwin/src/d3drm/backends/sdl3gpu/renderer.cpp index a499193c..98704c66 100644 --- a/miniwin/src/d3drm/backends/sdl3gpu/renderer.cpp +++ b/miniwin/src/d3drm/backends/sdl3gpu/renderer.cpp @@ -203,49 +203,31 @@ Direct3DRMRenderer* Direct3DRMSDL3GPURenderer::Create(DWORD width, DWORD height) return nullptr; } - SDL_Window* window = DDWindow; - bool testWindow = false; - if (!window) { - window = SDL_CreateWindow("SDL_GPU test", width, height, SDL_WINDOW_HIDDEN); - if (!window) { - SDL_Log("SDL_CreateWindow: %s", SDL_GetError()); - return nullptr; - } - testWindow = true; - } - - if (!SDL_ClaimWindowForGPUDevice(device.ptr, window)) { - SDL_LogError(LOG_CATEGORY_MINIWIN, "SDL_ClaimWindowForGPUDevice: %s", SDL_GetError()); - if (testWindow) { - SDL_DestroyWindow(window); - } + if (!DDWindow) { + SDL_Log("No window handler"); return nullptr; } - ScopedPipeline opaquePipeline{device.ptr, InitializeGraphicsPipeline(device.ptr, window, true, true)}; + if (!SDL_ClaimWindowForGPUDevice(device.ptr, DDWindow)) { + SDL_LogError(LOG_CATEGORY_MINIWIN, "SDL_ClaimWindowForGPUDevice: %s", SDL_GetError()); + return nullptr; + } + + ScopedPipeline opaquePipeline{device.ptr, InitializeGraphicsPipeline(device.ptr, DDWindow, true, true)}; if (!opaquePipeline.ptr) { SDL_LogError(LOG_CATEGORY_MINIWIN, "InitializeGraphicsPipeline for opaquePipeline"); - if (testWindow) { - SDL_DestroyWindow(window); - } return nullptr; } - ScopedPipeline transparentPipeline{device.ptr, InitializeGraphicsPipeline(device.ptr, window, true, false)}; + ScopedPipeline transparentPipeline{device.ptr, InitializeGraphicsPipeline(device.ptr, DDWindow, true, false)}; if (!transparentPipeline.ptr) { SDL_LogError(LOG_CATEGORY_MINIWIN, "InitializeGraphicsPipeline for transparentPipeline"); - if (testWindow) { - SDL_DestroyWindow(window); - } return nullptr; } - ScopedPipeline uiPipeline{device.ptr, InitializeGraphicsPipeline(device.ptr, window, false, false)}; + ScopedPipeline uiPipeline{device.ptr, InitializeGraphicsPipeline(device.ptr, DDWindow, false, false)}; if (!uiPipeline.ptr) { SDL_LogError(LOG_CATEGORY_MINIWIN, "InitializeGraphicsPipeline for uiPipeline"); - if (testWindow) { - SDL_DestroyWindow(window); - } return nullptr; } @@ -257,9 +239,6 @@ Direct3DRMRenderer* Direct3DRMSDL3GPURenderer::Create(DWORD width, DWORD height) ScopedTransferBuffer uploadBuffer{device.ptr, SDL_CreateGPUTransferBuffer(device.ptr, &uploadBufferInfo)}; if (!uploadBuffer.ptr) { SDL_LogError(LOG_CATEGORY_MINIWIN, "SDL_CreateGPUTransferBuffer filed for upload buffer (%s)", SDL_GetError()); - if (testWindow) { - SDL_DestroyWindow(window); - } return nullptr; } @@ -273,9 +252,6 @@ Direct3DRMRenderer* Direct3DRMSDL3GPURenderer::Create(DWORD width, DWORD height) ScopedSampler sampler{device.ptr, SDL_CreateGPUSampler(device.ptr, &samplerInfo)}; if (!sampler.ptr) { SDL_LogError(LOG_CATEGORY_MINIWIN, "Failed to create sampler: %s", SDL_GetError()); - if (testWindow) { - SDL_DestroyWindow(window); - } return nullptr; } @@ -289,17 +265,9 @@ Direct3DRMRenderer* Direct3DRMSDL3GPURenderer::Create(DWORD width, DWORD height) ScopedSampler uiSampler{device.ptr, SDL_CreateGPUSampler(device.ptr, &uiSamplerInfo)}; if (!uiSampler.ptr) { SDL_LogError(LOG_CATEGORY_MINIWIN, "Failed to create sampler: %s", SDL_GetError()); - if (testWindow) { - SDL_DestroyWindow(window); - } return nullptr; } - if (testWindow) { - SDL_ReleaseWindowFromGPUDevice(device.ptr, window); - SDL_DestroyWindow(window); - } - auto renderer = new Direct3DRMSDL3GPURenderer( width, height, @@ -383,9 +351,7 @@ Direct3DRMSDL3GPURenderer::~Direct3DRMSDL3GPURenderer() { SDL_ReleaseGPUBuffer(m_device, m_uiMeshCache.vertexBuffer); SDL_ReleaseGPUBuffer(m_device, m_uiMeshCache.indexBuffer); - if (DDWindow) { - SDL_ReleaseWindowFromGPUDevice(m_device, DDWindow); - } + SDL_ReleaseWindowFromGPUDevice(m_device, DDWindow); if (m_downloadBuffer) { SDL_ReleaseGPUTransferBuffer(m_device, m_downloadBuffer); } @@ -839,10 +805,6 @@ void Direct3DRMSDL3GPURenderer::Resize(int width, int height, const ViewportTran m_height = height; m_viewportTransform = viewportTransform; - if (!DDWindow) { - return; - } - if (m_transferTexture) { SDL_ReleaseGPUTexture(m_device, m_transferTexture); } From 586327b58447a0ca91cb66196cb8b41c2c7cd3a8 Mon Sep 17 00:00:00 2001 From: Anders Jenbo Date: Tue, 1 Jul 2025 04:15:02 +0200 Subject: [PATCH 026/188] Destry shader before the context (#463) --- miniwin/src/d3drm/backends/opengles2/renderer.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/miniwin/src/d3drm/backends/opengles2/renderer.cpp b/miniwin/src/d3drm/backends/opengles2/renderer.cpp index d3594298..6695da81 100644 --- a/miniwin/src/d3drm/backends/opengles2/renderer.cpp +++ b/miniwin/src/d3drm/backends/opengles2/renderer.cpp @@ -274,8 +274,8 @@ OpenGLES2Renderer::OpenGLES2Renderer(DWORD width, DWORD height, SDL_GLContext co OpenGLES2Renderer::~OpenGLES2Renderer() { SDL_DestroySurface(m_renderedImage); - SDL_GL_DestroyContext(m_context); glDeleteProgram(m_shaderProgram); + SDL_GL_DestroyContext(m_context); } void OpenGLES2Renderer::PushLights(const SceneLight* lightsArray, size_t count) From 1ff768935ed25c4a726b2dee2455d97f77c64b66 Mon Sep 17 00:00:00 2001 From: Joshua Peisach Date: Mon, 30 Jun 2025 22:43:10 -0400 Subject: [PATCH 027/188] 3DS Port (#450) * [WIP] 3ds port Recommit of everything after the 2d renderer merge * VERY AWESOME FEATURE FRFR * Stopped CPU suicide and app crashing for now * put in Texture3DS function thing * Fix clear color * Implement 2D rendering via Citro3D * Set 3dsx smdh metadata * Render world content, sort of * Push mesh dynamically * Remove Citro3D init hacks * Clean up Citro3D implementation * Try to upload meshes and convert matricies * Fix 3D rendering * Apply optimizations * Implement lighting * Set 3dsx smdh metadata * Revert "Apply optimizations" This reverts commit 6660082fefb2cebbeeec8428abd8508099f93ad4. * Apply optimizations * Added a cleaner icon (#4) * Fix pure buffer clear frames (#9) * Disable OpenGL on 3DS (#10) * Fix tiled textures and improve UI image quality (#11) * Create 3DS default config overrides * 3ds: implement apt hooks * remove unused import * Apply suggestions from code review Co-authored-by: Christian Semmler Co-authored-by: Anonymous Maarten * Update miniwin/src/d3drm/backends/citro3d/renderer.cpp Co-authored-by: Anonymous Maarten * Separate 3DS apt hook code + move cmake 3ds into ISLE_BUILD_APP * miniwin: use citro3dd if debugging * Optimize texture encoding (#12) * Cleanup * Set correct mipmap level for UI textures (#13) * cpack: include the .3dsx * Add 3DS CI * Fix CI Co-authored-by: Anonymous Maarten * syntax * Refactor c3d renderer (#14) * Refactor c3d renderer * format * Apply suggestions from code review Co-authored-by: Anders Jenbo --------- Co-authored-by: Anders Jenbo * n3ds: just distribute the .3dsx * upload 3dsx * Skip uploading 3DS artifacts * Update ci.yml * Update ci.yml * Remove extraneous ifdef --------- Co-authored-by: MaxBrick Co-authored-by: Anders Jenbo Co-authored-by: Steven <139715581+StevenSYS@users.noreply.github.com> Co-authored-by: Christian Semmler Co-authored-by: Anonymous Maarten --- .github/workflows/ci.yml | 12 + CMakeLists.txt | 22 +- ISLE/3ds/apthooks.cpp | 30 + ISLE/3ds/apthooks.h | 9 + ISLE/3ds/config.cpp | 22 + ISLE/3ds/config.h | 8 + ISLE/isleapp.cpp | 13 +- ISLE/isleapp.h | 1 + ISLE/isledebug.cpp | 2 +- ISLE/res/3ds/isle.png | Bin 0 -> 438 bytes miniwin/CMakeLists.txt | 17 + .../src/d3drm/backends/citro3d/renderer.cpp | 606 ++++++++++++++++++ .../src/d3drm/backends/citro3d/vshader.v.pica | 130 ++++ .../src/d3drm/backends/software/renderer.cpp | 2 +- miniwin/src/d3drm/d3drm.cpp | 8 + miniwin/src/d3drm/d3drmdevice.cpp | 4 + miniwin/src/ddraw/ddraw.cpp | 11 + miniwin/src/internal/d3drmrenderer_citro3d.h | 82 +++ 18 files changed, 975 insertions(+), 4 deletions(-) create mode 100644 ISLE/3ds/apthooks.cpp create mode 100644 ISLE/3ds/apthooks.h create mode 100644 ISLE/3ds/config.cpp create mode 100644 ISLE/3ds/config.h create mode 100644 ISLE/res/3ds/isle.png create mode 100644 miniwin/src/d3drm/backends/citro3d/renderer.cpp create mode 100644 miniwin/src/d3drm/backends/citro3d/vshader.v.pica create mode 100644 miniwin/src/internal/d3drmrenderer_citro3d.h diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index c6124f37..57d6e539 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -25,6 +25,7 @@ jobs: build: name: ${{ matrix.name }} runs-on: ${{ matrix.os }} + container: ${{ matrix.container || '' }} defaults: run: shell: ${{ matrix.shell || 'sh' }} @@ -41,6 +42,7 @@ jobs: - { name: 'msys2 mingw64', os: 'windows-latest', dx5: false, config: true, mingw: true, werror: true, clang-tidy: true, msystem: 'mingw64', msys-env: 'mingw-w64-x86_64', shell: 'msys2 {0}' } - { name: 'macOS', os: 'macos-latest', dx5: false, config: true, brew: true, werror: true, clang-tidy: false } - { name: 'Emscripten', os: 'ubuntu-latest', dx5: false, config: false, emsdk: true, werror: true, clang-tidy: false, cmake-wrapper: 'emcmake' } + - { name: 'Nintendo 3DS', os: 'ubuntu-latest', dx5: false, config: false, n3ds: true, werror: true, clang-tidy: false, container: 'devkitpro/devkitarm:latest', cmake-args: '-DCMAKE_TOOLCHAIN_FILE=/opt/devkitpro/cmake/3DS.cmake' } steps: - name: Setup vcvars if: ${{ !!matrix.msvc }} @@ -89,6 +91,7 @@ jobs: - name: Configure (CMake) run: | ${{ matrix.cmake-wrapper || '' }} cmake -S . -B build -GNinja \ + ${{ matrix.cmake-args || '' }} \ -DCMAKE_BUILD_TYPE=Release \ -DISLE_USE_DX5=${{ !!matrix.dx5 }} \ -DISLE_BUILD_CONFIG=${{ !!matrix.config }} \ @@ -101,6 +104,7 @@ jobs: run: cmake --build build --verbose - name: Package (CPack) + if: ${{ !matrix.n3ds }} run: | cd build cpack . @@ -128,6 +132,13 @@ jobs: --output appimage && \ mv *.AppImage dist/ + - name: Package (3DS) + if: ${{ matrix.n3ds }} + run: | + cd build + mkdir dist + mv *.3dsx dist/ + - name: Upload Build Artifacts uses: actions/upload-artifact@v4 with: @@ -135,6 +146,7 @@ jobs: path: | build/dist/isle-* build/dist/*.AppImage + build/dist/*.3dsx flatpak: name: "Flatpak (${{ matrix.arch }})" diff --git a/CMakeLists.txt b/CMakeLists.txt index ac3663fc..e9812aea 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -38,7 +38,7 @@ option(ISLE_WERROR "Treat warnings as errors" OFF) option(ISLE_DEBUG "Enable imgui debug" ON) cmake_dependent_option(ISLE_USE_DX5 "Build with internal DirectX 5 SDK" "${NOT_MINGW}" "WIN32;CMAKE_SIZEOF_VOID_P EQUAL 4" OFF) cmake_dependent_option(ISLE_MINIWIN "Use miniwin" ON "NOT ISLE_USE_DX5" OFF) -cmake_dependent_option(ISLE_BUILD_CONFIG "Build CONFIG.EXE application" ON "MSVC OR ISLE_MINIWIN" OFF) +cmake_dependent_option(ISLE_BUILD_CONFIG "Build CONFIG.EXE application" ON "MSVC OR ISLE_MINIWIN;NOT NINTENDO_3DS" OFF) cmake_dependent_option(ISLE_COMPILE_SHADERS "Compile shaders" ON "SDL_SHADERCROSS_BIN;TARGET Python3::Interpreter" OFF) option(CMAKE_POSITION_INDEPENDENT_CODE "Build with -fPIC" ON) option(ENABLE_CLANG_TIDY "Enable clang-tidy") @@ -523,6 +523,12 @@ if (ISLE_BUILD_APP) target_compile_definitions(isle PRIVATE "ISLE_EMSCRIPTEN_HOST=\"${ISLE_EMSCRIPTEN_HOST}\"") set_property(TARGET isle PROPERTY SUFFIX ".html") endif() + if(NINTENDO_3DS) + target_sources(isle PRIVATE + ISLE/3ds/apthooks.cpp + ISLE/3ds/config.cpp + ) + endif() endif() if (ISLE_BUILD_CONFIG) @@ -652,9 +658,23 @@ add_subdirectory(packaging) set(CPACK_PACKAGE_DIRECTORY "dist") set(CPACK_PACKAGE_FILE_NAME "isle-${PROJECT_VERSION}-${ISLE_PACKAGE_NAME}") +if(NINTENDO_3DS) + ctr_generate_smdh(isle.smdh + NAME "LEGO Island" + TITLE "LEGO Island" + DESCRIPTION "LEGO Island for the Nintendo 3DS" + AUTHOR "isledecomp/isle-portable" + VERSION "${PROJECT_VERSION}" + ICON "ISLE/res/3ds/isle.png" + ) + + ctr_create_3dsx(isle SMDH isle.smdh) + install(FILES "$/isle.3dsx" DESTINATION "${CMAKE_INSTALL_BINDIR}") +endif() if(MSVC) set(CPACK_GENERATOR ZIP) else() set(CPACK_GENERATOR TGZ) endif() + include(CPack) diff --git a/ISLE/3ds/apthooks.cpp b/ISLE/3ds/apthooks.cpp new file mode 100644 index 00000000..3cd9c3e1 --- /dev/null +++ b/ISLE/3ds/apthooks.cpp @@ -0,0 +1,30 @@ +#include "apthooks.h" + +#include "legomain.h" +#include "misc.h" + +aptHookCookie g_aptCookie; + +void N3DS_AptHookCallback(APT_HookType hookType, void* param) +{ + switch (hookType) { + case APTHOOK_ONSLEEP: + case APTHOOK_ONSUSPEND: + Lego()->Pause(); + break; + case APTHOOK_ONWAKEUP: + case APTHOOK_ONRESTORE: + Lego()->Resume(); + break; + case APTHOOK_ONEXIT: + Lego()->CloseMainWindow(); + break; + default: + break; + } +} + +void N3DS_SetupAptHooks() +{ + aptHook(&g_aptCookie, N3DS_AptHookCallback, NULL); +} diff --git a/ISLE/3ds/apthooks.h b/ISLE/3ds/apthooks.h new file mode 100644 index 00000000..7bfa30f7 --- /dev/null +++ b/ISLE/3ds/apthooks.h @@ -0,0 +1,9 @@ +#ifndef N3DS_APTHOOKS_H +#define N3DS_APTHOOKS_H + +#include <3ds.h> + +void N3DS_AptHookCallback(APT_HookType hookType, void* param); +void N3DS_SetupAptHooks(); + +#endif // N3DS_APTHOOKS_H diff --git a/ISLE/3ds/config.cpp b/ISLE/3ds/config.cpp new file mode 100644 index 00000000..dc5dd8dc --- /dev/null +++ b/ISLE/3ds/config.cpp @@ -0,0 +1,22 @@ +#include "config.h" + +#include +#include + +void N3DS_SetupDefaultConfigOverrides(dictionary* p_dictionary) +{ + SDL_Log("Overriding default config for 3DS"); + + // We are currently not bundling the assets into romfs. + // User must place assets in sdmc:/3ds/isle where + // sdmc:/3ds/isle/LEGO/SCRIPTS/CREDITS.si exists, for example. + iniparser_set(p_dictionary, "isle:diskpath", "sdmc:/3ds/isle/LEGO/disk"); + iniparser_set(p_dictionary, "isle:cdpath", "sdmc:/3ds/isle"); + + // TODO: Save path: can we use libctru FS save data functions? Would be neat, especially for CIA install + // Extra / at the end causes some issues + iniparser_set(p_dictionary, "isle:savepath", "sdmc:/3ds/isle"); + + // Use e_noAnimation/cut transition + iniparser_set(p_dictionary, "isle:Transition Type", "1"); +} diff --git a/ISLE/3ds/config.h b/ISLE/3ds/config.h new file mode 100644 index 00000000..64d3a1b0 --- /dev/null +++ b/ISLE/3ds/config.h @@ -0,0 +1,8 @@ +#ifndef N3DS_CONFIG_H +#define N3DS_CONFIG_H + +#include "dictionary.h" + +void N3DS_SetupDefaultConfigOverrides(dictionary* p_dictionary); + +#endif // N3DS_CONFIG_H diff --git a/ISLE/isleapp.cpp b/ISLE/isleapp.cpp index e700d5b2..e12f9183 100644 --- a/ISLE/isleapp.cpp +++ b/ISLE/isleapp.cpp @@ -50,6 +50,11 @@ #include "emscripten/messagebox.h" #endif +#ifdef __3DS__ +#include "3ds/apthooks.h" +#include "3ds/config.h" +#endif + DECOMP_SIZE_ASSERT(IsleApp, 0x8c) // GLOBAL: ISLE 0x410030 @@ -313,6 +318,9 @@ SDL_AppResult SDL_AppInit(void** appstate, int argc, char** argv) }, NULL ); +#endif +#ifdef __3DS__ + N3DS_SetupAptHooks(); #endif return SDL_APP_CONTINUE; } @@ -654,7 +662,7 @@ MxResult IsleApp::SetupWindow() SDL_SetNumberProperty(props, SDL_PROP_WINDOW_CREATE_HEIGHT_NUMBER, g_targetHeight); SDL_SetBooleanProperty(props, SDL_PROP_WINDOW_CREATE_FULLSCREEN_BOOLEAN, m_fullScreen); SDL_SetStringProperty(props, SDL_PROP_WINDOW_CREATE_TITLE_STRING, WINDOW_TITLE); -#ifdef MINIWIN +#if defined(MINIWIN) && !defined(__3DS__) SDL_SetBooleanProperty(props, SDL_PROP_WINDOW_CREATE_OPENGL_BOOLEAN, true); SDL_GL_SetAttribute(SDL_GL_DOUBLEBUFFER, 1); SDL_GL_SetAttribute(SDL_GL_DEPTH_SIZE, 24); @@ -825,6 +833,9 @@ bool IsleApp::LoadConfig() iniparser_set(dict, "isle:Max Allowed Extras", SDL_itoa(m_maxAllowedExtras, buf, 10)); iniparser_set(dict, "isle:Transition Type", SDL_itoa(m_transitionType, buf, 10)); +#ifdef __3DS__ + N3DS_SetupDefaultConfigOverrides(dict); +#endif iniparser_dump_ini(dict, iniFP); SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION, "New config written at '%s'", iniConfig); fclose(iniFP); diff --git a/ISLE/isleapp.h b/ISLE/isleapp.h index dd7bfdb1..d0a7f523 100644 --- a/ISLE/isleapp.h +++ b/ISLE/isleapp.h @@ -96,6 +96,7 @@ class IsleApp { }; extern IsleApp* g_isle; +extern MxS32 g_closed; extern IDirect3DRMMiniwinDevice* GetD3DRMMiniwinDevice(); diff --git a/ISLE/isledebug.cpp b/ISLE/isledebug.cpp index a8950fa3..ec27004b 100644 --- a/ISLE/isledebug.cpp +++ b/ISLE/isledebug.cpp @@ -309,7 +309,7 @@ void IsleDebug_Render() if (ImGui::TreeNode("Sound Manager")) { LegoSoundManager* soundManager = lego->GetSoundManager(); Sint32 oldVolume = soundManager->GetVolume(); - Sint32 volume = oldVolume; + int volume = oldVolume; ImGui::SliderInt("volume", &volume, 0, 100); if (volume != oldVolume) { soundManager->SetVolume(volume); diff --git a/ISLE/res/3ds/isle.png b/ISLE/res/3ds/isle.png new file mode 100644 index 0000000000000000000000000000000000000000..8fd9f2c4ea61bad4ac49ef913f4f0a5eba8a507d GIT binary patch literal 438 zcmeAS@N?(olHy`uVBq!ia0vp^1|ZDA3?vioaBc-st^qzFu0WcB;lD8hLnyuVVNQ6as5u0Lg)PKpf|zsSgwsED7=phDalk zhh@*cV_;z9_jGX#@i_i=awp#*1p$}s+rC9ar-uIc?=KpVF@5osD>G(TWHwx=Ol)9n z35ZHkjF|q_IO(6ero KMWF%kqVNX_gEV;R43gF*y7-$W0otxA=crn+jmt*e4du; z@3NDj&!m(NMP1bncyujj;v4pr4l7q1BA3ysTzx`at{3CV2`)-H%tUHVt4y;xA!EC>l{bX2DLD};SUZ9ZjboFyt=akR{ E03U~_mH+?% literal 0 HcmV?d00001 diff --git a/miniwin/CMakeLists.txt b/miniwin/CMakeLists.txt index 508e685d..851403cc 100644 --- a/miniwin/CMakeLists.txt +++ b/miniwin/CMakeLists.txt @@ -54,6 +54,23 @@ else() message(STATUS "🧩 OpenGL ES 2.x support not enabled") endif() +if(NINTENDO_3DS) + if(ISLE_DEBUG) + find_library(CITRO3D_LIBRARY NAMES citro3dd) + else() + find_library(CITRO3D_LIBRARY NAMES citro3d) + endif() + if(CITRO3D_LIBRARY) + message(STATUS "Found citro3d: enabling Citro3D renderer") + target_sources(miniwin PRIVATE src/d3drm/backends/citro3d/renderer.cpp) + ctr_add_shader_library(vshader src/d3drm/backends/citro3d/vshader.v.pica) + dkp_add_embedded_binary_library(3ds_shaders vshader) + target_link_libraries(miniwin PRIVATE ${CITRO3D_LIBRARY} 3ds_shaders) + else() + message(STATUS "🧩 Citro3D support not enabled") + endif() +endif() + if(WIN32) target_sources(miniwin PRIVATE src/d3drm/backends/directx9/actual.cpp diff --git a/miniwin/src/d3drm/backends/citro3d/renderer.cpp b/miniwin/src/d3drm/backends/citro3d/renderer.cpp new file mode 100644 index 00000000..f9167401 --- /dev/null +++ b/miniwin/src/d3drm/backends/citro3d/renderer.cpp @@ -0,0 +1,606 @@ +#include "d3drmrenderer.h" +#include "d3drmrenderer_citro3d.h" +#include "d3drmtexture_impl.h" +#include "ddraw_impl.h" +#include "meshutils.h" +#include "miniwin.h" +#include "vshader_shbin.h" + +#include + +static bool g_rendering = false; + +static DVLB_s* vshader_dvlb; +static shaderProgram_s program; +static int uLoc_projection; +static int uLoc_modelView; +static int uLoc_meshColor; +static int uLoc_lightVec; +static int uLoc_lightClr; +static int uLoc_shininess; + +Citro3DRenderer::Citro3DRenderer(DWORD width, DWORD height) +{ + m_width = 320; + m_height = 240; + m_virtualWidth = width; + m_virtualHeight = height; + + gfxInitDefault(); + consoleInit(GFX_TOP, nullptr); + C3D_Init(C3D_DEFAULT_CMDBUF_SIZE); + + m_renderTarget = C3D_RenderTargetCreate(m_height, m_width, GPU_RB_RGBA8, GPU_RB_DEPTH24_STENCIL8); + C3D_RenderTargetSetOutput( + m_renderTarget, + GFX_BOTTOM, + GFX_LEFT, + GX_TRANSFER_FLIP_VERT(0) | GX_TRANSFER_OUT_TILED(0) | GX_TRANSFER_RAW_COPY(0) | + GX_TRANSFER_IN_FORMAT(GX_TRANSFER_FMT_RGBA8) | GX_TRANSFER_OUT_FORMAT(GX_TRANSFER_FMT_RGB8) | + GX_TRANSFER_SCALING(GX_TRANSFER_SCALE_NO) + ); + + vshader_dvlb = DVLB_ParseFile((u32*) vshader_shbin, vshader_shbin_size); + shaderProgramInit(&program); + shaderProgramSetVsh(&program, &vshader_dvlb->DVLE[0]); + C3D_BindProgram(&program); + + C3D_CullFace(GPU_CULL_FRONT_CCW); + + uLoc_projection = shaderInstanceGetUniformLocation(program.vertexShader, "projection"); + uLoc_modelView = shaderInstanceGetUniformLocation(program.vertexShader, "modelView"); + uLoc_meshColor = shaderInstanceGetUniformLocation(program.vertexShader, "meshColor"); + uLoc_lightVec = shaderInstanceGetUniformLocation(program.vertexShader, "lightVec"); + uLoc_lightClr = shaderInstanceGetUniformLocation(program.vertexShader, "lightClr"); + uLoc_shininess = shaderInstanceGetUniformLocation(program.vertexShader, "shininess"); + + C3D_AttrInfo* attrInfo = C3D_GetAttrInfo(); + AttrInfo_Init(attrInfo); + AttrInfo_AddLoader(attrInfo, 0, GPU_FLOAT, 3); // v0=position + AttrInfo_AddLoader(attrInfo, 1, GPU_FLOAT, 3); // v2=normal + AttrInfo_AddLoader(attrInfo, 2, GPU_FLOAT, 2); // v1=texcoord +} + +Citro3DRenderer::~Citro3DRenderer() +{ + shaderProgramFree(&program); + DVLB_Free(vshader_dvlb); + C3D_Fini(); + gfxExit(); +} + +void Citro3DRenderer::PushLights(const SceneLight* lights, size_t count) +{ + m_lights.assign(lights, lights + count); +} + +void Citro3DRenderer::SetProjection(const D3DRMMATRIX4D& projection, D3DVALUE front, D3DVALUE back) +{ + memcpy(&m_projection, projection, sizeof(D3DRMMATRIX4D)); +} + +void Citro3DRenderer::SetFrustumPlanes(const Plane* frustumPlanes) +{ +} + +struct Citro3DCacheDestroyContext { + Citro3DRenderer* renderer; + Uint32 id; +}; + +void Citro3DRenderer::AddTextureDestroyCallback(Uint32 id, IDirect3DRMTexture* texture) +{ + auto* ctx = new Citro3DCacheDestroyContext{this, id}; + texture->AddDestroyCallback( + [](IDirect3DRMObject* obj, void* arg) { + auto* ctx = static_cast(arg); + auto& entry = ctx->renderer->m_textures[ctx->id]; + if (entry.texture) { + C3D_TexDelete(&entry.c3dTex); + entry.texture = nullptr; + } + delete ctx; + }, + ctx + ); +} + +static int NearestPowerOfTwoClamp(int val) +{ + static const int sizes[] = {8, 16, 32, 64, 128, 256, 512}; + for (int size : sizes) { + if (val <= size) { + return size; + } + } + return 512; +} + +static SDL_Surface* ConvertAndResizeSurface(SDL_Surface* original, bool isUI, float scale) +{ + SDL_Surface* converted = SDL_ConvertSurface(original, SDL_PIXELFORMAT_RGBA8888); + if (!converted) { + return nullptr; + } + if (!isUI) { + return converted; + } + + int scaledW = static_cast(converted->w * scale); + int scaledH = static_cast(converted->h * scale); + + int paddedW = NearestPowerOfTwoClamp(scaledW); + int paddedH = NearestPowerOfTwoClamp(scaledH); + + SDL_Surface* padded = SDL_CreateSurface(paddedW, paddedH, SDL_PIXELFORMAT_RGBA8888); + if (!padded) { + SDL_DestroySurface(converted); + return nullptr; + } + + SDL_Rect dstRect = {0, 0, scaledW, scaledH}; + SDL_BlitSurfaceScaled(converted, nullptr, padded, &dstRect, SDL_SCALEMODE_LINEAR); + SDL_DestroySurface(converted); + + return padded; +} + +static void EncodeTextureLayout(const u8* src, u8* dst, int width, int height) +{ + const int tileSize = 8; + const int bytesPerPixel = 4; + + int tilesPerRow = (width + tileSize - 1) / tileSize; + + static const uint8_t mortonLUT[64] = {0, 1, 4, 5, 16, 17, 20, 21, 2, 3, 6, 7, 18, 19, 22, 23, + 8, 9, 12, 13, 24, 25, 28, 29, 10, 11, 14, 15, 26, 27, 30, 31, + 32, 33, 36, 37, 48, 49, 52, 53, 34, 35, 38, 39, 50, 51, 54, 55, + 40, 41, 44, 45, 56, 57, 60, 61, 42, 43, 46, 47, 58, 59, 62, 63}; + + for (int tileY = 0; tileY < height; tileY += tileSize) { + for (int tileX = 0; tileX < width; tileX += tileSize) { + int tileIndex = (tileY / tileSize) * tilesPerRow + (tileX / tileSize); + tileIndex *= tileSize * tileSize; + + for (int y = 0; y < tileSize; ++y) { + for (int x = 0; x < tileSize; ++x) { + int srcX = tileX + x; + int srcY = tileY + y; + + if (srcX >= width || srcY >= height) { + continue; + } + + int morton = mortonLUT[y * tileSize + x]; + int dstIndex = (tileIndex + morton) * bytesPerPixel; + int srcIndex = ((height - 1 - srcY) * width + srcX); + + *(u32*) &dst[dstIndex] = ((u32*) src)[srcIndex]; + } + } + } + } +} + +static bool ConvertAndUploadTexture(C3D_Tex* tex, SDL_Surface* originalSurface, bool isUI, float scale) +{ + SDL_Surface* resized = ConvertAndResizeSurface(originalSurface, isUI, scale); + if (!resized) { + return false; + } + + int width = resized->w; + int height = resized->h; + + C3D_TexInitParams params = {}; + params.width = width; + params.height = height; + params.format = GPU_RGBA8; + params.maxLevel = isUI ? 0 : 4; + params.type = GPU_TEX_2D; + if (!C3D_TexInitWithParams(tex, nullptr, params)) { + SDL_DestroySurface(resized); + return false; + } + + uint8_t* tiledData = (uint8_t*) malloc(width * height * 4); + if (!tiledData) { + SDL_DestroySurface(resized); + return false; + } + + EncodeTextureLayout((const u8*) resized->pixels, tiledData, width, height); + SDL_DestroySurface(resized); + + C3D_TexUpload(tex, tiledData); + free(tiledData); + + if (isUI) { + C3D_TexSetFilter(tex, GPU_NEAREST, GPU_NEAREST); + C3D_TexSetWrap(tex, GPU_CLAMP_TO_EDGE, GPU_CLAMP_TO_EDGE); + } + else { + C3D_TexSetFilter(tex, GPU_LINEAR, GPU_LINEAR); + C3D_TexSetWrap(tex, GPU_REPEAT, GPU_REPEAT); + C3D_TexSetFilterMipmap(tex, GPU_LINEAR); + C3D_TexGenerateMipmap(tex, GPU_TEXFACE_2D); + } + + return true; +} + +Uint32 Citro3DRenderer::GetTextureId(IDirect3DRMTexture* iTexture, bool isUi) +{ + auto texture = static_cast(iTexture); + auto surface = static_cast(texture->m_surface); + SDL_Surface* originalSurface = surface->m_surface; + + int originalW = originalSurface->w; + int originalH = originalSurface->h; + + for (Uint32 i = 0; i < m_textures.size(); ++i) { + auto& tex = m_textures[i]; + if (tex.texture == texture) { + if (tex.version != texture->m_version) { + C3D_TexDelete(&tex.c3dTex); + if (!ConvertAndUploadTexture(&tex.c3dTex, originalSurface, isUi, m_viewportTransform.scale)) { + return NO_TEXTURE_ID; + } + + tex.version = texture->m_version; + tex.width = NearestPowerOfTwoClamp(originalW * m_viewportTransform.scale); + tex.height = NearestPowerOfTwoClamp(originalH * m_viewportTransform.scale); + } + return i; + } + } + + C3DTextureCacheEntry entry; + entry.texture = texture; + entry.version = texture->m_version; + entry.width = NearestPowerOfTwoClamp(originalW * m_viewportTransform.scale); + entry.height = NearestPowerOfTwoClamp(originalH * m_viewportTransform.scale); + + if (!ConvertAndUploadTexture(&entry.c3dTex, originalSurface, isUi, m_viewportTransform.scale)) { + return NO_TEXTURE_ID; + } + + for (Uint32 i = 0; i < m_textures.size(); ++i) { + if (!m_textures[i].texture) { + m_textures[i] = std::move(entry); + AddTextureDestroyCallback(i, texture); + return i; + } + } + + m_textures.push_back(std::move(entry)); + AddTextureDestroyCallback((Uint32) (m_textures.size() - 1), texture); + return (Uint32) (m_textures.size() - 1); +} + +C3DMeshCacheEntry C3DUploadMesh(const MeshGroup& meshGroup) +{ + C3DMeshCacheEntry cache{&meshGroup, meshGroup.version}; + + std::vector vertexBuffer; + std::vector indexBuffer; + + if (meshGroup.quality == D3DRMRENDER_FLAT || meshGroup.quality == D3DRMRENDER_UNLITFLAT) { + FlattenSurfaces( + meshGroup.vertices.data(), + meshGroup.vertices.size(), + meshGroup.indices.data(), + meshGroup.indices.size(), + meshGroup.texture != nullptr, + vertexBuffer, + indexBuffer + ); + } + else { + vertexBuffer.assign(meshGroup.vertices.begin(), meshGroup.vertices.end()); + indexBuffer.assign(meshGroup.indices.begin(), meshGroup.indices.end()); + } + + // Flatten vertices as IBO is buggy on 3DS hardware + std::vector vertexUploadBuffer; + vertexUploadBuffer.reserve(indexBuffer.size()); + + for (size_t i = 0; i < indexBuffer.size(); ++i) { + vertexUploadBuffer.emplace_back(vertexBuffer[indexBuffer[i]]); + } + + size_t vertexBufferSize = vertexUploadBuffer.size() * sizeof(D3DRMVERTEX); + cache.vbo = linearAlloc(vertexBufferSize); + memcpy(cache.vbo, vertexUploadBuffer.data(), vertexBufferSize); + cache.vertexCount = vertexUploadBuffer.size(); + + return cache; +} + +void Citro3DRenderer::AddMeshDestroyCallback(Uint32 id, IDirect3DRMMesh* mesh) +{ + auto* ctx = new Citro3DCacheDestroyContext{this, id}; + mesh->AddDestroyCallback( + [](IDirect3DRMObject* obj, void* arg) { + auto* ctx = static_cast(arg); + auto& cacheEntry = ctx->renderer->m_meshs[ctx->id]; + if (cacheEntry.meshGroup) { + cacheEntry.meshGroup = nullptr; + linearFree(cacheEntry.vbo); + cacheEntry.vertexCount = 0; + } + delete ctx; + }, + ctx + ); +} + +Uint32 Citro3DRenderer::GetMeshId(IDirect3DRMMesh* mesh, const MeshGroup* meshGroup) +{ + for (Uint32 i = 0; i < m_meshs.size(); ++i) { + auto& cache = m_meshs[i]; + if (cache.meshGroup == meshGroup) { + if (cache.version != meshGroup->version) { + cache = std::move(C3DUploadMesh(*meshGroup)); + } + return i; + } + } + + auto newCache = C3DUploadMesh(*meshGroup); + + for (Uint32 i = 0; i < m_meshs.size(); ++i) { + auto& cache = m_meshs[i]; + if (!cache.meshGroup) { + cache = std::move(newCache); + AddMeshDestroyCallback(i, mesh); + return i; + } + } + + m_meshs.push_back(std::move(newCache)); + AddMeshDestroyCallback((Uint32) (m_meshs.size() - 1), mesh); + return (Uint32) (m_meshs.size() - 1); +} + +void Citro3DRenderer::StartFrame() +{ + if (g_rendering) { + return; + } + C3D_FrameBegin(C3D_FRAME_SYNCDRAW); + C3D_FrameDrawOn(m_renderTarget); + g_rendering = true; +} + +void ConvertPerspective(const D3DRMMATRIX4D in, C3D_Mtx* out) +{ + float f_h = in[0][0]; + float f_v = in[1][1]; + + float aspect = f_v / f_h; + float fovY = 2.0f * atanf(1.0f / f_v); + + float nearZ = -in[3][2] / in[2][2]; + float farZ = nearZ * in[2][2] / (in[2][2] - 1.0f); + + Mtx_PerspTilt(out, fovY, aspect, nearZ, farZ, true); +} + +HRESULT Citro3DRenderer::BeginFrame() +{ + StartFrame(); + C3D_DepthTest(true, GPU_GREATER, GPU_WRITE_ALL); + + C3D_Mtx projection; + ConvertPerspective(m_projection, &projection); + C3D_FVUnifMtx4x4(GPU_VERTEX_SHADER, uLoc_projection, &projection); + + for (const auto& light : m_lights) { + FColor lightColor = light.color; + if (light.positional == 0.0f && light.directional == 0.0f) { + // Ambient light + C3D_FVUnifSet(GPU_VERTEX_SHADER, uLoc_lightClr + 2, lightColor.r, lightColor.g, lightColor.b, 1.0f); + } + else if (light.directional == 1.0f) { + C3D_FVUnifSet( + GPU_VERTEX_SHADER, + uLoc_lightVec + 1, + -light.direction.x, + -light.direction.y, + -light.direction.z, + 0.0f + ); + C3D_FVUnifSet(GPU_VERTEX_SHADER, uLoc_lightClr + 1, lightColor.r, lightColor.g, lightColor.b, 0.0f); + } + else if (light.positional == 1.0f) { + C3D_FVUnifSet( + GPU_VERTEX_SHADER, + uLoc_lightVec + 0, + light.position.x, + light.position.y, + light.position.z, + 0.0f + ); + C3D_FVUnifSet(GPU_VERTEX_SHADER, uLoc_lightClr + 0, lightColor.r, lightColor.g, lightColor.b, 0.0f); + } + } + + return S_OK; +} + +void Citro3DRenderer::EnableTransparency() +{ + C3D_DepthTest(true, GPU_GREATER, GPU_WRITE_COLOR); +} + +void ConvertMatrix(const D3DRMMATRIX4D in, C3D_Mtx* out) +{ + for (int i = 0; i < 4; i++) { + out->r[i].x = in[0][i]; + out->r[i].y = in[1][i]; + out->r[i].z = in[2][i]; + out->r[i].w = in[3][i]; + } +} + +void Citro3DRenderer::SubmitDraw( + DWORD meshId, + const D3DRMMATRIX4D& modelViewMatrix, + const D3DRMMATRIX4D& worldMatrix, + const D3DRMMATRIX4D& viewMatrix, + const Matrix3x3& normalMatrix, + const Appearance& appearance +) +{ + C3D_Mtx modelView; + ConvertMatrix(modelViewMatrix, &modelView); + C3D_FVUnifMtx4x4(GPU_VERTEX_SHADER, uLoc_modelView, &modelView); + + auto& mesh = m_meshs[meshId]; + + C3D_BufInfo* bufInfo = C3D_GetBufInfo(); + BufInfo_Init(bufInfo); + BufInfo_Add(bufInfo, mesh.vbo, sizeof(D3DRMVERTEX), 3, 0x210); + + C3D_FVUnifSet( + GPU_VERTEX_SHADER, + uLoc_meshColor, + appearance.color.r / 255.0f, + appearance.color.g / 255.0f, + appearance.color.b / 255.0f, + appearance.color.a / 255.0f + ); + + C3D_FVUnifSet(GPU_VERTEX_SHADER, uLoc_shininess, appearance.shininess / 255.0f, 0.0f, 0.0f, 0.0f); + + if (appearance.textureId != NO_TEXTURE_ID) { + C3D_TexBind(0, &m_textures[appearance.textureId].c3dTex); + C3D_TexEnv* env = C3D_GetTexEnv(0); + C3D_TexEnvInit(env); + C3D_TexEnvSrc(env, C3D_Both, GPU_TEXTURE0, GPU_PRIMARY_COLOR, GPU_PRIMARY_COLOR); + C3D_TexEnvFunc(env, C3D_Both, GPU_MODULATE); + } + else { + C3D_TexBind(0, nullptr); + C3D_TexEnv* env = C3D_GetTexEnv(0); + C3D_TexEnvInit(env); + C3D_TexEnvSrc(env, C3D_Both, GPU_PRIMARY_COLOR, GPU_PRIMARY_COLOR, GPU_PRIMARY_COLOR); + C3D_TexEnvFunc(env, C3D_Both, GPU_REPLACE); + } + + C3D_DrawArrays(GPU_TRIANGLES, 0, mesh.vertexCount); +} + +HRESULT Citro3DRenderer::FinalizeFrame() +{ + return S_OK; +} + +void Citro3DRenderer::Resize(int width, int height, const ViewportTransform& viewportTransform) +{ + m_width = width; + m_height = height; + m_viewportTransform = viewportTransform; +} + +void Citro3DRenderer::Clear(float r, float g, float b) +{ + StartFrame(); + u32 color = + (static_cast(r * 255) << 24) | (static_cast(g * 255) << 16) | (static_cast(b * 255) << 8) | 255; + C3D_RenderTargetClear(m_renderTarget, C3D_CLEAR_ALL, color, 0); +} + +void Citro3DRenderer::Flip() +{ + C3D_FrameEnd(0); + gfxFlushBuffers(); + gspWaitForVBlank(); + g_rendering = false; +} + +void Citro3DRenderer::Draw2DImage(Uint32 textureId, const SDL_Rect& srcRect, const SDL_Rect& dstRect) +{ + C3D_AlphaBlend(GPU_BLEND_ADD, GPU_BLEND_ADD, GPU_ONE, GPU_ONE_MINUS_SRC_ALPHA, GPU_ONE, GPU_ONE_MINUS_SRC_ALPHA); + StartFrame(); + C3D_DepthTest(false, GPU_GREATER, GPU_WRITE_COLOR); + + float left = -m_viewportTransform.offsetX / m_viewportTransform.scale; + float right = (m_width - m_viewportTransform.offsetX) / m_viewportTransform.scale; + float top = -m_viewportTransform.offsetY / m_viewportTransform.scale; + float bottom = (m_height - m_viewportTransform.offsetY) / m_viewportTransform.scale; + + C3D_Mtx projection, modelView; + Mtx_OrthoTilt(&projection, left, right, bottom, top, 0.0f, 1.0f, true); + C3D_FVUnifMtx4x4(GPU_VERTEX_SHADER, uLoc_projection, &projection); + Mtx_Identity(&modelView); + C3D_FVUnifMtx4x4(GPU_VERTEX_SHADER, uLoc_modelView, &modelView); + + // Set light directions + C3D_FVUnifSet(GPU_VERTEX_SHADER, uLoc_lightVec + 0, 0.0f, 0.0f, 0.0f, 0.0f); + C3D_FVUnifSet(GPU_VERTEX_SHADER, uLoc_lightVec + 1, 0.0f, 0.0f, 0.0f, 0.0f); + + // Set light colors + C3D_FVUnifSet(GPU_VERTEX_SHADER, uLoc_lightClr + 0, 0.0f, 0.0f, 0.0f, 0.0f); + C3D_FVUnifSet(GPU_VERTEX_SHADER, uLoc_lightClr + 1, 0.0f, 0.0f, 0.0f, 0.0f); + C3D_FVUnifSet(GPU_VERTEX_SHADER, uLoc_lightClr + 2, 1.0f, 1.0f, 1.0f, 1.0f); // Ambient + + C3D_FVUnifSet(GPU_VERTEX_SHADER, uLoc_shininess, 0.0f, 0.0f, 0.0f, 0.0f); + C3D_FVUnifSet(GPU_VERTEX_SHADER, uLoc_meshColor, 1.0f, 1.0f, 1.0f, 1.0f); + + C3DTextureCacheEntry& texture = m_textures[textureId]; + + C3D_TexBind(0, &texture.c3dTex); + C3D_TexEnv* env = C3D_GetTexEnv(0); + C3D_TexEnvInit(env); + C3D_TexEnvSrc(env, C3D_Both, GPU_TEXTURE0, GPU_PRIMARY_COLOR, GPU_PRIMARY_COLOR); + C3D_TexEnvFunc(env, C3D_Both, GPU_MODULATE); + + float scale = m_viewportTransform.scale; + + float x1 = static_cast(dstRect.x); + float y1 = static_cast(dstRect.y); + float x2 = x1 + static_cast(dstRect.w); + float y2 = y1 + static_cast(dstRect.h); + + float u0 = (srcRect.x * scale) / texture.width; + float u1 = ((srcRect.x + srcRect.w) * scale) / texture.width; + float v0 = (srcRect.y * scale) / texture.height; + float v1 = ((srcRect.y + srcRect.h) * scale) / texture.height; + + C3D_ImmDrawBegin(GPU_TRIANGLES); + + // Triangle 1 + C3D_ImmSendAttrib(x1, y1, 0.5f, 0.0f); + C3D_ImmSendAttrib(0.0f, 0.0f, 1.0f, 0.0f); + C3D_ImmSendAttrib(u0, v0, 0.0f, 0.0f); + + C3D_ImmSendAttrib(x2, y1, 0.5f, 0.0f); + C3D_ImmSendAttrib(0.0f, 0.0f, 1.0f, 0.0f); + C3D_ImmSendAttrib(u1, v0, 0.0f, 0.0f); + + C3D_ImmSendAttrib(x2, y2, 0.5f, 0.0f); + C3D_ImmSendAttrib(0.0f, 0.0f, 1.0f, 0.0f); + C3D_ImmSendAttrib(u1, v1, 0.0f, 0.0f); + + // Triangle 2 + C3D_ImmSendAttrib(x2, y2, 0.5f, 0.0f); + C3D_ImmSendAttrib(0.0f, 0.0f, 1.0f, 0.0f); + C3D_ImmSendAttrib(u1, v1, 0.0f, 0.0f); + + C3D_ImmSendAttrib(x1, y2, 0.5f, 0.0f); + C3D_ImmSendAttrib(0.0f, 0.0f, 1.0f, 0.0f); + C3D_ImmSendAttrib(u0, v1, 0.0f, 0.0f); + + C3D_ImmSendAttrib(x1, y1, 0.5f, 0.0f); + C3D_ImmSendAttrib(0.0f, 0.0f, 1.0f, 0.0f); + C3D_ImmSendAttrib(u0, v0, 0.0f, 0.0f); + + C3D_ImmDrawEnd(); +} + +void Citro3DRenderer::Download(SDL_Surface* target) +{ + MINIWIN_NOT_IMPLEMENTED(); +} diff --git a/miniwin/src/d3drm/backends/citro3d/vshader.v.pica b/miniwin/src/d3drm/backends/citro3d/vshader.v.pica new file mode 100644 index 00000000..cd7f91c4 --- /dev/null +++ b/miniwin/src/d3drm/backends/citro3d/vshader.v.pica @@ -0,0 +1,130 @@ +; Uniforms +.fvec projection[4], modelView[4], meshColor +.fvec lightVec[2], lightClr[3], shininess + +; Constants +.constf myconst(0.0, 1.0, -1.0, -0.5) + +; Outputs +.out outpos position +.out outtc0 texcoord0 +.out outclr color + +; Inputs +.alias inpos v0 +.alias innrm v1 +.alias intex v2 + +.proc main + ; Prepare constants in usable temp regs + mov r15.x, myconst.x ; 0.0 + mov r15.y, myconst.y ; 1.0 + mov r15.z, myconst.z ; -1.0 + + ; Force the w component of inpos to be 1.0 + mov r0.xyz, inpos + mov r0.w, r15.y + + ; r1 = modelView * inpos + dp4 r1.x, modelView[0], r0 + dp4 r1.y, modelView[1], r0 + dp4 r1.z, modelView[2], r0 + dp4 r1.w, modelView[3], r0 + + ; outpos = projection * r1 + dp4 outpos.x, projection[0], r1 + dp4 outpos.y, projection[1], r1 + dp4 outpos.z, projection[2], r1 + dp4 outpos.w, projection[3], r1 + + ; outtex = intex + mov outtc0, intex + mov outtc0.zw, myconst.xy + + ; Transform normal + mov r2.xyz, innrm + mov r2.w, r15.x + dp4 r3.x, modelView[0], r2 + dp4 r3.y, modelView[1], r2 + dp4 r3.z, modelView[2], r2 + mov r3.w, r15.x + dp3 r4.x, r3, r3 + rsq r4.x, r4.x + mul r3, r4.xxxx, r3 ; r3 = normalized normal + + ; Normalize lightVec[0] + mov r5, lightVec[0] + dp3 r6.x, r5, r5 + rsq r6.x, r6.x + mul r5, r6.xxxx, r5 + + ; dot(normal, lightVec[0]) + dp3 r6.x, r3, r5 + max r6.x, r6.x, r15.xxxx + + ; Normalize lightVec[1] + mov r7, lightVec[1] + dp3 r8.x, r7, r7 + rsq r8.x, r8.x + mul r7, r8.xxxx, r7 + + ; dot(normal, lightVec[1]) + dp3 r6.y, r3, r7 + max r6.y, r6.y, r15.xxxx + + ; Load lightClr + mov r8, lightClr[2] ; ambient + mov r9, lightClr[0] ; point + mov r10, lightClr[1] ; directional + + ; diffuse = ambient + (lightClr[0] * dot0) + (lightClr[1] * dot1) + mul r11, r9, r6.xxxx + add r8, r8, r11 + mul r11, r10, r6.yyyy + add r8, r8, r11 ; r8 = diffuse + + ; Check if shininess > 0 + mov r12, shininess + slt r13.x, r15.x, r12.x + + ; viewVec = normalize(-position.xyz) + mov r14.xyz, r1.xyz + mul r14.xyz, r14.xyz, r15.zzz + dp3 r4.x, r14, r14 + rsq r4.x, r4.x + mul r14, r4.xxxx, r14 + + ; H = normalize(view + lightVec[1]) + add r11, r14, r7 + dp3 r4.x, r11, r11 + rsq r4.x, r4.x + mul r11, r4.xxxx, r11 + + ; dot(normal, H) + dp3 r4.x, r3, r11 + max r4.x, r4.x, r15.x + + ; Approximate pow(dotNH, 10) by repeated multiplication + mul r5.x, r4.x, r4.x ; dotNH^2 + mul r5.x, r5.x, r5.x ; dotNH^4 + mul r5.x, r5.x, r5.x ; dotNH^8 + mul r4.x, r5.x, r4.x ; dotNH^9 + mul r4.x, r4.x, r4.x ; dotNH^10 + + ; Multiply by shininess > 0 flag + mul r4.x, r4.x, r13.x + + ; specular = lightClr[1] * spec + mul r5, r10, r4.xxxx + + ; final = diffuse * meshColor + specular * lightClr[1] + mov r9, meshColor + mul r6, r8, r9 ; diffuse * meshColor + add r7.xyz, r6.xyz, r5.xyz ; add specular (already multiplied by lightClr) + min r7.xyz, r7.xyz, r15.yyyy + + mov outclr.xyz, r7.xyz + mov outclr.w, meshColor.w + + end +.end diff --git a/miniwin/src/d3drm/backends/software/renderer.cpp b/miniwin/src/d3drm/backends/software/renderer.cpp index 6b969769..02c5bfc0 100644 --- a/miniwin/src/d3drm/backends/software/renderer.cpp +++ b/miniwin/src/d3drm/backends/software/renderer.cpp @@ -80,7 +80,7 @@ void Direct3DRMSoftwareRenderer::ClearZBuffer() _mm_empty(); } #endif -#elif defined(__arm__) || defined(__aarch64__) +#elif (defined(__arm__) || defined(__aarch64__)) && !defined(__3DS__) if (SDL_HasNEON()) { float32x4_t inf4 = vdupq_n_f32(inf); for (; i + 4 <= size; i += 4) { diff --git a/miniwin/src/d3drm/d3drm.cpp b/miniwin/src/d3drm/d3drm.cpp index e8df37b1..45a4e3ad 100644 --- a/miniwin/src/d3drm/d3drm.cpp +++ b/miniwin/src/d3drm/d3drm.cpp @@ -13,6 +13,9 @@ #ifdef USE_OPENGLES2 #include "d3drmrenderer_opengles2.h" #endif +#ifdef __3DS__ +#include "d3drmrenderer_citro3d.h" +#endif #ifdef _WIN32 #include "d3drmrenderer_directx9.h" #endif @@ -159,6 +162,11 @@ HRESULT Direct3DRMImpl::CreateDeviceFromSurface( DDRenderer = OpenGL1Renderer::Create(DDSDesc.dwWidth, DDSDesc.dwHeight); } #endif +#ifdef __3DS__ + else if (SDL_memcmp(&guid, &Citro3D_GUID, sizeof(GUID)) == 0) { + DDRenderer = new Citro3DRenderer(DDSDesc.dwWidth, DDSDesc.dwHeight); + } +#endif #ifdef _WIN32 else if (SDL_memcmp(&guid, &DirectX9_GUID, sizeof(GUID)) == 0) { DDRenderer = DirectX9Renderer::Create(DDSDesc.dwWidth, DDSDesc.dwHeight); diff --git a/miniwin/src/d3drm/d3drmdevice.cpp b/miniwin/src/d3drm/d3drmdevice.cpp index f6b2f9f7..b738c809 100644 --- a/miniwin/src/d3drm/d3drmdevice.cpp +++ b/miniwin/src/d3drm/d3drmdevice.cpp @@ -155,6 +155,10 @@ void Direct3DRMDevice2Impl::Resize() { int width, height; SDL_GetWindowSizeInPixels(DDWindow, &width, &height); +#ifdef __3DS__ + width = 320; // We are on the lower screen + height = 240; +#endif m_viewportTransform = CalculateViewportTransform(m_virtualWidth, m_virtualHeight, width, height); m_renderer->Resize(width, height, m_viewportTransform); for (int i = 0; i < m_viewports->GetSize(); i++) { diff --git a/miniwin/src/ddraw/ddraw.cpp b/miniwin/src/ddraw/ddraw.cpp index 018fffd1..a3bc5d4d 100644 --- a/miniwin/src/ddraw/ddraw.cpp +++ b/miniwin/src/ddraw/ddraw.cpp @@ -4,6 +4,9 @@ #ifdef USE_OPENGLES2 #include "d3drmrenderer_opengles2.h" #endif +#ifdef __3DS__ +#include "d3drmrenderer_citro3d.h" +#endif #ifdef _WIN32 #include "d3drmrenderer_directx9.h" #endif @@ -232,6 +235,9 @@ HRESULT DirectDrawImpl::EnumDevices(LPD3DENUMDEVICESCALLBACK cb, void* ctx) #ifdef USE_OPENGL1 OpenGL1Renderer_EnumDevice(cb, ctx); #endif +#ifdef __3DS__ + Citro3DRenderer_EnumDevice(cb, ctx); +#endif #ifdef _WIN32 DirectX9Renderer_EnumDevice(cb, ctx); #endif @@ -346,6 +352,11 @@ HRESULT DirectDrawImpl::CreateDevice( DDRenderer = OpenGL1Renderer::Create(DDSDesc.dwWidth, DDSDesc.dwHeight); } #endif +#ifdef __3DS__ + else if (SDL_memcmp(&guid, &Citro3D_GUID, sizeof(GUID)) == 0) { + DDRenderer = new Citro3DRenderer(DDSDesc.dwWidth, DDSDesc.dwHeight); + } +#endif #ifdef _WIN32 else if (SDL_memcmp(&guid, &DirectX9_GUID, sizeof(GUID)) == 0) { DDRenderer = DirectX9Renderer::Create(DDSDesc.dwWidth, DDSDesc.dwHeight); diff --git a/miniwin/src/internal/d3drmrenderer_citro3d.h b/miniwin/src/internal/d3drmrenderer_citro3d.h new file mode 100644 index 00000000..426f75c8 --- /dev/null +++ b/miniwin/src/internal/d3drmrenderer_citro3d.h @@ -0,0 +1,82 @@ +#pragma once + +#include "d3drmrenderer.h" +#include "ddraw_impl.h" + +#include +#include +#include + +DEFINE_GUID(Citro3D_GUID, 0x682656F3, 0x0000, 0x0000, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3D, 0x53); + +struct C3DTextureCacheEntry { + IDirect3DRMTexture* texture; + Uint32 version; + C3D_Tex c3dTex; + uint16_t width; + uint16_t height; +}; + +struct C3DMeshCacheEntry { + const MeshGroup* meshGroup = nullptr; + int version = 0; + void* vbo = nullptr; + int vertexCount = 0; +}; + +class Citro3DRenderer : public Direct3DRMRenderer { +public: + Citro3DRenderer(DWORD width, DWORD height); + ~Citro3DRenderer() override; + + void PushLights(const SceneLight* lightsArray, size_t count) override; + void SetProjection(const D3DRMMATRIX4D& projection, D3DVALUE front, D3DVALUE back) override; + void SetFrustumPlanes(const Plane* frustumPlanes) override; + Uint32 GetTextureId(IDirect3DRMTexture* texture, bool isUi) override; + Uint32 GetMeshId(IDirect3DRMMesh* mesh, const MeshGroup* meshGroup) override; + HRESULT BeginFrame() override; + void EnableTransparency() override; + void SubmitDraw( + DWORD meshId, + const D3DRMMATRIX4D& modelViewMatrix, + const D3DRMMATRIX4D& worldMatrix, + const D3DRMMATRIX4D& viewMatrix, + const Matrix3x3& normalMatrix, + const Appearance& appearance + ) override; + HRESULT FinalizeFrame() override; + void Resize(int width, int height, const ViewportTransform& viewportTransform) override; + void Clear(float r, float g, float b) override; + void Flip() override; + void Draw2DImage(Uint32 textureId, const SDL_Rect& srcRect, const SDL_Rect& dstRect) override; + void Download(SDL_Surface* target) override; + +private: + void AddTextureDestroyCallback(Uint32 id, IDirect3DRMTexture* texture); + void AddMeshDestroyCallback(Uint32 id, IDirect3DRMMesh* mesh); + void StartFrame(); + + D3DRMMATRIX4D m_projection; + SDL_Surface* m_renderedImage; + C3D_RenderTarget* m_renderTarget; + std::vector m_textures; + std::vector m_meshs; + ViewportTransform m_viewportTransform; + std::vector m_lights; +}; + +inline static void Citro3DRenderer_EnumDevice(LPD3DENUMDEVICESCALLBACK cb, void* ctx) +{ + D3DDEVICEDESC halDesc = {}; + halDesc.dcmColorModel = D3DCOLOR_RGB; + halDesc.dwFlags = D3DDD_DEVICEZBUFFERBITDEPTH; + halDesc.dwDeviceZBufferBitDepth = DDBD_24; + halDesc.dwDeviceRenderBitDepth = DDBD_32; + halDesc.dpcTriCaps.dwTextureCaps = D3DPTEXTURECAPS_PERSPECTIVE; + halDesc.dpcTriCaps.dwShadeCaps = D3DPSHADECAPS_ALPHAFLATBLEND; + halDesc.dpcTriCaps.dwTextureFilterCaps = D3DPTFILTERCAPS_LINEAR; + + D3DDEVICEDESC helDesc = {}; + + EnumDevice(cb, ctx, "Citro3D", &halDesc, &helDesc, Citro3D_GUID); +} From 12d4d6a89aebe800f0e68c9ea6bef2af14035371 Mon Sep 17 00:00:00 2001 From: Christian Semmler Date: Mon, 30 Jun 2025 19:45:07 -0700 Subject: [PATCH 028/188] Update README.md [skip ci] --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 33dbd232..8f5c6756 100644 --- a/README.md +++ b/README.md @@ -16,6 +16,7 @@ Please note: this project is dedicated to achieving platform independence withou | Linux | [![CI](https://github.com/isledecomp/isle-portable/actions/workflows/ci.yml/badge.svg)](https://github.com/isledecomp/isle-portable/actions/workflows/ci.yml) | | macOS | [![CI](https://github.com/isledecomp/isle-portable/actions/workflows/ci.yml/badge.svg)](https://github.com/isledecomp/isle-portable/actions/workflows/ci.yml) | | [Web](https://isle.pizza) | [![CI](https://github.com/isledecomp/isle-portable/actions/workflows/ci.yml/badge.svg)](https://github.com/isledecomp/isle-portable/actions/workflows/ci.yml) | +| Nintendo 3DS | [![CI](https://github.com/isledecomp/isle-portable/actions/workflows/ci.yml/badge.svg)](https://github.com/isledecomp/isle-portable/actions/workflows/ci.yml) | We are actively working to support more platforms. If you have experience with a particular platform, we encourage you to contribute to `isle-portable`. You can find a [list of ongoing efforts](https://github.com/isledecomp/isle-portable/wiki/Work%E2%80%90in%E2%80%90progress-ports) in our Wiki. From 7b06ee5ae610d94bbe51b32b9e5427394f65e22d Mon Sep 17 00:00:00 2001 From: Anders Jenbo Date: Wed, 2 Jul 2025 00:45:25 +0200 Subject: [PATCH 029/188] Add support for POT-only GPUs, fix VBO (#468) --- miniwin/src/d3drm/backends/opengl1/actual.cpp | 39 +++++---- miniwin/src/d3drm/backends/opengl1/actual.h | 6 +- .../src/d3drm/backends/opengl1/renderer.cpp | 86 +++++++++++++++---- .../src/d3drm/backends/opengles2/renderer.cpp | 1 + miniwin/src/internal/d3drmrenderer_opengl1.h | 1 + 5 files changed, 96 insertions(+), 37 deletions(-) diff --git a/miniwin/src/d3drm/backends/opengl1/actual.cpp b/miniwin/src/d3drm/backends/opengl1/actual.cpp index e1c56636..e809f7e5 100644 --- a/miniwin/src/d3drm/backends/opengl1/actual.cpp +++ b/miniwin/src/d3drm/backends/opengl1/actual.cpp @@ -42,12 +42,26 @@ void GL11_DestroyTexture(GLuint texId) glDeleteTextures(1, &texId); } -GLuint GL11_UploadTextureData(void* pixels, int width, int height) +GLuint GL11_UploadTextureData(void* pixels, int width, int height, bool isUi) { GLuint texId; glGenTextures(1, &texId); glBindTexture(GL_TEXTURE_2D, texId); + if (isUi) { + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); + } + else { + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT); + } + glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, pixels); + return texId; } @@ -111,7 +125,6 @@ void GL11_BeginFrame(const Matrix4x4* projection) glDepthMask(GL_TRUE); glEnable(GL_LIGHTING); glEnable(GL_COLOR_MATERIAL); - glColorMaterial(GL_FRONT, GL_AMBIENT_AND_DIFFUSE); // Disable all lights and reset global ambient for (int i = 0; i < 8; ++i) { @@ -198,6 +211,7 @@ void GL11_SubmitDraw( glLoadMatrixf(&modelViewMatrix[0][0]); glEnable(GL_NORMALIZE); + glColorMaterial(GL_FRONT, GL_AMBIENT_AND_DIFFUSE); glColor4ub(appearance.color.r, appearance.color.g, appearance.color.b, appearance.color.a); if (appearance.shininess != 0.0f) { @@ -220,8 +234,6 @@ void GL11_SubmitDraw( // Bind texture if present if (appearance.textureId != NO_TEXTURE_ID) { - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); glEnable(GL_TEXTURE_2D); glBindTexture(GL_TEXTURE_2D, texId); glEnableClientState(GL_TEXTURE_COORD_ARRAY); @@ -279,7 +291,7 @@ void GL11_Clear(float r, float g, float b) } void GL11_Draw2DImage( - GLuint texId, + GLTextureCacheEntry& cache, const SDL_Rect& srcRect, const SDL_Rect& dstRect, float left, @@ -305,24 +317,17 @@ void GL11_Draw2DImage( glColor4f(1.0f, 1.0f, 1.0f, 1.0f); glEnable(GL_TEXTURE_2D); - glBindTexture(GL_TEXTURE_2D, texId); + glBindTexture(GL_TEXTURE_2D, cache.glTextureId); glEnable(GL_BLEND); glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); - GLint boundTexture = 0; glGetIntegerv(GL_TEXTURE_BINDING_2D, &boundTexture); - GLfloat texW, texH; - glGetTexLevelParameterfv(GL_TEXTURE_2D, 0, GL_TEXTURE_WIDTH, &texW); - glGetTexLevelParameterfv(GL_TEXTURE_2D, 0, GL_TEXTURE_HEIGHT, &texH); - - float u1 = srcRect.x / texW; - float v1 = srcRect.y / texH; - float u2 = (srcRect.x + srcRect.w) / texW; - float v2 = (srcRect.y + srcRect.h) / texH; + float u1 = srcRect.x / cache.width; + float v1 = srcRect.y / cache.height; + float u2 = (srcRect.x + srcRect.w) / cache.width; + float v2 = (srcRect.y + srcRect.h) / cache.height; float x1 = (float) dstRect.x; float y1 = (float) dstRect.y; diff --git a/miniwin/src/d3drm/backends/opengl1/actual.h b/miniwin/src/d3drm/backends/opengl1/actual.h index 8a4fb8e8..1d5e407d 100644 --- a/miniwin/src/d3drm/backends/opengl1/actual.h +++ b/miniwin/src/d3drm/backends/opengl1/actual.h @@ -39,6 +39,8 @@ struct GLTextureCacheEntry { IDirect3DRMTexture* texture; Uint32 version; GLuint glTextureId; + float width; + float height; }; struct GLMeshCacheEntry { @@ -62,7 +64,7 @@ struct GLMeshCacheEntry { void GL11_InitState(); void GL11_LoadExtensions(); void GL11_DestroyTexture(GLuint texId); -GLuint GL11_UploadTextureData(void* pixels, int width, int height); +GLuint GL11_UploadTextureData(void* pixels, int width, int height, bool isUI); void GL11_UploadMesh(GLMeshCacheEntry& cache, bool hasTexture); void GL11_DestroyMesh(GLMeshCacheEntry& cache); void GL11_BeginFrame(const Matrix4x4* projection); @@ -77,7 +79,7 @@ void GL11_SubmitDraw( void GL11_Resize(int width, int height); void GL11_Clear(float r, float g, float b); void GL11_Draw2DImage( - GLuint texId, + GLTextureCacheEntry& cache, const SDL_Rect& srcRect, const SDL_Rect& dstRect, float left, diff --git a/miniwin/src/d3drm/backends/opengl1/renderer.cpp b/miniwin/src/d3drm/backends/opengl1/renderer.cpp index fb7fccd4..2e18b38a 100644 --- a/miniwin/src/d3drm/backends/opengl1/renderer.cpp +++ b/miniwin/src/d3drm/backends/opengl1/renderer.cpp @@ -58,6 +58,8 @@ OpenGL1Renderer::OpenGL1Renderer(DWORD width, DWORD height, SDL_GLContext contex m_virtualHeight = height; m_renderedImage = SDL_CreateSurface(m_width, m_height, SDL_PIXELFORMAT_RGBA32); GL11_LoadExtensions(); + m_useVBOs = SDL_GL_ExtensionSupported("GL_ARB_vertex_buffer_object"); + m_useNPOT = SDL_GL_ExtensionSupported("GL_OES_texture_npot"); } OpenGL1Renderer::~OpenGL1Renderer() @@ -108,6 +110,58 @@ void OpenGL1Renderer::AddTextureDestroyCallback(Uint32 id, IDirect3DRMTexture* t ); } +static int NextPowerOfTwo(int v) +{ + int power = 1; + while (power < v) { + power <<= 1; + } + return power; +} + +static Uint32 UploadTextureData(SDL_Surface* src, bool useNPOT, bool isUi) +{ + SDL_Surface* working = src; + if (src->format != SDL_PIXELFORMAT_RGBA32) { + working = SDL_ConvertSurface(src, SDL_PIXELFORMAT_RGBA32); + if (!working) { + SDL_Log("SDL_ConvertSurface failed: %s", SDL_GetError()); + return NO_TEXTURE_ID; + } + } + + SDL_Surface* finalSurface = working; + + int newW = NextPowerOfTwo(working->w); + int newH = NextPowerOfTwo(working->h); + + if (!useNPOT && (newW != working->w || newH != working->h)) { + SDL_Surface* resized = SDL_CreateSurface(newW, newH, working->format); + if (!resized) { + SDL_Log("SDL_CreateSurface (resize) failed: %s", SDL_GetError()); + if (working != src) { + SDL_DestroySurface(working); + } + return NO_TEXTURE_ID; + } + + SDL_Rect srcRect = {0, 0, working->w, working->h}; + SDL_Rect dstRect = {0, 0, newW, newH}; + SDL_BlitSurfaceScaled(working, &srcRect, resized, &dstRect, SDL_SCALEMODE_NEAREST); + + if (working != src) { + SDL_DestroySurface(working); + } + finalSurface = resized; + } + + Uint32 texId = GL11_UploadTextureData(finalSurface->pixels, finalSurface->w, finalSurface->h, isUi); + if (finalSurface != src) { + SDL_DestroySurface(finalSurface); + } + return texId; +} + Uint32 OpenGL1Renderer::GetTextureId(IDirect3DRMTexture* iTexture, bool isUi) { auto texture = static_cast(iTexture); @@ -118,28 +172,16 @@ Uint32 OpenGL1Renderer::GetTextureId(IDirect3DRMTexture* iTexture, bool isUi) if (tex.texture == texture) { if (tex.version != texture->m_version) { GL11_DestroyTexture(tex.glTextureId); - - SDL_Surface* surf = SDL_ConvertSurface(surface->m_surface, SDL_PIXELFORMAT_RGBA32); - if (!surf) { - return NO_TEXTURE_ID; - } - tex.glTextureId = GL11_UploadTextureData(surf->pixels, surf->w, surf->h); - SDL_DestroySurface(surf); - + tex.glTextureId = UploadTextureData(surface->m_surface, m_useNPOT, isUi); tex.version = texture->m_version; + tex.width = surface->m_surface->w; + tex.height = surface->m_surface->h; } return i; } } - GLuint texId; - - SDL_Surface* surf = SDL_ConvertSurface(surface->m_surface, SDL_PIXELFORMAT_RGBA32); - if (!surf) { - return NO_TEXTURE_ID; - } - texId = GL11_UploadTextureData(surf->pixels, surf->w, surf->h); - SDL_DestroySurface(surf); + GLuint texId = UploadTextureData(surface->m_surface, m_useNPOT, isUi); for (Uint32 i = 0; i < m_textures.size(); ++i) { auto& tex = m_textures[i]; @@ -147,12 +189,20 @@ Uint32 OpenGL1Renderer::GetTextureId(IDirect3DRMTexture* iTexture, bool isUi) tex.texture = texture; tex.version = texture->m_version; tex.glTextureId = texId; + tex.width = surface->m_surface->w; + tex.height = surface->m_surface->h; AddTextureDestroyCallback(i, texture); return i; } } - m_textures.push_back({texture, texture->m_version, texId}); + m_textures.push_back( + {texture, + texture->m_version, + texId, + static_cast(surface->m_surface->w), + static_cast(surface->m_surface->h)} + ); AddTextureDestroyCallback((Uint32) (m_textures.size() - 1), texture); return (Uint32) (m_textures.size() - 1); } @@ -331,7 +381,7 @@ void OpenGL1Renderer::Draw2DImage(Uint32 textureId, const SDL_Rect& srcRect, con float top = -m_viewportTransform.offsetY / m_viewportTransform.scale; float bottom = (m_height - m_viewportTransform.offsetY) / m_viewportTransform.scale; - GL11_Draw2DImage(m_textures[textureId].glTextureId, srcRect, dstRect, left, right, bottom, top); + GL11_Draw2DImage(m_textures[textureId], srcRect, dstRect, left, right, bottom, top); } void OpenGL1Renderer::Download(SDL_Surface* target) diff --git a/miniwin/src/d3drm/backends/opengles2/renderer.cpp b/miniwin/src/d3drm/backends/opengles2/renderer.cpp index 6695da81..9cb8455c 100644 --- a/miniwin/src/d3drm/backends/opengles2/renderer.cpp +++ b/miniwin/src/d3drm/backends/opengles2/renderer.cpp @@ -47,6 +47,7 @@ Direct3DRMRenderer* OpenGLES2Renderer::Create(DWORD width, DWORD height) SDL_GLContext context = SDL_GL_CreateContext(DDWindow); if (!context) { + SDL_Log("SDL_GL_CreateContext: %s", SDL_GetError()); return nullptr; } diff --git a/miniwin/src/internal/d3drmrenderer_opengl1.h b/miniwin/src/internal/d3drmrenderer_opengl1.h index 58406709..dce25e6b 100644 --- a/miniwin/src/internal/d3drmrenderer_opengl1.h +++ b/miniwin/src/internal/d3drmrenderer_opengl1.h @@ -46,6 +46,7 @@ class OpenGL1Renderer : public Direct3DRMRenderer { D3DRMMATRIX4D m_projection; SDL_Surface* m_renderedImage; bool m_useVBOs; + bool m_useNPOT; bool m_dirty = false; std::vector m_lights; SDL_GLContext m_context; From 1f1787b5ac8b1a5940b0f42e4a89e738f707cbc7 Mon Sep 17 00:00:00 2001 From: Christian Semmler Date: Tue, 1 Jul 2025 15:46:57 -0700 Subject: [PATCH 030/188] Consolidate into a single CMake folder (#469) * Consolidate into a single CMake folder' * Remove extra path thing --- {cmake => CMake}/Findiniparser.cmake | 0 {cmake => CMake}/detectcpu.cmake | 0 CMakeLists.txt | 2 +- packaging/linux/flatpak/org.legoisland.Isle.json | 11 +---------- 4 files changed, 2 insertions(+), 11 deletions(-) rename {cmake => CMake}/Findiniparser.cmake (100%) rename {cmake => CMake}/detectcpu.cmake (100%) diff --git a/cmake/Findiniparser.cmake b/CMake/Findiniparser.cmake similarity index 100% rename from cmake/Findiniparser.cmake rename to CMake/Findiniparser.cmake diff --git a/cmake/detectcpu.cmake b/CMake/detectcpu.cmake similarity index 100% rename from cmake/detectcpu.cmake rename to CMake/detectcpu.cmake diff --git a/CMakeLists.txt b/CMakeLists.txt index e9812aea..f758bd95 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -13,7 +13,7 @@ list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_LIST_DIR}/cmake") include(CheckCXXSourceCompiles) include(CMakeDependentOption) include(CMakePushCheckState) -include(cmake/detectcpu.cmake) +include(CMake/detectcpu.cmake) DetectTargetCPUArchitectures(ISLE_CPUS) diff --git a/packaging/linux/flatpak/org.legoisland.Isle.json b/packaging/linux/flatpak/org.legoisland.Isle.json index e8cd3f8e..178434fa 100644 --- a/packaging/linux/flatpak/org.legoisland.Isle.json +++ b/packaging/linux/flatpak/org.legoisland.Isle.json @@ -1,12 +1,9 @@ { "id": "org.legoisland.Isle", - "runtime": "org.kde.Platform", "sdk": "org.kde.Sdk", "runtime-version": "6.8", - "command": "isle", - "finish-args": [ "--share=ipc", "--socket=wayland", @@ -19,7 +16,6 @@ "--filesystem=/mnt/:ro", "--filesystem=home:ro" ], - "modules": [ { "name": "isle", @@ -34,11 +30,6 @@ "path": "../../../3rdparty", "dest": "3rdparty/" }, - { - "type": "dir", - "path": "../../../cmake", - "dest": "cmake/" - }, { "type": "dir", "path": "../../../CMake", @@ -86,4 +77,4 @@ } } ] -} +} \ No newline at end of file From de31c12a9ecc32384ef6549e0419971747489193 Mon Sep 17 00:00:00 2001 From: Christian Semmler Date: Tue, 1 Jul 2025 15:47:05 -0700 Subject: [PATCH 031/188] Add Docker image for Emscripten port (#466) * Add Docker web port image * Comment out master * Optimize * Slim down image * Revert "Comment out master" This reverts commit 115c9770e8e01c1a4bc2455eca944d4f7f4c1cf4. * Allow running from ISO --- .github/workflows/docker.yml | 55 ++++++++++++++++++++++++++++++++++++ docker/emscripten/Dockerfile | 37 ++++++++++++++++++++++++ docker/emscripten/nginx.conf | 27 ++++++++++++++++++ 3 files changed, 119 insertions(+) create mode 100644 .github/workflows/docker.yml create mode 100644 docker/emscripten/Dockerfile create mode 100644 docker/emscripten/nginx.conf diff --git a/.github/workflows/docker.yml b/.github/workflows/docker.yml new file mode 100644 index 00000000..ca316590 --- /dev/null +++ b/.github/workflows/docker.yml @@ -0,0 +1,55 @@ +name: Docker + +on: + push: + branches: ['master'] + +env: + REGISTRY: ghcr.io + IMAGE_NAME: ${{ github.repository }}-emscripten + +jobs: + publish-emscripten: + name: Publish web port + env: + IMAGE_NAME: ${{ github.repository }}-emscripten + runs-on: ubuntu-latest + permissions: + contents: read + packages: write + attestations: write + id-token: write + + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Log in to the Container registry + uses: docker/login-action@65b78e6e13532edd9afa3aa52ac7964289d1a9c1 + with: + registry: ${{ env.REGISTRY }} + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + + - name: Extract metadata (tags, labels) for Docker + id: meta + uses: docker/metadata-action@9ec57ed1fcdbf14dcef7dfbe97b2010124a938b7 + with: + images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }} + + - name: Build and push Docker image + id: push + uses: docker/build-push-action@f2a1d5e99d037542a71f64918e516c093c6f3fc4 + with: + file: docker/emscripten/Dockerfile + context: . + push: true + tags: ${{ steps.meta.outputs.tags }} + labels: ${{ steps.meta.outputs.labels }} + + - name: Generate artifact attestation + uses: actions/attest-build-provenance@v2 + with: + subject-name: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME}} + subject-digest: ${{ steps.push.outputs.digest }} + push-to-registry: true diff --git a/docker/emscripten/Dockerfile b/docker/emscripten/Dockerfile new file mode 100644 index 00000000..d15d2406 --- /dev/null +++ b/docker/emscripten/Dockerfile @@ -0,0 +1,37 @@ +FROM emscripten/emsdk:latest AS builder + +ARG CMAKE_VERSION=3.29.3 + +WORKDIR /src + +USER root + +RUN apt-get update && apt-get install -y git wget && rm -rf /var/lib/apt/lists/* +RUN wget https://github.com/Kitware/CMake/releases/download/v${CMAKE_VERSION}/cmake-${CMAKE_VERSION}-Linux-x86_64.sh -O /tmp/cmake.sh && \ + chmod +x /tmp/cmake.sh && \ + /tmp/cmake.sh --skip-license --prefix=/usr/local && \ + rm /tmp/cmake.sh + +RUN chown -R emscripten:emscripten /src + +USER emscripten + +COPY ISLE/emscripten/libwasmfs_fetch.js.patch /tmp/ +RUN cd /emsdk/upstream/emscripten && \ + git apply --check /tmp/libwasmfs_fetch.js.patch && \ + git apply /tmp/libwasmfs_fetch.js.patch + +COPY --chown=emscripten:emscripten . . + +RUN emcmake cmake -S . -B build -DISLE_BUILD_CONFIG=OFF -DISLE_DEBUG=OFF -DCMAKE_BUILD_TYPE=Release -DISLE_EMSCRIPTEN_HOST=/assets && \ + emmake cmake --build build -j 32 + +RUN echo "Fetching isle.pizza frontend..."; \ + git clone --depth 1 https://github.com/isledecomp/isle.pizza /tmp/isle.pizza; + +FROM nginx:alpine + +COPY docker/emscripten/nginx.conf /etc/nginx/nginx.conf +COPY --from=builder /tmp/isle.pizza /usr/share/nginx/html +COPY --from=builder /src/build/isle.* /usr/share/nginx/html +EXPOSE 6931 diff --git a/docker/emscripten/nginx.conf b/docker/emscripten/nginx.conf new file mode 100644 index 00000000..30d9e34d --- /dev/null +++ b/docker/emscripten/nginx.conf @@ -0,0 +1,27 @@ +events { + worker_connections 1024; +} + +http { + include /etc/nginx/mime.types; + + server { + listen 6931; + server_name localhost; + + add_header 'Cross-Origin-Embedder-Policy' 'require-corp'; + add_header 'Cross-Origin-Opener-Policy' 'same-origin'; + add_header 'Cross-Origin-Resource-Policy' 'cross-origin'; + + location / { + root /usr/share/nginx/html; + index index.html isle.html; + try_files $uri $uri/ =404; + } + + location ~* ^/assets/(.*)$ { + alias /assets/; + try_files /$1 /DATA/disk/$1 =404; + } + } +} From cb7a35b7497f92e9bab6a82a9e00e62f531ad0bd Mon Sep 17 00:00:00 2001 From: Christian Semmler Date: Wed, 2 Jul 2025 01:04:02 +0200 Subject: [PATCH 032/188] Update README.md [skip ci] --- README.md | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/README.md b/README.md index 8f5c6756..fe4a285f 100644 --- a/README.md +++ b/README.md @@ -8,8 +8,6 @@ Please note: this project is dedicated to achieving platform independence withou ## Status -### Supported platforms - | Platform | Status | | - | - | | Windows | [![CI](https://github.com/isledecomp/isle-portable/actions/workflows/ci.yml/badge.svg)](https://github.com/isledecomp/isle-portable/actions/workflows/ci.yml) | @@ -20,7 +18,16 @@ Please note: this project is dedicated to achieving platform independence withou We are actively working to support more platforms. If you have experience with a particular platform, we encourage you to contribute to `isle-portable`. You can find a [list of ongoing efforts](https://github.com/isledecomp/isle-portable/wiki/Work%E2%80%90in%E2%80%90progress-ports) in our Wiki. -### Library substitutions + +## Usage + +**An existing copy of LEGO Island is required to use this project.** + +As it stands, builds provided in the [Releases tab](https://github.com/isledecomp/isle-portable/releases/tag/continuous) are mainly for developers; as such, they may not work properly for all end-users. Work is currently ongoing to create workable release builds ready for gameplay and general use by end-users. If you are technically inclined, you may find it easiest to compile the project yourself to get it running at this current point in time. + +[Installation instructions](https://github.com/isledecomp/isle-portable/wiki/Installation) for some ports can be found in our Wiki. + +## Library substitutions To achieve our goal of platform independence, we need to replace any Windows-only libraries with platform-independent alternatives. This ensures that our codebase remains versatile and compatible across various systems. The following table serves as an overview of major libraries / subsystems and their chosen replacements. For any significant changes or additions, it's recommended to discuss them with the team on the Matrix chat first to ensure consistency and alignment with our project's objectives. @@ -43,12 +50,6 @@ To achieve our goal of platform independence, we need to replace any Windows-onl This project uses the [CMake](https://cmake.org/) build system, which allows for a high degree of versatility regarding compilers and development environments. Please refer to the [GitHub action](/.github/workflows//ci.yml) for guidance. -## Usage - -**An existing copy of LEGO Island is required to use this project.** - -As it stands, the builds provided in the Releases tab are for developers; as such, they may not work properly for end-users. Work is currently ongoing to create workable release builds ready for gameplay and general use by end-users. If you are technically inclined, you may find it easiest to compile the project yourself to get it running at this current point in time. - ## Contributing If you're interested in helping or contributing to this project, check out the [CONTRIBUTING](/CONTRIBUTING.md) page. From 805c57b6aaf2e035388d55edbd22118cb9cea9e3 Mon Sep 17 00:00:00 2001 From: Fabian Neundorf Date: Wed, 2 Jul 2025 02:02:03 +0200 Subject: [PATCH 033/188] Clear unknowns in `ViewLOD` (#1599) --- .../legoomni/src/video/legopartpresenter.cpp | 2 +- LEGO1/lego/sources/roi/legolod.cpp | 10 +++++----- LEGO1/lego/sources/roi/legoroi.cpp | 4 ++-- LEGO1/viewmanager/viewlod.h | 16 ++++++++-------- LEGO1/viewmanager/viewmanager.cpp | 8 ++++---- 5 files changed, 20 insertions(+), 20 deletions(-) diff --git a/LEGO1/lego/legoomni/src/video/legopartpresenter.cpp b/LEGO1/lego/legoomni/src/video/legopartpresenter.cpp index f1d55a6d..88e31dd6 100644 --- a/LEGO1/lego/legoomni/src/video/legopartpresenter.cpp +++ b/LEGO1/lego/legoomni/src/video/legopartpresenter.cpp @@ -187,7 +187,7 @@ MxResult LegoPartPresenter::Read(MxDSChunk& p_chunk) } if (j == 0) { - if (surplusLODs != 0 && lod->GetUnknown0x08Test8()) { + if (surplusLODs != 0 && lod->IsExtraLOD()) { numLODs++; surplusLODs--; } diff --git a/LEGO1/lego/sources/roi/legolod.cpp b/LEGO1/lego/sources/roi/legolod.cpp index bf157b56..453a4dac 100644 --- a/LEGO1/lego/sources/roi/legolod.cpp +++ b/LEGO1/lego/sources/roi/legolod.cpp @@ -69,11 +69,11 @@ LegoResult LegoLOD::Read(Tgl::Renderer* p_renderer, LegoTextureContainer* p_text LegoU32 i, indexBackwards, indexForwards, tempNumVertsAndNormals; unsigned char paletteEntries[256]; - if (p_storage->Read(&m_unk0x08, sizeof(undefined4)) != SUCCESS) { + if (p_storage->Read(&m_flags, sizeof(LegoU32)) != SUCCESS) { goto done; } - if (GetUnknown0x08Test4()) { + if (SkipReadingData()) { return SUCCESS; } @@ -84,11 +84,11 @@ LegoResult LegoLOD::Read(Tgl::Renderer* p_renderer, LegoTextureContainer* p_text } if (m_numMeshes == 0) { - ClearFlag(c_bit4); + ClearFlag(c_hasMesh); return SUCCESS; } - SetFlag(c_bit4); + SetFlag(c_hasMesh); m_melems = new Mesh[m_numMeshes]; memset(m_melems, 0, sizeof(*m_melems) * m_numMeshes); @@ -315,7 +315,7 @@ LegoLOD* LegoLOD::Clone(Tgl::Renderer* p_renderer) dupLod->m_melems[i].m_textured = m_melems[i].m_textured; } - dupLod->m_unk0x08 = m_unk0x08; + dupLod->m_flags = m_flags; dupLod->m_numMeshes = m_numMeshes; dupLod->m_numVertices = m_numVertices; dupLod->m_numPolys = m_numPolys; diff --git a/LEGO1/lego/sources/roi/legoroi.cpp b/LEGO1/lego/sources/roi/legoroi.cpp index 97baaadf..afd5f711 100644 --- a/LEGO1/lego/sources/roi/legoroi.cpp +++ b/LEGO1/lego/sources/roi/legoroi.cpp @@ -259,7 +259,7 @@ LegoResult LegoROI::Read( } if (j == 0) { - if (surplusLODs != 0 && lod->GetUnknown0x08Test8()) { + if (surplusLODs != 0 && lod->IsExtraLOD()) { numLODs++; } } @@ -276,7 +276,7 @@ LegoResult LegoROI::Read( } if (i == 0) { - if (surplusLODs != 0 && lod->GetUnknown0x08Test8()) { + if (surplusLODs != 0 && lod->IsExtraLOD()) { numLODs++; } } diff --git a/LEGO1/viewmanager/viewlod.h b/LEGO1/viewmanager/viewlod.h index 7bf68b52..b7104526 100644 --- a/LEGO1/viewmanager/viewlod.h +++ b/LEGO1/viewmanager/viewlod.h @@ -14,10 +14,10 @@ class ViewLOD : public LODObject { public: enum { - c_bit4 = 0x10 + c_hasMesh = 0x10 }; - ViewLOD(Tgl::Renderer* pRenderer) : m_meshBuilder(NULL), m_unk0x08(3) {} + ViewLOD(Tgl::Renderer* pRenderer) : m_meshBuilder(NULL), m_flags(3) {} ~ViewLOD() override; // FUNCTION: LEGO1 0x100a6f30 @@ -28,19 +28,19 @@ class ViewLOD : public LODObject { Tgl::MeshBuilder* GetMeshBuilder() { return m_meshBuilder; } const Tgl::MeshBuilder* GetMeshBuilder() const { return m_meshBuilder; } - undefined4 GetUnknown0x08() { return m_unk0x08; } - unsigned char GetUnknown0x08Test4() { return m_unk0x08 & 0xffffff04; } - unsigned char GetUnknown0x08Test8() { return m_unk0x08 & 0xffffff08; } + unsigned int GetFlags() { return m_flags; } + unsigned char SkipReadingData() { return m_flags & 0xffffff04; } + unsigned char IsExtraLOD() { return m_flags & 0xffffff08; } - void SetFlag(unsigned char p_flag) { m_unk0x08 |= p_flag; } - void ClearFlag(unsigned char p_flag) { m_unk0x08 &= ~p_flag; } + void SetFlag(unsigned char p_flag) { m_flags |= p_flag; } + void ClearFlag(unsigned char p_flag) { m_flags &= ~p_flag; } // SYNTHETIC: LEGO1 0x100a6f60 // ViewLOD::`scalar deleting destructor' protected: Tgl::MeshBuilder* m_meshBuilder; // 0x04 - undefined4 m_unk0x08; // 0x08 + unsigned int m_flags; // 0x08 }; #endif // VIEWLOD_H diff --git a/LEGO1/viewmanager/viewmanager.cpp b/LEGO1/viewmanager/viewmanager.cpp index 44635ffd..27919424 100644 --- a/LEGO1/viewmanager/viewmanager.cpp +++ b/LEGO1/viewmanager/viewmanager.cpp @@ -165,7 +165,7 @@ void ViewManager::UpdateROIDetailBasedOnLOD(ViewROI* p_roi, int p_lodLevel) if (lodLevel < 0) { lod = (ViewLOD*) p_roi->GetLOD(p_lodLevel); - if (lod->GetUnknown0x08() & ViewLOD::c_bit4) { + if (lod->GetFlags() & ViewLOD::c_hasMesh) { scene->Add((Tgl::MeshBuilder*) group); SetAppData(p_roi, reinterpret_cast(p_roi)); } @@ -184,7 +184,7 @@ void ViewManager::UpdateROIDetailBasedOnLOD(ViewROI* p_roi, int p_lodLevel) lod = (ViewLOD*) p_roi->GetLOD(p_lodLevel); } - if (lod->GetUnknown0x08() & ViewLOD::c_bit4) { + if (lod->GetFlags() & ViewLOD::c_hasMesh) { meshBuilder = lod->GetMeshBuilder(); if (meshBuilder != NULL) { @@ -389,7 +389,7 @@ inline int ViewManager::GetFirstLODIndex(ViewROI* p_roi) const LODListBase* lods = p_roi->GetLODs(); if (lods != NULL && lods->Size() > 0) { - if (((ViewLOD*) p_roi->GetLOD(0))->GetUnknown0x08Test8()) { + if (((ViewLOD*) p_roi->GetLOD(0))->IsExtraLOD()) { return 1; } else { @@ -404,7 +404,7 @@ inline int ViewManager::GetFirstLODIndex(ViewROI* p_roi) const LODListBase* lods = ((ViewROI*) *it)->GetLODs(); if (lods != NULL && lods->Size() > 0) { - if (((ViewLOD*) ((ViewROI*) *it)->GetLOD(0))->GetUnknown0x08Test8()) { + if (((ViewLOD*) ((ViewROI*) *it)->GetLOD(0))->IsExtraLOD()) { return 1; } else { From e7c7ecf510a06582316eaf698f63026b04a12eb0 Mon Sep 17 00:00:00 2001 From: Christian Semmler Date: Tue, 1 Jul 2025 23:15:15 -0700 Subject: [PATCH 034/188] Add experimental WebGL support to Emscripten (#440) Co-authored-by: Anders Jenbo --- CMakeLists.txt | 2 +- miniwin/CMakeLists.txt | 6 ++++-- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index f758bd95..3808cbe1 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -4,7 +4,7 @@ project(isle LANGUAGES CXX C VERSION 0.1) if (EMSCRIPTEN) add_compile_options(-pthread) - add_link_options(-sALLOW_MEMORY_GROWTH=1 -sMAXIMUM_MEMORY=2gb -sUSE_PTHREADS=1 -sPROXY_TO_PTHREAD=1 -sPTHREAD_POOL_SIZE_STRICT=0 -sFORCE_FILESYSTEM=1 -sWASMFS=1 -sEXIT_RUNTIME=1) + add_link_options(-sALLOW_MEMORY_GROWTH=1 -sMAXIMUM_MEMORY=2gb -sUSE_PTHREADS=1 -sPROXY_TO_PTHREAD=1 -sOFFSCREENCANVAS_SUPPORT=1 -sPTHREAD_POOL_SIZE_STRICT=0 -sFORCE_FILESYSTEM=1 -sWASMFS=1 -sEXIT_RUNTIME=1) set(SDL_PTHREADS ON CACHE BOOL "Enable SDL pthreads" FORCE) endif() diff --git a/miniwin/CMakeLists.txt b/miniwin/CMakeLists.txt index 851403cc..c2aa0bd6 100644 --- a/miniwin/CMakeLists.txt +++ b/miniwin/CMakeLists.txt @@ -45,11 +45,13 @@ else() endif() find_library(OPENGL_ES2_LIBRARY NAMES GLESv2) -if(OPENGL_ES2_LIBRARY) +if(EMSCRIPTEN OR OPENGL_ES2_LIBRARY) message(STATUS "Found OpenGL: enabling OpenGL ES 2.x renderer") target_sources(miniwin PRIVATE src/d3drm/backends/opengles2/renderer.cpp) target_compile_definitions(miniwin PRIVATE USE_OPENGLES2) - target_link_libraries(miniwin PRIVATE OpenGL::GL) + if(OPENGL_ES2_LIBRARY) + target_link_libraries(miniwin PRIVATE OpenGL::GL) + endif() else() message(STATUS "🧩 OpenGL ES 2.x support not enabled") endif() From fb8fe5def423a3edfd1e02570f241c2ca0e325fb Mon Sep 17 00:00:00 2001 From: VoxelTek <53562267+VoxelTek@users.noreply.github.com> Date: Wed, 2 Jul 2025 17:10:44 +1000 Subject: [PATCH 035/188] Update tab order (#474) --- CONFIG/res/maindialog.ui | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CONFIG/res/maindialog.ui b/CONFIG/res/maindialog.ui index c2760e7c..c2d429ae 100644 --- a/CONFIG/res/maindialog.ui +++ b/CONFIG/res/maindialog.ui @@ -562,8 +562,10 @@ sound3DCheckBox musicCheckBox joystickCheckBox + fullscreenCheckBox devicesList okButton + launchButton cancelButton From cca65ba17827d86f966faeb91327301c6feb10bf Mon Sep 17 00:00:00 2001 From: Anders Jenbo Date: Wed, 2 Jul 2025 12:07:04 +0200 Subject: [PATCH 036/188] Remove unused function from OpenGL renderer (#475) --- miniwin/src/d3drm/backends/opengl1/actual.cpp | 3 --- 1 file changed, 3 deletions(-) diff --git a/miniwin/src/d3drm/backends/opengl1/actual.cpp b/miniwin/src/d3drm/backends/opengl1/actual.cpp index e809f7e5..9a2e314d 100644 --- a/miniwin/src/d3drm/backends/opengl1/actual.cpp +++ b/miniwin/src/d3drm/backends/opengl1/actual.cpp @@ -321,9 +321,6 @@ void GL11_Draw2DImage( glEnable(GL_BLEND); glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); - GLint boundTexture = 0; - glGetIntegerv(GL_TEXTURE_BINDING_2D, &boundTexture); - float u1 = srcRect.x / cache.width; float v1 = srcRect.y / cache.height; float u2 = (srcRect.x + srcRect.w) / cache.width; From 55c13cd1409537a5ada30876579e0d78b6239830 Mon Sep 17 00:00:00 2001 From: Anders Jenbo Date: Wed, 2 Jul 2025 12:07:20 +0200 Subject: [PATCH 037/188] Implement dithering where relevant (#473) --- miniwin/src/d3drm/backends/citro3d/renderer.cpp | 4 ++++ miniwin/src/d3drm/backends/directx9/renderer.cpp | 4 ++++ miniwin/src/d3drm/backends/opengl1/actual.cpp | 10 ++++++++++ miniwin/src/d3drm/backends/opengl1/actual.h | 1 + miniwin/src/d3drm/backends/opengl1/renderer.cpp | 5 +++++ miniwin/src/d3drm/backends/opengles2/renderer.cpp | 10 ++++++++++ miniwin/src/d3drm/backends/sdl3gpu/renderer.cpp | 4 ++++ miniwin/src/d3drm/backends/software/renderer.cpp | 4 ++++ miniwin/src/d3drm/d3drmdevice.cpp | 4 +--- miniwin/src/internal/d3drmrenderer.h | 1 + miniwin/src/internal/d3drmrenderer_citro3d.h | 1 + miniwin/src/internal/d3drmrenderer_directx9.h | 1 + miniwin/src/internal/d3drmrenderer_opengl1.h | 1 + miniwin/src/internal/d3drmrenderer_opengles2.h | 1 + miniwin/src/internal/d3drmrenderer_sdl3gpu.h | 1 + miniwin/src/internal/d3drmrenderer_software.h | 1 + 16 files changed, 50 insertions(+), 3 deletions(-) diff --git a/miniwin/src/d3drm/backends/citro3d/renderer.cpp b/miniwin/src/d3drm/backends/citro3d/renderer.cpp index f9167401..72d729bc 100644 --- a/miniwin/src/d3drm/backends/citro3d/renderer.cpp +++ b/miniwin/src/d3drm/backends/citro3d/renderer.cpp @@ -600,6 +600,10 @@ void Citro3DRenderer::Draw2DImage(Uint32 textureId, const SDL_Rect& srcRect, con C3D_ImmDrawEnd(); } +void Citro3DRenderer::SetDither(bool dither) +{ +} + void Citro3DRenderer::Download(SDL_Surface* target) { MINIWIN_NOT_IMPLEMENTED(); diff --git a/miniwin/src/d3drm/backends/directx9/renderer.cpp b/miniwin/src/d3drm/backends/directx9/renderer.cpp index 5917cd07..2701528b 100644 --- a/miniwin/src/d3drm/backends/directx9/renderer.cpp +++ b/miniwin/src/d3drm/backends/directx9/renderer.cpp @@ -278,6 +278,10 @@ void DirectX9Renderer::Draw2DImage(Uint32 textureId, const SDL_Rect& srcRect, co Actual_Draw2DImage(m_textures[textureId].dxTexture, srcRect, dstRect); } +void DirectX9Renderer::SetDither(bool dither) +{ +} + void DirectX9Renderer::Download(SDL_Surface* target) { Actual_Download(target); diff --git a/miniwin/src/d3drm/backends/opengl1/actual.cpp b/miniwin/src/d3drm/backends/opengl1/actual.cpp index 9a2e314d..b9234c8e 100644 --- a/miniwin/src/d3drm/backends/opengl1/actual.cpp +++ b/miniwin/src/d3drm/backends/opengl1/actual.cpp @@ -354,3 +354,13 @@ void GL11_Download(SDL_Surface* target) glFinish(); glReadPixels(0, 0, target->w, target->h, GL_RGBA, GL_UNSIGNED_BYTE, target->pixels); } + +void GL11_SetDither(bool dither) +{ + if (dither) { + glEnable(GL_DITHER); + } + else { + glDisable(GL_DITHER); + } +} diff --git a/miniwin/src/d3drm/backends/opengl1/actual.h b/miniwin/src/d3drm/backends/opengl1/actual.h index 1d5e407d..7a6a4432 100644 --- a/miniwin/src/d3drm/backends/opengl1/actual.h +++ b/miniwin/src/d3drm/backends/opengl1/actual.h @@ -87,4 +87,5 @@ void GL11_Draw2DImage( float bottom, float top ); +void GL11_SetDither(bool dither); void GL11_Download(SDL_Surface* target); diff --git a/miniwin/src/d3drm/backends/opengl1/renderer.cpp b/miniwin/src/d3drm/backends/opengl1/renderer.cpp index 2e18b38a..4c6aacf9 100644 --- a/miniwin/src/d3drm/backends/opengl1/renderer.cpp +++ b/miniwin/src/d3drm/backends/opengl1/renderer.cpp @@ -384,6 +384,11 @@ void OpenGL1Renderer::Draw2DImage(Uint32 textureId, const SDL_Rect& srcRect, con GL11_Draw2DImage(m_textures[textureId], srcRect, dstRect, left, right, bottom, top); } +void OpenGL1Renderer::SetDither(bool dither) +{ + GL11_SetDither(dither); +} + void OpenGL1Renderer::Download(SDL_Surface* target) { GL11_Download(m_renderedImage); diff --git a/miniwin/src/d3drm/backends/opengles2/renderer.cpp b/miniwin/src/d3drm/backends/opengles2/renderer.cpp index 9cb8455c..28466bea 100644 --- a/miniwin/src/d3drm/backends/opengles2/renderer.cpp +++ b/miniwin/src/d3drm/backends/opengles2/renderer.cpp @@ -706,3 +706,13 @@ void OpenGLES2Renderer::Download(SDL_Surface* target) SDL_DestroySurface(bufferClone); } + +void OpenGLES2Renderer::SetDither(bool dither) +{ + if (dither) { + glEnable(GL_DITHER); + } + else { + glDisable(GL_DITHER); + } +} diff --git a/miniwin/src/d3drm/backends/sdl3gpu/renderer.cpp b/miniwin/src/d3drm/backends/sdl3gpu/renderer.cpp index 98704c66..69b326b6 100644 --- a/miniwin/src/d3drm/backends/sdl3gpu/renderer.cpp +++ b/miniwin/src/d3drm/backends/sdl3gpu/renderer.cpp @@ -942,6 +942,10 @@ void Direct3DRMSDL3GPURenderer::Draw2DImage(Uint32 textureId, const SDL_Rect& sr SDL_SetGPUScissor(m_renderPass, &fullViewport); } +void Direct3DRMSDL3GPURenderer::SetDither(bool dither) +{ +} + void Direct3DRMSDL3GPURenderer::Download(SDL_Surface* target) { if (!m_cmdbuf) { diff --git a/miniwin/src/d3drm/backends/software/renderer.cpp b/miniwin/src/d3drm/backends/software/renderer.cpp index 02c5bfc0..b4fab671 100644 --- a/miniwin/src/d3drm/backends/software/renderer.cpp +++ b/miniwin/src/d3drm/backends/software/renderer.cpp @@ -788,6 +788,10 @@ void Direct3DRMSoftwareRenderer::Draw2DImage(Uint32 textureId, const SDL_Rect& s SDL_LockSurface(surface); } +void Direct3DRMSoftwareRenderer::SetDither(bool dither) +{ +} + void Direct3DRMSoftwareRenderer::Download(SDL_Surface* target) { SDL_Rect srcRect = { diff --git a/miniwin/src/d3drm/d3drmdevice.cpp b/miniwin/src/d3drm/d3drmdevice.cpp index b738c809..822f412e 100644 --- a/miniwin/src/d3drm/d3drmdevice.cpp +++ b/miniwin/src/d3drm/d3drmdevice.cpp @@ -84,9 +84,7 @@ D3DRMRENDERQUALITY Direct3DRMDevice2Impl::GetQuality() HRESULT Direct3DRMDevice2Impl::SetDither(BOOL dither) { - if (dither) { - MINIWIN_NOT_IMPLEMENTED(); - } + m_renderer->SetDither(dither); return DD_OK; } diff --git a/miniwin/src/internal/d3drmrenderer.h b/miniwin/src/internal/d3drmrenderer.h index 259b20eb..a871bb26 100644 --- a/miniwin/src/internal/d3drmrenderer.h +++ b/miniwin/src/internal/d3drmrenderer.h @@ -53,6 +53,7 @@ class Direct3DRMRenderer : public IDirect3DDevice2 { virtual void Flip() = 0; virtual void Draw2DImage(Uint32 textureId, const SDL_Rect& srcRect, const SDL_Rect& dstRect) = 0; virtual void Download(SDL_Surface* target) = 0; + virtual void SetDither(bool dither) = 0; protected: int m_width, m_height; diff --git a/miniwin/src/internal/d3drmrenderer_citro3d.h b/miniwin/src/internal/d3drmrenderer_citro3d.h index 426f75c8..af91b3dc 100644 --- a/miniwin/src/internal/d3drmrenderer_citro3d.h +++ b/miniwin/src/internal/d3drmrenderer_citro3d.h @@ -50,6 +50,7 @@ class Citro3DRenderer : public Direct3DRMRenderer { void Flip() override; void Draw2DImage(Uint32 textureId, const SDL_Rect& srcRect, const SDL_Rect& dstRect) override; void Download(SDL_Surface* target) override; + void SetDither(bool dither) override; private: void AddTextureDestroyCallback(Uint32 id, IDirect3DRMTexture* texture); diff --git a/miniwin/src/internal/d3drmrenderer_directx9.h b/miniwin/src/internal/d3drmrenderer_directx9.h index 900bbe03..18ac126f 100644 --- a/miniwin/src/internal/d3drmrenderer_directx9.h +++ b/miniwin/src/internal/d3drmrenderer_directx9.h @@ -36,6 +36,7 @@ class DirectX9Renderer : public Direct3DRMRenderer { void Flip() override; void Draw2DImage(Uint32 textureId, const SDL_Rect& srcRect, const SDL_Rect& dstRect) override; void Download(SDL_Surface* target) override; + void SetDither(bool dither) override; private: void AddTextureDestroyCallback(Uint32 id, IDirect3DRMTexture* texture); diff --git a/miniwin/src/internal/d3drmrenderer_opengl1.h b/miniwin/src/internal/d3drmrenderer_opengl1.h index dce25e6b..656a964b 100644 --- a/miniwin/src/internal/d3drmrenderer_opengl1.h +++ b/miniwin/src/internal/d3drmrenderer_opengl1.h @@ -36,6 +36,7 @@ class OpenGL1Renderer : public Direct3DRMRenderer { void Flip() override; void Draw2DImage(Uint32 textureId, const SDL_Rect& srcRect, const SDL_Rect& dstRect) override; void Download(SDL_Surface* target) override; + void SetDither(bool dither) override; private: void AddTextureDestroyCallback(Uint32 id, IDirect3DRMTexture* texture); diff --git a/miniwin/src/internal/d3drmrenderer_opengles2.h b/miniwin/src/internal/d3drmrenderer_opengles2.h index f5d62579..0aaec63d 100644 --- a/miniwin/src/internal/d3drmrenderer_opengles2.h +++ b/miniwin/src/internal/d3drmrenderer_opengles2.h @@ -57,6 +57,7 @@ class OpenGLES2Renderer : public Direct3DRMRenderer { void Flip() override; void Draw2DImage(Uint32 textureId, const SDL_Rect& srcRect, const SDL_Rect& dstRect) override; void Download(SDL_Surface* target) override; + void SetDither(bool dither) override; private: void AddTextureDestroyCallback(Uint32 id, IDirect3DRMTexture* texture); diff --git a/miniwin/src/internal/d3drmrenderer_sdl3gpu.h b/miniwin/src/internal/d3drmrenderer_sdl3gpu.h index 02ec05d0..cf68a8c4 100644 --- a/miniwin/src/internal/d3drmrenderer_sdl3gpu.h +++ b/miniwin/src/internal/d3drmrenderer_sdl3gpu.h @@ -67,6 +67,7 @@ class Direct3DRMSDL3GPURenderer : public Direct3DRMRenderer { void Flip() override; void Draw2DImage(Uint32 textureId, const SDL_Rect& srcRect, const SDL_Rect& dstRect) override; void Download(SDL_Surface* target) override; + void SetDither(bool dither) override; private: Direct3DRMSDL3GPURenderer( diff --git a/miniwin/src/internal/d3drmrenderer_software.h b/miniwin/src/internal/d3drmrenderer_software.h index 87715999..91abac32 100644 --- a/miniwin/src/internal/d3drmrenderer_software.h +++ b/miniwin/src/internal/d3drmrenderer_software.h @@ -49,6 +49,7 @@ class Direct3DRMSoftwareRenderer : public Direct3DRMRenderer { void Flip() override; void Draw2DImage(Uint32 textureId, const SDL_Rect& srcRect, const SDL_Rect& dstRect) override; void Download(SDL_Surface* target) override; + void SetDither(bool dither) override; private: void ClearZBuffer(); From 4c0035fa74966fc20fd2122d1222b45a6b8e71c0 Mon Sep 17 00:00:00 2001 From: Anders Jenbo Date: Wed, 2 Jul 2025 14:50:45 +0200 Subject: [PATCH 038/188] Stip out more unnecessary OpenGL calls (#476) --- miniwin/src/d3drm/backends/opengl1/actual.cpp | 12 +----------- 1 file changed, 1 insertion(+), 11 deletions(-) diff --git a/miniwin/src/d3drm/backends/opengl1/actual.cpp b/miniwin/src/d3drm/backends/opengl1/actual.cpp index b9234c8e..d75eb04e 100644 --- a/miniwin/src/d3drm/backends/opengl1/actual.cpp +++ b/miniwin/src/d3drm/backends/opengl1/actual.cpp @@ -130,8 +130,7 @@ void GL11_BeginFrame(const Matrix4x4* projection) for (int i = 0; i < 8; ++i) { glDisable(GL_LIGHT0 + i); } - const GLfloat zeroAmbient[4] = {0.f, 0.f, 0.f, 1.f}; - glLightModelfv(GL_LIGHT_MODEL_AMBIENT, zeroAmbient); + glLightModeli(GL_LIGHT_MODEL_LOCAL_VIEWER, GL_TRUE); // Projection and view @@ -145,7 +144,6 @@ void GL11_UploadLight(int lightIdx, GL11_BridgeSceneLight* l) { // Setup light glMatrixMode(GL_MODELVIEW); - glPushMatrix(); glLoadIdentity(); GLenum lightId = GL_LIGHT0 + lightIdx++; const FColor& c = l->color; @@ -188,8 +186,6 @@ void GL11_UploadLight(int lightIdx, GL11_BridgeSceneLight* l) glLightfv(lightId, GL_POSITION, pos); } glEnable(lightId); - - glPopMatrix(); } void GL11_EnableTransparency() @@ -273,8 +269,6 @@ void GL11_SubmitDraw( glDrawElements(GL_TRIANGLES, static_cast(mesh.indices.size()), GL_UNSIGNED_SHORT, mesh.indices.data()); } - - glPopMatrix(); } void GL11_Resize(int width, int height) @@ -304,13 +298,11 @@ void GL11_Draw2DImage( glDepthMask(GL_FALSE); glMatrixMode(GL_PROJECTION); - glPushMatrix(); glLoadIdentity(); glOrtho(left, right, bottom, top, -1, 1); glMatrixMode(GL_MODELVIEW); - glPushMatrix(); glLoadIdentity(); glDisable(GL_LIGHTING); @@ -344,9 +336,7 @@ void GL11_Draw2DImage( // Restore state glMatrixMode(GL_MODELVIEW); - glPopMatrix(); glMatrixMode(GL_PROJECTION); - glPopMatrix(); } void GL11_Download(SDL_Surface* target) From cb6fcd41946fdf1cb9a8792d3933276a6c1a76ca Mon Sep 17 00:00:00 2001 From: Anders Jenbo Date: Wed, 2 Jul 2025 14:52:24 +0200 Subject: [PATCH 039/188] Remove dead debug features (#471) --- LEGO1/mxdirectx/mxdirectdraw.cpp | 158 ------------------------------ LEGO1/mxdirectx/mxdirectdraw.h | 8 -- miniwin/include/miniwin/windows.h | 31 ------ miniwin/src/d3drm/d3drmdevice.cpp | 1 - miniwin/src/windows/windows.cpp | 66 ------------- 5 files changed, 264 deletions(-) diff --git a/LEGO1/mxdirectx/mxdirectdraw.cpp b/LEGO1/mxdirectx/mxdirectdraw.cpp index 104b6836..bedf4d2e 100644 --- a/LEGO1/mxdirectx/mxdirectdraw.cpp +++ b/LEGO1/mxdirectx/mxdirectdraw.cpp @@ -32,8 +32,6 @@ MxDirectDraw::MxDirectDraw() m_pPalette = NULL; m_pDirectDraw = NULL; m_bIsOnPrimaryDevice = TRUE; - m_pText1Surface = NULL; - m_pText2Surface = NULL; m_hWndMain = NULL; m_bIgnoreWMSIZE = FALSE; m_bPrimaryPalettized = FALSE; @@ -236,8 +234,6 @@ void MxDirectDraw::DestroyButNotDirectDraw() RELEASE(m_pPalette); RELEASE(m_pClipper); - RELEASE(m_pText1Surface); - RELEASE(m_pText2Surface); RELEASE(m_pZBuffer); RELEASE(m_pBackBuffer); RELEASE(m_pFrontBuffer); @@ -409,13 +405,6 @@ BOOL MxDirectDraw::DDSetMode(int width, int height, int bpp) } } - // create debug text only in windowed mode? - if (!m_bFullScreen) { - if (!CreateTextSurfaces()) { - return FALSE; - } - } - return TRUE; } @@ -565,133 +554,6 @@ void MxDirectDraw::ClearBackBuffers() } } -// FUNCTION: LEGO1 0x1009e110 -// FUNCTION: BETA10 0x101219de -BOOL MxDirectDraw::TextToTextSurface(const char* text, IDirectDrawSurface* pSurface, SIZE& textSizeOnSurface) -{ - HRESULT result; - HDC hdc; - RECT rc; - size_t textLength; - - if (!pSurface) { - return FALSE; - } - - result = pSurface->GetDC(&hdc); - if (result != DD_OK) { - Error("GetDC for text surface failed", result); - return FALSE; - } - - textLength = strlen(text); - - SelectObject(hdc, m_hFont); - SetTextColor(hdc, RGB(255, 255, 0)); - SetBkColor(hdc, RGB(0, 0, 0)); - SetBkMode(hdc, OPAQUE); - GetTextExtentPoint32(hdc, text, textLength, &textSizeOnSurface); - SetRect(&rc, 0, 0, textSizeOnSurface.cx, textSizeOnSurface.cy); - ExtTextOut(hdc, 0, 0, ETO_OPAQUE, &rc, text, textLength, NULL); - pSurface->ReleaseDC(hdc); - - return TRUE; -} - -// FUNCTION: LEGO1 0x1009e210 -// FUNCTION: BETA10 0x10121aea -BOOL MxDirectDraw::TextToTextSurface1(const char* text) -{ - return TextToTextSurface(text, m_pText1Surface, m_text1SizeOnSurface); -} - -// FUNCTION: LEGO1 0x1009e230 -// FUNCTION: BETA10 0x10121b1e -BOOL MxDirectDraw::TextToTextSurface2(const char* text) -{ - return TextToTextSurface(text, m_pText2Surface, m_text2SizeOnSurface); -} - -// FUNCTION: LEGO1 0x1009e250 -// FUNCTION: BETA10 0x10121b52 -BOOL MxDirectDraw::CreateTextSurfaces() -{ - HRESULT result; - DDCOLORKEY ddck; - DDSURFACEDESC ddsd; - HDC hdc; - char dummyinfo[] = "000x000x00 (RAMP) 0000"; - char dummyfps[] = "000.00 fps (000.00 fps (000.00 fps) 00000 tps)"; - - if (m_hFont != NULL) { - DeleteObject(m_hFont); - } - m_hFont = CreateFont( - m_currentMode.width <= 600 ? 12 : 24, - 0, - 0, - 0, - FW_NORMAL, - FALSE, - FALSE, - FALSE, - ANSI_CHARSET, - OUT_DEFAULT_PRECIS, - CLIP_DEFAULT_PRECIS, - DEFAULT_QUALITY, - VARIABLE_PITCH, - "Arial" - ); - - hdc = GetDC(NULL); - SelectObject(hdc, m_hFont); - GetTextExtentPoint(hdc, dummyfps, strlen(dummyfps), &m_text1SizeOnSurface); - GetTextExtentPoint(hdc, dummyinfo, strlen(dummyinfo), &m_text2SizeOnSurface); - ReleaseDC(NULL, hdc); - - memset(&ddsd, 0, sizeof(ddsd)); - ddsd.dwSize = sizeof(ddsd); - ddsd.dwFlags = DDSD_CAPS | DDSD_HEIGHT | DDSD_WIDTH; - ddsd.ddsCaps.dwCaps = DDSCAPS_OFFSCREENPLAIN; - if (m_bOnlySystemMemory) { - ddsd.ddsCaps.dwCaps |= DDSCAPS_SYSTEMMEMORY; - } - ddsd.dwHeight = m_text1SizeOnSurface.cy; - ddsd.dwWidth = m_text1SizeOnSurface.cx; - result = CreateDDSurface(&ddsd, &m_pText1Surface, NULL); - if (result != DD_OK) { - Error("CreateSurface for text surface 1 failed", result); - return FALSE; - } - memset(&ddck, 0, sizeof(ddck)); - m_pText1Surface->SetColorKey(DDCKEY_SRCBLT, &ddck); - if (!TextToTextSurface1(dummyfps)) { - return FALSE; - } - - memset(&ddsd, 0, sizeof(ddsd)); - ddsd.dwSize = sizeof(ddsd); - ddsd.dwFlags = DDSD_CAPS | DDSD_HEIGHT | DDSD_WIDTH; - ddsd.ddsCaps.dwCaps = DDSCAPS_OFFSCREENPLAIN; - if (m_bOnlySystemMemory) { - ddsd.ddsCaps.dwCaps |= DDSCAPS_SYSTEMMEMORY; - } - ddsd.dwHeight = m_text2SizeOnSurface.cy; - ddsd.dwWidth = m_text2SizeOnSurface.cx; - result = CreateDDSurface(&ddsd, &m_pText2Surface, NULL); - if (result != DD_OK) { - Error("CreateSurface for text surface 2 failed", result); - return FALSE; - } - memset(&ddck, 0, sizeof(ddck)); - m_pText2Surface->SetColorKey(DDCKEY_SRCBLT, &ddck); - if (!TextToTextSurface2(dummyinfo)) { - return FALSE; - } - - return TRUE; -} - // FUNCTION: LEGO1 0x1009e4d0 // FUNCTION: BETA10 0x10121e87 BOOL MxDirectDraw::RestoreSurfaces() @@ -728,26 +590,6 @@ BOOL MxDirectDraw::RestoreSurfaces() } } - if (m_pText1Surface != NULL) { - if (m_pText1Surface->IsLost() == DDERR_SURFACELOST) { - result = m_pText1Surface->Restore(); - if (result != DD_OK) { - Error("Restore of text surface 1 failed", result); - return FALSE; - } - } - } - - if (m_pText2Surface != NULL) { - if (m_pText2Surface->IsLost() == DDERR_SURFACELOST) { - result = m_pText2Surface->Restore(); - if (result != DD_OK) { - Error("Restore of text surface 2 failed", result); - return FALSE; - } - } - } - return TRUE; } diff --git a/LEGO1/mxdirectx/mxdirectdraw.h b/LEGO1/mxdirectx/mxdirectdraw.h index 574f8e54..554de189 100644 --- a/LEGO1/mxdirectx/mxdirectdraw.h +++ b/LEGO1/mxdirectx/mxdirectdraw.h @@ -58,9 +58,6 @@ class MxDirectDraw { int Pause(BOOL); BOOL RestoreSurfaces(); - BOOL TextToTextSurface1(const char* text); - BOOL TextToTextSurface2(const char* lpString); - virtual const char* ErrorToString(HRESULT p_error); // vtable+0x10 int FlipToGDISurface(); @@ -77,9 +74,6 @@ class MxDirectDraw { BOOL GetDDSurfaceDesc(LPDDSURFACEDESC lpDDSurfDesc, LPDIRECTDRAWSURFACE lpDDSurf); BOOL CreateZBuffer(DDSCapsFlags memorytype, DWORD depth); - BOOL CreateTextSurfaces(); - BOOL TextToTextSurface(const char* text, IDirectDrawSurface* pSurface, SIZE& textSizeOnSurface); - void Error(const char* p_message, int p_error); BOOL RecreateDirectDraw(GUID** a2); @@ -97,8 +91,6 @@ class MxDirectDraw { IDirectDrawSurface* m_pFrontBuffer; // 0x10 IDirectDrawSurface* m_pBackBuffer; // 0x14 IDirectDrawSurface* m_pZBuffer; // 0x18 - IDirectDrawSurface* m_pText1Surface; // 0x1c - IDirectDrawSurface* m_pText2Surface; // 0x20 IDirectDrawClipper* m_pClipper; // 0x24 IDirectDrawPalette* m_pPalette; // 0x28 PALETTEENTRY m_paletteEntries[256]; // 0x2c diff --git a/miniwin/include/miniwin/windows.h b/miniwin/include/miniwin/windows.h index 83726998..46bfd43c 100644 --- a/miniwin/include/miniwin/windows.h +++ b/miniwin/include/miniwin/windows.h @@ -169,37 +169,6 @@ int WINAPI GetDeviceCaps(HDC hdc, int index); BOOL RedrawWindow(void* hWnd, const void* lprcUpdate, void* hrgnUpdate, unsigned int flags); -int SetBkColor(void*, int); - -int SetBkMode(void*, int); - -int SetTextColor(HDC hdc, int color); - -BOOL GetTextExtentPoint(HDC hdc, LPCSTR lpString, int c, SIZE* psizl); - -int ExtTextOut(HDC, int, int, unsigned int, const RECT*, LPCSTR, unsigned int, void*); - -HFONT CreateFont( - int, - int, - int, - int, - int, - unsigned int, - unsigned int, - unsigned int, - unsigned int, - unsigned int, - unsigned int, - unsigned int, - unsigned int, - LPCSTR -); - -void* SelectObject(HDC, HFONT); - -int GetTextExtentPoint32(HDC hdc, LPCSTR str, int len, SIZE* out); - HMENU GetMenu(HWND hWnd); int DrawMenuBar(void* hWnd); diff --git a/miniwin/src/d3drm/d3drmdevice.cpp b/miniwin/src/d3drm/d3drmdevice.cpp index 822f412e..40dd1137 100644 --- a/miniwin/src/d3drm/d3drmdevice.cpp +++ b/miniwin/src/d3drm/d3drmdevice.cpp @@ -116,7 +116,6 @@ D3DRMRENDERMODE Direct3DRMDevice2Impl::GetRenderMode() HRESULT Direct3DRMDevice2Impl::Update() { - MINIWIN_NOT_IMPLEMENTED(); return DD_OK; } diff --git a/miniwin/src/windows/windows.cpp b/miniwin/src/windows/windows.cpp index 5e174b8a..0e8b53ed 100644 --- a/miniwin/src/windows/windows.cpp +++ b/miniwin/src/windows/windows.cpp @@ -83,72 +83,6 @@ BOOL RedrawWindow(void* hWnd, const void* lprcUpdate, void* hrgnUpdate, unsigned return 1; } -int SetBkColor(void*, int) -{ - MINIWIN_NOT_IMPLEMENTED(); - return 0; -} - -int SetBkMode(void*, int) -{ - MINIWIN_NOT_IMPLEMENTED(); - return 0; -} - -int SetTextColor(HDC hdc, int color) -{ - MINIWIN_NOT_IMPLEMENTED(); - return color; -} - -BOOL GetTextExtentPoint(HDC hdc, LPCSTR lpString, int c, SIZE* psizl) -{ - MINIWIN_NOT_IMPLEMENTED(); - if (psizl) { - psizl->cx = 8 * c; - psizl->cy = 16; - } - return TRUE; -} - -int ExtTextOut(HDC, int, int, unsigned int, const RECT*, LPCSTR, unsigned int, void*) -{ - MINIWIN_NOT_IMPLEMENTED(); - return 1; -} - -HFONT CreateFont( - int, - int, - int, - int, - int, - unsigned int, - unsigned int, - unsigned int, - unsigned int, - unsigned int, - unsigned int, - unsigned int, - unsigned int, - LPCSTR -) -{ - MINIWIN_NOT_IMPLEMENTED(); - return nullptr; -} - -void* SelectObject(HDC, HFONT) -{ - MINIWIN_NOT_IMPLEMENTED(); - return nullptr; -} - -int GetTextExtentPoint32(HDC hdc, LPCSTR str, int len, SIZE* out) -{ - return GetTextExtentPoint(hdc, str, len, out); -} - HMENU GetMenu(HWND hWnd) { return NULL; From 0e70735549d59d848bbda5f751d2f1fb100509f5 Mon Sep 17 00:00:00 2001 From: VoxelTek <53562267+VoxelTek@users.noreply.github.com> Date: Wed, 2 Jul 2025 23:16:54 +1000 Subject: [PATCH 040/188] Add resizable property to window creation (#477) Add resizable property to window creation. Allows Close button to be present in some cases where it did not appear. --- ISLE/isleapp.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/ISLE/isleapp.cpp b/ISLE/isleapp.cpp index e12f9183..dd45a1b0 100644 --- a/ISLE/isleapp.cpp +++ b/ISLE/isleapp.cpp @@ -661,6 +661,7 @@ MxResult IsleApp::SetupWindow() SDL_SetNumberProperty(props, SDL_PROP_WINDOW_CREATE_WIDTH_NUMBER, g_targetWidth); SDL_SetNumberProperty(props, SDL_PROP_WINDOW_CREATE_HEIGHT_NUMBER, g_targetHeight); SDL_SetBooleanProperty(props, SDL_PROP_WINDOW_CREATE_FULLSCREEN_BOOLEAN, m_fullScreen); + SDL_SetBooleanProperty(props, SDL_PROP_WINDOW_CREATE_RESIZABLE_BOOLEAN, true); SDL_SetStringProperty(props, SDL_PROP_WINDOW_CREATE_TITLE_STRING, WINDOW_TITLE); #if defined(MINIWIN) && !defined(__3DS__) SDL_SetBooleanProperty(props, SDL_PROP_WINDOW_CREATE_OPENGL_BOOLEAN, true); From 10b6d28cf810f469e7a5479878b63040bc964127 Mon Sep 17 00:00:00 2001 From: Christian Semmler Date: Wed, 2 Jul 2025 08:06:09 -0700 Subject: [PATCH 041/188] Revert "Add resizable property to window creation (#477)" This reverts commit 0e70735549d59d848bbda5f751d2f1fb100509f5. --- ISLE/isleapp.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/ISLE/isleapp.cpp b/ISLE/isleapp.cpp index dd45a1b0..e12f9183 100644 --- a/ISLE/isleapp.cpp +++ b/ISLE/isleapp.cpp @@ -661,7 +661,6 @@ MxResult IsleApp::SetupWindow() SDL_SetNumberProperty(props, SDL_PROP_WINDOW_CREATE_WIDTH_NUMBER, g_targetWidth); SDL_SetNumberProperty(props, SDL_PROP_WINDOW_CREATE_HEIGHT_NUMBER, g_targetHeight); SDL_SetBooleanProperty(props, SDL_PROP_WINDOW_CREATE_FULLSCREEN_BOOLEAN, m_fullScreen); - SDL_SetBooleanProperty(props, SDL_PROP_WINDOW_CREATE_RESIZABLE_BOOLEAN, true); SDL_SetStringProperty(props, SDL_PROP_WINDOW_CREATE_TITLE_STRING, WINDOW_TITLE); #if defined(MINIWIN) && !defined(__3DS__) SDL_SetBooleanProperty(props, SDL_PROP_WINDOW_CREATE_OPENGL_BOOLEAN, true); From 8f429e85fba310870e4e6b8fa595c71a9b76c993 Mon Sep 17 00:00:00 2001 From: Christian Semmler Date: Wed, 2 Jul 2025 08:48:23 -0700 Subject: [PATCH 042/188] Improve Web port Docker setup (#479) --- docker/emscripten/Dockerfile | 17 +++-- docker/emscripten/nginx.conf | 65 +++++++++++++++++-- docker/emscripten/ssl_example/Caddyfile | 3 + .../emscripten/ssl_example/docker-compose.yml | 18 +++++ 4 files changed, 93 insertions(+), 10 deletions(-) create mode 100644 docker/emscripten/ssl_example/Caddyfile create mode 100644 docker/emscripten/ssl_example/docker-compose.yml diff --git a/docker/emscripten/Dockerfile b/docker/emscripten/Dockerfile index d15d2406..f234e9ed 100644 --- a/docker/emscripten/Dockerfile +++ b/docker/emscripten/Dockerfile @@ -21,7 +21,14 @@ RUN cd /emsdk/upstream/emscripten && \ git apply --check /tmp/libwasmfs_fetch.js.patch && \ git apply /tmp/libwasmfs_fetch.js.patch -COPY --chown=emscripten:emscripten . . +COPY --chown=emscripten:emscripten 3rdparty/ ./3rdparty/ +COPY --chown=emscripten:emscripten LEGO1/ ./LEGO1/ +COPY --chown=emscripten:emscripten ISLE/ ./ISLE/ +COPY --chown=emscripten:emscripten miniwin/ ./miniwin/ +COPY --chown=emscripten:emscripten util/ ./util/ +COPY --chown=emscripten:emscripten CMake/ ./CMake/ +COPY --chown=emscripten:emscripten packaging/ ./packaging/ +COPY --chown=emscripten:emscripten CMakeLists.txt . RUN emcmake cmake -S . -B build -DISLE_BUILD_CONFIG=OFF -DISLE_DEBUG=OFF -DCMAKE_BUILD_TYPE=Release -DISLE_EMSCRIPTEN_HOST=/assets && \ emmake cmake --build build -j 32 @@ -29,9 +36,9 @@ RUN emcmake cmake -S . -B build -DISLE_BUILD_CONFIG=OFF -DISLE_DEBUG=OFF -DCMAKE RUN echo "Fetching isle.pizza frontend..."; \ git clone --depth 1 https://github.com/isledecomp/isle.pizza /tmp/isle.pizza; -FROM nginx:alpine +FROM openresty/openresty:alpine -COPY docker/emscripten/nginx.conf /etc/nginx/nginx.conf -COPY --from=builder /tmp/isle.pizza /usr/share/nginx/html -COPY --from=builder /src/build/isle.* /usr/share/nginx/html +COPY docker/emscripten/nginx.conf /usr/local/openresty/nginx/conf/nginx.conf +COPY --from=builder /tmp/isle.pizza /usr/local/openresty/nginx/html +COPY --from=builder /src/build/isle.* /usr/local/openresty/nginx/html EXPOSE 6931 diff --git a/docker/emscripten/nginx.conf b/docker/emscripten/nginx.conf index 30d9e34d..cbd53d25 100644 --- a/docker/emscripten/nginx.conf +++ b/docker/emscripten/nginx.conf @@ -3,7 +3,43 @@ events { } http { - include /etc/nginx/mime.types; + init_by_lua_block { + function find_entry_in_dir(parent_dir, name_to_find) + local safe_parent_dir = string.gsub(parent_dir, "'", "'\\''") + local lower_name_to_find = string.lower(name_to_find) + local pipe = io.popen("ls -A '" .. safe_parent_dir .. "' 2>/dev/null") + if not pipe then return nil end + + for entry in pipe:lines() do + if string.lower(entry) == lower_name_to_find then + pipe:close() + return entry + end + end + pipe:close() + return nil + end + + function find_recursive_path(path_to_check) + local current_resolved_path = "/" + + for component in string.gmatch(path_to_check, "([^/]+)") do + local found_entry = find_entry_in_dir(current_resolved_path, component) + if not found_entry then + return nil + end + + if current_resolved_path == "/" then + current_resolved_path = current_resolved_path .. found_entry + else + current_resolved_path = current_resolved_path .. "/" .. found_entry + end + end + return current_resolved_path + end + } + + include /usr/local/openresty/nginx/conf/mime.types; server { listen 6931; @@ -14,14 +50,33 @@ http { add_header 'Cross-Origin-Resource-Policy' 'cross-origin'; location / { - root /usr/share/nginx/html; + root /usr/local/openresty/nginx/html; index index.html isle.html; try_files $uri $uri/ =404; } - location ~* ^/assets/(.*)$ { - alias /assets/; - try_files /$1 /DATA/disk/$1 =404; + location /assets/ { + content_by_lua_block { + local request_uri = ngx.var.uri + local resolved_path = find_recursive_path(request_uri) + + if not resolved_path then + local fallback_uri = ngx.re.sub(request_uri, [[^/assets/]], "/assets/DATA/disk/", "i") + resolved_path = find_recursive_path(fallback_uri) + end + + if resolved_path then + ngx.exec("/internal" .. resolved_path) + else + ngx.exit(ngx.HTTP_NOT_FOUND) + end + } + } + + location /internal/assets/ { + internal; + root /; + rewrite ^/internal(.*)$ $1 break; } } } diff --git a/docker/emscripten/ssl_example/Caddyfile b/docker/emscripten/ssl_example/Caddyfile new file mode 100644 index 00000000..a5fc9766 --- /dev/null +++ b/docker/emscripten/ssl_example/Caddyfile @@ -0,0 +1,3 @@ +https://localhost:6932 { + reverse_proxy app:6931 +} diff --git a/docker/emscripten/ssl_example/docker-compose.yml b/docker/emscripten/ssl_example/docker-compose.yml new file mode 100644 index 00000000..84e88ea9 --- /dev/null +++ b/docker/emscripten/ssl_example/docker-compose.yml @@ -0,0 +1,18 @@ +services: + app: + image: ghcr.io/isledecomp/isle-portable-emscripten:master + ports: + - "6931:6931" + volumes: + - ${ASSETS_PATH}:/assets + + caddy: + image: caddy:latest + ports: + - "6932:6932" + volumes: + - ./Caddyfile:/etc/caddy/Caddyfile + - caddy_data:/data + +volumes: + caddy_data: \ No newline at end of file From 40ec9118118530ef6b1324cbd911616c040964bf Mon Sep 17 00:00:00 2001 From: Fabian Neundorf Date: Wed, 2 Jul 2025 21:09:25 +0200 Subject: [PATCH 043/188] Clear unknown in `MxWavePresenter` (#1600) --- LEGO1/omni/include/mxwavepresenter.h | 2 +- LEGO1/omni/src/audio/mxwavepresenter.cpp | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/LEGO1/omni/include/mxwavepresenter.h b/LEGO1/omni/include/mxwavepresenter.h index cf64b90f..52758951 100644 --- a/LEGO1/omni/include/mxwavepresenter.h +++ b/LEGO1/omni/include/mxwavepresenter.h @@ -71,7 +71,7 @@ class MxWavePresenter : public MxSoundPresenter { void Destroy(MxBool p_fromDestructor); MxS8 GetPlayedChunks(); - MxBool FUN_100b1ba0(); + MxBool ReadyForNextChunk(); void WriteToSoundBuffer(void* p_audioPtr, MxU32 p_length); WaveFormat* m_waveFormat; // 0x54 diff --git a/LEGO1/omni/src/audio/mxwavepresenter.cpp b/LEGO1/omni/src/audio/mxwavepresenter.cpp index ffc7e708..37a75e5e 100644 --- a/LEGO1/omni/src/audio/mxwavepresenter.cpp +++ b/LEGO1/omni/src/audio/mxwavepresenter.cpp @@ -67,7 +67,7 @@ MxS8 MxWavePresenter::GetPlayedChunks() } // FUNCTION: LEGO1 0x100b1ba0 -MxBool MxWavePresenter::FUN_100b1ba0() +MxBool MxWavePresenter::ReadyForNextChunk() { return !m_started || GetPlayedChunks() != m_writtenChunks; } @@ -248,7 +248,7 @@ MxResult MxWavePresenter::PutData() if (IsEnabled()) { switch (m_currentTickleState) { case e_streaming: - if (m_currentChunk && FUN_100b1ba0()) { + if (m_currentChunk && ReadyForNextChunk()) { WriteToSoundBuffer(m_currentChunk->GetData(), m_currentChunk->GetLength()); m_subscriber->FreeDataChunk(m_currentChunk); m_currentChunk = NULL; From 89fe7fa9246b4dfbb0e5a53eec4e030fbffd60ac Mon Sep 17 00:00:00 2001 From: Christian Semmler Date: Wed, 2 Jul 2025 13:43:57 -0700 Subject: [PATCH 044/188] Fix wrong notification type in `LegoRace::Notify` (#1601) --- LEGO1/lego/legoomni/include/carrace.h | 14 +++++++------- LEGO1/lego/legoomni/include/jetskirace.h | 12 ++++++------ LEGO1/lego/legoomni/include/legorace.h | 4 ++-- LEGO1/lego/legoomni/src/race/carrace.cpp | 8 +++----- LEGO1/lego/legoomni/src/race/jetskirace.cpp | 6 +++--- LEGO1/lego/legoomni/src/race/legorace.cpp | 4 ++-- 6 files changed, 23 insertions(+), 25 deletions(-) diff --git a/LEGO1/lego/legoomni/include/carrace.h b/LEGO1/lego/legoomni/include/carrace.h index 03e7a958..7a3b2f6f 100644 --- a/LEGO1/lego/legoomni/include/carrace.h +++ b/LEGO1/lego/legoomni/include/carrace.h @@ -52,13 +52,13 @@ class CarRace : public LegoRace { return !strcmp(p_name, CarRace::ClassName()) || LegoRace::IsA(p_name); } - MxResult Create(MxDSAction& p_dsAction) override; // vtable+0x18 - void ReadyWorld() override; // vtable+0x50 - MxBool Escape() override; // vtable+0x64 - MxLong HandleClick(LegoEventNotificationParam&) override; // vtable+0x6c - MxLong HandlePathStruct(LegoPathStructNotificationParam&) override; // vtable+0x70 - MxLong HandleEndAction(MxEndActionNotificationParam&) override; // vtable+0x74 - MxLong HandleType0Notification(MxNotificationParam&) override; // vtable+0x78 + MxResult Create(MxDSAction& p_dsAction) override; // vtable+0x18 + void ReadyWorld() override; // vtable+0x50 + MxBool Escape() override; // vtable+0x64 + MxLong HandleControl(LegoControlManagerNotificationParam&) override; // vtable+0x6c + MxLong HandlePathStruct(LegoPathStructNotificationParam&) override; // vtable+0x70 + MxLong HandleEndAction(MxEndActionNotificationParam&) override; // vtable+0x74 + MxLong HandleType0Notification(MxNotificationParam&) override; // vtable+0x78 // FUNCTION: BETA10 0x100cd060 RaceSkel* GetSkeleton() { return m_skeleton; } diff --git a/LEGO1/lego/legoomni/include/jetskirace.h b/LEGO1/lego/legoomni/include/jetskirace.h index 7542d851..61407cce 100644 --- a/LEGO1/lego/legoomni/include/jetskirace.h +++ b/LEGO1/lego/legoomni/include/jetskirace.h @@ -29,12 +29,12 @@ class JetskiRace : public LegoRace { return !strcmp(p_name, JetskiRace::ClassName()) || LegoRace::IsA(p_name); } - MxResult Create(MxDSAction& p_dsAction) override; // vtable+0x18 - void ReadyWorld() override; // vtable+0x50 - MxBool Escape() override; // vtable+0x64 - MxLong HandleClick(LegoEventNotificationParam&) override; // vtable+0x6c - MxLong HandlePathStruct(LegoPathStructNotificationParam&) override; // vtable+0x70 - MxLong HandleEndAction(MxEndActionNotificationParam&) override; // vtable+0x74 + MxResult Create(MxDSAction& p_dsAction) override; // vtable+0x18 + void ReadyWorld() override; // vtable+0x50 + MxBool Escape() override; // vtable+0x64 + MxLong HandleControl(LegoControlManagerNotificationParam&) override; // vtable+0x6c + MxLong HandlePathStruct(LegoPathStructNotificationParam&) override; // vtable+0x70 + MxLong HandleEndAction(MxEndActionNotificationParam&) override; // vtable+0x74 void FUN_10016930(MxS32 p_param1, MxS16 p_param2); diff --git a/LEGO1/lego/legoomni/include/legorace.h b/LEGO1/lego/legoomni/include/legorace.h index 55d43785..e1b93211 100644 --- a/LEGO1/lego/legoomni/include/legorace.h +++ b/LEGO1/lego/legoomni/include/legorace.h @@ -11,7 +11,7 @@ #include "mxtypes.h" class Act1State; -class LegoEventNotificationParam; +class LegoControlManagerNotificationParam; class LegoPathActor; class MxEndActionNotificationParam; class MxNotificationParam; @@ -117,7 +117,7 @@ class LegoRace : public LegoWorld { MxResult Create(MxDSAction& p_dsAction) override; // vtable+0x18 - virtual MxLong HandleClick(LegoEventNotificationParam&) = 0; // vtable+0x6c + virtual MxLong HandleControl(LegoControlManagerNotificationParam&) = 0; // vtable+0x6c // FUNCTION: LEGO1 0x10015b70 virtual MxLong HandlePathStruct(LegoPathStructNotificationParam&) { return 0; } // vtable+0x70 diff --git a/LEGO1/lego/legoomni/src/race/carrace.cpp b/LEGO1/lego/legoomni/src/race/carrace.cpp index 86c50b4a..0c3c3a51 100644 --- a/LEGO1/lego/legoomni/src/race/carrace.cpp +++ b/LEGO1/lego/legoomni/src/race/carrace.cpp @@ -333,12 +333,10 @@ MxLong CarRace::HandlePathStruct(LegoPathStructNotificationParam& p_param) } // FUNCTION: LEGO1 0x10017650 -MxLong CarRace::HandleClick(LegoEventNotificationParam& p_param) +MxLong CarRace::HandleControl(LegoControlManagerNotificationParam& p_param) { - LegoControlManagerNotificationParam* param = (LegoControlManagerNotificationParam*) &p_param; - - if (param->m_unk0x28 == 1) { - switch (param->m_clickedObjectId) { + if (p_param.m_unk0x28 == 1) { + switch (p_param.m_clickedObjectId) { case 3: InvokeAction(Extra::e_stop, *g_carraceScript, CarraceScript::c_irtx08ra_PlayWav, NULL); m_act1State->m_unk0x018 = 0; diff --git a/LEGO1/lego/legoomni/src/race/jetskirace.cpp b/LEGO1/lego/legoomni/src/race/jetskirace.cpp index 54ad806c..1297ed36 100644 --- a/LEGO1/lego/legoomni/src/race/jetskirace.cpp +++ b/LEGO1/lego/legoomni/src/race/jetskirace.cpp @@ -120,12 +120,12 @@ MxLong JetskiRace::HandleEndAction(MxEndActionNotificationParam& p_param) } // FUNCTION: LEGO1 0x100165a0 -MxLong JetskiRace::HandleClick(LegoEventNotificationParam& p_param) +MxLong JetskiRace::HandleControl(LegoControlManagerNotificationParam& p_param) { MxLong result = 0; - if (((LegoControlManagerNotificationParam*) &p_param)->m_unk0x28 == 1) { - switch (((LegoControlManagerNotificationParam*) &p_param)->m_clickedObjectId) { + if (p_param.m_unk0x28 == 1) { + switch (p_param.m_clickedObjectId) { case JetraceScript::c_JetskiArms_Ctl: m_act1State->m_unk0x018 = 0; VariableTable()->SetVariable(g_raceState, ""); diff --git a/LEGO1/lego/legoomni/src/race/legorace.cpp b/LEGO1/lego/legoomni/src/race/legorace.cpp index a2c0555d..91374766 100644 --- a/LEGO1/lego/legoomni/src/race/legorace.cpp +++ b/LEGO1/lego/legoomni/src/race/legorace.cpp @@ -82,8 +82,8 @@ MxLong LegoRace::Notify(MxParam& p_param) case c_notificationEndAction: result = HandleEndAction((MxEndActionNotificationParam&) p_param); break; - case c_notificationClick: - result = HandleClick((LegoEventNotificationParam&) p_param); + case c_notificationControl: + result = HandleControl((LegoControlManagerNotificationParam&) p_param); break; case c_notificationPathStruct: result = HandlePathStruct((LegoPathStructNotificationParam&) p_param); From d74e6ab4014053a8d1a863d2833213644da5df68 Mon Sep 17 00:00:00 2001 From: Anders Jenbo Date: Thu, 3 Jul 2025 00:49:47 +0200 Subject: [PATCH 045/188] Use DDBLT_COLORFILL to clear screen on start (#487) --- LEGO1/mxdirectx/mxdirect3d.cpp | 35 ++++------------------- LEGO1/omni/src/video/mxdisplaysurface.cpp | 6 ++-- 2 files changed, 8 insertions(+), 33 deletions(-) diff --git a/LEGO1/mxdirectx/mxdirect3d.cpp b/LEGO1/mxdirectx/mxdirect3d.cpp index d6043a2c..e338046f 100644 --- a/LEGO1/mxdirectx/mxdirect3d.cpp +++ b/LEGO1/mxdirectx/mxdirect3d.cpp @@ -170,39 +170,16 @@ BOOL MxDirect3D::D3DSetMode() LPDIRECTDRAWSURFACE frontBuffer = FrontBuffer(); LPDIRECTDRAWSURFACE backBuffer = BackBuffer(); - DDSURFACEDESC desc; - memset(&desc, 0, sizeof(desc)); - desc.dwSize = sizeof(desc); + DDBLTFX ddBltFx = {}; + ddBltFx.dwSize = sizeof(DDBLTFX); + ddBltFx.dwFillColor = 0; - if (backBuffer->Lock(NULL, &desc, DDLOCK_WAIT | DDLOCK_WRITEONLY, NULL) == DD_OK) { - unsigned char* surface = (unsigned char*) desc.lpSurface; - - for (int i = 0; i < mode.height; i++) { - memset(surface, 0, desc.lPitch); - surface += desc.lPitch; - } - - backBuffer->Unlock(desc.lpSurface); - } - else { - SDL_Log("MxDirect3D::D3DSetMode() back lock failed\n"); + if (backBuffer->Blt(NULL, NULL, NULL, DDBLT_WAIT | DDBLT_COLORFILL, &ddBltFx) != DD_OK) { + SDL_Log("MxDirect3D::D3DSetMode() color fill failed\n"); } if (IsFullScreen()) { - memset(&desc, 0, sizeof(desc)); - desc.dwSize = sizeof(desc); - - if (frontBuffer->Lock(NULL, &desc, DDLOCK_WAIT | DDLOCK_WRITEONLY, NULL) == DD_OK) { - unsigned char* surface = (unsigned char*) desc.lpSurface; - - for (int i = 0; i < mode.height; i++) { - memset(surface, 0, desc.lPitch); - surface += desc.lPitch; - } - - frontBuffer->Unlock(desc.lpSurface); - } - else { + if (frontBuffer->Blt(NULL, NULL, NULL, DDBLT_WAIT | DDBLT_COLORFILL, &ddBltFx) != DD_OK) { SDL_Log("MxDirect3D::D3DSetMode() front lock failed\n"); } } diff --git a/LEGO1/omni/src/video/mxdisplaysurface.cpp b/LEGO1/omni/src/video/mxdisplaysurface.cpp index c2886ebe..badf05d1 100644 --- a/LEGO1/omni/src/video/mxdisplaysurface.cpp +++ b/LEGO1/omni/src/video/mxdisplaysurface.cpp @@ -64,8 +64,6 @@ void MxDisplaySurface::ClearScreen() MxS32 width = m_videoParam.GetRect().GetWidth(); MxS32 height = m_videoParam.GetRect().GetHeight(); - RECT rc = {0, 0, width, height}; - memset(&desc, 0, sizeof(desc)); desc.dwSize = sizeof(desc); if (m_ddSurface2->GetSurfaceDesc(&desc) != DD_OK) { @@ -77,9 +75,9 @@ void MxDisplaySurface::ClearScreen() ddBltFx.dwFillColor = 0; for (MxS32 i = 0; i < backBuffers; i++) { - if (m_ddSurface2->Blt(&rc, NULL, NULL, DDBLT_COLORFILL | DDBLT_WAIT, &ddBltFx) == DDERR_SURFACELOST) { + if (m_ddSurface2->Blt(NULL, NULL, NULL, DDBLT_COLORFILL | DDBLT_WAIT, &ddBltFx) == DDERR_SURFACELOST) { m_ddSurface2->Restore(); - m_ddSurface2->Blt(&rc, NULL, NULL, DDBLT_COLORFILL | DDBLT_WAIT, &ddBltFx); + m_ddSurface2->Blt(NULL, NULL, NULL, DDBLT_COLORFILL | DDBLT_WAIT, &ddBltFx); } if (m_videoParam.Flags().GetFlipSurfaces()) { From 4d8ba1986e3d46e9a6bcf7b182d8b77a5bc51bf2 Mon Sep 17 00:00:00 2001 From: Christian Semmler Date: Wed, 2 Jul 2025 17:07:16 -0700 Subject: [PATCH 046/188] Fix writing default texture (#1602) --- LEGO1/lego/legoomni/src/common/legoutils.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/LEGO1/lego/legoomni/src/common/legoutils.cpp b/LEGO1/lego/legoomni/src/common/legoutils.cpp index a9ea98a7..1e0dbba5 100644 --- a/LEGO1/lego/legoomni/src/common/legoutils.cpp +++ b/LEGO1/lego/legoomni/src/common/legoutils.cpp @@ -705,14 +705,14 @@ void WriteDefaultTexture(LegoStorage* p_storage, const char* p_name) if (image != NULL) { if (desc.dwWidth == desc.lPitch) { - memcpy(desc.lpSurface, image->GetBits(), desc.dwWidth * desc.dwHeight); + memcpy(image->GetBits(), desc.lpSurface, desc.dwWidth * desc.dwHeight); } else { MxU8* surface = (MxU8*) desc.lpSurface; - const LegoU8* bits = image->GetBits(); + LegoU8* bits = image->GetBits(); for (MxS32 i = 0; i < desc.dwHeight; i++) { - memcpy(surface, bits, desc.dwWidth); + memcpy(bits, surface, desc.dwWidth); surface += desc.lPitch; bits += desc.dwWidth; } From 04b0e9a47015160124d419873c7cf3fd47f65216 Mon Sep 17 00:00:00 2001 From: Christian Semmler Date: Wed, 2 Jul 2025 17:45:21 -0700 Subject: [PATCH 047/188] Fix saving of default textures (#490) --- LEGO1/lego/legoomni/src/common/legoutils.cpp | 8 ++++---- LEGO1/lego/sources/misc/legoimage.h | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/LEGO1/lego/legoomni/src/common/legoutils.cpp b/LEGO1/lego/legoomni/src/common/legoutils.cpp index 0946a0ab..a2ffbe5e 100644 --- a/LEGO1/lego/legoomni/src/common/legoutils.cpp +++ b/LEGO1/lego/legoomni/src/common/legoutils.cpp @@ -708,19 +708,19 @@ void WriteDefaultTexture(LegoStorage* p_storage, const char* p_name) memset(&desc, 0, sizeof(desc)); desc.dwSize = sizeof(desc); - if (surface->Lock(NULL, &desc, DDLOCK_SURFACEMEMORYPTR | DDLOCK_WRITEONLY, NULL) == DD_OK) { + if (surface->Lock(NULL, &desc, DDLOCK_SURFACEMEMORYPTR, NULL) == DD_OK) { LegoImage* image = new LegoImage(desc.dwWidth, desc.dwHeight); if (image != NULL) { if (desc.dwWidth == desc.lPitch) { - memcpy(desc.lpSurface, image->GetBits(), desc.dwWidth * desc.dwHeight); + memcpy(image->GetBits(), desc.lpSurface, desc.dwWidth * desc.dwHeight); } else { MxU8* surface = (MxU8*) desc.lpSurface; - const LegoU8* bits = image->GetBits(); + LegoU8* bits = image->GetBits(); for (MxS32 i = 0; i < desc.dwHeight; i++) { - memcpy(surface, bits, desc.dwWidth); + memcpy(bits, surface, desc.dwWidth); surface += desc.lPitch; bits += desc.dwWidth; } diff --git a/LEGO1/lego/sources/misc/legoimage.h b/LEGO1/lego/sources/misc/legoimage.h index ee1dc55d..d0269b82 100644 --- a/LEGO1/lego/sources/misc/legoimage.h +++ b/LEGO1/lego/sources/misc/legoimage.h @@ -46,7 +46,7 @@ class LegoImage { { m_palette->colors[p_i] = p_paletteEntry.GetColor(); } - const LegoU8* GetBits() const { return (LegoU8*) m_surface->pixels; } + LegoU8* GetBits() const { return (LegoU8*) m_surface->pixels; } LegoResult Read(LegoStorage* p_storage, LegoU32 p_square); LegoResult Write(LegoStorage* p_storage); From 3894d58efc25004e51c7eb6662ab320a196a533a Mon Sep 17 00:00:00 2001 From: Anders Jenbo Date: Thu, 3 Jul 2025 04:06:45 +0200 Subject: [PATCH 048/188] Fix disolve and wipe transitions on 32bit (#492) --- .../src/common/mxtransitionmanager.cpp | 23 +++++++++++++++---- 1 file changed, 19 insertions(+), 4 deletions(-) diff --git a/LEGO1/lego/legoomni/src/common/mxtransitionmanager.cpp b/LEGO1/lego/legoomni/src/common/mxtransitionmanager.cpp index d54563ab..9e052430 100644 --- a/LEGO1/lego/legoomni/src/common/mxtransitionmanager.cpp +++ b/LEGO1/lego/legoomni/src/common/mxtransitionmanager.cpp @@ -248,7 +248,7 @@ void MxTransitionManager::DissolveTransition() } else { MxU8* surf = (MxU8*) ddsd.lpSurface + ddsd.lPitch * row + xShift * 4; - *(MxU32*) surf = 0; + *(MxU32*) surf = 0xFF000000; } } } @@ -416,10 +416,25 @@ void MxTransitionManager::WipeDownTransition() // For each of the 240 animation ticks, blank out two scanlines // starting at the top of the screen. MxU8* line = (MxU8*) ddsd.lpSurface + 2 * ddsd.lPitch * m_animationTimer; - memset(line, 0, ddsd.lPitch); - line += ddsd.lPitch; - memset(line, 0, ddsd.lPitch); + if (ddsd.ddpfPixelFormat.dwRGBBitCount == 32) { + MxU32* pixels = (MxU32*) line; + int pixelsPerLine = ddsd.lPitch / 4; + for (int i = 0; i < pixelsPerLine; i++) { + pixels[i] = 0xFF000000; + } + line += ddsd.lPitch; + pixels = (MxU32*) line; + for (int i = 0; i < pixelsPerLine; i++) { + pixels[i] = 0xFF000000; + } + } + else { + memset(line, 0, ddsd.lPitch); + + line += ddsd.lPitch; + memset(line, 0, ddsd.lPitch); + } SetupCopyRect(&ddsd); m_ddSurface->Unlock(ddsd.lpSurface); From be73b40ae8bb16bd51d08fbfda2c823886b5f093 Mon Sep 17 00:00:00 2001 From: Christian Semmler Date: Wed, 2 Jul 2025 19:10:02 -0700 Subject: [PATCH 049/188] Add monkey patch to Emscripten to disable `OffscreenCanvas` at runtime (#493) * Add runtime option to disable OffscreenCanvas in web port * Remove old patch --- .../{libwasmfs_fetch.js.patch => emscripten.patch} | 13 +++++++++++++ docker/emscripten/Dockerfile | 6 +++--- 2 files changed, 16 insertions(+), 3 deletions(-) rename ISLE/emscripten/{libwasmfs_fetch.js.patch => emscripten.patch} (88%) diff --git a/ISLE/emscripten/libwasmfs_fetch.js.patch b/ISLE/emscripten/emscripten.patch similarity index 88% rename from ISLE/emscripten/libwasmfs_fetch.js.patch rename to ISLE/emscripten/emscripten.patch index de8fab60..899d72eb 100644 --- a/ISLE/emscripten/libwasmfs_fetch.js.patch +++ b/ISLE/emscripten/emscripten.patch @@ -1,3 +1,16 @@ +diff --git a/src/lib/libpthread.js b/src/lib/libpthread.js +index 6d979627e..97e3f8684 100644 +--- a/src/lib/libpthread.js ++++ b/src/lib/libpthread.js +@@ -697,7 +697,7 @@ var LibraryPThread = { + { + transferredCanvasNames = UTF8ToString(transferredCanvasNames).trim(); + } +- transferredCanvasNames = transferredCanvasNames ? transferredCanvasNames.split(',') : []; ++ transferredCanvasNames = transferredCanvasNames && !Module['disableOffscreenCanvases'] ? transferredCanvasNames.split(',') : []; + #if GL_DEBUG + dbg(`pthread_create: transferredCanvasNames="${transferredCanvasNames}"`); + #endif diff --git a/src/lib/libwasmfs_fetch.js b/src/lib/libwasmfs_fetch.js index e8c9f7e21..caf1971d2 100644 --- a/src/lib/libwasmfs_fetch.js diff --git a/docker/emscripten/Dockerfile b/docker/emscripten/Dockerfile index f234e9ed..a213093c 100644 --- a/docker/emscripten/Dockerfile +++ b/docker/emscripten/Dockerfile @@ -16,10 +16,10 @@ RUN chown -R emscripten:emscripten /src USER emscripten -COPY ISLE/emscripten/libwasmfs_fetch.js.patch /tmp/ +COPY ISLE/emscripten/emscripten.patch /tmp/ RUN cd /emsdk/upstream/emscripten && \ - git apply --check /tmp/libwasmfs_fetch.js.patch && \ - git apply /tmp/libwasmfs_fetch.js.patch + git apply --check /tmp/emscripten.patch && \ + git apply /tmp/emscripten.patch COPY --chown=emscripten:emscripten 3rdparty/ ./3rdparty/ COPY --chown=emscripten:emscripten LEGO1/ ./LEGO1/ From 605163b755273fd46fad57194bddd186b221b259 Mon Sep 17 00:00:00 2001 From: Anders Jenbo Date: Thu, 3 Jul 2025 04:57:57 +0200 Subject: [PATCH 050/188] Fix WindowsTransition (#495) --- .../src/common/mxtransitionmanager.cpp | 39 +++++++++++++++---- 1 file changed, 32 insertions(+), 7 deletions(-) diff --git a/LEGO1/lego/legoomni/src/common/mxtransitionmanager.cpp b/LEGO1/lego/legoomni/src/common/mxtransitionmanager.cpp index 9e052430..8c2c994d 100644 --- a/LEGO1/lego/legoomni/src/common/mxtransitionmanager.cpp +++ b/LEGO1/lego/legoomni/src/common/mxtransitionmanager.cpp @@ -469,17 +469,42 @@ void MxTransitionManager::WindowsTransition() MxS32 bytesPerPixel = ddsd.ddpfPixelFormat.dwRGBBitCount / 8; - memset(line, 0, ddsd.lPitch); + if (bytesPerPixel == 4) { + MxU32* pixels = (MxU32*) line; + for (int i = 0; i < 640; i++) { + pixels[i] = 0xFF000000; + } - for (MxS32 i = m_animationTimer + 1; i < 480 - m_animationTimer; i++) { - line += ddsd.lPitch; + for (MxS32 i = m_animationTimer + 1; i < 480 - m_animationTimer - 1; i++) { + line += ddsd.lPitch; + pixels = (MxU32*) line; + pixels[m_animationTimer] = 0xFF000000; + pixels[639 - m_animationTimer] = 0xFF000000; + } - memset(line + m_animationTimer * bytesPerPixel, 0, bytesPerPixel); - memset(line + 640 + (-1 - m_animationTimer) * bytesPerPixel, 0, bytesPerPixel); + if (m_animationTimer < 240 - 1) { + line += ddsd.lPitch; + pixels = (MxU32*) line; + for (int i = 0; i < 640; i++) { + pixels[i] = 0xFF000000; + } + } } + else { + memset(line, 0, 640 * bytesPerPixel); - line += ddsd.lPitch; - memset(line, 0, ddsd.lPitch); + for (MxS32 i = m_animationTimer + 1; i < 480 - m_animationTimer - 1; i++) { + line += ddsd.lPitch; + + memset(line + m_animationTimer * bytesPerPixel, 0, bytesPerPixel); + memset(line + (639 - m_animationTimer) * bytesPerPixel, 0, bytesPerPixel); + } + + if (m_animationTimer < 240 - 1) { + line += ddsd.lPitch; + memset(line, 0, 640 * bytesPerPixel); + } + } SetupCopyRect(&ddsd); m_ddSurface->Unlock(ddsd.lpSurface); From fde5e6ced33d9dd31a6146b49de499e5243504ad Mon Sep 17 00:00:00 2001 From: Joshua Peisach Date: Thu, 3 Jul 2025 06:43:27 -0400 Subject: [PATCH 051/188] citro3d: Let SDL3 take care of initiatizing/deinitializing GFX (#500) --- miniwin/src/d3drm/backends/citro3d/renderer.cpp | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/miniwin/src/d3drm/backends/citro3d/renderer.cpp b/miniwin/src/d3drm/backends/citro3d/renderer.cpp index 72d729bc..f076cf70 100644 --- a/miniwin/src/d3drm/backends/citro3d/renderer.cpp +++ b/miniwin/src/d3drm/backends/citro3d/renderer.cpp @@ -26,7 +26,7 @@ Citro3DRenderer::Citro3DRenderer(DWORD width, DWORD height) m_virtualWidth = width; m_virtualHeight = height; - gfxInitDefault(); + gfxSetScreenFormat(GFX_BOTTOM, GSP_BGR8_OES); consoleInit(GFX_TOP, nullptr); C3D_Init(C3D_DEFAULT_CMDBUF_SIZE); @@ -66,7 +66,6 @@ Citro3DRenderer::~Citro3DRenderer() shaderProgramFree(&program); DVLB_Free(vshader_dvlb); C3D_Fini(); - gfxExit(); } void Citro3DRenderer::PushLights(const SceneLight* lights, size_t count) From b2ee6c114e34c619427eb993d5e779b256524635 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gregor=20W=C3=BCnsch?= <41543402+gwuen@users.noreply.github.com> Date: Thu, 3 Jul 2025 16:14:09 +0200 Subject: [PATCH 052/188] Add `StartupWMClass` to the desktop file (#501) This explicitly adds a `StartupWMClass` to the desktop file to help desktop environments properly track the game window and associate it with the correct app icon and name. This adheres to the XDG Desktop Entry Specification [^1]. [^1]: https://specifications.freedesktop.org/desktop-entry-spec/latest/recognized-keys.html --- packaging/linux/isledecomp.desktop.in | 1 + 1 file changed, 1 insertion(+) diff --git a/packaging/linux/isledecomp.desktop.in b/packaging/linux/isledecomp.desktop.in index 28309205..3bb0a870 100644 --- a/packaging/linux/isledecomp.desktop.in +++ b/packaging/linux/isledecomp.desktop.in @@ -21,6 +21,7 @@ Keywords[ru]=LEGO;lego;Остров LEGO Keywords[uk_UA]=LEGO;lego;LEGO острів SingleMainWindow=true +StartupWMClass=isle TryExec=isle Exec=isle From 8e9f531b880364c7294f3a1e0d6b1e21907938d7 Mon Sep 17 00:00:00 2001 From: Anders Jenbo Date: Thu, 3 Jul 2025 16:58:32 +0200 Subject: [PATCH 053/188] Render surfaces in a single blit (#489) --- LEGO1/omni/src/video/mxvideopresenter.cpp | 76 ++++++++--------------- 1 file changed, 26 insertions(+), 50 deletions(-) diff --git a/LEGO1/omni/src/video/mxvideopresenter.cpp b/LEGO1/omni/src/video/mxvideopresenter.cpp index 84b1c663..e5ed4564 100644 --- a/LEGO1/omni/src/video/mxvideopresenter.cpp +++ b/LEGO1/omni/src/video/mxvideopresenter.cpp @@ -275,61 +275,37 @@ void MxVideoPresenter::PutFrame() } } else { - MxRegionCursor cursor(region); - MxRect32* regionRect; + RECT src, dest; - while ((regionRect = cursor.Next(rect))) { - if (regionRect->GetWidth() >= 1 && regionRect->GetHeight() >= 1) { - RECT src, dest; + if (m_unk0x58) { + src.left = 0; + src.top = 0; + src.right = GetWidth(); + src.bottom = GetHeight(); - if (m_unk0x58) { - src.left = regionRect->GetLeft() - GetX(); - src.top = regionRect->GetTop() - GetY(); - src.right = src.left + regionRect->GetWidth(); - src.bottom = src.top + regionRect->GetHeight(); + dest.left = GetX(); + dest.top = GetY(); + dest.right = dest.left + GetWidth(); + dest.bottom = dest.top + GetHeight(); + } - dest.left = regionRect->GetLeft(); - dest.top = regionRect->GetTop(); - dest.right = dest.left + regionRect->GetWidth(); - dest.bottom = dest.top + regionRect->GetHeight(); - } - - if (m_action->GetFlags() & MxDSAction::c_bit4) { - if (m_unk0x58) { - if (PrepareRects(src, dest) >= 0) { - ddSurface->Blt(&dest, m_unk0x58, &src, DDBLT_KEYSRC, NULL); - } - } - else { - displaySurface->VTable0x30( - m_frameBitmap, - regionRect->GetLeft() - GetX(), - regionRect->GetTop() - GetY(), - regionRect->GetLeft(), - regionRect->GetTop(), - regionRect->GetWidth(), - regionRect->GetHeight(), - FALSE - ); - } - } - else if (m_unk0x58) { - if (PrepareRects(src, dest) >= 0) { - ddSurface->Blt(&dest, m_unk0x58, &src, DDBLT_NONE, NULL); - } - } - else { - displaySurface->VTable0x28( - m_frameBitmap, - regionRect->GetLeft() - GetX(), - regionRect->GetTop() - GetY(), - regionRect->GetLeft(), - regionRect->GetTop(), - regionRect->GetWidth(), - regionRect->GetHeight() - ); + if (m_action->GetFlags() & MxDSAction::c_bit4) { + if (m_unk0x58) { + if (PrepareRects(src, dest) >= 0) { + ddSurface->Blt(&dest, m_unk0x58, &src, DDBLT_KEYSRC, NULL); } } + else { + displaySurface->VTable0x30(m_frameBitmap, 0, 0, GetX(), GetY(), GetWidth(), GetHeight(), FALSE); + } + } + else if (m_unk0x58) { + if (PrepareRects(src, dest) >= 0) { + ddSurface->Blt(&dest, m_unk0x58, &src, DDBLT_NONE, NULL); + } + } + else { + displaySurface->VTable0x28(m_frameBitmap, 0, 0, GetX(), GetY(), GetWidth(), GetHeight()); } } } From 0191be7461b812c336bbc4c9c006355a8f501f79 Mon Sep 17 00:00:00 2001 From: Helloyunho Date: Fri, 4 Jul 2025 01:05:46 +0900 Subject: [PATCH 054/188] Make `draw cursor` feature work for modern platforms (#480) --- CMakeLists.txt | 36 ++++++ ISLE/isleapp.cpp | 38 +++++- ISLE/isleapp.h | 5 + ISLE/res/arrow.png | Bin 0 -> 165 bytes ISLE/res/arrow_bmp.h | 37 ++++++ ISLE/res/busy.png | Bin 0 -> 173 bytes ISLE/res/busy_bmp.h | 37 ++++++ ISLE/res/no.png | Bin 0 -> 191 bytes ISLE/res/no_bmp.h | 37 ++++++ LEGO1/cursor.h | 8 ++ .../lego/legoomni/include/legovideomanager.h | 2 + .../legoomni/src/video/legovideomanager.cpp | 36 +++++- LEGO1/omni/include/mxdisplaysurface.h | 2 + LEGO1/omni/src/video/mxdisplaysurface.cpp | 122 ++++++++++++++++++ tools/curpng2h.py | 75 +++++++++++ 15 files changed, 426 insertions(+), 9 deletions(-) create mode 100644 ISLE/res/arrow.png create mode 100644 ISLE/res/arrow_bmp.h create mode 100644 ISLE/res/busy.png create mode 100644 ISLE/res/busy_bmp.h create mode 100644 ISLE/res/no.png create mode 100644 ISLE/res/no_bmp.h create mode 100644 LEGO1/cursor.h create mode 100755 tools/curpng2h.py diff --git a/CMakeLists.txt b/CMakeLists.txt index 3808cbe1..00ba25ea 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -473,6 +473,9 @@ if (ISLE_BUILD_APP) ISLE/res/isle.rc ISLE/isleapp.cpp ISLE/islefiles.cpp + ${CMAKE_SOURCE_DIR}/ISLE/res/arrow_bmp.h + ${CMAKE_SOURCE_DIR}/ISLE/res/busy_bmp.h + ${CMAKE_SOURCE_DIR}/ISLE/res/no_bmp.h ) list(APPEND isle_targets isle) if (WIN32) @@ -529,6 +532,39 @@ if (ISLE_BUILD_APP) ISLE/3ds/config.cpp ) endif() + if(Python3_FOUND) + if(NOT DEFINED PYTHON_PIL_AVAILABLE) + execute_process( + COMMAND ${Python3_EXECUTABLE} -c "import PIL; print('pil')" + RESULT_VARIABLE PIL_RESULT + OUTPUT_VARIABLE PIL_OUTPUT + ERROR_QUIET + OUTPUT_STRIP_TRAILING_WHITESPACE + ) + if(PIL_RESULT EQUAL 0 AND PIL_OUTPUT STREQUAL "pil") + set(PIL_AVAILABLE 1) + else() + message(STATUS "Python PIL not found, using pre-generated headers.") + set(PIL_AVAILABLE 0) + endif() + set(PYTHON_PIL_AVAILABLE ${PIL_AVAILABLE} CACHE BOOL "Is Python3 Pillow available?") + endif() + if(PYTHON_PIL_AVAILABLE) + add_custom_command( + OUTPUT + ${CMAKE_SOURCE_DIR}/ISLE/res/arrow_bmp.h + ${CMAKE_SOURCE_DIR}/ISLE/res/busy_bmp.h + ${CMAKE_SOURCE_DIR}/ISLE/res/no_bmp.h + COMMAND ${Python3_EXECUTABLE} tools/curpng2h.py ISLE/res/arrow.png ISLE/res/busy.png ISLE/res/no.png + WORKING_DIRECTORY ${CMAKE_SOURCE_DIR} + DEPENDS + ${CMAKE_SOURCE_DIR}/tools/curpng2h.py + ${CMAKE_SOURCE_DIR}/ISLE/res/arrow.png + ${CMAKE_SOURCE_DIR}/ISLE/res/busy.png + ${CMAKE_SOURCE_DIR}/ISLE/res/no.png + ) + endif() + endif() endif() if (ISLE_BUILD_CONFIG) diff --git a/ISLE/isleapp.cpp b/ISLE/isleapp.cpp index e12f9183..cfa3c50a 100644 --- a/ISLE/isleapp.cpp +++ b/ISLE/isleapp.cpp @@ -28,7 +28,10 @@ #include "mxtransitionmanager.h" #include "mxutilities.h" #include "mxvariabletable.h" +#include "res/arrow_bmp.h" +#include "res/busy_bmp.h" #include "res/isle_bmp.h" +#include "res/no_bmp.h" #include "res/resource.h" #include "roi/legoroi.h" #include "tgl/d3drm/impl.h" @@ -135,6 +138,10 @@ IsleApp::IsleApp() m_cursorBusy = NULL; m_cursorNo = NULL; m_cursorCurrent = NULL; + m_cursorArrowBitmap = NULL; + m_cursorBusyBitmap = NULL; + m_cursorNoBitmap = NULL; + m_cursorCurrentBitmap = NULL; LegoOmni::CreateInstance(); @@ -656,6 +663,12 @@ MxResult IsleApp::SetupWindow() m_cursorBusy = SDL_CreateSystemCursor(SDL_SYSTEM_CURSOR_WAIT); m_cursorNo = SDL_CreateSystemCursor(SDL_SYSTEM_CURSOR_NOT_ALLOWED); SDL_SetCursor(m_cursorCurrent); + if (g_isle->GetDrawCursor()) { + SDL_HideCursor(); + m_cursorCurrentBitmap = m_cursorArrowBitmap = &arrow_cursor; + m_cursorBusyBitmap = &busy_cursor; + m_cursorNoBitmap = &no_cursor; + } SDL_PropertiesID props = SDL_CreateProperties(); SDL_SetNumberProperty(props, SDL_PROP_WINDOW_CREATE_WIDTH_NUMBER, g_targetWidth); @@ -739,6 +752,9 @@ MxResult IsleApp::SetupWindow() LegoOmni::GetInstance()->GetInputManager()->SetUseJoystick(m_useJoystick); LegoOmni::GetInstance()->GetInputManager()->SetJoystickIndex(m_joystickIndex); } + if (LegoOmni::GetInstance()->GetVideoManager() && g_isle->GetDrawCursor()) { + LegoOmni::GetInstance()->GetVideoManager()->SetCursorBitmap(m_cursorCurrentBitmap); + } MxDirect3D* d3d = LegoOmni::GetInstance()->GetVideoManager()->GetDirect3D(); if (d3d) { SDL_Log( @@ -1023,15 +1039,19 @@ void IsleApp::SetupCursor(Cursor p_cursor) switch (p_cursor) { case e_cursorArrow: m_cursorCurrent = m_cursorArrow; + m_cursorCurrentBitmap = m_cursorArrowBitmap; break; case e_cursorBusy: m_cursorCurrent = m_cursorBusy; + m_cursorCurrentBitmap = m_cursorBusyBitmap; break; case e_cursorNo: m_cursorCurrent = m_cursorNo; + m_cursorCurrentBitmap = m_cursorNoBitmap; break; case e_cursorNone: m_cursorCurrent = NULL; + m_cursorCurrentBitmap = NULL; case e_cursorUnused3: case e_cursorUnused4: case e_cursorUnused5: @@ -1043,12 +1063,22 @@ void IsleApp::SetupCursor(Cursor p_cursor) break; } - if (m_cursorCurrent != NULL) { - SDL_SetCursor(m_cursorCurrent); - SDL_ShowCursor(); + if (g_isle->GetDrawCursor()) { + if (m_cursorCurrentBitmap == NULL) { + VideoManager()->SetCursorBitmap(NULL); + } + else { + VideoManager()->SetCursorBitmap(m_cursorCurrentBitmap); + } } else { - SDL_HideCursor(); + if (m_cursorCurrent != NULL) { + SDL_SetCursor(m_cursorCurrent); + SDL_ShowCursor(); + } + else { + SDL_HideCursor(); + } } } diff --git a/ISLE/isleapp.h b/ISLE/isleapp.h index d0a7f523..4064dc25 100644 --- a/ISLE/isleapp.h +++ b/ISLE/isleapp.h @@ -1,6 +1,7 @@ #ifndef ISLEAPP_H #define ISLEAPP_H +#include "cursor.h" #include "lego1_export.h" #include "legoutils.h" #include "mxtransitionmanager.h" @@ -87,6 +88,10 @@ class IsleApp { SDL_Cursor* m_cursorBusy; // 0x80 SDL_Cursor* m_cursorNo; // 0x84 SDL_Cursor* m_cursorCurrent; // 0x88 + const CursorBitmap* m_cursorArrowBitmap; + const CursorBitmap* m_cursorBusyBitmap; + const CursorBitmap* m_cursorNoBitmap; + const CursorBitmap* m_cursorCurrentBitmap; char* m_mediaPath; char* m_iniPath; diff --git a/ISLE/res/arrow.png b/ISLE/res/arrow.png new file mode 100644 index 0000000000000000000000000000000000000000..eaa08ca6315b98cd5e563f32ebb208680d377a5a GIT binary patch literal 165 zcmeAS@N?(olHy`uVBq!ia0vp^3LwnE3?yBabR7dyoB=)|u0R?H{{R2qeE0k%Ad9gi z$S;_|;n|HeAjiwo#WBR<^xI2@d<=>_%#PRo*PE~gw7EX&(k`%VeY(VySAv7hML^Jn zMbPkon@>PDtCyj*!&V8-phsH{3$=~-+aE?8yFUsY~#=Z+2-l$=d#Wz Gp$PyxA2M_R literal 0 HcmV?d00001 diff --git a/ISLE/res/arrow_bmp.h b/ISLE/res/arrow_bmp.h new file mode 100644 index 00000000..84e3452d --- /dev/null +++ b/ISLE/res/arrow_bmp.h @@ -0,0 +1,37 @@ +#pragma once + +// Generated from ISLE/res/arrow.png +// Dimensions: 32x32 +// This file is auto-generated, do not edit it. + +#include "cursor.h" + +static const unsigned char arrow_data[] = { + 0x00, 0x00, 0x00, 0x00, 0x60, 0x00, 0x00, 0x00, 0x50, 0x00, 0x00, 0x00, + 0x48, 0x00, 0x00, 0x00, 0x44, 0x00, 0x00, 0x00, 0x42, 0x00, 0x00, 0x00, + 0x41, 0x00, 0x00, 0x00, 0x40, 0x80, 0x00, 0x00, 0x40, 0x40, 0x00, 0x00, + 0x40, 0x20, 0x00, 0x00, 0x41, 0xF0, 0x00, 0x00, 0x49, 0x00, 0x00, 0x00, + 0x54, 0x80, 0x00, 0x00, 0x64, 0x80, 0x00, 0x00, 0x42, 0x40, 0x00, 0x00, + 0x02, 0x40, 0x00, 0x00, 0x01, 0x20, 0x00, 0x00, 0x01, 0x20, 0x00, 0x00, + 0x00, 0xC0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +}; + +static const unsigned char arrow_mask[] = { + 0x00, 0x00, 0x00, 0x00, 0x60, 0x00, 0x00, 0x00, 0x70, 0x00, 0x00, 0x00, + 0x78, 0x00, 0x00, 0x00, 0x7C, 0x00, 0x00, 0x00, 0x7E, 0x00, 0x00, 0x00, + 0x7F, 0x00, 0x00, 0x00, 0x7F, 0x80, 0x00, 0x00, 0x7F, 0xC0, 0x00, 0x00, + 0x7F, 0xE0, 0x00, 0x00, 0x7F, 0xF0, 0x00, 0x00, 0x7F, 0x00, 0x00, 0x00, + 0x77, 0x80, 0x00, 0x00, 0x67, 0x80, 0x00, 0x00, 0x43, 0xC0, 0x00, 0x00, + 0x03, 0xC0, 0x00, 0x00, 0x01, 0xE0, 0x00, 0x00, 0x01, 0xE0, 0x00, 0x00, + 0x00, 0xC0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +}; + +static const CursorBitmap arrow_cursor = { 32, 32, arrow_data, arrow_mask }; diff --git a/ISLE/res/busy.png b/ISLE/res/busy.png new file mode 100644 index 0000000000000000000000000000000000000000..cd8f5bf13ffca8b24d9e283ce7fb289a7c33b64f GIT binary patch literal 173 zcmeAS@N?(olHy`uVBq!ia0vp^3LwnE3?yBabR7dyoB=)|u0R?H{{R2qeE0k%Ad9gi z$S;_|;n|HeAScMv#WBR<^wmj@TtKZXvj6|T;7b%)(lp^hTzN!E=o9V2S4=PTxH#Qe zKJhpN+9U|h%WrvqTf(JOt&VvD@65HHceXrLKRzo|yubD5oyNZ9wM!XIxEPoG3)-g) Ovenbo&t;ucLK6V#COQlN literal 0 HcmV?d00001 diff --git a/ISLE/res/busy_bmp.h b/ISLE/res/busy_bmp.h new file mode 100644 index 00000000..020512bc --- /dev/null +++ b/ISLE/res/busy_bmp.h @@ -0,0 +1,37 @@ +#pragma once + +// Generated from ISLE/res/busy.png +// Dimensions: 32x32 +// This file is auto-generated, do not edit it. + +#include "cursor.h" + +static const unsigned char busy_data[] = { + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7F, 0xFF, 0x00, + 0x00, 0x40, 0x01, 0x00, 0x00, 0x7F, 0xFF, 0x00, 0x00, 0x20, 0x02, 0x00, + 0x00, 0x20, 0x02, 0x00, 0x00, 0x20, 0x02, 0x00, 0x00, 0x22, 0xA2, 0x00, + 0x00, 0x11, 0x44, 0x00, 0x00, 0x08, 0x88, 0x00, 0x00, 0x04, 0x10, 0x00, + 0x00, 0x02, 0x20, 0x00, 0x00, 0x02, 0x20, 0x00, 0x00, 0x02, 0x20, 0x00, + 0x00, 0x02, 0x20, 0x00, 0x00, 0x04, 0x10, 0x00, 0x00, 0x08, 0x88, 0x00, + 0x00, 0x10, 0x04, 0x00, 0x00, 0x20, 0x82, 0x00, 0x00, 0x21, 0x42, 0x00, + 0x00, 0x22, 0xA2, 0x00, 0x00, 0x25, 0x52, 0x00, 0x00, 0x7F, 0xFF, 0x00, + 0x00, 0x40, 0x01, 0x00, 0x00, 0x7F, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +}; + +static const unsigned char busy_mask[] = { + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7F, 0xFF, 0x00, + 0x00, 0x7F, 0xFF, 0x00, 0x00, 0x7F, 0xFF, 0x00, 0x00, 0x3F, 0xFE, 0x00, + 0x00, 0x3F, 0xFE, 0x00, 0x00, 0x3F, 0xFE, 0x00, 0x00, 0x3F, 0xFE, 0x00, + 0x00, 0x1F, 0xFC, 0x00, 0x00, 0x0F, 0xF8, 0x00, 0x00, 0x07, 0xF0, 0x00, + 0x00, 0x03, 0xE0, 0x00, 0x00, 0x03, 0xE0, 0x00, 0x00, 0x03, 0xE0, 0x00, + 0x00, 0x03, 0xE0, 0x00, 0x00, 0x07, 0xF0, 0x00, 0x00, 0x0F, 0xF8, 0x00, + 0x00, 0x1F, 0xFC, 0x00, 0x00, 0x3F, 0xFE, 0x00, 0x00, 0x3F, 0xFE, 0x00, + 0x00, 0x3F, 0xFE, 0x00, 0x00, 0x3F, 0xFE, 0x00, 0x00, 0x7F, 0xFF, 0x00, + 0x00, 0x7F, 0xFF, 0x00, 0x00, 0x7F, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +}; + +static const CursorBitmap busy_cursor = { 32, 32, busy_data, busy_mask }; diff --git a/ISLE/res/no.png b/ISLE/res/no.png new file mode 100644 index 0000000000000000000000000000000000000000..968adccd3c3cecf10e849b01e2d43a42fa9ea577 GIT binary patch literal 191 zcmeAS@N?(olHy`uVBq!ia0vp^3LwnE3?yBabR7dyoB=)|u0R?H{{R2qeE0k%Ad9gi z$S;_|;n|HeAScDs#WBR<^xaF2dM1U#oHb+G*%N}MG#%l+T#wFQ#HZ}TdJaB3XHxOX`ss2geh0G#>#Vk)09&ZX* iw>jeb&xCX8<@q)TSQ$g^@=L4&xx>@d&t;ucLK6VSOhO_6 literal 0 HcmV?d00001 diff --git a/ISLE/res/no_bmp.h b/ISLE/res/no_bmp.h new file mode 100644 index 00000000..a4ebe829 --- /dev/null +++ b/ISLE/res/no_bmp.h @@ -0,0 +1,37 @@ +#pragma once + +// Generated from ISLE/res/no.png +// Dimensions: 32x32 +// This file is auto-generated, do not edit it. + +#include "cursor.h" + +static const unsigned char no_data[] = { + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0xE0, 0x00, 0x00, 0x1F, 0xF8, 0x00, + 0x00, 0x3C, 0x3C, 0x00, 0x00, 0x70, 0x0E, 0x00, 0x00, 0xF8, 0x07, 0x00, + 0x00, 0xDC, 0x03, 0x00, 0x01, 0xCE, 0x03, 0x80, 0x01, 0x87, 0x01, 0x80, + 0x01, 0x83, 0x81, 0x80, 0x01, 0x81, 0xC1, 0x80, 0x01, 0x80, 0xE1, 0x80, + 0x01, 0xC0, 0x73, 0x80, 0x00, 0xC0, 0x3B, 0x00, 0x00, 0xE0, 0x1F, 0x00, + 0x00, 0x70, 0x0E, 0x00, 0x00, 0x3C, 0x1C, 0x00, 0x00, 0x1F, 0xF8, 0x00, + 0x00, 0x07, 0xE0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +}; + +static const unsigned char no_mask[] = { + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x07, 0xE0, 0x00, 0x00, 0x1F, 0xF8, 0x00, 0x00, 0x3F, 0xFC, 0x00, + 0x00, 0x7F, 0xFE, 0x00, 0x00, 0xFC, 0x3F, 0x00, 0x01, 0xFC, 0x0F, 0x80, + 0x01, 0xFE, 0x07, 0x80, 0x03, 0xFF, 0x07, 0xC0, 0x03, 0xCF, 0x83, 0xC0, + 0x03, 0xC7, 0xC3, 0xC0, 0x03, 0xC3, 0xE3, 0xC0, 0x03, 0xC1, 0xF3, 0xC0, + 0x03, 0xE0, 0xFF, 0xC0, 0x01, 0xE0, 0x7F, 0x80, 0x01, 0xF0, 0x3F, 0x80, + 0x00, 0xFC, 0x1F, 0x00, 0x00, 0x7F, 0xFE, 0x00, 0x00, 0x3F, 0xFC, 0x00, + 0x00, 0x1F, 0xF8, 0x00, 0x00, 0x07, 0xE0, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +}; + +static const CursorBitmap no_cursor = { 32, 32, no_data, no_mask }; diff --git a/LEGO1/cursor.h b/LEGO1/cursor.h new file mode 100644 index 00000000..171972c2 --- /dev/null +++ b/LEGO1/cursor.h @@ -0,0 +1,8 @@ +#pragma once + +typedef struct CursorBitmap { + int width; + int height; + const unsigned char* data; + const unsigned char* mask; +} CursorBitmap; diff --git a/LEGO1/lego/legoomni/include/legovideomanager.h b/LEGO1/lego/legoomni/include/legovideomanager.h index 6c12d863..ba96541c 100644 --- a/LEGO1/lego/legoomni/include/legovideomanager.h +++ b/LEGO1/lego/legoomni/include/legovideomanager.h @@ -1,6 +1,7 @@ #ifndef LEGOVIDEOMANAGER_H #define LEGOVIDEOMANAGER_H +#include "cursor.h" #include "decomp.h" #include "lego1_export.h" #include "legophonemelist.h" @@ -37,6 +38,7 @@ class LegoVideoManager : public MxVideoManager { void EnableFullScreenMovie(MxBool p_enable); LEGO1_EXPORT void EnableFullScreenMovie(MxBool p_enable, MxBool p_scale); LEGO1_EXPORT void MoveCursor(MxS32 p_cursorX, MxS32 p_cursorY); + LEGO1_EXPORT void SetCursorBitmap(const CursorBitmap* p_cursorBitmap); void ToggleFPS(MxBool p_visible); MxResult Tickle() override; // vtable+0x08 diff --git a/LEGO1/lego/legoomni/src/video/legovideomanager.cpp b/LEGO1/lego/legoomni/src/video/legovideomanager.cpp index 7e2f7e42..8b92065c 100644 --- a/LEGO1/lego/legoomni/src/video/legovideomanager.cpp +++ b/LEGO1/lego/legoomni/src/video/legovideomanager.cpp @@ -272,14 +272,13 @@ void LegoVideoManager::MoveCursor(MxS32 p_cursorX, MxS32 p_cursorY) { m_cursorX = p_cursorX; m_cursorY = p_cursorY; - m_drawCursor = TRUE; - if (623 < p_cursorX) { - m_cursorX = 623; + if (640 < p_cursorX) { + m_cursorX = 640; } - if (463 < p_cursorY) { - m_cursorY = 463; + if (480 < p_cursorY) { + m_cursorY = 480; } } @@ -836,3 +835,30 @@ void LegoVideoManager::DrawTextToSurface32( ++p_text; } } + +void LegoVideoManager::SetCursorBitmap(const CursorBitmap* p_cursorBitmap) +{ + if (p_cursorBitmap == NULL) { + m_drawCursor = FALSE; + return; + } + + if (m_cursorSurface != NULL) { + m_cursorSurface->Release(); + m_cursorSurface = NULL; + } + + m_cursorRect.top = 0; + m_cursorRect.left = 0; + m_cursorRect.bottom = p_cursorBitmap->height; + m_cursorRect.right = p_cursorBitmap->width; + + m_cursorSurface = MxDisplaySurface::CreateCursorSurface(p_cursorBitmap); + + if (m_cursorSurface == NULL) { + m_drawCursor = FALSE; + return; + } + + m_drawCursor = TRUE; +} diff --git a/LEGO1/omni/include/mxdisplaysurface.h b/LEGO1/omni/include/mxdisplaysurface.h index 8d31ff4f..ec9c6aa5 100644 --- a/LEGO1/omni/include/mxdisplaysurface.h +++ b/LEGO1/omni/include/mxdisplaysurface.h @@ -1,6 +1,7 @@ #ifndef MXDISPLAYSURFACE_H #define MXDISPLAYSURFACE_H +#include "cursor.h" #include "decomp.h" #include "mxcore.h" #include "mxvideoparam.h" @@ -97,6 +98,7 @@ class MxDisplaySurface : public MxCore { void ClearScreen(); static LPDIRECTDRAWSURFACE CreateCursorSurface(); + static LPDIRECTDRAWSURFACE CreateCursorSurface(const CursorBitmap* p_cursorBitmap); static LPDIRECTDRAWSURFACE CopySurface(LPDIRECTDRAWSURFACE p_src); LPDIRECTDRAWSURFACE GetDirectDrawSurface1() { return m_ddSurface1; } diff --git a/LEGO1/omni/src/video/mxdisplaysurface.cpp b/LEGO1/omni/src/video/mxdisplaysurface.cpp index badf05d1..bcd38000 100644 --- a/LEGO1/omni/src/video/mxdisplaysurface.cpp +++ b/LEGO1/omni/src/video/mxdisplaysurface.cpp @@ -1296,3 +1296,125 @@ LPDIRECTDRAWSURFACE MxDisplaySurface::FUN_100bc8b0(MxS32 p_width, MxS32 p_height return surface; } + +LPDIRECTDRAWSURFACE MxDisplaySurface::CreateCursorSurface(const CursorBitmap* p_cursorBitmap) +{ + LPDIRECTDRAWSURFACE newSurface = NULL; + IDirectDraw* draw = MVideoManager()->GetDirectDraw(); + MVideoManager(); + + DDSURFACEDESC ddsd; + memset(&ddsd, 0, sizeof(ddsd)); + ddsd.dwSize = sizeof(ddsd); + + if (draw->GetDisplayMode(&ddsd) != DD_OK) { + return NULL; + } + + MxS32 bytesPerPixel = ddsd.ddpfPixelFormat.dwRGBBitCount / 8; + + ddsd.dwWidth = p_cursorBitmap->width; + ddsd.dwHeight = p_cursorBitmap->height; + ddsd.dwFlags = DDSD_PIXELFORMAT | DDSD_WIDTH | DDSD_HEIGHT | DDSD_CAPS; + ddsd.ddsCaps.dwCaps = DDSCAPS_VIDEOMEMORY | DDSCAPS_OFFSCREENPLAIN; + + if (draw->CreateSurface(&ddsd, &newSurface, NULL) != DD_OK) { + ddsd.ddsCaps.dwCaps &= ~DDSCAPS_VIDEOMEMORY; + ddsd.ddsCaps.dwCaps |= DDSCAPS_SYSTEMMEMORY; + + if (draw->CreateSurface(&ddsd, &newSurface, NULL) != DD_OK) { + goto done; + } + } + + memset(&ddsd, 0, sizeof(ddsd)); + ddsd.dwSize = sizeof(ddsd); + + if (newSurface->Lock(NULL, &ddsd, DDLOCK_WAIT | DDLOCK_WRITEONLY, NULL) != DD_OK) { + goto done; + } + else { + for (int y = 0; y < p_cursorBitmap->height; y++) { + for (int x = 0; x < p_cursorBitmap->width; x++) { + MxS32 bitIndex = y * p_cursorBitmap->width + x; + MxS32 byteIndex = bitIndex / 8; + MxS32 bitOffset = 7 - (bitIndex % 8); + + MxBool isOpaque = (p_cursorBitmap->mask[byteIndex] >> bitOffset) & 1; + MxBool isBlack = (p_cursorBitmap->data[byteIndex] >> bitOffset) & 1; + + switch (bytesPerPixel) { + case 1: { + MxU8* surface = (MxU8*) ddsd.lpSurface; + + MxU8 pixel; + if (!isOpaque) { + pixel = 0x10; + } + else { + pixel = isBlack ? 0 : 0xff; + } + } + case 2: { + MxU16* surface = (MxU16*) ddsd.lpSurface; + + MxU16 pixel; + if (!isOpaque) { + pixel = RGB555_CREATE(0x1f, 0, 0x1f); + } + else { + pixel = isBlack ? RGB555_CREATE(0, 0, 0) : RGB555_CREATE(0x1f, 0x1f, 0x1f); + } + + surface[x + y * p_cursorBitmap->width] = pixel; + break; + } + default: { + MxU32* surface = (MxU32*) ddsd.lpSurface; + + MxS32 pixel; + if (!isOpaque) { + pixel = RGB8888_CREATE(0, 0, 0, 0); // Transparent pixel + } + else { + pixel = isBlack ? RGB8888_CREATE(0, 0, 0, 0xff) : RGB8888_CREATE(0xff, 0xff, 0xff, 0xff); + } + + surface[x + y * p_cursorBitmap->width] = pixel; + break; + } + } + } + } + + newSurface->Unlock(ddsd.lpSurface); + switch (bytesPerPixel) { + case 1: { + DDCOLORKEY colorkey; + colorkey.dwColorSpaceHighValue = 0x10; + colorkey.dwColorSpaceLowValue = 0x10; + newSurface->SetColorKey(DDCKEY_SRCBLT, &colorkey); + break; + } + case 2: { + DDCOLORKEY colorkey; + colorkey.dwColorSpaceHighValue = RGB555_CREATE(0x1f, 0, 0x1f); + colorkey.dwColorSpaceLowValue = RGB555_CREATE(0x1f, 0, 0x1f); + newSurface->SetColorKey(DDCKEY_SRCBLT, &colorkey); + break; + } + default: { + break; + } + } + + return newSurface; + } + +done: + if (newSurface) { + newSurface->Release(); + } + + return NULL; +} diff --git a/tools/curpng2h.py b/tools/curpng2h.py new file mode 100755 index 00000000..d55231b0 --- /dev/null +++ b/tools/curpng2h.py @@ -0,0 +1,75 @@ +#!/usr/bin/env python3 +import argparse +import itertools +from PIL import Image +from pathlib import Path + + +def encode_cursor(image_path: Path): + img = Image.open(image_path).convert("RGBA") + width, height = img.size + pixels = img.load() + + num_pixels = width * height + num_bytes = (num_pixels + 7) // 8 + + data = bytearray(num_bytes) + mask = bytearray(num_bytes) + + for y in range(height): + for x in range(width): + i = y * width + x + byte_index = i // 8 + bit_offset = 7 - (i % 8) + + r, g, b, a = pixels[x, y] + + if a >= 128: + mask[byte_index] |= 1 << bit_offset # opaque + lum = int(0.299 * r + 0.587 * g + 0.114 * b) + if lum < 128: + data[byte_index] |= 1 << bit_offset # black pixel + + return data, mask, width, height + + +def to_c_array(name, data): + lines = [] + for rowdata in itertools.batched(data, 12): + lines.append(", ".join(f"0x{byte:02X}" for byte in rowdata) + ",") + array_str = "\n ".join(lines) + return f"static const unsigned char {name}[] = {{\n {array_str}\n}};\n" + + +def main(): + parser = argparse.ArgumentParser(allow_abbrev=False) + parser.add_argument("inputs", nargs="+", help="PNG images", type=Path) + args = parser.parse_args() + + input_files: list[Path] = args.inputs + + for input_file in input_files: + data, mask, width, height = encode_cursor(input_file) + + input_file_name = input_file.stem + output_file = input_file.with_name(f"{input_file_name}_bmp.h") + + with output_file.open("w", newline="\n") as f: + f.write(f"#pragma once\n\n") + f.write(f"// Generated from {input_file}\n") + f.write(f"// Dimensions: {width}x{height}\n") + f.write("// This file is auto-generated, do not edit it.\n\n") + f.write(f'#include "cursor.h"\n\n') + f.write(to_c_array(f"{input_file_name}_data", data)) + f.write("\n") + f.write(to_c_array(f"{input_file_name}_mask", mask)) + f.write("\n") + f.write( + f"static const CursorBitmap {input_file_name}_cursor = {'{'} {width}, {height}, {input_file_name}_data, {input_file_name}_mask {'}'};\n" + ) + + print(f"Written {output_file} with cursor data.") + + +if __name__ == "__main__": + raise SystemExit(main()) From 3868071fb8d155bc29979fed124ab9855ba69be2 Mon Sep 17 00:00:00 2001 From: Anders Jenbo Date: Thu, 3 Jul 2025 20:12:55 +0200 Subject: [PATCH 055/188] Use DDBLT_COLORFILL to clear screen (#505) --- LEGO1/mxdirectx/mxdirectdraw.cpp | 24 +++++++----------------- 1 file changed, 7 insertions(+), 17 deletions(-) diff --git a/LEGO1/mxdirectx/mxdirectdraw.cpp b/LEGO1/mxdirectx/mxdirectdraw.cpp index bedf4d2e..1bec4624 100644 --- a/LEGO1/mxdirectx/mxdirectdraw.cpp +++ b/LEGO1/mxdirectx/mxdirectdraw.cpp @@ -519,35 +519,25 @@ BOOL MxDirectDraw::DDCreateSurfaces() void MxDirectDraw::ClearBackBuffers() { HRESULT result; - byte* line; - DDSURFACEDESC ddsd; + DDBLTFX ddbltfx; int count = m_bFlipSurfaces ? 2 : 1; - int value = 0; for (int i = 0; i < count; i++) { - memset(&ddsd, 0, sizeof(ddsd)); - ddsd.dwSize = sizeof(ddsd); + memset(&ddbltfx, 0, sizeof(ddbltfx)); + ddbltfx.dwSize = sizeof(ddbltfx); + ddbltfx.dwFillColor = 0; - result = m_pBackBuffer->Lock(NULL, &ddsd, DDLOCK_WAIT | DDLOCK_WRITEONLY, NULL); + result = m_pBackBuffer->Blt(NULL, NULL, NULL, DDBLT_COLORFILL | DDBLT_WAIT, &ddbltfx); if (result == DDERR_SURFACELOST) { m_pBackBuffer->Restore(); - result = m_pBackBuffer->Lock(NULL, &ddsd, DDLOCK_WAIT | DDLOCK_WRITEONLY, NULL); + result = m_pBackBuffer->Blt(NULL, NULL, NULL, DDBLT_COLORFILL | DDBLT_WAIT, &ddbltfx); } if (result != DD_OK) { - // lock failed + // blt failed return; } - // clear backBuffer - line = (byte*) ddsd.lpSurface; - for (int j = ddsd.dwHeight; j--;) { - memset(line, value, ddsd.dwWidth); - line += ddsd.lPitch; - } - - m_pBackBuffer->Unlock(ddsd.lpSurface); - if (m_bFlipSurfaces) { m_pFrontBuffer->Flip(NULL, DDFLIP_WAIT); } From 920ba63a31b73c733f1f346c5d2d13e0b20742c2 Mon Sep 17 00:00:00 2001 From: Anders Jenbo Date: Thu, 3 Jul 2025 21:56:58 +0200 Subject: [PATCH 056/188] Avoid texture updates when we just need the size (#507) --- LEGO1/lego/sources/misc/legocontainer.cpp | 12 ++---------- 1 file changed, 2 insertions(+), 10 deletions(-) diff --git a/LEGO1/lego/sources/misc/legocontainer.cpp b/LEGO1/lego/sources/misc/legocontainer.cpp index 98836d6a..693f9b27 100644 --- a/LEGO1/lego/sources/misc/legocontainer.cpp +++ b/LEGO1/lego/sources/misc/legocontainer.cpp @@ -22,10 +22,9 @@ LegoTextureInfo* LegoTextureContainer::GetCached(LegoTextureInfo* p_textureInfo) memset(&desc, 0, sizeof(desc)); desc.dwSize = sizeof(desc); - if (p_textureInfo->m_surface->Lock(NULL, &desc, DDLOCK_SURFACEMEMORYPTR | DDLOCK_WRITEONLY, NULL) == DD_OK) { + if (p_textureInfo->m_surface->GetSurfaceDesc(&desc) == DD_OK) { width = desc.dwWidth; height = desc.dwHeight; - p_textureInfo->m_surface->Unlock(desc.lpSurface); } for (LegoCachedTextureList::iterator it = m_cached.begin(); it != m_cached.end(); it++) { @@ -35,15 +34,8 @@ LegoTextureInfo* LegoTextureContainer::GetCached(LegoTextureInfo* p_textureInfo) memset(&newDesc, 0, sizeof(newDesc)); newDesc.dwSize = sizeof(newDesc); - if (surface->Lock(NULL, &newDesc, DDLOCK_SURFACEMEMORYPTR | DDLOCK_WRITEONLY, NULL) == DD_OK) { - BOOL und = FALSE; + if (surface->GetSurfaceDesc(&newDesc) == DD_OK) { if (newDesc.dwWidth == width && newDesc.dwHeight == height) { - und = TRUE; - } - - surface->Unlock(newDesc.lpSurface); - - if (und) { (*it).second = TRUE; (*it).first->m_texture->AddRef(); return (*it).first; From 44d1ad13fed7416451348f0401477bf202eb680a Mon Sep 17 00:00:00 2001 From: Anders Jenbo Date: Thu, 3 Jul 2025 21:57:47 +0200 Subject: [PATCH 057/188] Set dwDDFX using flag (#1603) --- LEGO1/omni/src/video/mxdisplaysurface.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/LEGO1/omni/src/video/mxdisplaysurface.cpp b/LEGO1/omni/src/video/mxdisplaysurface.cpp index 86f1b4dc..bd751a57 100644 --- a/LEGO1/omni/src/video/mxdisplaysurface.cpp +++ b/LEGO1/omni/src/video/mxdisplaysurface.cpp @@ -861,7 +861,7 @@ void MxDisplaySurface::Display(MxS32 p_left, MxS32 p_top, MxS32 p_left2, MxS32 p DDBLTFX data; memset(&data, 0, sizeof(data)); data.dwSize = sizeof(data); - data.dwDDFX = 8; + data.dwDDFX = DDBLTFX_NOTEARING; if (m_ddSurface1->Blt((LPRECT) &b, m_ddSurface2, (LPRECT) &a, 0, &data) == DDERR_SURFACELOST) { m_ddSurface1->Restore(); From 1ae9933bd5fe1b5972737cd3cb9d8db9d8bb8129 Mon Sep 17 00:00:00 2001 From: Anders Jenbo Date: Thu, 3 Jul 2025 22:04:13 +0200 Subject: [PATCH 058/188] Drop old cursor code (#509) --- .../legoomni/src/video/legovideomanager.cpp | 10 +-- LEGO1/omni/include/mxdisplaysurface.h | 1 - LEGO1/omni/src/video/mxdisplaysurface.cpp | 80 ------------------- 3 files changed, 1 insertion(+), 90 deletions(-) diff --git a/LEGO1/lego/legoomni/src/video/legovideomanager.cpp b/LEGO1/lego/legoomni/src/video/legovideomanager.cpp index 8b92065c..cdb20197 100644 --- a/LEGO1/lego/legoomni/src/video/legovideomanager.cpp +++ b/LEGO1/lego/legoomni/src/video/legovideomanager.cpp @@ -387,15 +387,7 @@ inline void LegoVideoManager::DrawCursor() LPDIRECTDRAWSURFACE ddSurface2 = m_displaySurface->GetDirectDrawSurface2(); if (!m_cursorSurface) { - m_cursorRect.top = 0; - m_cursorRect.left = 0; - m_cursorRect.bottom = 16; - m_cursorRect.right = 16; - m_cursorSurface = MxDisplaySurface::CreateCursorSurface(); - - if (!m_cursorSurface) { - m_drawCursor = FALSE; - } + return; } ddSurface2 diff --git a/LEGO1/omni/include/mxdisplaysurface.h b/LEGO1/omni/include/mxdisplaysurface.h index ec9c6aa5..df57c5c4 100644 --- a/LEGO1/omni/include/mxdisplaysurface.h +++ b/LEGO1/omni/include/mxdisplaysurface.h @@ -97,7 +97,6 @@ class MxDisplaySurface : public MxCore { ); // vtable+0x44 void ClearScreen(); - static LPDIRECTDRAWSURFACE CreateCursorSurface(); static LPDIRECTDRAWSURFACE CreateCursorSurface(const CursorBitmap* p_cursorBitmap); static LPDIRECTDRAWSURFACE CopySurface(LPDIRECTDRAWSURFACE p_src); diff --git a/LEGO1/omni/src/video/mxdisplaysurface.cpp b/LEGO1/omni/src/video/mxdisplaysurface.cpp index bcd38000..8d137e34 100644 --- a/LEGO1/omni/src/video/mxdisplaysurface.cpp +++ b/LEGO1/omni/src/video/mxdisplaysurface.cpp @@ -1015,86 +1015,6 @@ LPDIRECTDRAWSURFACE MxDisplaySurface::CopySurface(LPDIRECTDRAWSURFACE p_src) return newSurface; } -// FUNCTION: LEGO1 0x100bc070 -LPDIRECTDRAWSURFACE MxDisplaySurface::CreateCursorSurface() -{ - LPDIRECTDRAWSURFACE newSurface = NULL; - IDirectDraw* draw = MVideoManager()->GetDirectDraw(); - MVideoManager(); - - DDSURFACEDESC ddsd; - memset(&ddsd, 0, sizeof(ddsd)); - ddsd.dwSize = sizeof(ddsd); - - if (draw->GetDisplayMode(&ddsd) != DD_OK) { - return NULL; - } - - if (ddsd.ddpfPixelFormat.dwRGBBitCount != 16) { - return NULL; - } - - ddsd.dwWidth = 16; - ddsd.dwHeight = 16; - ddsd.dwFlags = DDSD_PIXELFORMAT | DDSD_WIDTH | DDSD_HEIGHT | DDSD_CAPS; - ddsd.ddsCaps.dwCaps = DDSCAPS_VIDEOMEMORY | DDSCAPS_OFFSCREENPLAIN; - - if (draw->CreateSurface(&ddsd, &newSurface, NULL) != DD_OK) { - ddsd.ddsCaps.dwCaps &= ~DDSCAPS_VIDEOMEMORY; - ddsd.ddsCaps.dwCaps |= DDSCAPS_SYSTEMMEMORY; - - if (draw->CreateSurface(&ddsd, &newSurface, NULL) != DD_OK) { - goto done; - } - } - - memset(&ddsd, 0, sizeof(ddsd)); - ddsd.dwSize = sizeof(ddsd); - - if (newSurface->Lock(NULL, &ddsd, DDLOCK_WAIT | DDLOCK_WRITEONLY, NULL) != DD_OK) { - goto done; - } - else { - MxU16* surface = (MxU16*) ddsd.lpSurface; - MxLong pitch = ddsd.lPitch; - - // draw a simple cursor to the surface - for (MxS32 x = 0; x < 16; x++) { - MxU16* surface2 = surface; - for (MxS32 y = 0; y < 16; y++) { - if ((y > 10 || x) && (x > 10 || y) && x + y != 10) { - if (x + y > 10) { - *surface2 = RGB555_CREATE(0x1f, 0, 0x1f); - } - else { - *surface2 = -1; - } - } - else { - *surface2 = 0; - } - surface2++; - } - surface = (MxU16*) ((MxU8*) surface + pitch); - } - - newSurface->Unlock(ddsd.lpSurface); - DDCOLORKEY colorkey; - colorkey.dwColorSpaceHighValue = RGB555_CREATE(0x1f, 0, 0x1f); - colorkey.dwColorSpaceLowValue = RGB555_CREATE(0x1f, 0, 0x1f); - newSurface->SetColorKey(DDCKEY_SRCBLT, &colorkey); - - return newSurface; - } - -done: - if (newSurface) { - newSurface->Release(); - } - - return NULL; -} - // FUNCTION: LEGO1 0x100bc200 void MxDisplaySurface::VTable0x24( LPDDSURFACEDESC p_desc, From e2eda71883cd872a88d8a016d8a314e2d585de61 Mon Sep 17 00:00:00 2001 From: Anders Jenbo Date: Thu, 3 Jul 2025 22:04:21 +0200 Subject: [PATCH 059/188] Yet another clearscreen ported to use COLORFILL (#508) --- LEGO1/mxdirectx/mxdirect3d.cpp | 2 +- LEGO1/mxdirectx/mxdirectdraw.cpp | 8 +++----- LEGO1/omni/src/video/mxdisplaysurface.cpp | 22 ++++++---------------- miniwin/include/miniwin/ddraw.h | 6 +++++- 4 files changed, 15 insertions(+), 23 deletions(-) diff --git a/LEGO1/mxdirectx/mxdirect3d.cpp b/LEGO1/mxdirectx/mxdirect3d.cpp index e338046f..0dae36e1 100644 --- a/LEGO1/mxdirectx/mxdirect3d.cpp +++ b/LEGO1/mxdirectx/mxdirect3d.cpp @@ -172,7 +172,7 @@ BOOL MxDirect3D::D3DSetMode() DDBLTFX ddBltFx = {}; ddBltFx.dwSize = sizeof(DDBLTFX); - ddBltFx.dwFillColor = 0; + ddBltFx.dwFillColor = 0xFF000000; if (backBuffer->Blt(NULL, NULL, NULL, DDBLT_WAIT | DDBLT_COLORFILL, &ddBltFx) != DD_OK) { SDL_Log("MxDirect3D::D3DSetMode() color fill failed\n"); diff --git a/LEGO1/mxdirectx/mxdirectdraw.cpp b/LEGO1/mxdirectx/mxdirectdraw.cpp index 1bec4624..7ccb78c2 100644 --- a/LEGO1/mxdirectx/mxdirectdraw.cpp +++ b/LEGO1/mxdirectx/mxdirectdraw.cpp @@ -519,14 +519,12 @@ BOOL MxDirectDraw::DDCreateSurfaces() void MxDirectDraw::ClearBackBuffers() { HRESULT result; - DDBLTFX ddbltfx; + DDBLTFX ddbltfx = {}; + ddbltfx.dwSize = sizeof(DDBLTFX); + ddbltfx.dwFillColor = 0xFF000000; int count = m_bFlipSurfaces ? 2 : 1; for (int i = 0; i < count; i++) { - memset(&ddbltfx, 0, sizeof(ddbltfx)); - ddbltfx.dwSize = sizeof(ddbltfx); - ddbltfx.dwFillColor = 0; - result = m_pBackBuffer->Blt(NULL, NULL, NULL, DDBLT_COLORFILL | DDBLT_WAIT, &ddbltfx); if (result == DDERR_SURFACELOST) { m_pBackBuffer->Restore(); diff --git a/LEGO1/omni/src/video/mxdisplaysurface.cpp b/LEGO1/omni/src/video/mxdisplaysurface.cpp index 8d137e34..d38cb093 100644 --- a/LEGO1/omni/src/video/mxdisplaysurface.cpp +++ b/LEGO1/omni/src/video/mxdisplaysurface.cpp @@ -72,7 +72,7 @@ void MxDisplaySurface::ClearScreen() DDBLTFX ddBltFx = {}; ddBltFx.dwSize = sizeof(DDBLTFX); - ddBltFx.dwFillColor = 0; + ddBltFx.dwFillColor = 0xFF000000; for (MxS32 i = 0; i < backBuffers; i++) { if (m_ddSurface2->Blt(NULL, NULL, NULL, DDBLT_COLORFILL | DDBLT_WAIT, &ddBltFx) == DDERR_SURFACELOST) { @@ -779,21 +779,11 @@ void MxDisplaySurface::Display(MxS32 p_left, MxS32 p_top, MxS32 p_left2, MxS32 p if (g_unk0x1010215c < 2) { g_unk0x1010215c++; - DDSURFACEDESC ddsd; - memset(&ddsd, 0, sizeof(ddsd)); - ddsd.dwSize = sizeof(ddsd); - if (m_ddSurface2->Lock(NULL, &ddsd, DDLOCK_WAIT | DDLOCK_WRITEONLY, NULL) == DD_OK) { - MxU8* surface = (MxU8*) ddsd.lpSurface; - MxS32 height = m_videoParam.GetRect().GetHeight(); + DDBLTFX ddbltfx = {}; + ddbltfx.dwSize = sizeof(ddbltfx); + ddbltfx.dwFillColor = 0xFF000000; - for (MxU32 i = 0; i < ddsd.dwHeight; i++) { - memset(surface, 0, ddsd.lPitch); - surface += ddsd.lPitch; - } - - m_ddSurface2->Unlock(ddsd.lpSurface); - } - else { + if (m_ddSurface2->Blt(NULL, NULL, NULL, DDBLT_COLORFILL | DDBLT_WAIT, &ddbltfx) != DD_OK) { SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "MxDisplaySurface::Display error\n"); } } @@ -812,7 +802,7 @@ void MxDisplaySurface::Display(MxS32 p_left, MxS32 p_top, MxS32 p_left2, MxS32 p DDBLTFX data; memset(&data, 0, sizeof(data)); data.dwSize = sizeof(data); - data.dwDDFX = 8; + data.dwDDFX = DDBLTFX_NOTEARING; if (m_ddSurface1->Blt((LPRECT) &b, m_ddSurface2, (LPRECT) &a, DDBLT_NONE, &data) == DDERR_SURFACELOST) { m_ddSurface1->Restore(); diff --git a/miniwin/include/miniwin/ddraw.h b/miniwin/include/miniwin/ddraw.h index 1e3fd17f..2a8932a2 100644 --- a/miniwin/include/miniwin/ddraw.h +++ b/miniwin/include/miniwin/ddraw.h @@ -246,9 +246,13 @@ struct DDSCAPS { }; typedef struct DDSCAPS* LPDDSCAPS; +#define DDBLTFX_NOTEARING DDBLTFXFlags::NOTEARING +enum class DDBLTFXFlags : uint8_t { + NOTEARING = 1 << 3, +}; struct DDBLTFX { DWORD dwSize; - DWORD dwDDFX; + DDBLTFXFlags dwDDFX; DWORD dwROP; DWORD dwFillColor; }; From 16c76c96f23aa26967ba2fdc3291d8f33536ee80 Mon Sep 17 00:00:00 2001 From: Fabian Neundorf Date: Thu, 3 Jul 2025 23:56:48 +0200 Subject: [PATCH 060/188] Clear time started unknown in `MxDSAction` (#1604) This should not be confused with `GetStartTime()`, as that represents the time offset from the SI file. --- .../legoomni/src/common/legoanimmmpresenter.cpp | 2 +- .../src/common/mxcompositemediapresenter.cpp | 4 ++-- .../legoomni/src/common/mxcontrolpresenter.cpp | 2 +- .../lego/legoomni/src/video/legoanimpresenter.cpp | 2 +- LEGO1/omni/include/mxdsaction.h | 6 +++--- LEGO1/omni/include/mxdsmultiaction.h | 2 +- LEGO1/omni/src/action/mxdsaction.cpp | 14 +++++++------- LEGO1/omni/src/action/mxdsmultiaction.cpp | 6 +++--- LEGO1/omni/src/common/mxmediapresenter.cpp | 2 +- LEGO1/omni/src/stream/mxdsbuffer.cpp | 2 +- LEGO1/omni/src/stream/mxstreamcontroller.cpp | 2 +- 11 files changed, 22 insertions(+), 22 deletions(-) diff --git a/LEGO1/lego/legoomni/src/common/legoanimmmpresenter.cpp b/LEGO1/lego/legoomni/src/common/legoanimmmpresenter.cpp index dfa55d51..ca6c2412 100644 --- a/LEGO1/lego/legoomni/src/common/legoanimmmpresenter.cpp +++ b/LEGO1/lego/legoomni/src/common/legoanimmmpresenter.cpp @@ -400,7 +400,7 @@ MxBool LegoAnimMMPresenter::FUN_1004b610(MxLong p_time) } } - m_action->SetUnknown90(Timer()->GetTime()); + m_action->SetTimeStarted(Timer()->GetTime()); if (m_compositePresenter != NULL) { m_compositePresenter->VTable0x60(this); diff --git a/LEGO1/lego/legoomni/src/common/mxcompositemediapresenter.cpp b/LEGO1/lego/legoomni/src/common/mxcompositemediapresenter.cpp index dadd8de7..04437877 100644 --- a/LEGO1/lego/legoomni/src/common/mxcompositemediapresenter.cpp +++ b/LEGO1/lego/legoomni/src/common/mxcompositemediapresenter.cpp @@ -84,7 +84,7 @@ MxResult MxCompositeMediaPresenter::StartAction(MxStreamController* p_controller if (!m_compositePresenter) { SetTickleState(e_ready); MxLong time = Timer()->GetTime(); - m_action->SetUnknown90(time); + m_action->SetTimeStarted(time); } result = SUCCESS; @@ -134,7 +134,7 @@ void MxCompositeMediaPresenter::StartingTickle() if (!m_unk0x4c) { ProgressTickleState(e_streaming); MxLong time = Timer()->GetTime(); - m_action->SetUnknown90(time); + m_action->SetTimeStarted(time); } } } diff --git a/LEGO1/lego/legoomni/src/common/mxcontrolpresenter.cpp b/LEGO1/lego/legoomni/src/common/mxcontrolpresenter.cpp index 13ce2cee..76780d6e 100644 --- a/LEGO1/lego/legoomni/src/common/mxcontrolpresenter.cpp +++ b/LEGO1/lego/legoomni/src/common/mxcontrolpresenter.cpp @@ -189,7 +189,7 @@ void MxControlPresenter::VTable0x6c(MxS16 p_unk0x4e) m_unk0x4e = p_unk0x4e; } - m_action->SetUnknown90(Timer()->GetTime()); + m_action->SetTimeStarted(Timer()->GetTime()); MxS16 i = 0; for (MxCompositePresenterList::iterator it = m_list.begin(); it != m_list.end(); it++) { diff --git a/LEGO1/lego/legoomni/src/video/legoanimpresenter.cpp b/LEGO1/lego/legoomni/src/video/legoanimpresenter.cpp index 73918606..82c76aa6 100644 --- a/LEGO1/lego/legoomni/src/video/legoanimpresenter.cpp +++ b/LEGO1/lego/legoomni/src/video/legoanimpresenter.cpp @@ -815,7 +815,7 @@ void LegoAnimPresenter::StartingTickle() m_compositePresenter->VTable0x60(this); } else { - m_action->SetUnknown90(Timer()->GetTime()); + m_action->SetTimeStarted(Timer()->GetTime()); } ProgressTickleState(e_streaming); diff --git a/LEGO1/omni/include/mxdsaction.h b/LEGO1/omni/include/mxdsaction.h index 7e2c4d89..2899d3fb 100644 --- a/LEGO1/omni/include/mxdsaction.h +++ b/LEGO1/omni/include/mxdsaction.h @@ -55,8 +55,8 @@ class MxDSAction : public MxDSObject { virtual MxDSAction* Clone(); // vtable+0x2c virtual void MergeFrom(MxDSAction& p_dsAction); // vtable+0x30 virtual MxBool HasId(MxU32 p_objectId); // vtable+0x34 - virtual void SetUnknown90(MxLong p_unk0x90); // vtable+0x38 - virtual MxLong GetUnknown90(); // vtable+0x3c + virtual void SetTimeStarted(MxLong p_timeStarted); // vtable+0x38 + virtual MxLong GetTimeStarted(); // vtable+0x3c virtual MxLong GetElapsedTime(); // vtable+0x40 void AppendExtra(MxU16 p_extraLength, const char* p_extraData); @@ -130,7 +130,7 @@ class MxDSAction : public MxDSObject { MxCore* m_notificationObject; // 0x84 undefined4 m_unk0x88; // 0x88 MxCore* m_origin; // 0x8c - MxLong m_unk0x90; // 0x90 + MxLong m_timeStarted; // 0x90 }; #endif // MXDSACTION_H diff --git a/LEGO1/omni/include/mxdsmultiaction.h b/LEGO1/omni/include/mxdsmultiaction.h index 90cc5192..77ae8d67 100644 --- a/LEGO1/omni/include/mxdsmultiaction.h +++ b/LEGO1/omni/include/mxdsmultiaction.h @@ -38,7 +38,7 @@ class MxDSMultiAction : public MxDSAction { MxDSAction* Clone() override; // vtable+0x2c void MergeFrom(MxDSAction& p_dsAction) override; // vtable+0x30 MxBool HasId(MxU32 p_objectId) override; // vtable+0x34 - void SetUnknown90(MxLong p_unk0x90) override; // vtable+0x38 + void SetTimeStarted(MxLong p_timeStarted) override; // vtable+0x38 // FUNCTION: BETA10 0x1004e180 MxDSActionList* GetActionList() const { return m_actionList; } diff --git a/LEGO1/omni/src/action/mxdsaction.cpp b/LEGO1/omni/src/action/mxdsaction.cpp index 035b1046..4b77c6ed 100644 --- a/LEGO1/omni/src/action/mxdsaction.cpp +++ b/LEGO1/omni/src/action/mxdsaction.cpp @@ -31,7 +31,7 @@ MxDSAction::MxDSAction() m_notificationObject = NULL; m_unk0x88 = 0; m_origin = NULL; - m_unk0x90 = INT_MIN; + m_timeStarted = INT_MIN; } // FUNCTION: LEGO1 0x100ad940 @@ -57,16 +57,16 @@ MxBool MxDSAction::HasId(MxU32 p_objectId) // FUNCTION: LEGO1 0x100ada40 // FUNCTION: BETA10 0x1012bdf0 -void MxDSAction::SetUnknown90(MxLong p_unk0x90) +void MxDSAction::SetTimeStarted(MxLong p_timeStarted) { - m_unk0x90 = p_unk0x90; + m_timeStarted = p_timeStarted; } // FUNCTION: LEGO1 0x100ada50 // FUNCTION: BETA10 0x1012be20 -MxLong MxDSAction::GetUnknown90() +MxLong MxDSAction::GetTimeStarted() { - return m_unk0x90; + return m_timeStarted; } // FUNCTION: LEGO1 0x100ada80 @@ -92,7 +92,7 @@ void MxDSAction::CopyFrom(MxDSAction& p_dsAction) m_notificationObject = p_dsAction.m_notificationObject; m_unk0x88 = p_dsAction.m_unk0x88; m_origin = p_dsAction.m_origin; - m_unk0x90 = p_dsAction.m_unk0x90; + m_timeStarted = p_dsAction.m_timeStarted; } // FUNCTION: BETA10 0x1012b2b3 @@ -158,7 +158,7 @@ MxDSAction* MxDSAction::Clone() // FUNCTION: BETA10 0x1012b4ca MxLong MxDSAction::GetElapsedTime() { - return Timer()->GetTime() - m_unk0x90; + return Timer()->GetTime() - m_timeStarted; } // FUNCTION: LEGO1 0x100add00 diff --git a/LEGO1/omni/src/action/mxdsmultiaction.cpp b/LEGO1/omni/src/action/mxdsmultiaction.cpp index 39adfe9b..648de181 100644 --- a/LEGO1/omni/src/action/mxdsmultiaction.cpp +++ b/LEGO1/omni/src/action/mxdsmultiaction.cpp @@ -57,14 +57,14 @@ MxDSMultiAction& MxDSMultiAction::operator=(MxDSMultiAction& p_dsMultiAction) // FUNCTION: LEGO1 0x100ca290 // FUNCTION: BETA10 0x10159728 -void MxDSMultiAction::SetUnknown90(MxLong p_unk0x90) +void MxDSMultiAction::SetTimeStarted(MxLong p_timeStarted) { - m_unk0x90 = p_unk0x90; + m_timeStarted = p_timeStarted; MxDSActionListCursor cursor(m_actionList); MxDSAction* action; while (cursor.Next(action)) { - action->SetUnknown90(p_unk0x90); + action->SetTimeStarted(p_timeStarted); } } diff --git a/LEGO1/omni/src/common/mxmediapresenter.cpp b/LEGO1/omni/src/common/mxmediapresenter.cpp index 0a1b96d9..c8b9abcd 100644 --- a/LEGO1/omni/src/common/mxmediapresenter.cpp +++ b/LEGO1/omni/src/common/mxmediapresenter.cpp @@ -252,7 +252,7 @@ void MxMediaPresenter::Enable(MxBool p_enable) if (p_enable) { MxLong time = Timer()->GetTime(); - m_action->SetUnknown90(time); + m_action->SetTimeStarted(time); SetTickleState(e_repeating); } else { diff --git a/LEGO1/omni/src/stream/mxdsbuffer.cpp b/LEGO1/omni/src/stream/mxdsbuffer.cpp index d9216b3f..a73be62f 100644 --- a/LEGO1/omni/src/stream/mxdsbuffer.cpp +++ b/LEGO1/omni/src/stream/mxdsbuffer.cpp @@ -209,7 +209,7 @@ MxResult MxDSBuffer::StartPresenterFromAction( p_objectheader->SetUnknown28(p_action1->GetUnknown28()); p_objectheader->SetNotificationObject(p_action1->GetNotificationObject()); p_objectheader->SetOrigin(p_action1->GetOrigin()); - p_objectheader->SetUnknown90(p_action1->GetUnknown90()); + p_objectheader->SetTimeStarted(p_action1->GetTimeStarted()); p_objectheader->MergeFrom(*p_action1); m_unk0x30->SetInternalAction(p_objectheader->Clone()); diff --git a/LEGO1/omni/src/stream/mxstreamcontroller.cpp b/LEGO1/omni/src/stream/mxstreamcontroller.cpp index dadd03ca..8c3cf079 100644 --- a/LEGO1/omni/src/stream/mxstreamcontroller.cpp +++ b/LEGO1/omni/src/stream/mxstreamcontroller.cpp @@ -202,7 +202,7 @@ MxResult MxStreamController::FUN_100c1a00(MxDSAction* p_action, MxU32 p_offset) streamingAction->SetObjectId(p_action->GetObjectId()); MxLong time = Timer()->GetTime(); - streamingAction->SetUnknown90(time); + streamingAction->SetTimeStarted(time); m_unk0x3c.PushBack(streamingAction); return SUCCESS; From c63d725b641eade5cc0be158ea933a5ead68ed36 Mon Sep 17 00:00:00 2001 From: Anders Jenbo Date: Fri, 4 Jul 2025 00:20:31 +0200 Subject: [PATCH 061/188] Port WipeDown and Window to use DDBLT_COLORFILL (#506) --- .../src/common/mxtransitionmanager.cpp | 116 ++++-------------- .../src/d3drm/backends/citro3d/renderer.cpp | 85 +++++++------ .../src/d3drm/backends/directx9/actual.cpp | 73 +++++------ miniwin/src/d3drm/backends/directx9/actual.h | 2 +- .../src/d3drm/backends/directx9/renderer.cpp | 4 +- miniwin/src/d3drm/backends/opengl1/actual.cpp | 33 +++-- miniwin/src/d3drm/backends/opengl1/actual.h | 3 +- .../src/d3drm/backends/opengl1/renderer.cpp | 9 +- .../src/d3drm/backends/opengles2/renderer.cpp | 43 ++++--- .../src/d3drm/backends/sdl3gpu/renderer.cpp | 44 ++++--- .../src/d3drm/backends/software/renderer.cpp | 25 +++- miniwin/src/ddraw/framebuffer.cpp | 20 ++- miniwin/src/internal/d3drmrenderer.h | 2 +- miniwin/src/internal/d3drmrenderer_citro3d.h | 2 +- miniwin/src/internal/d3drmrenderer_directx9.h | 2 +- miniwin/src/internal/d3drmrenderer_opengl1.h | 2 +- .../src/internal/d3drmrenderer_opengles2.h | 2 +- miniwin/src/internal/d3drmrenderer_sdl3gpu.h | 2 +- miniwin/src/internal/d3drmrenderer_software.h | 2 +- 19 files changed, 248 insertions(+), 223 deletions(-) diff --git a/LEGO1/lego/legoomni/src/common/mxtransitionmanager.cpp b/LEGO1/lego/legoomni/src/common/mxtransitionmanager.cpp index 8c2c994d..6fd211c6 100644 --- a/LEGO1/lego/legoomni/src/common/mxtransitionmanager.cpp +++ b/LEGO1/lego/legoomni/src/common/mxtransitionmanager.cpp @@ -400,47 +400,18 @@ void MxTransitionManager::WipeDownTransition() return; } - DDSURFACEDESC ddsd; - memset(&ddsd, 0, sizeof(ddsd)); - ddsd.dwSize = sizeof(ddsd); + RECT fillRect = g_fullScreenRect; + // For each of the 240 animation ticks, blank out two scanlines + // starting at the top of the screen. + fillRect.bottom = 2 * (m_animationTimer + 1); - HRESULT res = m_ddSurface->Lock(NULL, &ddsd, DDLOCK_WAIT | DDLOCK_WRITEONLY, NULL); - if (res == DDERR_SURFACELOST) { - m_ddSurface->Restore(); - res = m_ddSurface->Lock(NULL, &ddsd, DDLOCK_WAIT | DDLOCK_WRITEONLY, NULL); - } + DDBLTFX bltFx = {}; + bltFx.dwSize = sizeof(bltFx); + bltFx.dwFillColor = 0xFF000000; - if (res == DD_OK) { - SubmitCopyRect(&ddsd); + m_ddSurface->Blt(&fillRect, NULL, NULL, DDBLT_COLORFILL | DDBLT_WAIT, &bltFx); - // For each of the 240 animation ticks, blank out two scanlines - // starting at the top of the screen. - MxU8* line = (MxU8*) ddsd.lpSurface + 2 * ddsd.lPitch * m_animationTimer; - - if (ddsd.ddpfPixelFormat.dwRGBBitCount == 32) { - MxU32* pixels = (MxU32*) line; - int pixelsPerLine = ddsd.lPitch / 4; - for (int i = 0; i < pixelsPerLine; i++) { - pixels[i] = 0xFF000000; - } - line += ddsd.lPitch; - pixels = (MxU32*) line; - for (int i = 0; i < pixelsPerLine; i++) { - pixels[i] = 0xFF000000; - } - } - else { - memset(line, 0, ddsd.lPitch); - - line += ddsd.lPitch; - memset(line, 0, ddsd.lPitch); - } - - SetupCopyRect(&ddsd); - m_ddSurface->Unlock(ddsd.lpSurface); - - m_animationTimer++; - } + m_animationTimer++; } // FUNCTION: LEGO1 0x1004c270 @@ -452,65 +423,28 @@ void MxTransitionManager::WindowsTransition() return; } - DDSURFACEDESC ddsd; - memset(&ddsd, 0, sizeof(ddsd)); - ddsd.dwSize = sizeof(ddsd); + DDBLTFX bltFx = {}; + bltFx.dwSize = sizeof(bltFx); + bltFx.dwFillColor = 0xFF000000; - HRESULT res = m_ddSurface->Lock(NULL, &ddsd, DDLOCK_WAIT | DDLOCK_WRITEONLY, NULL); - if (res == DDERR_SURFACELOST) { - m_ddSurface->Restore(); - res = m_ddSurface->Lock(NULL, &ddsd, DDLOCK_WAIT | DDLOCK_WRITEONLY, NULL); - } + int top = m_animationTimer; + int bottom = 480 - m_animationTimer - 1; + int left = m_animationTimer; + int right = 639 - m_animationTimer; - if (res == DD_OK) { - SubmitCopyRect(&ddsd); + RECT topRect = {0, top, 640, top + 1}; + m_ddSurface->Blt(&topRect, NULL, NULL, DDBLT_COLORFILL | DDBLT_WAIT, &bltFx); - MxU8* line = (MxU8*) ddsd.lpSurface + m_animationTimer * ddsd.lPitch; + RECT bottomRect = {0, bottom, 640, bottom + 1}; + m_ddSurface->Blt(&bottomRect, NULL, NULL, DDBLT_COLORFILL | DDBLT_WAIT, &bltFx); - MxS32 bytesPerPixel = ddsd.ddpfPixelFormat.dwRGBBitCount / 8; + RECT leftRect = {left, top + 1, left + 1, bottom}; + m_ddSurface->Blt(&leftRect, NULL, NULL, DDBLT_COLORFILL | DDBLT_WAIT, &bltFx); - if (bytesPerPixel == 4) { - MxU32* pixels = (MxU32*) line; - for (int i = 0; i < 640; i++) { - pixels[i] = 0xFF000000; - } + RECT rightRect = {right, top + 1, right + 1, bottom}; + m_ddSurface->Blt(&rightRect, NULL, NULL, DDBLT_COLORFILL | DDBLT_WAIT, &bltFx); - for (MxS32 i = m_animationTimer + 1; i < 480 - m_animationTimer - 1; i++) { - line += ddsd.lPitch; - pixels = (MxU32*) line; - pixels[m_animationTimer] = 0xFF000000; - pixels[639 - m_animationTimer] = 0xFF000000; - } - - if (m_animationTimer < 240 - 1) { - line += ddsd.lPitch; - pixels = (MxU32*) line; - for (int i = 0; i < 640; i++) { - pixels[i] = 0xFF000000; - } - } - } - else { - memset(line, 0, 640 * bytesPerPixel); - - for (MxS32 i = m_animationTimer + 1; i < 480 - m_animationTimer - 1; i++) { - line += ddsd.lPitch; - - memset(line + m_animationTimer * bytesPerPixel, 0, bytesPerPixel); - memset(line + (639 - m_animationTimer) * bytesPerPixel, 0, bytesPerPixel); - } - - if (m_animationTimer < 240 - 1) { - line += ddsd.lPitch; - memset(line, 0, 640 * bytesPerPixel); - } - } - - SetupCopyRect(&ddsd); - m_ddSurface->Unlock(ddsd.lpSurface); - - m_animationTimer++; - } + m_animationTimer++; } // FUNCTION: LEGO1 0x1004c3e0 diff --git a/miniwin/src/d3drm/backends/citro3d/renderer.cpp b/miniwin/src/d3drm/backends/citro3d/renderer.cpp index f076cf70..f71f9255 100644 --- a/miniwin/src/d3drm/backends/citro3d/renderer.cpp +++ b/miniwin/src/d3drm/backends/citro3d/renderer.cpp @@ -443,6 +443,32 @@ void ConvertMatrix(const D3DRMMATRIX4D in, C3D_Mtx* out) } } +void SetMaterialAppearance( + const FColor& color, + float shininess, + int uLoc_meshColor, + int uLoc_shininess, + C3D_Tex* textures +) +{ + C3D_FVUnifSet(GPU_VERTEX_SHADER, uLoc_meshColor, color.r, color.g, color.b, color.a); + C3D_FVUnifSet(GPU_VERTEX_SHADER, uLoc_shininess, shininess, 0.0f, 0.0f, 0.0f); + + C3D_TexEnv* env = C3D_GetTexEnv(0); + C3D_TexEnvInit(env); + + if (textures) { + C3D_TexBind(0, textures); + C3D_TexEnvSrc(env, C3D_Both, GPU_TEXTURE0, GPU_PRIMARY_COLOR, GPU_PRIMARY_COLOR); + C3D_TexEnvFunc(env, C3D_Both, GPU_MODULATE); + } + else { + C3D_TexBind(0, nullptr); + C3D_TexEnvSrc(env, C3D_Both, GPU_PRIMARY_COLOR, GPU_PRIMARY_COLOR, GPU_PRIMARY_COLOR); + C3D_TexEnvFunc(env, C3D_Both, GPU_REPLACE); + } +} + void Citro3DRenderer::SubmitDraw( DWORD meshId, const D3DRMMATRIX4D& modelViewMatrix, @@ -462,32 +488,17 @@ void Citro3DRenderer::SubmitDraw( BufInfo_Init(bufInfo); BufInfo_Add(bufInfo, mesh.vbo, sizeof(D3DRMVERTEX), 3, 0x210); - C3D_FVUnifSet( - GPU_VERTEX_SHADER, + SetMaterialAppearance( + {appearance.color.r / 255.0f, + appearance.color.g / 255.0f, + appearance.color.b / 255.0f, + appearance.color.a / 255.0f}, + appearance.shininess, uLoc_meshColor, - appearance.color.r / 255.0f, - appearance.color.g / 255.0f, - appearance.color.b / 255.0f, - appearance.color.a / 255.0f + uLoc_shininess, + appearance.textureId != NO_TEXTURE_ID ? &m_textures[appearance.textureId].c3dTex : nullptr ); - C3D_FVUnifSet(GPU_VERTEX_SHADER, uLoc_shininess, appearance.shininess / 255.0f, 0.0f, 0.0f, 0.0f); - - if (appearance.textureId != NO_TEXTURE_ID) { - C3D_TexBind(0, &m_textures[appearance.textureId].c3dTex); - C3D_TexEnv* env = C3D_GetTexEnv(0); - C3D_TexEnvInit(env); - C3D_TexEnvSrc(env, C3D_Both, GPU_TEXTURE0, GPU_PRIMARY_COLOR, GPU_PRIMARY_COLOR); - C3D_TexEnvFunc(env, C3D_Both, GPU_MODULATE); - } - else { - C3D_TexBind(0, nullptr); - C3D_TexEnv* env = C3D_GetTexEnv(0); - C3D_TexEnvInit(env); - C3D_TexEnvSrc(env, C3D_Both, GPU_PRIMARY_COLOR, GPU_PRIMARY_COLOR, GPU_PRIMARY_COLOR); - C3D_TexEnvFunc(env, C3D_Both, GPU_REPLACE); - } - C3D_DrawArrays(GPU_TRIANGLES, 0, mesh.vertexCount); } @@ -519,7 +530,7 @@ void Citro3DRenderer::Flip() g_rendering = false; } -void Citro3DRenderer::Draw2DImage(Uint32 textureId, const SDL_Rect& srcRect, const SDL_Rect& dstRect) +void Citro3DRenderer::Draw2DImage(Uint32 textureId, const SDL_Rect& srcRect, const SDL_Rect& dstRect, FColor color) { C3D_AlphaBlend(GPU_BLEND_ADD, GPU_BLEND_ADD, GPU_ONE, GPU_ONE_MINUS_SRC_ALPHA, GPU_ONE, GPU_ONE_MINUS_SRC_ALPHA); StartFrame(); @@ -545,16 +556,9 @@ void Citro3DRenderer::Draw2DImage(Uint32 textureId, const SDL_Rect& srcRect, con C3D_FVUnifSet(GPU_VERTEX_SHADER, uLoc_lightClr + 1, 0.0f, 0.0f, 0.0f, 0.0f); C3D_FVUnifSet(GPU_VERTEX_SHADER, uLoc_lightClr + 2, 1.0f, 1.0f, 1.0f, 1.0f); // Ambient - C3D_FVUnifSet(GPU_VERTEX_SHADER, uLoc_shininess, 0.0f, 0.0f, 0.0f, 0.0f); - C3D_FVUnifSet(GPU_VERTEX_SHADER, uLoc_meshColor, 1.0f, 1.0f, 1.0f, 1.0f); + C3DTextureCacheEntry* texture = (textureId != NO_TEXTURE_ID) ? &m_textures[textureId] : nullptr; - C3DTextureCacheEntry& texture = m_textures[textureId]; - - C3D_TexBind(0, &texture.c3dTex); - C3D_TexEnv* env = C3D_GetTexEnv(0); - C3D_TexEnvInit(env); - C3D_TexEnvSrc(env, C3D_Both, GPU_TEXTURE0, GPU_PRIMARY_COLOR, GPU_PRIMARY_COLOR); - C3D_TexEnvFunc(env, C3D_Both, GPU_MODULATE); + SetMaterialAppearance(color, 0.0f, uLoc_meshColor, uLoc_shininess, texture ? &texture->c3dTex : nullptr); float scale = m_viewportTransform.scale; @@ -563,10 +567,17 @@ void Citro3DRenderer::Draw2DImage(Uint32 textureId, const SDL_Rect& srcRect, con float x2 = x1 + static_cast(dstRect.w); float y2 = y1 + static_cast(dstRect.h); - float u0 = (srcRect.x * scale) / texture.width; - float u1 = ((srcRect.x + srcRect.w) * scale) / texture.width; - float v0 = (srcRect.y * scale) / texture.height; - float v1 = ((srcRect.y + srcRect.h) * scale) / texture.height; + float u0 = 0.0f; + float u1 = 0.0f; + float v0 = 0.0f; + float v1 = 0.0f; + + if (texture) { + u0 = (srcRect.x * scale) / texture->width; + u1 = ((srcRect.x + srcRect.w) * scale) / texture->width; + v0 = (srcRect.y * scale) / texture->height; + v1 = ((srcRect.y + srcRect.h) * scale) / texture->height; + } C3D_ImmDrawBegin(GPU_TRIANGLES); diff --git a/miniwin/src/d3drm/backends/directx9/actual.cpp b/miniwin/src/d3drm/backends/directx9/actual.cpp index 33dc2864..4ccea210 100644 --- a/miniwin/src/d3drm/backends/directx9/actual.cpp +++ b/miniwin/src/d3drm/backends/directx9/actual.cpp @@ -297,44 +297,23 @@ D3DMATRIX ToD3DMATRIX(const Matrix4x4& in) return out; } -void Actual_SubmitDraw( - const D3D9MeshCacheEntry* mesh, - const Matrix4x4* modelViewMatrix, - const Matrix4x4* worldMatrix, - const Matrix4x4* viewMatrix, - const Matrix3x3* normalMatrix, - const Appearance* appearance, - IDirect3DTexture9* texture -) +void SetMaterialAndTexture(const FColor& color, float shininess, IDirect3DTexture9* texture) { - D3DMATRIX proj = ToD3DMATRIX(g_projection); - g_device->SetTransform(D3DTS_PROJECTION, &proj); - D3DMATRIX view = ToD3DMATRIX(*viewMatrix); - g_device->SetTransform(D3DTS_VIEW, &view); - D3DMATRIX world = ToD3DMATRIX(*worldMatrix); - g_device->SetTransform(D3DTS_WORLD, &world); - D3DMATERIAL9 mat = {}; - mat.Diffuse.r = appearance->color.r / 255.0f; - mat.Diffuse.g = appearance->color.g / 255.0f; - mat.Diffuse.b = appearance->color.b / 255.0f; - mat.Diffuse.a = appearance->color.a / 255.0f; + mat.Diffuse.r = color.r / 255.0f; + mat.Diffuse.g = color.g / 255.0f; + mat.Diffuse.b = color.b / 255.0f; + mat.Diffuse.a = color.a / 255.0f; mat.Ambient = mat.Diffuse; - if (appearance->shininess != 0) { + if (shininess != 0) { g_device->SetRenderState(D3DRS_SPECULARENABLE, TRUE); - mat.Specular.r = 1.0f; - mat.Specular.g = 1.0f; - mat.Specular.b = 1.0f; - mat.Specular.a = 1.0f; - mat.Power = appearance->shininess; + mat.Specular = {1.0f, 1.0f, 1.0f, 1.0f}; + mat.Power = shininess; } else { g_device->SetRenderState(D3DRS_SPECULARENABLE, FALSE); - mat.Specular.r = 0.0f; - mat.Specular.g = 0.0f; - mat.Specular.b = 0.0f; - mat.Specular.a = 0.0f; + mat.Specular = {0.0f, 0.0f, 0.0f, 0.0f}; mat.Power = 0.0f; } @@ -352,6 +331,33 @@ void Actual_SubmitDraw( else { g_device->SetTexture(0, nullptr); } +} + +void Actual_SubmitDraw( + const D3D9MeshCacheEntry* mesh, + const Matrix4x4* modelViewMatrix, + const Matrix4x4* worldMatrix, + const Matrix4x4* viewMatrix, + const Matrix3x3* normalMatrix, + const Appearance* appearance, + IDirect3DTexture9* texture +) +{ + D3DMATRIX proj = ToD3DMATRIX(g_projection); + g_device->SetTransform(D3DTS_PROJECTION, &proj); + D3DMATRIX view = ToD3DMATRIX(*viewMatrix); + g_device->SetTransform(D3DTS_VIEW, &view); + D3DMATRIX world = ToD3DMATRIX(*worldMatrix); + g_device->SetTransform(D3DTS_WORLD, &world); + + SetMaterialAndTexture( + {appearance->color.r / 255.0f, + appearance->color.g / 255.0f, + appearance->color.b / 255.0f, + appearance->color.a / 255.0f}, + appearance->shininess, + texture + ); g_device->SetRenderState(D3DRS_SHADEMODE, mesh->flat ? D3DSHADE_FLAT : D3DSHADE_GOURAUD); @@ -367,7 +373,7 @@ uint32_t Actual_Flip() return g_device->Present(nullptr, nullptr, nullptr, nullptr); } -void Actual_Draw2DImage(IDirect3DTexture9* texture, const SDL_Rect& srcRect, const SDL_Rect& dstRect) +void Actual_Draw2DImage(IDirect3DTexture9* texture, const SDL_Rect& srcRect, const SDL_Rect& dstRect, FColor color) { StartScene(); @@ -405,10 +411,7 @@ void Actual_Draw2DImage(IDirect3DTexture9* texture, const SDL_Rect& srcRect, con g_device->SetRenderState(D3DRS_SRCBLEND, D3DBLEND_SRCALPHA); g_device->SetRenderState(D3DRS_DESTBLEND, D3DBLEND_INVSRCALPHA); - g_device->SetTexture(0, texture); - g_device->SetTextureStageState(0, D3DTSS_COLOROP, D3DTOP_MODULATE); - g_device->SetTextureStageState(0, D3DTSS_COLORARG1, D3DTA_TEXTURE); - g_device->SetTextureStageState(0, D3DTSS_COLORARG2, D3DTA_DIFFUSE); + SetMaterialAndTexture(color, 0, texture); D3DSURFACE_DESC texDesc; texture->GetLevelDesc(0, &texDesc); diff --git a/miniwin/src/d3drm/backends/directx9/actual.h b/miniwin/src/d3drm/backends/directx9/actual.h index e3369ec9..bf09cb00 100644 --- a/miniwin/src/d3drm/backends/directx9/actual.h +++ b/miniwin/src/d3drm/backends/directx9/actual.h @@ -78,5 +78,5 @@ void Actual_SubmitDraw( void Actual_Resize(int width, int height, const ViewportTransform& viewportTransform); void Actual_Clear(float r, float g, float b); uint32_t Actual_Flip(); -void Actual_Draw2DImage(IDirect3DTexture9* texture, const SDL_Rect& srcRect, const SDL_Rect& dstRect); +void Actual_Draw2DImage(IDirect3DTexture9* texture, const SDL_Rect& srcRect, const SDL_Rect& dstRect, FColor color); uint32_t Actual_Download(SDL_Surface* target); diff --git a/miniwin/src/d3drm/backends/directx9/renderer.cpp b/miniwin/src/d3drm/backends/directx9/renderer.cpp index 2701528b..5c8a2354 100644 --- a/miniwin/src/d3drm/backends/directx9/renderer.cpp +++ b/miniwin/src/d3drm/backends/directx9/renderer.cpp @@ -273,9 +273,9 @@ void DirectX9Renderer::Flip() Actual_Flip(); } -void DirectX9Renderer::Draw2DImage(Uint32 textureId, const SDL_Rect& srcRect, const SDL_Rect& dstRect) +void DirectX9Renderer::Draw2DImage(Uint32 textureId, const SDL_Rect& srcRect, const SDL_Rect& dstRect, FColor color) { - Actual_Draw2DImage(m_textures[textureId].dxTexture, srcRect, dstRect); + Actual_Draw2DImage(m_textures[textureId].dxTexture, srcRect, dstRect, color); } void DirectX9Renderer::SetDither(bool dither) diff --git a/miniwin/src/d3drm/backends/opengl1/actual.cpp b/miniwin/src/d3drm/backends/opengl1/actual.cpp index d75eb04e..b1a464d5 100644 --- a/miniwin/src/d3drm/backends/opengl1/actual.cpp +++ b/miniwin/src/d3drm/backends/opengl1/actual.cpp @@ -285,9 +285,10 @@ void GL11_Clear(float r, float g, float b) } void GL11_Draw2DImage( - GLTextureCacheEntry& cache, + const GLTextureCacheEntry* cache, const SDL_Rect& srcRect, const SDL_Rect& dstRect, + const FColor& color, float left, float right, float bottom, @@ -296,6 +297,7 @@ void GL11_Draw2DImage( { glDisable(GL_DEPTH_TEST); glDepthMask(GL_FALSE); + glShadeModel(GL_FLAT); glMatrixMode(GL_PROJECTION); glLoadIdentity(); @@ -306,17 +308,28 @@ void GL11_Draw2DImage( glLoadIdentity(); glDisable(GL_LIGHTING); - glColor4f(1.0f, 1.0f, 1.0f, 1.0f); + glColor4f(color.r, color.g, color.b, color.a); - glEnable(GL_TEXTURE_2D); - glBindTexture(GL_TEXTURE_2D, cache.glTextureId); - glEnable(GL_BLEND); - glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); + float u1 = 0; + float v1 = 0; + float u2 = 0; + float v2 = 0; - float u1 = srcRect.x / cache.width; - float v1 = srcRect.y / cache.height; - float u2 = (srcRect.x + srcRect.w) / cache.width; - float v2 = (srcRect.y + srcRect.h) / cache.height; + if (cache) { + glEnable(GL_TEXTURE_2D); + glBindTexture(GL_TEXTURE_2D, cache->glTextureId); + glEnable(GL_BLEND); + glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); + + u1 = srcRect.x / cache->width; + v1 = srcRect.y / cache->height; + u2 = (srcRect.x + srcRect.w) / cache->width; + v2 = (srcRect.y + srcRect.h) / cache->height; + } + else { + glDisable(GL_TEXTURE_2D); + glDisableClientState(GL_TEXTURE_COORD_ARRAY); + } float x1 = (float) dstRect.x; float y1 = (float) dstRect.y; diff --git a/miniwin/src/d3drm/backends/opengl1/actual.h b/miniwin/src/d3drm/backends/opengl1/actual.h index 7a6a4432..9460119c 100644 --- a/miniwin/src/d3drm/backends/opengl1/actual.h +++ b/miniwin/src/d3drm/backends/opengl1/actual.h @@ -79,9 +79,10 @@ void GL11_SubmitDraw( void GL11_Resize(int width, int height); void GL11_Clear(float r, float g, float b); void GL11_Draw2DImage( - GLTextureCacheEntry& cache, + const GLTextureCacheEntry* cache, const SDL_Rect& srcRect, const SDL_Rect& dstRect, + const FColor& color, float left, float right, float bottom, diff --git a/miniwin/src/d3drm/backends/opengl1/renderer.cpp b/miniwin/src/d3drm/backends/opengl1/renderer.cpp index 4c6aacf9..8c43d14a 100644 --- a/miniwin/src/d3drm/backends/opengl1/renderer.cpp +++ b/miniwin/src/d3drm/backends/opengl1/renderer.cpp @@ -372,7 +372,7 @@ void OpenGL1Renderer::Flip() } } -void OpenGL1Renderer::Draw2DImage(Uint32 textureId, const SDL_Rect& srcRect, const SDL_Rect& dstRect) +void OpenGL1Renderer::Draw2DImage(Uint32 textureId, const SDL_Rect& srcRect, const SDL_Rect& dstRect, FColor color) { m_dirty = true; @@ -381,7 +381,12 @@ void OpenGL1Renderer::Draw2DImage(Uint32 textureId, const SDL_Rect& srcRect, con float top = -m_viewportTransform.offsetY / m_viewportTransform.scale; float bottom = (m_height - m_viewportTransform.offsetY) / m_viewportTransform.scale; - GL11_Draw2DImage(m_textures[textureId], srcRect, dstRect, left, right, bottom, top); + const GLTextureCacheEntry* texture = nullptr; + if (textureId != NO_TEXTURE_ID) { + texture = &m_textures[textureId]; + } + + GL11_Draw2DImage(texture, srcRect, dstRect, color, left, right, bottom, top); } void OpenGL1Renderer::SetDither(bool dither) diff --git a/miniwin/src/d3drm/backends/opengles2/renderer.cpp b/miniwin/src/d3drm/backends/opengles2/renderer.cpp index 28466bea..850e8b6e 100644 --- a/miniwin/src/d3drm/backends/opengles2/renderer.cpp +++ b/miniwin/src/d3drm/backends/opengles2/renderer.cpp @@ -598,7 +598,7 @@ void OpenGLES2Renderer::Flip() } } -void OpenGLES2Renderer::Draw2DImage(Uint32 textureId, const SDL_Rect& srcRect, const SDL_Rect& dstRect) +void OpenGLES2Renderer::Draw2DImage(Uint32 textureId, const SDL_Rect& srcRect, const SDL_Rect& dstRect, FColor color) { m_dirty = true; @@ -607,25 +607,37 @@ void OpenGLES2Renderer::Draw2DImage(Uint32 textureId, const SDL_Rect& srcRect, c glUseProgram(m_shaderProgram); - float color[] = {1.0f, 1.0f, 1.0f, 1.0f}; + float ambient[] = {1.0f, 1.0f, 1.0f, 1.0f}; float blank[] = {0.0f, 0.0f, 0.0f, 0.0f}; - glUniform4fv(u_lightLocs[0][0], 1, color); + glUniform4fv(u_lightLocs[0][0], 1, ambient); glUniform4fv(u_lightLocs[0][1], 1, blank); glUniform4fv(u_lightLocs[0][2], 1, blank); glUniform1i(m_lightCountLoc, 1); - glUniform4f(m_colorLoc, 1.0f, 1.0f, 1.0f, 1.0f); + glUniform4f(m_colorLoc, color.r, color.g, color.b, color.a); glUniform1f(m_shinLoc, 0.0f); - const GLES2TextureCacheEntry& texture = m_textures[textureId]; - float scaleX = static_cast(dstRect.w) / srcRect.w; - float scaleY = static_cast(dstRect.h) / srcRect.h; - SDL_Rect expandedDstRect = { - static_cast(std::round(dstRect.x - srcRect.x * scaleX)), - static_cast(std::round(dstRect.y - srcRect.y * scaleY)), - static_cast(std::round(texture.width * scaleX)), - static_cast(std::round(texture.height * scaleY)) - }; + SDL_Rect expandedDstRect; + if (textureId != NO_TEXTURE_ID) { + const GLES2TextureCacheEntry& texture = m_textures[textureId]; + float scaleX = static_cast(dstRect.w) / srcRect.w; + float scaleY = static_cast(dstRect.h) / srcRect.h; + expandedDstRect = { + static_cast(std::round(dstRect.x - srcRect.x * scaleX)), + static_cast(std::round(dstRect.y - srcRect.y * scaleY)), + static_cast(std::round(texture.width * scaleX)), + static_cast(std::round(texture.height * scaleY)) + }; + + glActiveTexture(GL_TEXTURE0); + glUniform1i(m_useTextureLoc, 1); + glBindTexture(GL_TEXTURE_2D, texture.glTextureId); + glUniform1i(m_textureLoc, 0); + } + else { + expandedDstRect = dstRect; + glUniform1i(m_useTextureLoc, 0); + } D3DRMMATRIX4D modelView, projection; Create2DTransformMatrix( @@ -645,11 +657,6 @@ void OpenGLES2Renderer::Draw2DImage(Uint32 textureId, const SDL_Rect& srcRect, c glEnable(GL_BLEND); glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); - glActiveTexture(GL_TEXTURE0); - glUniform1i(m_useTextureLoc, 1); - glBindTexture(GL_TEXTURE_2D, texture.glTextureId); - glUniform1i(m_textureLoc, 0); - glEnable(GL_SCISSOR_TEST); glScissor( static_cast(std::round(dstRect.x * m_viewportTransform.scale + m_viewportTransform.offsetX)), diff --git a/miniwin/src/d3drm/backends/sdl3gpu/renderer.cpp b/miniwin/src/d3drm/backends/sdl3gpu/renderer.cpp index 69b326b6..c6324f03 100644 --- a/miniwin/src/d3drm/backends/sdl3gpu/renderer.cpp +++ b/miniwin/src/d3drm/backends/sdl3gpu/renderer.cpp @@ -884,24 +884,37 @@ void Direct3DRMSDL3GPURenderer::Flip() m_cmdbuf = nullptr; } -void Direct3DRMSDL3GPURenderer::Draw2DImage(Uint32 textureId, const SDL_Rect& srcRect, const SDL_Rect& dstRect) +void Direct3DRMSDL3GPURenderer::Draw2DImage( + Uint32 textureId, + const SDL_Rect& srcRect, + const SDL_Rect& dstRect, + FColor color +) { if (!m_renderPass) { StartRenderPass(0, 0, 0, false); } SDL_BindGPUGraphicsPipeline(m_renderPass, m_uiPipeline); - const SDL3TextureCache& tex = m_textures[textureId]; - - auto surface = static_cast(tex.texture->m_surface); - float scaleX = static_cast(dstRect.w) / srcRect.w; - float scaleY = static_cast(dstRect.h) / srcRect.h; - SDL_Rect expandedDstRect = { - static_cast(std::round(dstRect.x - srcRect.x * scaleX)), - static_cast(std::round(dstRect.y - srcRect.y * scaleY)), - static_cast(std::round(static_cast(surface->m_surface->w) * scaleX)), - static_cast(std::round(static_cast(surface->m_surface->h) * scaleY)), - }; + SDL_GPUTexture* tex; + SDL_Rect expandedDstRect; + if (textureId == NO_TEXTURE_ID) { + expandedDstRect = dstRect; + tex = m_dummyTexture; + } + else { + SDL3TextureCache& cache = m_textures[textureId]; + tex = cache.gpuTexture; + auto surface = static_cast(cache.texture->m_surface); + float scaleX = static_cast(dstRect.w) / srcRect.w; + float scaleY = static_cast(dstRect.h) / srcRect.h; + expandedDstRect = { + static_cast(std::round(dstRect.x - srcRect.x * scaleX)), + static_cast(std::round(dstRect.y - srcRect.y * scaleY)), + static_cast(std::round(static_cast(surface->m_surface->w) * scaleX)), + static_cast(std::round(static_cast(surface->m_surface->h) * scaleY)), + }; + } Create2DTransformMatrix( expandedDstRect, @@ -916,11 +929,14 @@ void Direct3DRMSDL3GPURenderer::Draw2DImage(Uint32 textureId, const SDL_Rect& sr SceneLight fullBright = {{1, 1, 1, 1}, {0, 0, 0}, 0, {0, 0, 0}, 0}; memcpy(&m_fragmentShadingData.lights, &fullBright, sizeof(SceneLight)); m_fragmentShadingData.lightCount = 1; - m_fragmentShadingData.color = {0xff, 0xff, 0xff, 0xff}; + m_fragmentShadingData.color.r = static_cast(color.r * 255); + m_fragmentShadingData.color.g = static_cast(color.g * 255); + m_fragmentShadingData.color.b = static_cast(color.b * 255); + m_fragmentShadingData.color.a = static_cast(color.a * 255); m_fragmentShadingData.shininess = 0.0f; m_fragmentShadingData.useTexture = 1; - SDL_GPUTextureSamplerBinding samplerBinding = {tex.gpuTexture, m_uiSampler}; + SDL_GPUTextureSamplerBinding samplerBinding = {tex, m_uiSampler}; SDL_BindGPUFragmentSamplers(m_renderPass, 0, &samplerBinding, 1); SDL_PushGPUVertexUniformData(m_cmdbuf, 0, &m_uniforms, sizeof(m_uniforms)); SDL_PushGPUFragmentUniformData(m_cmdbuf, 0, &m_fragmentShadingData, sizeof(m_fragmentShadingData)); diff --git a/miniwin/src/d3drm/backends/software/renderer.cpp b/miniwin/src/d3drm/backends/software/renderer.cpp index b4fab671..2ae8697e 100644 --- a/miniwin/src/d3drm/backends/software/renderer.cpp +++ b/miniwin/src/d3drm/backends/software/renderer.cpp @@ -774,16 +774,35 @@ void Direct3DRMSoftwareRenderer::Flip() SDL_RenderPresent(m_renderer); } -void Direct3DRMSoftwareRenderer::Draw2DImage(Uint32 textureId, const SDL_Rect& srcRect, const SDL_Rect& dstRect) +void Direct3DRMSoftwareRenderer::Draw2DImage( + Uint32 textureId, + const SDL_Rect& srcRect, + const SDL_Rect& dstRect, + FColor color +) { - SDL_Surface* surface = m_textures[textureId].cached; - SDL_UnlockSurface(surface); SDL_Rect centeredRect = { static_cast(dstRect.x * m_viewportTransform.scale + m_viewportTransform.offsetX), static_cast(dstRect.y * m_viewportTransform.scale + m_viewportTransform.offsetY), static_cast(dstRect.w * m_viewportTransform.scale), static_cast(dstRect.h * m_viewportTransform.scale), }; + + if (textureId == NO_TEXTURE_ID) { + Uint32 sdlColor = SDL_MapRGBA( + m_format, + m_palette, + static_cast(color.r * 255), + static_cast(color.g * 255), + static_cast(color.b * 255), + static_cast(color.a * 255) + ); + SDL_FillSurfaceRect(m_renderedImage, ¢eredRect, sdlColor); + return; + } + + SDL_Surface* surface = m_textures[textureId].cached; + SDL_UnlockSurface(surface); SDL_BlitSurfaceScaled(surface, &srcRect, m_renderedImage, ¢eredRect, SDL_SCALEMODE_LINEAR); SDL_LockSurface(surface); } diff --git a/miniwin/src/ddraw/framebuffer.cpp b/miniwin/src/ddraw/framebuffer.cpp index 9f4224fd..20490b42 100644 --- a/miniwin/src/ddraw/framebuffer.cpp +++ b/miniwin/src/ddraw/framebuffer.cpp @@ -48,16 +48,32 @@ HRESULT FrameBufferImpl::Blt( if (!DDRenderer) { return DDERR_GENERIC; } + if (dynamic_cast(lpDDSrcSurface) == this) { return Flip(nullptr, DDFLIP_WAIT); } + if ((dwFlags & DDBLT_COLORFILL) == DDBLT_COLORFILL) { + Uint8 a = (lpDDBltFx->dwFillColor >> 24) & 0xFF; Uint8 r = (lpDDBltFx->dwFillColor >> 16) & 0xFF; Uint8 g = (lpDDBltFx->dwFillColor >> 8) & 0xFF; Uint8 b = lpDDBltFx->dwFillColor & 0xFF; - DDRenderer->Clear(r / 255.0f, g / 255.0f, b / 255.0f); + + float fa = a / 255.0f; + float fr = r / 255.0f; + float fg = g / 255.0f; + float fb = b / 255.0f; + + if (lpDestRect) { + SDL_Rect dstRect = ConvertRect(lpDestRect); + DDRenderer->Draw2DImage(NO_TEXTURE_ID, SDL_Rect{}, dstRect, {fr, fg, fb, fa}); + } + else { + DDRenderer->Clear(fr, fg, fb); + } return DD_OK; } + auto surface = static_cast(lpDDSrcSurface); if (!surface) { return DDERR_GENERIC; @@ -67,7 +83,7 @@ HRESULT FrameBufferImpl::Blt( lpSrcRect ? ConvertRect(lpSrcRect) : SDL_Rect{0, 0, surface->m_surface->w, surface->m_surface->h}; SDL_Rect dstRect = lpDestRect ? ConvertRect(lpDestRect) : SDL_Rect{0, 0, (int) m_virtualWidth, (int) m_virtualHeight}; - DDRenderer->Draw2DImage(textureId, srcRect, dstRect); + DDRenderer->Draw2DImage(textureId, srcRect, dstRect, {1.0f, 1.0f, 1.0f, 1.0f}); return DD_OK; } diff --git a/miniwin/src/internal/d3drmrenderer.h b/miniwin/src/internal/d3drmrenderer.h index a871bb26..d6464eb7 100644 --- a/miniwin/src/internal/d3drmrenderer.h +++ b/miniwin/src/internal/d3drmrenderer.h @@ -51,7 +51,7 @@ class Direct3DRMRenderer : public IDirect3DDevice2 { virtual void Resize(int width, int height, const ViewportTransform& viewportTransform) = 0; virtual void Clear(float r, float g, float b) = 0; virtual void Flip() = 0; - virtual void Draw2DImage(Uint32 textureId, const SDL_Rect& srcRect, const SDL_Rect& dstRect) = 0; + virtual void Draw2DImage(Uint32 textureId, const SDL_Rect& srcRect, const SDL_Rect& dstRect, FColor color) = 0; virtual void Download(SDL_Surface* target) = 0; virtual void SetDither(bool dither) = 0; diff --git a/miniwin/src/internal/d3drmrenderer_citro3d.h b/miniwin/src/internal/d3drmrenderer_citro3d.h index af91b3dc..34863497 100644 --- a/miniwin/src/internal/d3drmrenderer_citro3d.h +++ b/miniwin/src/internal/d3drmrenderer_citro3d.h @@ -48,7 +48,7 @@ class Citro3DRenderer : public Direct3DRMRenderer { void Resize(int width, int height, const ViewportTransform& viewportTransform) override; void Clear(float r, float g, float b) override; void Flip() override; - void Draw2DImage(Uint32 textureId, const SDL_Rect& srcRect, const SDL_Rect& dstRect) override; + void Draw2DImage(Uint32 textureId, const SDL_Rect& srcRect, const SDL_Rect& dstRect, FColor color) override; void Download(SDL_Surface* target) override; void SetDither(bool dither) override; diff --git a/miniwin/src/internal/d3drmrenderer_directx9.h b/miniwin/src/internal/d3drmrenderer_directx9.h index 18ac126f..d1b0a581 100644 --- a/miniwin/src/internal/d3drmrenderer_directx9.h +++ b/miniwin/src/internal/d3drmrenderer_directx9.h @@ -34,7 +34,7 @@ class DirectX9Renderer : public Direct3DRMRenderer { void Resize(int width, int height, const ViewportTransform& viewportTransform) override; void Clear(float r, float g, float b) override; void Flip() override; - void Draw2DImage(Uint32 textureId, const SDL_Rect& srcRect, const SDL_Rect& dstRect) override; + void Draw2DImage(Uint32 textureId, const SDL_Rect& srcRect, const SDL_Rect& dstRect, FColor color) override; void Download(SDL_Surface* target) override; void SetDither(bool dither) override; diff --git a/miniwin/src/internal/d3drmrenderer_opengl1.h b/miniwin/src/internal/d3drmrenderer_opengl1.h index 656a964b..bd0fa003 100644 --- a/miniwin/src/internal/d3drmrenderer_opengl1.h +++ b/miniwin/src/internal/d3drmrenderer_opengl1.h @@ -34,7 +34,7 @@ class OpenGL1Renderer : public Direct3DRMRenderer { void Resize(int width, int height, const ViewportTransform& viewportTransform) override; void Clear(float r, float g, float b) override; void Flip() override; - void Draw2DImage(Uint32 textureId, const SDL_Rect& srcRect, const SDL_Rect& dstRect) override; + void Draw2DImage(Uint32 textureId, const SDL_Rect& srcRect, const SDL_Rect& dstRect, FColor color) override; void Download(SDL_Surface* target) override; void SetDither(bool dither) override; diff --git a/miniwin/src/internal/d3drmrenderer_opengles2.h b/miniwin/src/internal/d3drmrenderer_opengles2.h index 0aaec63d..7dbb1f4b 100644 --- a/miniwin/src/internal/d3drmrenderer_opengles2.h +++ b/miniwin/src/internal/d3drmrenderer_opengles2.h @@ -55,7 +55,7 @@ class OpenGLES2Renderer : public Direct3DRMRenderer { void Resize(int width, int height, const ViewportTransform& viewportTransform) override; void Clear(float r, float g, float b) override; void Flip() override; - void Draw2DImage(Uint32 textureId, const SDL_Rect& srcRect, const SDL_Rect& dstRect) override; + void Draw2DImage(Uint32 textureId, const SDL_Rect& srcRect, const SDL_Rect& dstRect, FColor color) override; void Download(SDL_Surface* target) override; void SetDither(bool dither) override; diff --git a/miniwin/src/internal/d3drmrenderer_sdl3gpu.h b/miniwin/src/internal/d3drmrenderer_sdl3gpu.h index cf68a8c4..b3afd674 100644 --- a/miniwin/src/internal/d3drmrenderer_sdl3gpu.h +++ b/miniwin/src/internal/d3drmrenderer_sdl3gpu.h @@ -65,7 +65,7 @@ class Direct3DRMSDL3GPURenderer : public Direct3DRMRenderer { void Resize(int width, int height, const ViewportTransform& viewportTransform) override; void Clear(float r, float g, float b) override; void Flip() override; - void Draw2DImage(Uint32 textureId, const SDL_Rect& srcRect, const SDL_Rect& dstRect) override; + void Draw2DImage(Uint32 textureId, const SDL_Rect& srcRect, const SDL_Rect& dstRect, FColor color) override; void Download(SDL_Surface* target) override; void SetDither(bool dither) override; diff --git a/miniwin/src/internal/d3drmrenderer_software.h b/miniwin/src/internal/d3drmrenderer_software.h index 91abac32..8b8c0dde 100644 --- a/miniwin/src/internal/d3drmrenderer_software.h +++ b/miniwin/src/internal/d3drmrenderer_software.h @@ -47,7 +47,7 @@ class Direct3DRMSoftwareRenderer : public Direct3DRMRenderer { void Resize(int width, int height, const ViewportTransform& viewportTransform) override; void Clear(float r, float g, float b) override; void Flip() override; - void Draw2DImage(Uint32 textureId, const SDL_Rect& srcRect, const SDL_Rect& dstRect) override; + void Draw2DImage(Uint32 textureId, const SDL_Rect& srcRect, const SDL_Rect& dstRect, FColor color) override; void Download(SDL_Surface* target) override; void SetDither(bool dither) override; From f1b22ee02568779b1ea23c944936eadf9bd6f76c Mon Sep 17 00:00:00 2001 From: Christian Semmler Date: Thu, 3 Jul 2025 16:28:45 -0700 Subject: [PATCH 062/188] Allow Web port to be playable in Firefox Private (#513) --- ISLE/emscripten/emscripten.patch | 18 ++++++++++++++++++ ISLE/emscripten/filesystem.cpp | 17 +++++++++++++---- ISLE/emscripten/filesystem.h | 2 +- ISLE/isleapp.cpp | 11 +++++++---- 4 files changed, 39 insertions(+), 9 deletions(-) diff --git a/ISLE/emscripten/emscripten.patch b/ISLE/emscripten/emscripten.patch index 899d72eb..0bc8222d 100644 --- a/ISLE/emscripten/emscripten.patch +++ b/ISLE/emscripten/emscripten.patch @@ -139,3 +139,21 @@ index e8c9f7e21..caf1971d2 100644 }, - }); +diff --git a/src/preamble.js b/src/preamble.js +index 572694517..0d2f4421b 100644 +--- a/src/preamble.js ++++ b/src/preamble.js +@@ -1062,3 +1062,13 @@ function getCompilerSetting(name) { + // dynamic linker as symbols are loaded. + var asyncifyStubs = {}; + #endif ++ ++(async () => { ++ try { ++ await navigator.storage.getDirectory(); ++ Module["disableOpfs"] = false; ++ } catch (e) { ++ Module["disableOpfs"] = true; ++ } ++})(); ++ diff --git a/ISLE/emscripten/filesystem.cpp b/ISLE/emscripten/filesystem.cpp index 395242a1..50cd9012 100644 --- a/ISLE/emscripten/filesystem.cpp +++ b/ISLE/emscripten/filesystem.cpp @@ -6,6 +6,7 @@ #include #include +#include #include static backend_t opfs = nullptr; @@ -13,10 +14,16 @@ static backend_t fetchfs = nullptr; extern const char* g_files[46]; -void Emscripten_SetupConfig(const char* p_iniConfig) +bool Emscripten_OPFSDisabled() { - if (!p_iniConfig || !*p_iniConfig) { - return; + return MAIN_THREAD_EM_ASM_INT({return !!Module["disableOpfs"]}); +} + +bool Emscripten_SetupConfig(const char* p_iniConfig) +{ + if (Emscripten_OPFSDisabled()) { + SDL_Log("OPFS is disabled; ignoring .ini path"); + return false; } opfs = wasmfs_create_opfs_backend(); @@ -28,6 +35,8 @@ void Emscripten_SetupConfig(const char* p_iniConfig) wasmfs_create_directory(iniConfig.GetData(), 0644, opfs); *parse = '/'; } + + return true; } void Emscripten_SetupFilesystem() @@ -66,7 +75,7 @@ void Emscripten_SetupFilesystem() registerFile(file); } - if (GameState()->GetSavePath() && *GameState()->GetSavePath()) { + if (GameState()->GetSavePath() && *GameState()->GetSavePath() && !Emscripten_OPFSDisabled()) { if (!opfs) { opfs = wasmfs_create_opfs_backend(); } diff --git a/ISLE/emscripten/filesystem.h b/ISLE/emscripten/filesystem.h index 131df1c8..ad78a349 100644 --- a/ISLE/emscripten/filesystem.h +++ b/ISLE/emscripten/filesystem.h @@ -10,7 +10,7 @@ inline static const char* Emscripten_savePath = "/save"; inline static const char* Emscripten_streamPath = "/"; inline static const char* Emscripten_streamHost = ISLE_EMSCRIPTEN_HOST; -void Emscripten_SetupConfig(const char* p_iniConfig); +bool Emscripten_SetupConfig(const char* p_iniConfig); void Emscripten_SetupFilesystem(); #endif // EMSCRIPTEN_FILESYSTEM_H diff --git a/ISLE/isleapp.cpp b/ISLE/isleapp.cpp index cfa3c50a..2e2066d8 100644 --- a/ISLE/isleapp.cpp +++ b/ISLE/isleapp.cpp @@ -778,6 +778,13 @@ bool IsleApp::LoadConfig() { char* prefPath = SDL_GetPrefPath("isledecomp", "isle"); char* iniConfig; + +#ifdef __EMSCRIPTEN__ + if (m_iniPath && !Emscripten_SetupConfig(m_iniPath)) { + m_iniPath = NULL; + } +#endif + if (m_iniPath) { iniConfig = new char[strlen(m_iniPath) + 1]; strcpy(iniConfig, m_iniPath); @@ -793,10 +800,6 @@ bool IsleApp::LoadConfig() } SDL_Log("Reading configuration from \"%s\"", iniConfig); -#ifdef __EMSCRIPTEN__ - Emscripten_SetupConfig(iniConfig); -#endif - dictionary* dict = iniparser_load(iniConfig); // [library:config] From df3d144ed9b71006e142074ff8fbb9b62d8561b5 Mon Sep 17 00:00:00 2001 From: MasterTimeThief Date: Thu, 3 Jul 2025 20:23:07 -0400 Subject: [PATCH 063/188] Renamed more functions and variables in Car Build sections (#1605) * Renamed variables and functions related to the Car Build sections * Car Build variable renaming - fixed issues and added enum `ShelfState` * Fixed some spacing in `legocarbuildpresenter.h` * Fixed tab spacing * Changed `m_shelfState` back to `MxU16` * Cleared up `InitBuildPlatform` * Fixed comments * Renamed more functions and variables in Car Build sections * Named `SelectPartFromMousePosition` * Updated spacing formatting in `LegoCarBuild` --- LEGO1/lego/legoomni/include/legocarbuild.h | 14 +-- .../legoomni/include/legocarbuildpresenter.h | 6 +- .../lego/legoomni/src/build/legocarbuild.cpp | 112 +++++++++--------- .../src/build/legocarbuildpresenter.cpp | 16 +-- 4 files changed, 74 insertions(+), 74 deletions(-) diff --git a/LEGO1/lego/legoomni/include/legocarbuild.h b/LEGO1/lego/legoomni/include/legocarbuild.h index f13f0450..a96b7b67 100644 --- a/LEGO1/lego/legoomni/include/legocarbuild.h +++ b/LEGO1/lego/legoomni/include/legocarbuild.h @@ -135,11 +135,11 @@ class LegoCarBuild : public LegoWorld { void FUN_10022f00(); void FUN_10022f30(); void FUN_10023130(MxLong p_x, MxLong p_y); - void FUN_100236d0(); + void AddSelectedPartToBuild(); undefined4 FUN_10024250(LegoEventNotificationParam* p_param); void FUN_100243a0(); undefined4 FUN_10024480(MxActionNotificationParam* p_param); - undefined4 FUN_100244e0(MxLong p_x, MxLong p_y); + undefined4 SelectPartFromMousePosition(MxLong p_x, MxLong p_y); undefined4 FUN_100246e0(MxLong p_x, MxLong p_y); MxS32 FUN_10024850(MxLong p_x, MxLong p_y); undefined4 FUN_10024890(MxParam* p_param); @@ -182,7 +182,7 @@ class LegoCarBuild : public LegoWorld { MxU8 m_unk0x109; // 0x109 MxU16 m_unk0x10a; // 0x10a DWORD m_unk0x10c; // 0x10c - LegoROI* m_unk0x110; // 0x110 + LegoROI* m_selectedPart; // 0x110 BoundingSphere m_unk0x114; // 0x114 MxMatrix m_unk0x12c; // 0x12c undefined m_unk0x174; // 0x174 @@ -200,10 +200,10 @@ class LegoCarBuild : public LegoWorld { MxS32 m_unk0x290[2]; // 0x290 MxS32 m_unk0x298[2]; // 0x298 - MxFloat m_unk0x2a0; // 0x2a0 - Mx4DPointFloat m_unk0x2a4; // 0x2a4 - Mx4DPointFloat m_unk0x2bc; // 0x2bc - MxBool m_unk0x2d4; // 0x2d4 + MxFloat m_unk0x2a0; // 0x2a0 + Mx4DPointFloat m_unk0x2a4; // 0x2a4 + Mx4DPointFloat m_unk0x2bc; // 0x2bc + MxBool m_selectedPartIsPlaced; // 0x2d4 // variable names verified by BETA10 0x1006b27a MxStillPresenter* m_ColorBook_Bitmap; // 0x2dc diff --git a/LEGO1/lego/legoomni/include/legocarbuildpresenter.h b/LEGO1/lego/legoomni/include/legocarbuildpresenter.h index a13c1adf..31423e32 100644 --- a/LEGO1/lego/legoomni/include/legocarbuildpresenter.h +++ b/LEGO1/lego/legoomni/include/legocarbuildpresenter.h @@ -77,11 +77,11 @@ class LegoCarBuildAnimPresenter : public LegoAnimPresenter { void FUN_10079050(MxS16 p_index); void SwapNodesByName(LegoChar* p_param1, LegoChar* p_param2); void InitBuildPlatform(); - void FUN_100795d0(LegoChar* p_param); - void FUN_10079680(LegoChar* p_param); + void HideBuildPartByName(LegoChar* p_param); + void ShowBuildPartByName(LegoChar* p_param); LegoAnimNodeData* FindNodeDataByName(LegoTreeNode* p_treeNode, const LegoChar* p_name); LegoTreeNode* FindNodeByName(LegoTreeNode* p_treeNode, const LegoChar* p_name); - void FUN_10079790(const LegoChar* p_name); + void AddPartToBuildByName(const LegoChar* p_name); void RotateAroundYAxis(MxFloat p_angle); MxBool FUN_10079c30(const LegoChar* p_name); MxBool PartIsPlaced(const LegoChar* p_name); diff --git a/LEGO1/lego/legoomni/src/build/legocarbuild.cpp b/LEGO1/lego/legoomni/src/build/legocarbuild.cpp index 6876d199..a7b1e863 100644 --- a/LEGO1/lego/legoomni/src/build/legocarbuild.cpp +++ b/LEGO1/lego/legoomni/src/build/legocarbuild.cpp @@ -93,9 +93,9 @@ MxS16 LegoCarBuild::g_unk0x100f11cc = -1; LegoCarBuild::LegoCarBuild() { m_unk0x100 = 0; - m_unk0x110 = 0; + m_selectedPart = 0; m_unk0xf8 = c_unknownminusone; - m_unk0x2d4 = FALSE; + m_selectedPartIsPlaced = FALSE; m_animPresenter = NULL; m_ColorBook_Bitmap = NULL; m_Yellow_Ctl = NULL; @@ -135,7 +135,7 @@ LegoCarBuild::LegoCarBuild() LegoCarBuild::~LegoCarBuild() { m_unk0x100 = 0; - m_unk0x110 = NULL; + m_selectedPart = NULL; if (m_animPresenter) { m_animPresenter->SetShelfState(LegoCarBuildAnimPresenter::e_selected); @@ -288,7 +288,7 @@ void LegoCarBuild::InitPresenters() // FUNCTION: LEGO1 0x10022f00 void LegoCarBuild::FUN_10022f00() { - if (m_unk0x110) { + if (m_selectedPart) { VTable0x6c(); m_animPresenter->SetShelfState(LegoCarBuildAnimPresenter::e_selected); m_unk0x100 = 5; @@ -299,18 +299,18 @@ void LegoCarBuild::FUN_10022f00() // FUNCTION: BETA10 0x1006b835 void LegoCarBuild::FUN_10022f30() { - if (m_unk0x110) { + if (m_selectedPart) { FUN_10024f70(FALSE); FUN_100250e0(FALSE); - if (m_animPresenter->PartIsPlaced(m_unk0x110->GetName())) { + if (m_animPresenter->PartIsPlaced(m_selectedPart->GetName())) { m_PlaceBrick_Sound->Enable(FALSE); m_PlaceBrick_Sound->Enable(TRUE); } m_animPresenter->SetShelfState(LegoCarBuildAnimPresenter::e_stopped); m_animPresenter->PutFrame(); - m_unk0x110 = NULL; + m_selectedPart = NULL; m_unk0x100 = 0; } } @@ -320,8 +320,8 @@ void LegoCarBuild::FUN_10022f30() void LegoCarBuild::VTable0x6c() { m_unk0x178 = m_unk0x1c0; - m_unk0x110->WrappedSetLocal2WorldWithWorldDataUpdate(m_unk0x178); - m_unk0x2a4 = Vector4(m_unk0x110->GetWorldPosition()); + m_selectedPart->WrappedSetLocal2WorldWithWorldDataUpdate(m_unk0x178); + m_unk0x2a4 = Vector4(m_selectedPart->GetWorldPosition()); VTable0x70(); } @@ -360,7 +360,7 @@ void LegoCarBuild::VTable0x70() // FUNCTION: BETA10 0x1006bb22 void LegoCarBuild::FUN_10023130(MxLong p_x, MxLong p_y) { - if (m_unk0x110) { + if (m_selectedPart) { MxFloat pfVar3[2]; MxFloat local30[3]; MxFloat local84[3]; @@ -408,7 +408,7 @@ void LegoCarBuild::FUN_10023130(MxLong p_x, MxLong p_y) local78[3][2] = m_unk0x178[3][2] + local18[2]; local78[3][3] = 1.0; - m_unk0x110->WrappedSetLocal2WorldWithWorldDataUpdate(local78); + m_selectedPart->WrappedSetLocal2WorldWithWorldDataUpdate(local78); } } } @@ -474,15 +474,15 @@ void LegoCarBuild::VTable0x80(MxFloat p_param1[2], MxFloat p_param2[2], MxFloat // FUNCTION: LEGO1 0x100236d0 // FUNCTION: BETA10 0x1006c076 -void LegoCarBuild::FUN_100236d0() +void LegoCarBuild::AddSelectedPartToBuild() { MxS32 pLVar2; FUN_10024f70(FALSE); FUN_100250e0(FALSE); - m_animPresenter->FUN_10079790(m_unk0x110->GetName()); + m_animPresenter->AddPartToBuildByName(m_selectedPart->GetName()); m_animPresenter->SetShelfState(LegoCarBuildAnimPresenter::e_stopped); - m_unk0x110 = NULL; + m_selectedPart = NULL; m_unk0x100 = 0; if (m_animPresenter->AllPartsPlaced()) { @@ -541,15 +541,15 @@ MxResult LegoCarBuild::Tickle() FUN_10024f50(); } - if (m_unk0x110) { - if (m_animPresenter->PartIsPlaced(m_unk0x110->GetName())) { + if (m_selectedPart) { + if (m_animPresenter->PartIsPlaced(m_selectedPart->GetName())) { FUN_10022f30(); } } } - if (m_unk0x100 == 5 && m_unk0x110) { - RotateY(m_unk0x110, g_unk0x100d65a4); + if (m_unk0x100 == 5 && m_selectedPart) { + RotateY(m_selectedPart, g_unk0x100d65a4); } if (m_unk0x10a) { @@ -687,7 +687,7 @@ MxLong LegoCarBuild::Notify(MxParam& p_param) if (((m_buildState->m_animationState != 4) && (m_buildState->m_animationState != 6)) && (m_buildState->m_animationState != 2)) { m_buildState->m_animationState = LegoVehicleBuildState::e_unknown0; - result = FUN_100244e0( + result = SelectPartFromMousePosition( ((LegoEventNotificationParam&) p_param).GetX(), ((LegoEventNotificationParam&) p_param).GetY() ); @@ -817,7 +817,7 @@ undefined4 LegoCarBuild::FUN_10024480(MxActionNotificationParam* p_param) // FUNCTION: LEGO1 0x100244e0 // FUNCTION: BETA10 0x1006cfb6 -undefined4 LegoCarBuild::FUN_100244e0(MxLong p_x, MxLong p_y) +undefined4 LegoCarBuild::SelectPartFromMousePosition(MxLong p_x, MxLong p_y) { m_unk0x250[0] = p_x; m_unk0x250[1] = p_y; @@ -828,34 +828,34 @@ undefined4 LegoCarBuild::FUN_100244e0(MxLong p_x, MxLong p_y) return 0; } - if (m_unk0x110 != roi) { + if (m_selectedPart != roi) { FUN_10022f30(); - m_unk0x110 = roi; + m_selectedPart = roi; FUN_10024f70(TRUE); FUN_100250e0(TRUE); } - if (m_unk0x100 == 5 && m_animPresenter->PartIsPlaced(m_unk0x110->GetName())) { - m_unk0x2d4 = TRUE; + if (m_unk0x100 == 5 && m_animPresenter->PartIsPlaced(m_selectedPart->GetName())) { + m_selectedPartIsPlaced = TRUE; } else { - m_unk0x2d4 = FALSE; + m_selectedPartIsPlaced = FALSE; } FUN_10025450(); VTable0x70(); - if (m_animPresenter->PartIsPlaced(m_unk0x110->GetName())) { + if (m_animPresenter->PartIsPlaced(m_selectedPart->GetName())) { if (m_unk0x100 != 5) { m_unk0x250[0] += m_unk0x290[0] - m_unk0x298[0]; m_unk0x250[1] += m_unk0x290[1] - m_unk0x298[1]; } if (m_unk0x100 == 0) { - m_unk0x114 = m_unk0x110->GetWorldBoundingSphere(); + m_unk0x114 = m_selectedPart->GetWorldBoundingSphere(); } } else { - if (m_animPresenter->FUN_10079c30(m_unk0x110->GetName())) { + if (m_animPresenter->FUN_10079c30(m_selectedPart->GetName())) { m_unk0x114 = m_animPresenter->FUN_10079e20(); } } @@ -892,21 +892,21 @@ undefined4 LegoCarBuild::FUN_100246e0(MxLong p_x, MxLong p_y) result = 1; break; case 6: - if (m_animPresenter->PartIsPlaced(m_unk0x110->GetName()) && - SpheresIntersect(m_unk0x114, m_unk0x110->GetWorldBoundingSphere())) { + if (m_animPresenter->PartIsPlaced(m_selectedPart->GetName()) && + SpheresIntersect(m_unk0x114, m_selectedPart->GetWorldBoundingSphere())) { FUN_10024f70(FALSE); FUN_100250e0(FALSE); m_unk0x100 = 0; - m_unk0x110 = NULL; + m_selectedPart = NULL; m_PlaceBrick_Sound->Enable(FALSE); m_PlaceBrick_Sound->Enable(TRUE); m_animPresenter->SetShelfState(LegoCarBuildAnimPresenter::e_stopped); } - else if (m_animPresenter->FUN_10079c30(m_unk0x110->GetName())) { - if (SpheresIntersect(m_unk0x114, m_unk0x110->GetWorldBoundingSphere())) { + else if (m_animPresenter->FUN_10079c30(m_selectedPart->GetName())) { + if (SpheresIntersect(m_unk0x114, m_selectedPart->GetWorldBoundingSphere())) { m_PlaceBrick_Sound->Enable(FALSE); m_PlaceBrick_Sound->Enable(TRUE); - FUN_100236d0(); + AddSelectedPartToBuild(); } else { VTable0x6c(); @@ -1021,7 +1021,7 @@ undefined4 LegoCarBuild::FUN_10024890(MxParam* p_param) (m_Decals_Ctl5 && m_Decals_Ctl5->GetAction()->GetObjectId() == param->m_clickedObjectId) || (m_Decals_Ctl6 && m_Decals_Ctl6->GetAction()->GetObjectId() == param->m_clickedObjectId) || (m_Decals_Ctl7 && m_Decals_Ctl7->GetAction()->GetObjectId() == param->m_clickedObjectId)) { - m_animPresenter->SetPartObjectIdByName(m_unk0x110->GetName(), param->m_clickedObjectId); + m_animPresenter->SetPartObjectIdByName(m_selectedPart->GetName(), param->m_clickedObjectId); m_Decal_Sound->Enable(FALSE); m_Decal_Sound->Enable(TRUE); } @@ -1125,7 +1125,7 @@ undefined4 LegoCarBuild::FUN_10024890(MxParam* p_param) (m_Decals_Ctl5 && m_Decals_Ctl5->GetAction()->GetObjectId() == param->m_clickedObjectId) || (m_Decals_Ctl6 && m_Decals_Ctl6->GetAction()->GetObjectId() == param->m_clickedObjectId) || (m_Decals_Ctl7 && m_Decals_Ctl7->GetAction()->GetObjectId() == param->m_clickedObjectId)) { - m_animPresenter->SetPartObjectIdByName(m_unk0x110->GetName(), param->m_clickedObjectId); + m_animPresenter->SetPartObjectIdByName(m_selectedPart->GetName(), param->m_clickedObjectId); m_Decal_Sound->Enable(FALSE); m_Decal_Sound->Enable(TRUE); } @@ -1268,7 +1268,7 @@ void LegoCarBuild::FUN_10024f30() // FUNCTION: BETA10 0x1006dfce void LegoCarBuild::FUN_10024f50() { - m_unk0x2d4 = FALSE; + m_selectedPartIsPlaced = FALSE; m_animPresenter->RotateAroundYAxis(g_rotationAngleStepYAxis); } @@ -1276,7 +1276,7 @@ void LegoCarBuild::FUN_10024f50() // FUNCTION: BETA10 0x1006e002 void LegoCarBuild::FUN_10024f70(MxBool p_enabled) { - if (m_animPresenter->StringEndsOnY(m_unk0x110->GetName())) { + if (m_animPresenter->StringEndsOnY(m_selectedPart->GetName())) { SetPresentersEnabled(p_enabled); } } @@ -1311,31 +1311,31 @@ void LegoCarBuild::TogglePresentersEnabled() // FUNCTION: BETA10 0x1006e124 void LegoCarBuild::FUN_100250e0(MxBool p_enabled) { - if (m_animPresenter->StringDoesNotEndOnZero(m_unk0x110->GetName()) && m_Decals_Ctl) { - if (strnicmp(m_unk0x110->GetName(), "JSFRNT", strlen("JSFRNT")) == 0) { + if (m_animPresenter->StringDoesNotEndOnZero(m_selectedPart->GetName()) && m_Decals_Ctl) { + if (strnicmp(m_selectedPart->GetName(), "JSFRNT", strlen("JSFRNT")) == 0) { m_Decal_Bitmap->Enable(p_enabled); m_Decals_Ctl->Enable(p_enabled); m_Decals_Ctl1->Enable(p_enabled); m_Decals_Ctl2->Enable(p_enabled); m_Decals_Ctl3->Enable(p_enabled); } - else if (strnicmp(m_unk0x110->GetName(), "JSWNSH", strlen("JSWNSH")) == 0) { + else if (strnicmp(m_selectedPart->GetName(), "JSWNSH", strlen("JSWNSH")) == 0) { m_Decal_Bitmap->Enable(p_enabled); m_Decals_Ctl4->Enable(p_enabled); m_Decals_Ctl5->Enable(p_enabled); m_Decals_Ctl6->Enable(p_enabled); m_Decals_Ctl7->Enable(p_enabled); } - else if (strnicmp(m_unk0x110->GetName(), "RCBACK", strlen("RCBACK")) == 0) { + else if (strnicmp(m_selectedPart->GetName(), "RCBACK", strlen("RCBACK")) == 0) { m_Decals_Ctl1->Enable(p_enabled); } - else if (strnicmp(m_unk0x110->GetName(), "RCTAIL", strlen("RCTAIL")) == 0) { + else if (strnicmp(m_selectedPart->GetName(), "RCTAIL", strlen("RCTAIL")) == 0) { m_Decals_Ctl2->Enable(p_enabled); } - else if (m_Decals_Ctl1 && strnicmp(m_unk0x110->GetName(), "chljety", strlen("chljety")) == 0) { + else if (m_Decals_Ctl1 && strnicmp(m_selectedPart->GetName(), "chljety", strlen("chljety")) == 0) { m_Decals_Ctl1->Enable(p_enabled); } - else if (m_Decals_Ctl2 && strnicmp(m_unk0x110->GetName(), "chrjety", strlen("chrjety")) == 0) { + else if (m_Decals_Ctl2 && strnicmp(m_selectedPart->GetName(), "chrjety", strlen("chrjety")) == 0) { m_Decals_Ctl2->Enable(p_enabled); } else if (m_Decals_Ctl) { @@ -1351,7 +1351,7 @@ void LegoCarBuild::FUN_10025350(MxS32 p_objectId) const LegoChar* color; LegoChar buffer[256]; - if (!m_unk0x110) { + if (!m_selectedPart) { return; } @@ -1379,8 +1379,8 @@ void LegoCarBuild::FUN_10025350(MxS32 p_objectId) m_Paint_Sound->Enable(FALSE); m_Paint_Sound->Enable(TRUE); - m_unk0x110->FUN_100a93b0(color); - sprintf(buffer, "c_%s", m_unk0x110->GetName()); + m_selectedPart->FUN_100a93b0(color); + sprintf(buffer, "c_%s", m_selectedPart->GetName()); VariableTable()->SetVariable(buffer, color); } @@ -1388,7 +1388,7 @@ void LegoCarBuild::FUN_10025350(MxS32 p_objectId) // FUNCTION: BETA10 0x1006e599 void LegoCarBuild::FUN_10025450() { - m_unk0x12c = m_unk0x110->GetLocal2World(); + m_unk0x12c = m_selectedPart->GetLocal2World(); m_unk0x1c0 = m_unk0x12c; Vector3 lastColumnOfUnk0x1c0(m_unk0x1c0[3]); @@ -1401,10 +1401,10 @@ void LegoCarBuild::FUN_10025450() MxMatrix* unk0x178 = &m_unk0x178; *unk0x178 = m_unk0x12c; - if (m_animPresenter->PartIsPlaced(m_unk0x110->GetName())) { - m_unk0x2a4 = Vector4(m_unk0x110->GetWorldPosition()); + if (m_animPresenter->PartIsPlaced(m_selectedPart->GetName())) { + m_unk0x2a4 = Vector4(m_selectedPart->GetWorldPosition()); - if (!m_unk0x2d4) { + if (!m_selectedPartIsPlaced) { m_unk0x2bc = m_unk0x2a4; m_unk0x208 = m_unk0x12c; @@ -1418,17 +1418,17 @@ void LegoCarBuild::FUN_10025450() else { const LegoChar* wiredName; - if (!m_animPresenter->FUN_10079c30(m_unk0x110->GetName())) { - wiredName = m_animPresenter->GetWiredNameByPartName(m_unk0x110->GetName()); + if (!m_animPresenter->FUN_10079c30(m_selectedPart->GetName())) { + wiredName = m_animPresenter->GetWiredNameByPartName(m_selectedPart->GetName()); } else { wiredName = m_animPresenter->GetWiredNameOfLastPlacedPart(); } - LegoROI* parentROI = (LegoROI*) m_unk0x110->GetParentROI(); + LegoROI* parentROI = (LegoROI*) m_selectedPart->GetParentROI(); m_unk0x208 = parentROI->FindChildROI(wiredName, parentROI)->GetLocal2World(); m_unk0x2bc = Vector4(parentROI->FindChildROI(wiredName, parentROI)->GetWorldPosition()); - m_unk0x2a4 = Vector4(m_unk0x110->GetWorldPosition()); + m_unk0x2a4 = Vector4(m_selectedPart->GetWorldPosition()); m_unk0x2a4[2] += (m_unk0x1c0[3][2] - m_unk0x12c[3][2]); m_unk0x178[3][2] = m_unk0x1c0[3][2]; diff --git a/LEGO1/lego/legoomni/src/build/legocarbuildpresenter.cpp b/LEGO1/lego/legoomni/src/build/legocarbuildpresenter.cpp index d6512c8b..c2dbbd8a 100644 --- a/LEGO1/lego/legoomni/src/build/legocarbuildpresenter.cpp +++ b/LEGO1/lego/legoomni/src/build/legocarbuildpresenter.cpp @@ -202,15 +202,15 @@ void LegoCarBuildAnimPresenter::StreamingTickle() for (i = 0; i < m_numberOfParts; i++) { if (m_placedPartCount == i) { - FUN_10079680(m_parts[i].m_wiredName); + ShowBuildPartByName(m_parts[i].m_wiredName); } else { - FUN_100795d0(m_parts[i].m_wiredName); + HideBuildPartByName(m_parts[i].m_wiredName); } if (i < m_placedPartCount) { FUN_10079050(i); - FUN_10079680(m_parts[i].m_name); + ShowBuildPartByName(m_parts[i].m_name); } LegoChar* name = m_parts[i].m_wiredName; @@ -319,7 +319,7 @@ MxResult LegoCarBuildAnimPresenter::Serialize(LegoStorage* p_storage) void LegoCarBuildAnimPresenter::FUN_10079050(MxS16 p_index) { SwapNodesByName(m_parts[p_index].m_wiredName, m_parts[p_index].m_name); - FUN_100795d0(m_parts[p_index].m_wiredName); + HideBuildPartByName(m_parts[p_index].m_wiredName); } // FUNCTION: LEGO1 0x10079090 @@ -445,7 +445,7 @@ void LegoCarBuildAnimPresenter::InitBuildPlatform() // FUNCTION: LEGO1 0x100795d0 // FUNCTION: BETA10 0x10071d96 -void LegoCarBuildAnimPresenter::FUN_100795d0(LegoChar* p_param) +void LegoCarBuildAnimPresenter::HideBuildPartByName(LegoChar* p_param) { LegoAnimNodeData* data = FindNodeDataByName(m_anim->GetRoot(), p_param); @@ -467,7 +467,7 @@ void LegoCarBuildAnimPresenter::FUN_100795d0(LegoChar* p_param) // FUNCTION: LEGO1 0x10079680 // FUNCTION: BETA10 0x10071ec5 -void LegoCarBuildAnimPresenter::FUN_10079680(LegoChar* p_param) +void LegoCarBuildAnimPresenter::ShowBuildPartByName(LegoChar* p_param) { LegoAnimNodeData* data = FindNodeDataByName(m_anim->GetRoot(), p_param); @@ -534,7 +534,7 @@ LegoTreeNode* LegoCarBuildAnimPresenter::FindNodeByName(LegoTreeNode* p_treeNode // FUNCTION: LEGO1 0x10079790 // FUNCTION: BETA10 0x100720a3 -void LegoCarBuildAnimPresenter::FUN_10079790(const LegoChar* p_name) +void LegoCarBuildAnimPresenter::AddPartToBuildByName(const LegoChar* p_name) { MxS16 i; LegoChar buffer[40]; @@ -557,7 +557,7 @@ void LegoCarBuildAnimPresenter::FUN_10079790(const LegoChar* p_name) ((LegoCarBuild*) m_currentWorld)->SetPlacedPartCount(m_placedPartCount); if (m_placedPartCount < m_numberOfParts) { - FUN_10079680(m_parts[m_placedPartCount].m_wiredName); + ShowBuildPartByName(m_parts[m_placedPartCount].m_wiredName); } } From 4167c55c051e4aea4d4b055b5f56aca9e72a212c Mon Sep 17 00:00:00 2001 From: Steven <139715581+StevenSYS@users.noreply.github.com> Date: Fri, 4 Jul 2025 02:41:22 +0000 Subject: [PATCH 064/188] Added changing the transition type to the config tool (#511) --- CONFIG/MainDlg.cpp | 9 +++++ CONFIG/MainDlg.h | 1 + CONFIG/config.cpp | 4 ++ CONFIG/config.h | 1 + CONFIG/res/maindialog.ui | 85 +++++++++++++++++++++++++++++++++------- 5 files changed, 85 insertions(+), 15 deletions(-) diff --git a/CONFIG/MainDlg.cpp b/CONFIG/MainDlg.cpp index e2c526f6..828395bd 100644 --- a/CONFIG/MainDlg.cpp +++ b/CONFIG/MainDlg.cpp @@ -59,6 +59,7 @@ CMainDialog::CMainDialog(QWidget* pParent) : QDialog(pParent) connect(m_ui->sound3DCheckBox, &QCheckBox::toggled, this, &CMainDialog::OnCheckbox3DSound); connect(m_ui->joystickCheckBox, &QCheckBox::toggled, this, &CMainDialog::OnCheckboxJoystick); connect(m_ui->fullscreenCheckBox, &QCheckBox::toggled, this, &CMainDialog::OnCheckboxFullscreen); + connect(m_ui->transitionTypeComboBox, &QComboBox::currentIndexChanged, this, &CMainDialog::TransitionTypeChanged); connect(m_ui->okButton, &QPushButton::clicked, this, &CMainDialog::accept); connect(m_ui->cancelButton, &QPushButton::clicked, this, &CMainDialog::reject); connect(m_ui->launchButton, &QPushButton::clicked, this, &CMainDialog::launch); @@ -212,6 +213,7 @@ void CMainDialog::UpdateInterface() m_ui->joystickCheckBox->setChecked(currentConfigApp->m_use_joystick); m_ui->musicCheckBox->setChecked(currentConfigApp->m_music); m_ui->fullscreenCheckBox->setChecked(currentConfigApp->m_full_screen); + m_ui->transitionTypeComboBox->setCurrentIndex(currentConfigApp->m_transition_type); m_ui->dataPath->setText(QString::fromStdString(currentConfigApp->m_cd_path)); m_ui->savePath->setText(QString::fromStdString(currentConfigApp->m_save_path)); } @@ -296,6 +298,13 @@ void CMainDialog::OnCheckboxFullscreen(bool checked) UpdateInterface(); } +void CMainDialog::TransitionTypeChanged(int index) +{ + currentConfigApp->m_transition_type = index; + m_modified = true; + UpdateInterface(); +} + void CMainDialog::SelectDataPathDialog() { QString data_path = QString::fromStdString(currentConfigApp->m_cd_path); diff --git a/CONFIG/MainDlg.h b/CONFIG/MainDlg.h index 372a1156..72062486 100644 --- a/CONFIG/MainDlg.h +++ b/CONFIG/MainDlg.h @@ -43,6 +43,7 @@ private slots: void OnCheckboxJoystick(bool checked); void OnCheckboxMusic(bool checked); void OnCheckboxFullscreen(bool checked); + void TransitionTypeChanged(int index); void accept() override; void reject() override; void launch(); diff --git a/CONFIG/config.cpp b/CONFIG/config.cpp index 9030ae67..b4435298 100644 --- a/CONFIG/config.cpp +++ b/CONFIG/config.cpp @@ -70,6 +70,7 @@ bool CConfigApp::InitInstance() m_driver = NULL; m_device = NULL; m_full_screen = TRUE; + m_transition_type = 3; // 3: Mosaic m_wide_view_angle = TRUE; m_use_joystick = TRUE; m_music = TRUE; @@ -153,6 +154,7 @@ bool CConfigApp::ReadRegisterSettings() m_display_bit_depth = iniparser_getint(dict, "isle:Display Bit Depth", -1); m_flip_surfaces = iniparser_getboolean(dict, "isle:Flip Surfaces", m_flip_surfaces); m_full_screen = iniparser_getboolean(dict, "isle:Full Screen", m_full_screen); + m_transition_type = iniparser_getint(dict, "isle:Transition Type", m_transition_type); m_3d_video_ram = iniparser_getboolean(dict, "isle:Back Buffers in Video RAM", m_3d_video_ram); m_wide_view_angle = iniparser_getboolean(dict, "isle:Wide View Angle", m_wide_view_angle); m_3d_sound = iniparser_getboolean(dict, "isle:3DSound", m_3d_sound); @@ -308,6 +310,8 @@ void CConfigApp::WriteRegisterSettings() const SetIniBool(dict, "isle:Full Screen", m_full_screen); SetIniBool(dict, "isle:Wide View Angle", m_wide_view_angle); + SetIniInt(dict, "isle:Transition Type", m_transition_type); + SetIniBool(dict, "isle:3DSound", m_3d_sound); SetIniBool(dict, "isle:Music", m_music); diff --git a/CONFIG/config.h b/CONFIG/config.h index a621655a..dffc2b6c 100644 --- a/CONFIG/config.h +++ b/CONFIG/config.h @@ -65,6 +65,7 @@ class CConfigApp { int m_display_bit_depth; bool m_flip_surfaces; bool m_full_screen; + int m_transition_type; bool m_3d_video_ram; bool m_wide_view_angle; bool m_3d_sound; diff --git a/CONFIG/res/maindialog.ui b/CONFIG/res/maindialog.ui index c2d429ae..66e2b52e 100644 --- a/CONFIG/res/maindialog.ui +++ b/CONFIG/res/maindialog.ui @@ -57,7 +57,7 @@ false - Qt::AlignCenter + Qt::AlignmentFlag::AlignCenter @@ -129,10 +129,10 @@ Save Path: - Qt::PlainText + Qt::TextFormat::PlainText - Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + Qt::AlignmentFlag::AlignRight|Qt::AlignmentFlag::AlignTrailing|Qt::AlignmentFlag::AlignVCenter @@ -162,10 +162,10 @@ Data Path: - Qt::PlainText + Qt::TextFormat::PlainText - Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + Qt::AlignmentFlag::AlignRight|Qt::AlignmentFlag::AlignTrailing|Qt::AlignmentFlag::AlignVCenter @@ -241,7 +241,7 @@ Maximum LOD - Qt::AlignCenter + Qt::AlignmentFlag::AlignCenter @@ -262,10 +262,10 @@ false - Qt::Horizontal + Qt::Orientation::Horizontal - QSlider::TicksBothSides + QSlider::TickPosition::TicksBothSides 10 @@ -316,7 +316,7 @@ Maximum Actors (5..40) - Qt::AlignCenter + Qt::AlignmentFlag::AlignCenter @@ -340,10 +340,10 @@ false - Qt::Horizontal + Qt::Orientation::Horizontal - QSlider::TicksBothSides + QSlider::TickPosition::TicksBothSides 5 @@ -402,6 +402,61 @@ + + + + Transition Type + + + + + + + Sets the transition effect to be used in game. + + + Idle - Broken + + + + + + + Idle - Broken + + + + + No Animation + + + + + Dissolve + + + + + Mosaic + + + + + Wipe Down + + + + + Windows + + + + + Unknown - Broken + + + + @@ -417,7 +472,7 @@ - Qt::AlignCenter + Qt::AlignmentFlag::AlignCenter false @@ -453,7 +508,7 @@ Direct 3D Devices - Qt::AlignCenter + Qt::AlignmentFlag::AlignCenter @@ -475,13 +530,13 @@ - QAbstractItemView::NoEditTriggers + QAbstractItemView::EditTrigger::NoEditTriggers true - QAbstractItemView::SelectRows + QAbstractItemView::SelectionBehavior::SelectRows From 92a96282b3960b30677ebd06433596b724800d60 Mon Sep 17 00:00:00 2001 From: Anders Jenbo Date: Fri, 4 Jul 2025 04:48:09 +0200 Subject: [PATCH 065/188] Fix tabbign in and out of fullscreen (#514) --- ISLE/isleapp.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ISLE/isleapp.cpp b/ISLE/isleapp.cpp index 2e2066d8..6cb0375c 100644 --- a/ISLE/isleapp.cpp +++ b/ISLE/isleapp.cpp @@ -447,7 +447,7 @@ SDL_AppResult SDL_AppEvent(void* appstate, SDL_Event* event) SDL_Keycode keyCode = event->key.key; - if (event->key.mod == SDL_KMOD_LALT && keyCode == SDLK_RETURN) { + if ((event->key.mod & SDL_KMOD_LALT) && keyCode == SDLK_RETURN) { SDL_SetWindowFullscreen(window, !(SDL_GetWindowFlags(window) & SDL_WINDOW_FULLSCREEN)); } else { From 5e62e7e39fc0e618cc3eda2e18df673dba0526a9 Mon Sep 17 00:00:00 2001 From: Anders Jenbo Date: Fri, 4 Jul 2025 05:32:35 +0200 Subject: [PATCH 066/188] Remove unused rendering function (#517) --- LEGO1/omni/include/mxdisplaysurface.h | 8 ---- LEGO1/omni/src/video/mxdisplaysurface.cpp | 55 ----------------------- 2 files changed, 63 deletions(-) diff --git a/LEGO1/omni/include/mxdisplaysurface.h b/LEGO1/omni/include/mxdisplaysurface.h index df57c5c4..3a3dcd08 100644 --- a/LEGO1/omni/include/mxdisplaysurface.h +++ b/LEGO1/omni/include/mxdisplaysurface.h @@ -71,14 +71,6 @@ class MxDisplaySurface : public MxCore { MxS32 p_height, MxBool p_RLE ); // vtable+0x30 - virtual void VTable0x34( - MxU8* p_pixels, - MxS32 p_bpp, - MxS32 p_width, - MxS32 p_height, - MxS32 p_x, - MxS32 p_y - ); // vtable+0x34 virtual void Display( MxS32 p_left, MxS32 p_top, diff --git a/LEGO1/omni/src/video/mxdisplaysurface.cpp b/LEGO1/omni/src/video/mxdisplaysurface.cpp index d38cb093..b10330f2 100644 --- a/LEGO1/omni/src/video/mxdisplaysurface.cpp +++ b/LEGO1/omni/src/video/mxdisplaysurface.cpp @@ -716,61 +716,6 @@ void MxDisplaySurface::DrawTransparentRLE( } } -// FUNCTION: LEGO1 0x100bb850 -// FUNCTION: BETA10 0x10141191 -void MxDisplaySurface::VTable0x34(MxU8* p_pixels, MxS32 p_bpp, MxS32 p_width, MxS32 p_height, MxS32 p_x, MxS32 p_y) -{ - DDSURFACEDESC surfaceDesc; - memset(&surfaceDesc, 0, sizeof(surfaceDesc)); - surfaceDesc.dwSize = sizeof(surfaceDesc); - - HRESULT result = m_ddSurface2->Lock(NULL, &surfaceDesc, DDLOCK_WAIT | DDLOCK_WRITEONLY, NULL); - - if (result == DDERR_SURFACELOST) { - m_ddSurface2->Restore(); - result = m_ddSurface2->Lock(NULL, &surfaceDesc, DDLOCK_WAIT | DDLOCK_WRITEONLY, NULL); - } - - if (result == DD_OK) { - MxU8* pixels = p_pixels; - MxS32 bytesPerPixel = m_surfaceDesc.ddpfPixelFormat.dwRGBBitCount / 8; - if (p_bpp != 8 && bytesPerPixel != p_bpp) { - MxTrace("Source format to display format NOT_IMPLEMENTED"); - assert(0); - return; - } - - MxU8* dst = (MxU8*) surfaceDesc.lpSurface + p_y * surfaceDesc.lPitch + bytesPerPixel * p_x; - MxLong stride = p_width * bytesPerPixel; - MxLong length = -bytesPerPixel * p_width + surfaceDesc.lPitch; - - if (bytesPerPixel == p_bpp) { - while (p_height--) { - memcpy(dst, pixels, p_width * bytesPerPixel); - pixels += stride; - dst += length; - } - } - else { - for (MxS32 i = 0; i < p_height; i++) { - for (MxS32 j = 0; j < p_width; j++) { - if (bytesPerPixel == 2) { - *(MxU16*) dst = m_16bitPal[*pixels++]; - } - else { - *(MxU32*) dst = m_32bitPal[*pixels++]; - } - dst += bytesPerPixel; - } - pixels += stride; - dst += length; - } - } - - m_ddSurface2->Unlock(surfaceDesc.lpSurface); - } -} - // FUNCTION: LEGO1 0x100bba50 void MxDisplaySurface::Display(MxS32 p_left, MxS32 p_top, MxS32 p_left2, MxS32 p_top2, MxS32 p_width, MxS32 p_height) { From 4983da422c56e71f193658273ee337d7286928fa Mon Sep 17 00:00:00 2001 From: Anders Jenbo Date: Fri, 4 Jul 2025 14:47:24 +0200 Subject: [PATCH 067/188] OpenGL: Respect max supported texture size (#518) --- miniwin/src/d3drm/backends/opengl1/actual.cpp | 7 +++++++ miniwin/src/d3drm/backends/opengl1/actual.h | 1 + miniwin/src/d3drm/backends/opengl1/renderer.cpp | 17 ++++++++++++++--- 3 files changed, 22 insertions(+), 3 deletions(-) diff --git a/miniwin/src/d3drm/backends/opengl1/actual.cpp b/miniwin/src/d3drm/backends/opengl1/actual.cpp index b1a464d5..a86ebec1 100644 --- a/miniwin/src/d3drm/backends/opengl1/actual.cpp +++ b/miniwin/src/d3drm/backends/opengl1/actual.cpp @@ -42,6 +42,13 @@ void GL11_DestroyTexture(GLuint texId) glDeleteTextures(1, &texId); } +int GL11_GetMaxTextureSize() +{ + GLint maxTextureSize = 0; + glGetIntegerv(GL_MAX_TEXTURE_SIZE, &maxTextureSize); + return maxTextureSize; +} + GLuint GL11_UploadTextureData(void* pixels, int width, int height, bool isUi) { GLuint texId; diff --git a/miniwin/src/d3drm/backends/opengl1/actual.h b/miniwin/src/d3drm/backends/opengl1/actual.h index 9460119c..87f33687 100644 --- a/miniwin/src/d3drm/backends/opengl1/actual.h +++ b/miniwin/src/d3drm/backends/opengl1/actual.h @@ -64,6 +64,7 @@ struct GLMeshCacheEntry { void GL11_InitState(); void GL11_LoadExtensions(); void GL11_DestroyTexture(GLuint texId); +int GL11_GetMaxTextureSize(); GLuint GL11_UploadTextureData(void* pixels, int width, int height, bool isUI); void GL11_UploadMesh(GLMeshCacheEntry& cache, bool hasTexture); void GL11_DestroyMesh(GLMeshCacheEntry& cache); diff --git a/miniwin/src/d3drm/backends/opengl1/renderer.cpp b/miniwin/src/d3drm/backends/opengl1/renderer.cpp index 8c43d14a..00088da9 100644 --- a/miniwin/src/d3drm/backends/opengl1/renderer.cpp +++ b/miniwin/src/d3drm/backends/opengl1/renderer.cpp @@ -132,10 +132,21 @@ static Uint32 UploadTextureData(SDL_Surface* src, bool useNPOT, bool isUi) SDL_Surface* finalSurface = working; - int newW = NextPowerOfTwo(working->w); - int newH = NextPowerOfTwo(working->h); + int newW = working->w; + int newH = working->h; + if (!useNPOT) { + newW = NextPowerOfTwo(newW); + newH = NextPowerOfTwo(newH); + } + int max = GL11_GetMaxTextureSize(); + if (newW > max) { + newW = max; + } + if (newH > max) { + newH = max; + } - if (!useNPOT && (newW != working->w || newH != working->h)) { + if (newW != working->w || newH != working->h) { SDL_Surface* resized = SDL_CreateSurface(newW, newH, working->format); if (!resized) { SDL_Log("SDL_CreateSurface (resize) failed: %s", SDL_GetError()); From 0d2cbd50d58eb217311c8ceb2160bd1ce76acb8e Mon Sep 17 00:00:00 2001 From: Anders Jenbo Date: Fri, 4 Jul 2025 14:48:01 +0200 Subject: [PATCH 068/188] Fake mosaic transition (#516) --- .../legoomni/include/mxtransitionmanager.h | 4 +- .../src/common/mxtransitionmanager.cpp | 128 ++++++++++++++++++ LEGO1/omni/include/mxdisplaysurface.h | 3 + LEGO1/omni/src/video/mxdisplaysurface.cpp | 3 - 4 files changed, 134 insertions(+), 4 deletions(-) diff --git a/LEGO1/lego/legoomni/include/mxtransitionmanager.h b/LEGO1/lego/legoomni/include/mxtransitionmanager.h index dd9dc9ed..7c31daf7 100644 --- a/LEGO1/lego/legoomni/include/mxtransitionmanager.h +++ b/LEGO1/lego/legoomni/include/mxtransitionmanager.h @@ -48,7 +48,8 @@ class MxTransitionManager : public MxCore { e_mosaic, e_wipeDown, e_windows, - e_broken // Unknown what this is supposed to be, it locks the game up + e_broken, // Unknown what this is supposed to be, it locks the game up + e_fakeMosaic }; MxResult StartTransition(TransitionType p_animationType, MxS32 p_speed, MxBool p_doCopy, MxBool p_playMusicInAnim); @@ -68,6 +69,7 @@ class MxTransitionManager : public MxCore { void WipeDownTransition(); void WindowsTransition(); void BrokenTransition(); + void FakeMosaicTransition(); void SubmitCopyRect(LPDDSURFACEDESC p_ddsc); void SetupCopyRect(LPDDSURFACEDESC p_ddsc); diff --git a/LEGO1/lego/legoomni/src/common/mxtransitionmanager.cpp b/LEGO1/lego/legoomni/src/common/mxtransitionmanager.cpp index 6fd211c6..1987b6cd 100644 --- a/LEGO1/lego/legoomni/src/common/mxtransitionmanager.cpp +++ b/LEGO1/lego/legoomni/src/common/mxtransitionmanager.cpp @@ -84,6 +84,9 @@ MxResult MxTransitionManager::Tickle() case e_broken: BrokenTransition(); break; + case e_fakeMosaic: + FakeMosaicTransition(); + break; } return SUCCESS; } @@ -613,3 +616,128 @@ void MxTransitionManager::configureMxTransitionManager(TransitionType p_transiti { g_transitionManagerConfig = p_transitionManagerConfig; } + +int g_colorOffset; +int GetColorIndexWithLocality(int p_col, int p_row) +{ + int islandX = p_col / 8; + int islandY = p_row / 8; // Dvide screen in 8x6 tiles + + int island = islandY * 8 + islandX; // tile id + + if (SDL_rand(3) > island / 8) { + return 6 + SDL_rand(2); // emulate sky + } + + if (SDL_rand(16) > 2) { + island += SDL_rand(3) - 1 + (SDL_rand(3) - 1) * 8; // blure tiles + } + + int hash = (island + g_colorOffset) * 2654435761u; + int scrambled = (hash >> 16) % 32; + + int finalIndex = scrambled + SDL_rand(3) - 1; + return finalIndex % 32; +} + +void MxTransitionManager::FakeMosaicTransition() +{ + if (m_animationTimer == 16) { + m_animationTimer = 0; + EndTransition(TRUE); + return; + } + + if (m_animationTimer == 0) { + g_colorOffset = SDL_rand(32); + for (MxS32 i = 0; i < 64; i++) { + m_columnOrder[i] = i; + } + for (MxS32 i = 0; i < 64; i++) { + MxS32 swap = SDL_rand(64); + std::swap(m_columnOrder[i], m_columnOrder[swap]); + } + for (MxS32 i = 0; i < 48; i++) { + m_randomShift[i] = SDL_rand(64); + } + } + + DDSURFACEDESC ddsd = {}; + ddsd.dwSize = sizeof(ddsd); + HRESULT res = m_ddSurface->Lock(NULL, &ddsd, DDLOCK_WAIT | DDLOCK_WRITEONLY, NULL); + if (res == DDERR_SURFACELOST) { + m_ddSurface->Restore(); + res = m_ddSurface->Lock(NULL, &ddsd, DDLOCK_WAIT | DDLOCK_WRITEONLY, NULL); + } + + if (res == DD_OK) { + SubmitCopyRect(&ddsd); + + static const MxU8 g_palette[32][3] = { + {0x00, 0x00, 0x00}, {0x12, 0x1e, 0x50}, {0x00, 0x22, 0x6c}, {0x14, 0x2d, 0x9f}, {0x0e, 0x36, 0xb0}, + {0x0e, 0x39, 0xd0}, {0x47, 0x96, 0xe2}, {0x79, 0xaa, 0xca}, {0xff, 0xff, 0xff}, {0xc9, 0xcd, 0xcb}, + {0xad, 0xad, 0xab}, {0xa6, 0x91, 0x8e}, {0xaf, 0x59, 0x49}, {0xc0, 0x00, 0x00}, {0xab, 0x18, 0x18}, + {0x61, 0x0c, 0x0c}, {0x04, 0x38, 0x12}, {0x2c, 0x67, 0x28}, {0x4a, 0xb4, 0x6b}, {0x94, 0xb7, 0x7c}, + {0xb6, 0xb9, 0x87}, {0x52, 0x4a, 0x67}, {0x87, 0x8d, 0x8a}, {0xa6, 0x91, 0x8e}, {0xf8, 0xee, 0xdc}, + {0xf4, 0xe2, 0xc3}, {0x87, 0x8d, 0x8a}, {0xba, 0x9f, 0x12}, {0xb5, 0x83, 0x00}, {0x6a, 0x44, 0x27}, + {0x36, 0x37, 0x34}, {0x2b, 0x23, 0x0f} + }; + + MxS32 bytesPerPixel = ddsd.ddpfPixelFormat.dwRGBBitCount / 8; + + for (MxS32 col = 0; col < 64; col++) { + if (m_animationTimer * 4 > m_columnOrder[col]) { + continue; + } + if (m_animationTimer * 4 + 3 < m_columnOrder[col]) { + continue; + } + + for (MxS32 row = 0; row < 48; row++) { + MxS32 xShift = 10 * ((m_randomShift[row] + col) % 64); + MxS32 yStart = 10 * row; + + int paletteIndex = GetColorIndexWithLocality(xShift / 10, row); + + const MxU8* color = g_palette[paletteIndex]; + + for (MxS32 y = 0; y < 10; y++) { + MxU8* dest = (MxU8*) ddsd.lpSurface + (yStart + y) * ddsd.lPitch + xShift * bytesPerPixel; + switch (bytesPerPixel) { + case 1: + memset(dest, paletteIndex, 10); + break; + case 2: { + MxU32 pixel = RGB555_CREATE(color[2], color[1], color[0]); + MxU16* p = (MxU16*) dest; + for (MxS32 i = 0; i < 10; i++) { + p[i] = pixel; + } + break; + } + default: { + MxU32 pixel = RGB8888_CREATE(color[2], color[1], color[0], 255); + MxU32* p = (MxU32*) dest; + for (MxS32 i = 0; i < 10; i++) { + p[i] = pixel; + } + break; + } + } + } + } + } + + SetupCopyRect(&ddsd); + m_ddSurface->Unlock(ddsd.lpSurface); + + if (VideoManager()->GetVideoParam().Flags().GetFlipSurfaces()) { + VideoManager() + ->GetDisplaySurface() + ->GetDirectDrawSurface1() + ->BltFast(0, 0, m_ddSurface, &g_fullScreenRect, DDBLTFAST_WAIT); + } + + m_animationTimer++; + } +} diff --git a/LEGO1/omni/include/mxdisplaysurface.h b/LEGO1/omni/include/mxdisplaysurface.h index 3a3dcd08..aa30b543 100644 --- a/LEGO1/omni/include/mxdisplaysurface.h +++ b/LEGO1/omni/include/mxdisplaysurface.h @@ -12,6 +12,9 @@ #include #endif +#define RGB555_CREATE(R, G, B) (((R) << 10) | (G) << 5 | (B) << 0) +#define RGB8888_CREATE(R, G, B, A) (((A) << 24) | ((R) << 16) | ((G) << 8) | (B)) + class MxBitmap; class MxPalette; diff --git a/LEGO1/omni/src/video/mxdisplaysurface.cpp b/LEGO1/omni/src/video/mxdisplaysurface.cpp index b10330f2..c07f475e 100644 --- a/LEGO1/omni/src/video/mxdisplaysurface.cpp +++ b/LEGO1/omni/src/video/mxdisplaysurface.cpp @@ -18,9 +18,6 @@ DECOMP_SIZE_ASSERT(MxDisplaySurface, 0xac); -#define RGB555_CREATE(R, G, B) (((R) << 10) | (G) << 5 | (B) << 0) -#define RGB8888_CREATE(R, G, B, A) (((A) << 24) | ((R) << 16) | ((G) << 8) | (B)) - // GLOBAL: LEGO1 0x1010215c MxU32 g_unk0x1010215c = 0; From af045eefed747ca26e4f90f50131cff9141aaa3c Mon Sep 17 00:00:00 2001 From: Anders Jenbo Date: Fri, 4 Jul 2025 17:41:18 +0200 Subject: [PATCH 069/188] Add fake mosaic to config tool (#522) --- CONFIG/res/maindialog.ui | 5 +++++ ISLE/3ds/config.cpp | 2 +- LEGO1/lego/legoomni/src/common/mxtransitionmanager.cpp | 2 +- 3 files changed, 7 insertions(+), 2 deletions(-) diff --git a/CONFIG/res/maindialog.ui b/CONFIG/res/maindialog.ui index 66e2b52e..12739f27 100644 --- a/CONFIG/res/maindialog.ui +++ b/CONFIG/res/maindialog.ui @@ -455,6 +455,11 @@ Unknown - Broken + + + Fake Mosaic + + diff --git a/ISLE/3ds/config.cpp b/ISLE/3ds/config.cpp index dc5dd8dc..e70ec4dd 100644 --- a/ISLE/3ds/config.cpp +++ b/ISLE/3ds/config.cpp @@ -18,5 +18,5 @@ void N3DS_SetupDefaultConfigOverrides(dictionary* p_dictionary) iniparser_set(p_dictionary, "isle:savepath", "sdmc:/3ds/isle"); // Use e_noAnimation/cut transition - iniparser_set(p_dictionary, "isle:Transition Type", "1"); + iniparser_set(p_dictionary, "isle:Transition Type", "7"); } diff --git a/LEGO1/lego/legoomni/src/common/mxtransitionmanager.cpp b/LEGO1/lego/legoomni/src/common/mxtransitionmanager.cpp index 1987b6cd..6c4f94ea 100644 --- a/LEGO1/lego/legoomni/src/common/mxtransitionmanager.cpp +++ b/LEGO1/lego/legoomni/src/common/mxtransitionmanager.cpp @@ -637,7 +637,7 @@ int GetColorIndexWithLocality(int p_col, int p_row) int scrambled = (hash >> 16) % 32; int finalIndex = scrambled + SDL_rand(3) - 1; - return finalIndex % 32; + return abs(finalIndex) % 32; } void MxTransitionManager::FakeMosaicTransition() From 77c832c237fe90c5bc2793c6349d2f52c7502bc6 Mon Sep 17 00:00:00 2001 From: Anders Jenbo Date: Fri, 4 Jul 2025 21:27:01 +0200 Subject: [PATCH 070/188] Use sprites to render things on screen (#478) --- LEGO1/omni/src/video/mxdisplaysurface.cpp | 119 +++++++++++----------- 1 file changed, 57 insertions(+), 62 deletions(-) diff --git a/LEGO1/omni/src/video/mxdisplaysurface.cpp b/LEGO1/omni/src/video/mxdisplaysurface.cpp index c07f475e..5d08a64b 100644 --- a/LEGO1/omni/src/video/mxdisplaysurface.cpp +++ b/LEGO1/omni/src/video/mxdisplaysurface.cpp @@ -396,86 +396,81 @@ void MxDisplaySurface::VTable0x28( DDSURFACEDESC ddsd; memset(&ddsd, 0, sizeof(ddsd)); ddsd.dwSize = sizeof(ddsd); + ddsd.dwFlags = DDSD_WIDTH | DDSD_HEIGHT | DDSD_PIXELFORMAT | DDSD_CAPS; + ddsd.dwWidth = p_width; + ddsd.dwHeight = p_height; + ddsd.ddpfPixelFormat = m_surfaceDesc.ddpfPixelFormat; + ddsd.ddsCaps.dwCaps = DDSCAPS_OFFSCREENPLAIN; - HRESULT hr = m_ddSurface2->Lock(NULL, &ddsd, DDLOCK_WAIT | DDLOCK_WRITEONLY, NULL); + LPDIRECTDRAWSURFACE tempSurface = nullptr; + LPDIRECTDRAW draw = MVideoManager()->GetDirectDraw(); + HRESULT hr = draw->CreateSurface(&ddsd, &tempSurface, nullptr); + if (hr != DD_OK || !tempSurface) { + return; + } + + DDCOLORKEY colorKey; + colorKey.dwColorSpaceLowValue = colorKey.dwColorSpaceHighValue = RGB555_CREATE(0x1f, 0, 0x1f); + tempSurface->SetColorKey(DDCKEY_SRCBLT, &colorKey); + + DDSURFACEDESC tempDesc; + memset(&tempDesc, 0, sizeof(tempDesc)); + tempDesc.dwSize = sizeof(tempDesc); + + hr = tempSurface->Lock(NULL, &tempDesc, DDLOCK_WAIT | DDLOCK_WRITEONLY, NULL); if (hr == DDERR_SURFACELOST) { - m_ddSurface2->Restore(); - hr = m_ddSurface2->Lock(NULL, &ddsd, DDLOCK_WAIT | DDLOCK_WRITEONLY, NULL); + tempSurface->Restore(); + hr = tempSurface->Lock(NULL, &tempDesc, DDLOCK_WAIT | DDLOCK_WRITEONLY, NULL); } if (hr != DD_OK) { + tempSurface->Release(); return; } MxU8* data = p_bitmap->GetStart(p_left, p_top); MxS32 bytesPerPixel = m_surfaceDesc.ddpfPixelFormat.dwRGBBitCount / 8; - if (m_videoParam.Flags().GetF1bit3()) { - p_bottom *= 2; - p_right *= 2; + MxU8* surface = (MxU8*) tempDesc.lpSurface; - MxU8* surface = (MxU8*) ddsd.lpSurface + (bytesPerPixel * p_right) + (p_bottom * ddsd.lPitch); - MxLong stride = -p_width + GetAdjustedStride(p_bitmap); - MxS32 copyWidth = p_width * bytesPerPixel * 2; - MxLong length = -(copyWidth) + ddsd.lPitch; + MxLong stride = (bytesPerPixel == 1) ? GetAdjustedStride(p_bitmap) : -p_width + GetAdjustedStride(p_bitmap); + MxLong length = tempDesc.lPitch - (p_width * bytesPerPixel); - while (p_height--) { - MxU8* surfaceBefore = surface; - - for (MxS32 i = 0; i < p_width; i++) { - if (bytesPerPixel == 1) { - surface[0] = surface[1] = *data; - } - else if (bytesPerPixel == 2) { - ((MxU16*) surface)[0] = ((MxU16*) surface)[1] = m_16bitPal[*data]; - } - else { - ((MxU32*) surface)[0] = ((MxU32*) surface)[1] = m_32bitPal[*data]; - } - surface += bytesPerPixel * 2; - data++; - } - - if (stride || length != ddsd.lPitch - copyWidth) { - data += stride; - surface += length; - } - - memcpy(surface, surfaceBefore, copyWidth); - surface += ddsd.lPitch; + for (MxS32 i = 0; i < p_height; i++) { + if (bytesPerPixel == 1) { + memcpy(surface, data, p_width); + surface += length + p_width; } + else if (bytesPerPixel == 2) { + for (MxS32 j = 0; j < p_width; j++) { + *(MxU16*) surface = m_16bitPal[*data++]; + surface += bytesPerPixel; + } + + surface += length; + } + else { + for (MxS32 j = 0; j < p_width; j++) { + *(MxU32*) surface = m_32bitPal[*data++]; + surface += bytesPerPixel; + } + surface += length; + } + + data += stride; + } + + tempSurface->Unlock(NULL); + + if (m_videoParam.Flags().GetF1bit3()) { + RECT destRect = {p_right, p_bottom, p_right + p_width * 2, p_bottom + p_height * 2}; + m_ddSurface2->Blt(&destRect, tempSurface, NULL, DDBLT_WAIT | DDBLT_KEYSRC, NULL); } else { - MxU8* surface = (MxU8*) ddsd.lpSurface + (bytesPerPixel * p_right) + (p_bottom * ddsd.lPitch); - MxLong stride = (bytesPerPixel == 1) ? GetAdjustedStride(p_bitmap) : -p_width + GetAdjustedStride(p_bitmap); - MxLong length = ddsd.lPitch - (p_width * bytesPerPixel); - - for (MxS32 i = 0; i < p_height; i++) { - if (bytesPerPixel == 1) { - memcpy(surface, data, p_width); - surface += length + p_width; - } - else if (bytesPerPixel == 2) { - for (MxS32 j = 0; j < p_width; j++) { - *(MxU16*) surface = m_16bitPal[*data++]; - surface += bytesPerPixel; - } - - surface += length; - } - else { - for (MxS32 j = 0; j < p_width; j++) { - *(MxU32*) surface = m_32bitPal[*data++]; - surface += bytesPerPixel; - } - surface += length; - } - - data += stride; - } + m_ddSurface2->BltFast(p_right, p_bottom, tempSurface, NULL, DDBLTFAST_WAIT | DDBLTFAST_SRCCOLORKEY); } - m_ddSurface2->Unlock(ddsd.lpSurface); + tempSurface->Release(); } // FUNCTION: LEGO1 0x100bb1d0 From 5544640b228ccf3ac2e735363c72b57e9e961c42 Mon Sep 17 00:00:00 2001 From: Fabian Neundorf Date: Fri, 4 Jul 2025 21:53:50 +0200 Subject: [PATCH 071/188] Clear unknowns in `MxControlPresenter` (#1606) --- .../legoomni/include/mxcontrolpresenter.h | 29 ++++--- LEGO1/lego/legoomni/src/actors/radio.cpp | 2 +- .../lego/legoomni/src/build/legocarbuild.cpp | 6 +- .../src/common/mxcontrolpresenter.cpp | 86 +++++++++---------- .../src/control/legocontrolmanager.cpp | 10 +-- 5 files changed, 70 insertions(+), 63 deletions(-) diff --git a/LEGO1/lego/legoomni/include/mxcontrolpresenter.h b/LEGO1/lego/legoomni/include/mxcontrolpresenter.h index 8ecfeacf..d3d2a96a 100644 --- a/LEGO1/lego/legoomni/include/mxcontrolpresenter.h +++ b/LEGO1/lego/legoomni/include/mxcontrolpresenter.h @@ -44,21 +44,28 @@ class MxControlPresenter : public MxCompositePresenter { void EndAction() override; // vtable+0x40 MxBool HasTickleStatePassed(TickleState p_tickleState) override; // vtable+0x48 void Enable(MxBool p_enable) override; // vtable+0x54 - virtual void VTable0x6c(MxS16 p_unk0x4e); // vtable+0x6c + virtual void UpdateEnabledChild(MxS16 p_enabledChild); // vtable+0x6c - MxBool FUN_10044480(LegoControlManagerNotificationParam* p_param, MxPresenter* p_presenter); - MxBool FUN_10044270(MxS32 p_x, MxS32 p_y, MxPresenter* p_presenter); + MxBool Notify(LegoControlManagerNotificationParam* p_param, MxPresenter* p_presenter); + MxBool CheckButtonDown(MxS32 p_x, MxS32 p_y, MxPresenter* p_presenter); - MxS16 GetUnknown0x4e() { return m_unk0x4e; } + MxS16 GetEnabledChild() { return m_enabledChild; } private: - MxS16 m_unk0x4c; // 0x4c - MxS16 m_unk0x4e; // 0x4e - MxBool m_unk0x50; // 0x50 - MxS16 m_unk0x52; // 0x52 - MxS16 m_unk0x54; // 0x54 - MxS16 m_unk0x56; // 0x56 - MxS16* m_states; // 0x58 + enum { + e_none, + e_toggle, + e_grid, + e_map, + }; + + MxS16 m_style; // 0x4c + MxS16 m_enabledChild; // 0x4e + MxBool m_unk0x50; // 0x50 + MxS16 m_columnsOrRows; // 0x52 + MxS16 m_rowsOrColumns; // 0x54 + MxS16 m_stateOrCellIndex; // 0x56 + MxS16* m_states; // 0x58 }; // SYNTHETIC: LEGO1 0x100440f0 diff --git a/LEGO1/lego/legoomni/src/actors/radio.cpp b/LEGO1/lego/legoomni/src/actors/radio.cpp index d32955fe..fa600b7d 100644 --- a/LEGO1/lego/legoomni/src/actors/radio.cpp +++ b/LEGO1/lego/legoomni/src/actors/radio.cpp @@ -131,7 +131,7 @@ void Radio::Stop() MxControlPresenter* presenter = (MxControlPresenter*) world->Find(world->GetAtomId(), IsleScript::c_Radio_Ctl); if (presenter) { - presenter->VTable0x6c(0); + presenter->UpdateEnabledChild(0); } BackgroundAudioManager()->Stop(); diff --git a/LEGO1/lego/legoomni/src/build/legocarbuild.cpp b/LEGO1/lego/legoomni/src/build/legocarbuild.cpp index a7b1e863..0cea0c0d 100644 --- a/LEGO1/lego/legoomni/src/build/legocarbuild.cpp +++ b/LEGO1/lego/legoomni/src/build/legocarbuild.cpp @@ -1650,8 +1650,8 @@ void LegoCarBuild::FUN_10025db0(const char* p_param1, undefined4 p_param2) } } else { - if (m_unk0x33c->GetUnknown0x4e() != sVar3) { - m_unk0x33c->VTable0x6c(sVar3); + if (m_unk0x33c->GetEnabledChild() != sVar3) { + m_unk0x33c->UpdateEnabledChild(sVar3); } g_unk0x100f11cc = -1; @@ -1664,7 +1664,7 @@ void LegoCarBuild::FUN_10025e40() { SetPresentersEnabled(m_presentersEnabled); if (m_unk0x33c && m_Yellow_Ctl != m_unk0x33c) { - m_unk0x33c->VTable0x6c(0); + m_unk0x33c->UpdateEnabledChild(0); } } diff --git a/LEGO1/lego/legoomni/src/common/mxcontrolpresenter.cpp b/LEGO1/lego/legoomni/src/common/mxcontrolpresenter.cpp index 76780d6e..9374cb9f 100644 --- a/LEGO1/lego/legoomni/src/common/mxcontrolpresenter.cpp +++ b/LEGO1/lego/legoomni/src/common/mxcontrolpresenter.cpp @@ -16,12 +16,12 @@ DECOMP_SIZE_ASSERT(MxControlPresenter, 0x5c) // FUNCTION: LEGO1 0x10043f50 MxControlPresenter::MxControlPresenter() { - m_unk0x4c = 0; - m_unk0x4e = -1; + m_style = e_none; + m_enabledChild = -1; m_unk0x50 = FALSE; - m_unk0x52 = 0; + m_columnsOrRows = 0; m_states = NULL; - m_unk0x54 = 0; + m_rowsOrColumns = 0; } // FUNCTION: LEGO1 0x10044110 @@ -35,7 +35,7 @@ MxControlPresenter::~MxControlPresenter() // FUNCTION: LEGO1 0x10044180 MxResult MxControlPresenter::AddToManager() { - m_unk0x4e = 0; + m_enabledChild = 0; return SUCCESS; } @@ -49,11 +49,11 @@ MxResult MxControlPresenter::StartAction(MxStreamController* p_controller, MxDSA MxS16 i = 0; for (MxCompositePresenterList::iterator it = m_list.begin(); it != m_list.end(); it++) { - (*it)->Enable((m_unk0x4c != 3 || m_unk0x4e) && IsEnabled() ? m_unk0x4e == i : FALSE); + (*it)->Enable((m_style != e_map || m_enabledChild) && IsEnabled() ? m_enabledChild == i : FALSE); i++; } - if (m_unk0x4c == 3) { + if (m_style == e_map) { MxDSAction* action = (*m_list.begin())->GetAction(); action->SetFlags(action->GetFlags() | MxDSAction::c_bit11); } @@ -74,12 +74,12 @@ void MxControlPresenter::EndAction() // FUNCTION: LEGO1 0x10044270 // FUNCTION: BETA10 0x100eae68 -MxBool MxControlPresenter::FUN_10044270(MxS32 p_x, MxS32 p_y, MxPresenter* p_presenter) +MxBool MxControlPresenter::CheckButtonDown(MxS32 p_x, MxS32 p_y, MxPresenter* p_presenter) { assert(p_presenter); MxVideoPresenter* presenter = (MxVideoPresenter*) p_presenter; - if (m_unk0x4c == 3) { + if (m_style == e_map) { MxStillPresenter* map = (MxStillPresenter*) m_list.front(); assert(map && map->IsA("MxStillPresenter")); @@ -94,23 +94,23 @@ MxBool MxControlPresenter::FUN_10044270(MxS32 p_x, MxS32 p_y, MxPresenter* p_pre ? NULL : map->GetBitmap()->GetStart(p_x - rect.GetLeft(), p_y - rect.GetTop()); - m_unk0x56 = 0; + m_stateOrCellIndex = 0; if (m_states) { for (MxS16 i = 1; i <= *m_states; i++) { // TODO: Can we match without the cast here? if (m_states[i] == (MxS16) *start) { - m_unk0x56 = i; + m_stateOrCellIndex = i; break; } } } else { if (*start != 0) { - m_unk0x56 = 1; + m_stateOrCellIndex = 1; } } - if (m_unk0x56) { + if (m_stateOrCellIndex) { return TRUE; } } @@ -119,21 +119,21 @@ MxBool MxControlPresenter::FUN_10044270(MxS32 p_x, MxS32 p_y, MxPresenter* p_pre } else { if (ContainsPresenter(m_list, presenter)) { - if (m_unk0x4c == 2) { + if (m_style == e_grid) { MxS32 width = presenter->GetWidth(); MxS32 height = presenter->GetHeight(); - if (m_unk0x52 == 2 && m_unk0x54 == 2) { + if (m_columnsOrRows == 2 && m_rowsOrColumns == 2) { if (p_x < presenter->GetX() + width / 2) { - m_unk0x56 = (p_y >= presenter->GetY() + height / 2) ? 3 : 1; + m_stateOrCellIndex = (p_y >= presenter->GetY() + height / 2) ? 3 : 1; } else { - m_unk0x56 = (p_y >= presenter->GetY() + height / 2) ? 4 : 2; + m_stateOrCellIndex = (p_y >= presenter->GetY() + height / 2) ? 4 : 2; } } } else { - m_unk0x56 = -1; + m_stateOrCellIndex = -1; } return TRUE; @@ -144,27 +144,27 @@ MxBool MxControlPresenter::FUN_10044270(MxS32 p_x, MxS32 p_y, MxPresenter* p_pre } // FUNCTION: LEGO1 0x10044480 -MxBool MxControlPresenter::FUN_10044480(LegoControlManagerNotificationParam* p_param, MxPresenter* p_presenter) +MxBool MxControlPresenter::Notify(LegoControlManagerNotificationParam* p_param, MxPresenter* p_presenter) { if (IsEnabled()) { switch (p_param->GetNotification()) { case c_notificationButtonUp: - if (m_unk0x4c == 0 || m_unk0x4c == 2 || m_unk0x4c == 3) { + if (m_style == e_none || m_style == e_grid || m_style == e_map) { p_param->SetClickedObjectId(m_action->GetObjectId()); p_param->SetClickedAtom(m_action->GetAtomId().GetInternal()); - VTable0x6c(0); + UpdateEnabledChild(0); p_param->SetNotification(c_notificationControl); - p_param->SetUnknown0x28(m_unk0x4e); + p_param->SetUnknown0x28(m_enabledChild); return TRUE; } break; case c_notificationButtonDown: - if (FUN_10044270(p_param->GetX(), p_param->GetY(), p_presenter)) { + if (CheckButtonDown(p_param->GetX(), p_param->GetY(), p_presenter)) { p_param->SetClickedObjectId(m_action->GetObjectId()); p_param->SetClickedAtom(m_action->GetAtomId().GetInternal()); - VTable0x6c(m_unk0x56); + UpdateEnabledChild(m_stateOrCellIndex); p_param->SetNotification(c_notificationControl); - p_param->SetUnknown0x28(m_unk0x4e); + p_param->SetUnknown0x28(m_enabledChild); return TRUE; } break; @@ -175,25 +175,25 @@ MxBool MxControlPresenter::FUN_10044480(LegoControlManagerNotificationParam* p_p } // FUNCTION: LEGO1 0x10044540 -void MxControlPresenter::VTable0x6c(MxS16 p_unk0x4e) +void MxControlPresenter::UpdateEnabledChild(MxS16 p_enabledChild) { - if (p_unk0x4e == -1) { - if ((MxS16) ((MxDSMultiAction*) m_action)->GetActionList()->GetNumElements() - m_unk0x4e == 1) { - m_unk0x4e = 0; + if (p_enabledChild == -1) { + if ((MxS16) ((MxDSMultiAction*) m_action)->GetActionList()->GetNumElements() - m_enabledChild == 1) { + m_enabledChild = 0; } else { - m_unk0x4e++; + m_enabledChild++; } } else { - m_unk0x4e = p_unk0x4e; + m_enabledChild = p_enabledChild; } m_action->SetTimeStarted(Timer()->GetTime()); MxS16 i = 0; for (MxCompositePresenterList::iterator it = m_list.begin(); it != m_list.end(); it++) { - (*it)->Enable(((m_unk0x4c == 3 && m_unk0x4e == 0) || !IsEnabled()) ? FALSE : m_unk0x4e == i); + (*it)->Enable(((m_style == e_map && m_enabledChild == 0) || !IsEnabled()) ? FALSE : m_enabledChild == i); i++; } } @@ -224,20 +224,20 @@ void MxControlPresenter::ParseExtra() char* token = strtok(output, g_parseExtraTokens); if (!strcmpi(token, g_strTOGGLE)) { - m_unk0x4c = 1; + m_style = e_toggle; } else if (!strcmpi(token, g_strGRID)) { - m_unk0x4c = 2; + m_style = e_grid; token = strtok(NULL, g_parseExtraTokens); assert(token); - m_unk0x52 = atoi(token); + m_columnsOrRows = atoi(token); token = strtok(NULL, g_parseExtraTokens); assert(token); - m_unk0x54 = atoi(token); + m_rowsOrColumns = atoi(token); } else if (!strcmpi(token, g_strMAP)) { - m_unk0x4c = 3; + m_style = e_map; token = strtok(NULL, g_parseExtraTokens); if (token) { @@ -254,7 +254,7 @@ void MxControlPresenter::ParseExtra() } } else { - m_unk0x4c = 0; + m_style = e_none; } } @@ -274,8 +274,8 @@ void MxControlPresenter::Enable(MxBool p_enable) MxS16 i = 0; for (MxCompositePresenterList::iterator it = m_list.begin(); it != m_list.end(); it++) { - if (i == m_unk0x4e) { - (*it)->Enable((m_unk0x4c != 3 || i != 0) ? p_enable : 0); + if (i == m_enabledChild) { + (*it)->Enable((m_style != e_map || i != 0) ? p_enable : 0); break; } @@ -283,7 +283,7 @@ void MxControlPresenter::Enable(MxBool p_enable) } if (!p_enable) { - m_unk0x4e = 0; + m_enabledChild = 0; } } } @@ -294,10 +294,10 @@ MxBool MxControlPresenter::HasTickleStatePassed(TickleState p_tickleState) MxCompositePresenterList::const_iterator it = m_list.begin(); #ifdef COMPAT_MODE - advance(it, m_unk0x4e); + advance(it, m_enabledChild); #else // Uses forward iterator logic instead of bidrectional for some reason. - _Advance(it, m_unk0x4e, forward_iterator_tag()); + _Advance(it, m_enabledChild, forward_iterator_tag()); #endif return (*it)->HasTickleStatePassed(p_tickleState); diff --git a/LEGO1/lego/legoomni/src/control/legocontrolmanager.cpp b/LEGO1/lego/legoomni/src/control/legocontrolmanager.cpp index 8dd094f9..dc355be7 100644 --- a/LEGO1/lego/legoomni/src/control/legocontrolmanager.cpp +++ b/LEGO1/lego/legoomni/src/control/legocontrolmanager.cpp @@ -131,9 +131,9 @@ void LegoControlManager::FUN_100293c0(MxU32 p_objectId, const char* p_atom, MxS1 MxDSAction* action = control->GetAction(); if (action->GetObjectId() == p_objectId && action->GetAtomId().GetInternal() == p_atom) { - ((MxControlPresenter*) control)->VTable0x6c(p_unk0x4e); + ((MxControlPresenter*) control)->UpdateEnabledChild(p_unk0x4e); - if (((MxControlPresenter*) control)->GetUnknown0x4e() == 0) { + if (((MxControlPresenter*) control)->GetEnabledChild() == 0) { g_unk0x100f31b0 = -1; g_unk0x100f31b4 = NULL; break; @@ -154,7 +154,7 @@ MxControlPresenter* LegoControlManager::FUN_100294e0(MxS32 p_x, MxS32 p_y) if (presenter) { while (cursor.Next(control)) { - if (((MxControlPresenter*) control)->FUN_10044270(p_x, p_y, presenter)) { + if (((MxControlPresenter*) control)->CheckButtonDown(p_x, p_y, presenter)) { return (MxControlPresenter*) control; } } @@ -185,7 +185,7 @@ MxBool LegoControlManager::FUN_10029630() MxPresenter* presenter; while (cursor.Next(presenter)) { - if (((MxControlPresenter*) presenter)->FUN_10044480(&m_event, m_unk0x14)) { + if (((MxControlPresenter*) presenter)->Notify(&m_event, m_unk0x14)) { g_unk0x100f31b0 = m_event.m_clickedObjectId; g_unk0x100f31b4 = m_event.GetClickedAtom(); FUN_100292e0(); @@ -206,7 +206,7 @@ MxBool LegoControlManager::FUN_10029750() while (cursor.Next(presenter)) { if (presenter->GetAction() && presenter->GetAction()->GetObjectId() == g_unk0x100f31b0 && presenter->GetAction()->GetAtomId().GetInternal() == g_unk0x100f31b4) { - if (((MxControlPresenter*) presenter)->FUN_10044480(&m_event, m_unk0x14)) { + if (((MxControlPresenter*) presenter)->Notify(&m_event, m_unk0x14)) { FUN_100292e0(); } From af9f7cd7915bd2ef7b87a34e051f2bebcb8b61d6 Mon Sep 17 00:00:00 2001 From: Anders Jenbo Date: Sat, 5 Jul 2025 01:48:13 +0200 Subject: [PATCH 072/188] 3DS: Account for sprite scaling when resizing UI (#503) --- .../src/common/mxtransitionmanager.cpp | 88 +++++++++++-------- .../src/d3drm/backends/citro3d/renderer.cpp | 61 ++++++++----- .../src/d3drm/backends/directx9/renderer.cpp | 2 +- miniwin/src/d3drm/backends/opengl1/actual.cpp | 4 +- miniwin/src/d3drm/backends/opengl1/actual.h | 2 +- .../src/d3drm/backends/opengl1/renderer.cpp | 10 +-- .../src/d3drm/backends/opengles2/renderer.cpp | 10 +-- .../src/d3drm/backends/sdl3gpu/renderer.cpp | 2 +- .../src/d3drm/backends/software/renderer.cpp | 2 +- miniwin/src/ddraw/framebuffer.cpp | 4 +- miniwin/src/internal/d3drmrenderer.h | 2 +- miniwin/src/internal/d3drmrenderer_citro3d.h | 2 +- miniwin/src/internal/d3drmrenderer_directx9.h | 2 +- miniwin/src/internal/d3drmrenderer_opengl1.h | 2 +- .../src/internal/d3drmrenderer_opengles2.h | 2 +- miniwin/src/internal/d3drmrenderer_sdl3gpu.h | 2 +- miniwin/src/internal/d3drmrenderer_software.h | 2 +- 17 files changed, 116 insertions(+), 83 deletions(-) diff --git a/LEGO1/lego/legoomni/src/common/mxtransitionmanager.cpp b/LEGO1/lego/legoomni/src/common/mxtransitionmanager.cpp index 6c4f94ea..27086eb3 100644 --- a/LEGO1/lego/legoomni/src/common/mxtransitionmanager.cpp +++ b/LEGO1/lego/legoomni/src/common/mxtransitionmanager.cpp @@ -642,8 +642,14 @@ int GetColorIndexWithLocality(int p_col, int p_row) void MxTransitionManager::FakeMosaicTransition() { + static LPDIRECTDRAWSURFACE g_fakeTranstionSurface = nullptr; + if (m_animationTimer == 16) { m_animationTimer = 0; + if (g_fakeTranstionSurface) { + g_fakeTranstionSurface->Release(); + g_fakeTranstionSurface = nullptr; + } EndTransition(TRUE); return; } @@ -662,12 +668,33 @@ void MxTransitionManager::FakeMosaicTransition() } } + if (!g_fakeTranstionSurface) { + DDSURFACEDESC mainDesc = {}; + mainDesc.dwSize = sizeof(mainDesc); + if (m_ddSurface->GetSurfaceDesc(&mainDesc) != DD_OK) { + return; + } + + DDSURFACEDESC tempDesc = {}; + tempDesc.dwSize = sizeof(tempDesc); + tempDesc.dwFlags = DDSD_WIDTH | DDSD_HEIGHT | DDSD_PIXELFORMAT | DDSD_CAPS; + tempDesc.dwWidth = 64; + tempDesc.dwHeight = 48; + tempDesc.ddpfPixelFormat = mainDesc.ddpfPixelFormat; + tempDesc.ddsCaps.dwCaps = DDSCAPS_OFFSCREENPLAIN; + + HRESULT hr = MVideoManager()->GetDirectDraw()->CreateSurface(&tempDesc, &g_fakeTranstionSurface, nullptr); + if (hr != DD_OK || !g_fakeTranstionSurface) { + return; + } + } + DDSURFACEDESC ddsd = {}; ddsd.dwSize = sizeof(ddsd); - HRESULT res = m_ddSurface->Lock(NULL, &ddsd, DDLOCK_WAIT | DDLOCK_WRITEONLY, NULL); + HRESULT res = g_fakeTranstionSurface->Lock(NULL, &ddsd, DDLOCK_WAIT | DDLOCK_WRITEONLY, NULL); if (res == DDERR_SURFACELOST) { - m_ddSurface->Restore(); - res = m_ddSurface->Lock(NULL, &ddsd, DDLOCK_WAIT | DDLOCK_WRITEONLY, NULL); + g_fakeTranstionSurface->Restore(); + res = g_fakeTranstionSurface->Lock(NULL, &ddsd, DDLOCK_WAIT | DDLOCK_WRITEONLY, NULL); } if (res == DD_OK) { @@ -694,50 +721,33 @@ void MxTransitionManager::FakeMosaicTransition() } for (MxS32 row = 0; row < 48; row++) { - MxS32 xShift = 10 * ((m_randomShift[row] + col) % 64); - MxS32 yStart = 10 * row; - - int paletteIndex = GetColorIndexWithLocality(xShift / 10, row); + int paletteIndex = GetColorIndexWithLocality(col, row); const MxU8* color = g_palette[paletteIndex]; - for (MxS32 y = 0; y < 10; y++) { - MxU8* dest = (MxU8*) ddsd.lpSurface + (yStart + y) * ddsd.lPitch + xShift * bytesPerPixel; - switch (bytesPerPixel) { - case 1: - memset(dest, paletteIndex, 10); - break; - case 2: { - MxU32 pixel = RGB555_CREATE(color[2], color[1], color[0]); - MxU16* p = (MxU16*) dest; - for (MxS32 i = 0; i < 10; i++) { - p[i] = pixel; - } - break; - } - default: { - MxU32 pixel = RGB8888_CREATE(color[2], color[1], color[0], 255); - MxU32* p = (MxU32*) dest; - for (MxS32 i = 0; i < 10; i++) { - p[i] = pixel; - } - break; - } - } + MxS32 xShift = (m_randomShift[row] + col) % 64; + MxU8* dest = (MxU8*) ddsd.lpSurface + row * ddsd.lPitch + xShift * bytesPerPixel; + + switch (bytesPerPixel) { + case 1: + *dest = paletteIndex; + break; + case 2: + *((MxU16*) dest) = RGB555_CREATE(color[2], color[1], color[0]); + break; + default: + *((MxU32*) dest) = RGB8888_CREATE(color[2], color[1], color[0], 255); + break; } } } SetupCopyRect(&ddsd); - m_ddSurface->Unlock(ddsd.lpSurface); + g_fakeTranstionSurface->Unlock(ddsd.lpSurface); - if (VideoManager()->GetVideoParam().Flags().GetFlipSurfaces()) { - VideoManager() - ->GetDisplaySurface() - ->GetDirectDrawSurface1() - ->BltFast(0, 0, m_ddSurface, &g_fullScreenRect, DDBLTFAST_WAIT); - } - - m_animationTimer++; + RECT srcRect = {0, 0, 64, 48}; + m_ddSurface->Blt(&g_fullScreenRect, g_fakeTranstionSurface, &srcRect, DDBLT_WAIT, NULL); } + + m_animationTimer++; } diff --git a/miniwin/src/d3drm/backends/citro3d/renderer.cpp b/miniwin/src/d3drm/backends/citro3d/renderer.cpp index f71f9255..ab9088bc 100644 --- a/miniwin/src/d3drm/backends/citro3d/renderer.cpp +++ b/miniwin/src/d3drm/backends/citro3d/renderer.cpp @@ -115,31 +115,34 @@ static int NearestPowerOfTwoClamp(int val) return 512; } -static SDL_Surface* ConvertAndResizeSurface(SDL_Surface* original, bool isUI, float scale) +static SDL_Surface* ConvertAndResizeSurface(SDL_Surface* original, bool isUI, float scaleX, float scaleY) { - SDL_Surface* converted = SDL_ConvertSurface(original, SDL_PIXELFORMAT_RGBA8888); - if (!converted) { - return nullptr; - } if (!isUI) { - return converted; + return SDL_ConvertSurface(original, SDL_PIXELFORMAT_RGBA8888); } - int scaledW = static_cast(converted->w * scale); - int scaledH = static_cast(converted->h * scale); + scaleX = std::min(scaleX, 1.0f); + scaleY = std::min(scaleY, 1.0f); + + int scaledW = static_cast(original->w * scaleX); + int scaledH = static_cast(original->h * scaleY); int paddedW = NearestPowerOfTwoClamp(scaledW); int paddedH = NearestPowerOfTwoClamp(scaledH); SDL_Surface* padded = SDL_CreateSurface(paddedW, paddedH, SDL_PIXELFORMAT_RGBA8888); if (!padded) { - SDL_DestroySurface(converted); return nullptr; } - SDL_Rect dstRect = {0, 0, scaledW, scaledH}; - SDL_BlitSurfaceScaled(converted, nullptr, padded, &dstRect, SDL_SCALEMODE_LINEAR); - SDL_DestroySurface(converted); + if (scaleX == 1.0f && scaleY == 1.0f) { + SDL_BlitSurface(original, nullptr, padded, nullptr); + } + else { + SDL_ScaleMode scaleMode = (scaleX >= 1.0f && scaleY >= 1.0f) ? SDL_SCALEMODE_NEAREST : SDL_SCALEMODE_LINEAR; + SDL_Rect dstRect = {0, 0, scaledW, scaledH}; + SDL_BlitSurfaceScaled(original, nullptr, padded, &dstRect, scaleMode); + } return padded; } @@ -181,9 +184,9 @@ static void EncodeTextureLayout(const u8* src, u8* dst, int width, int height) } } -static bool ConvertAndUploadTexture(C3D_Tex* tex, SDL_Surface* originalSurface, bool isUI, float scale) +static bool ConvertAndUploadTexture(C3D_Tex* tex, SDL_Surface* originalSurface, bool isUI, float scaleX, float scaleY) { - SDL_Surface* resized = ConvertAndResizeSurface(originalSurface, isUI, scale); + SDL_Surface* resized = ConvertAndResizeSurface(originalSurface, isUI, scaleX, scaleY); if (!resized) { return false; } @@ -198,18 +201,24 @@ static bool ConvertAndUploadTexture(C3D_Tex* tex, SDL_Surface* originalSurface, params.maxLevel = isUI ? 0 : 4; params.type = GPU_TEX_2D; if (!C3D_TexInitWithParams(tex, nullptr, params)) { - SDL_DestroySurface(resized); + if (resized != originalSurface) { + SDL_DestroySurface(resized); + } return false; } uint8_t* tiledData = (uint8_t*) malloc(width * height * 4); if (!tiledData) { - SDL_DestroySurface(resized); + if (resized != originalSurface) { + SDL_DestroySurface(resized); + } return false; } EncodeTextureLayout((const u8*) resized->pixels, tiledData, width, height); - SDL_DestroySurface(resized); + if (resized != originalSurface) { + SDL_DestroySurface(resized); + } C3D_TexUpload(tex, tiledData); free(tiledData); @@ -228,7 +237,7 @@ static bool ConvertAndUploadTexture(C3D_Tex* tex, SDL_Surface* originalSurface, return true; } -Uint32 Citro3DRenderer::GetTextureId(IDirect3DRMTexture* iTexture, bool isUi) +Uint32 Citro3DRenderer::GetTextureId(IDirect3DRMTexture* iTexture, bool isUI, float scaleX, float scaleY) { auto texture = static_cast(iTexture); auto surface = static_cast(texture->m_surface); @@ -242,7 +251,13 @@ Uint32 Citro3DRenderer::GetTextureId(IDirect3DRMTexture* iTexture, bool isUi) if (tex.texture == texture) { if (tex.version != texture->m_version) { C3D_TexDelete(&tex.c3dTex); - if (!ConvertAndUploadTexture(&tex.c3dTex, originalSurface, isUi, m_viewportTransform.scale)) { + if (!ConvertAndUploadTexture( + &tex.c3dTex, + originalSurface, + isUI, + scaleX * m_viewportTransform.scale, + scaleY * m_viewportTransform.scale + )) { return NO_TEXTURE_ID; } @@ -260,7 +275,13 @@ Uint32 Citro3DRenderer::GetTextureId(IDirect3DRMTexture* iTexture, bool isUi) entry.width = NearestPowerOfTwoClamp(originalW * m_viewportTransform.scale); entry.height = NearestPowerOfTwoClamp(originalH * m_viewportTransform.scale); - if (!ConvertAndUploadTexture(&entry.c3dTex, originalSurface, isUi, m_viewportTransform.scale)) { + if (!ConvertAndUploadTexture( + &entry.c3dTex, + originalSurface, + isUI, + scaleX * m_viewportTransform.scale, + scaleY * m_viewportTransform.scale + )) { return NO_TEXTURE_ID; } diff --git a/miniwin/src/d3drm/backends/directx9/renderer.cpp b/miniwin/src/d3drm/backends/directx9/renderer.cpp index 5c8a2354..e97dfa17 100644 --- a/miniwin/src/d3drm/backends/directx9/renderer.cpp +++ b/miniwin/src/d3drm/backends/directx9/renderer.cpp @@ -76,7 +76,7 @@ void DirectX9Renderer::AddTextureDestroyCallback(Uint32 id, IDirect3DRMTexture* ); } -Uint32 DirectX9Renderer::GetTextureId(IDirect3DRMTexture* iTexture, bool isUi) +Uint32 DirectX9Renderer::GetTextureId(IDirect3DRMTexture* iTexture, bool isUI, float scaleX, float scaleY) { auto texture = static_cast(iTexture); auto surface = static_cast(texture->m_surface); diff --git a/miniwin/src/d3drm/backends/opengl1/actual.cpp b/miniwin/src/d3drm/backends/opengl1/actual.cpp index a86ebec1..4b83daff 100644 --- a/miniwin/src/d3drm/backends/opengl1/actual.cpp +++ b/miniwin/src/d3drm/backends/opengl1/actual.cpp @@ -49,12 +49,12 @@ int GL11_GetMaxTextureSize() return maxTextureSize; } -GLuint GL11_UploadTextureData(void* pixels, int width, int height, bool isUi) +GLuint GL11_UploadTextureData(void* pixels, int width, int height, bool isUI, float scaleX, float scaleY) { GLuint texId; glGenTextures(1, &texId); glBindTexture(GL_TEXTURE_2D, texId); - if (isUi) { + if (isUI) { glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); diff --git a/miniwin/src/d3drm/backends/opengl1/actual.h b/miniwin/src/d3drm/backends/opengl1/actual.h index 87f33687..232fc88d 100644 --- a/miniwin/src/d3drm/backends/opengl1/actual.h +++ b/miniwin/src/d3drm/backends/opengl1/actual.h @@ -65,7 +65,7 @@ void GL11_InitState(); void GL11_LoadExtensions(); void GL11_DestroyTexture(GLuint texId); int GL11_GetMaxTextureSize(); -GLuint GL11_UploadTextureData(void* pixels, int width, int height, bool isUI); +GLuint GL11_UploadTextureData(void* pixels, int width, int height, bool isUI, float scaleX, float scaleY); void GL11_UploadMesh(GLMeshCacheEntry& cache, bool hasTexture); void GL11_DestroyMesh(GLMeshCacheEntry& cache); void GL11_BeginFrame(const Matrix4x4* projection); diff --git a/miniwin/src/d3drm/backends/opengl1/renderer.cpp b/miniwin/src/d3drm/backends/opengl1/renderer.cpp index 00088da9..e537a396 100644 --- a/miniwin/src/d3drm/backends/opengl1/renderer.cpp +++ b/miniwin/src/d3drm/backends/opengl1/renderer.cpp @@ -119,7 +119,7 @@ static int NextPowerOfTwo(int v) return power; } -static Uint32 UploadTextureData(SDL_Surface* src, bool useNPOT, bool isUi) +static Uint32 UploadTextureData(SDL_Surface* src, bool useNPOT, bool isUI, float scaleX, float scaleY) { SDL_Surface* working = src; if (src->format != SDL_PIXELFORMAT_RGBA32) { @@ -166,14 +166,14 @@ static Uint32 UploadTextureData(SDL_Surface* src, bool useNPOT, bool isUi) finalSurface = resized; } - Uint32 texId = GL11_UploadTextureData(finalSurface->pixels, finalSurface->w, finalSurface->h, isUi); + Uint32 texId = GL11_UploadTextureData(finalSurface->pixels, finalSurface->w, finalSurface->h, isUI, scaleX, scaleY); if (finalSurface != src) { SDL_DestroySurface(finalSurface); } return texId; } -Uint32 OpenGL1Renderer::GetTextureId(IDirect3DRMTexture* iTexture, bool isUi) +Uint32 OpenGL1Renderer::GetTextureId(IDirect3DRMTexture* iTexture, bool isUI, float scaleX, float scaleY) { auto texture = static_cast(iTexture); auto surface = static_cast(texture->m_surface); @@ -183,7 +183,7 @@ Uint32 OpenGL1Renderer::GetTextureId(IDirect3DRMTexture* iTexture, bool isUi) if (tex.texture == texture) { if (tex.version != texture->m_version) { GL11_DestroyTexture(tex.glTextureId); - tex.glTextureId = UploadTextureData(surface->m_surface, m_useNPOT, isUi); + tex.glTextureId = UploadTextureData(surface->m_surface, m_useNPOT, isUI, scaleX, scaleY); tex.version = texture->m_version; tex.width = surface->m_surface->w; tex.height = surface->m_surface->h; @@ -192,7 +192,7 @@ Uint32 OpenGL1Renderer::GetTextureId(IDirect3DRMTexture* iTexture, bool isUi) } } - GLuint texId = UploadTextureData(surface->m_surface, m_useNPOT, isUi); + GLuint texId = UploadTextureData(surface->m_surface, m_useNPOT, isUI, scaleX, scaleY); for (Uint32 i = 0; i < m_textures.size(); ++i) { auto& tex = m_textures[i]; diff --git a/miniwin/src/d3drm/backends/opengles2/renderer.cpp b/miniwin/src/d3drm/backends/opengles2/renderer.cpp index 850e8b6e..ebcb38e6 100644 --- a/miniwin/src/d3drm/backends/opengles2/renderer.cpp +++ b/miniwin/src/d3drm/backends/opengles2/renderer.cpp @@ -321,7 +321,7 @@ void OpenGLES2Renderer::AddTextureDestroyCallback(Uint32 id, IDirect3DRMTexture* ); } -bool UploadTexture(SDL_Surface* source, GLuint& outTexId, bool isUi) +bool UploadTexture(SDL_Surface* source, GLuint& outTexId, bool isUI) { SDL_Surface* surf = source; if (source->format != SDL_PIXELFORMAT_RGBA32) { @@ -335,7 +335,7 @@ bool UploadTexture(SDL_Surface* source, GLuint& outTexId, bool isUi) glBindTexture(GL_TEXTURE_2D, outTexId); glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, surf->w, surf->h, 0, GL_RGBA, GL_UNSIGNED_BYTE, surf->pixels); - if (isUi) { + if (isUI) { glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); @@ -362,7 +362,7 @@ bool UploadTexture(SDL_Surface* source, GLuint& outTexId, bool isUi) return true; } -Uint32 OpenGLES2Renderer::GetTextureId(IDirect3DRMTexture* iTexture, bool isUi) +Uint32 OpenGLES2Renderer::GetTextureId(IDirect3DRMTexture* iTexture, bool isUI, float scaleX, float scaleY) { auto texture = static_cast(iTexture); auto surface = static_cast(texture->m_surface); @@ -372,7 +372,7 @@ Uint32 OpenGLES2Renderer::GetTextureId(IDirect3DRMTexture* iTexture, bool isUi) if (tex.texture == texture) { if (tex.version != texture->m_version) { glDeleteTextures(1, &tex.glTextureId); - if (UploadTexture(surface->m_surface, tex.glTextureId, isUi)) { + if (UploadTexture(surface->m_surface, tex.glTextureId, isUI)) { tex.version = texture->m_version; } } @@ -381,7 +381,7 @@ Uint32 OpenGLES2Renderer::GetTextureId(IDirect3DRMTexture* iTexture, bool isUi) } GLuint texId; - if (!UploadTexture(surface->m_surface, texId, isUi)) { + if (!UploadTexture(surface->m_surface, texId, isUI)) { return NO_TEXTURE_ID; } diff --git a/miniwin/src/d3drm/backends/sdl3gpu/renderer.cpp b/miniwin/src/d3drm/backends/sdl3gpu/renderer.cpp index c6324f03..0609bbdc 100644 --- a/miniwin/src/d3drm/backends/sdl3gpu/renderer.cpp +++ b/miniwin/src/d3drm/backends/sdl3gpu/renderer.cpp @@ -499,7 +499,7 @@ SDL_GPUTexture* Direct3DRMSDL3GPURenderer::CreateTextureFromSurface(SDL_Surface* return texptr; } -Uint32 Direct3DRMSDL3GPURenderer::GetTextureId(IDirect3DRMTexture* iTexture, bool isUi) +Uint32 Direct3DRMSDL3GPURenderer::GetTextureId(IDirect3DRMTexture* iTexture, bool isUI, float scaleX, float scaleY) { auto texture = static_cast(iTexture); auto surface = static_cast(texture->m_surface); diff --git a/miniwin/src/d3drm/backends/software/renderer.cpp b/miniwin/src/d3drm/backends/software/renderer.cpp index 2ae8697e..bd99d2aa 100644 --- a/miniwin/src/d3drm/backends/software/renderer.cpp +++ b/miniwin/src/d3drm/backends/software/renderer.cpp @@ -554,7 +554,7 @@ void Direct3DRMSoftwareRenderer::AddTextureDestroyCallback(Uint32 id, IDirect3DR ); } -Uint32 Direct3DRMSoftwareRenderer::GetTextureId(IDirect3DRMTexture* iTexture, bool isUi) +Uint32 Direct3DRMSoftwareRenderer::GetTextureId(IDirect3DRMTexture* iTexture, bool isUI, float scaleX, float scaleY) { auto texture = static_cast(iTexture); auto surface = static_cast(texture->m_surface); diff --git a/miniwin/src/ddraw/framebuffer.cpp b/miniwin/src/ddraw/framebuffer.cpp index 20490b42..843c36ec 100644 --- a/miniwin/src/ddraw/framebuffer.cpp +++ b/miniwin/src/ddraw/framebuffer.cpp @@ -78,11 +78,13 @@ HRESULT FrameBufferImpl::Blt( if (!surface) { return DDERR_GENERIC; } - Uint32 textureId = DDRenderer->GetTextureId(surface->ToTexture(), true); SDL_Rect srcRect = lpSrcRect ? ConvertRect(lpSrcRect) : SDL_Rect{0, 0, surface->m_surface->w, surface->m_surface->h}; SDL_Rect dstRect = lpDestRect ? ConvertRect(lpDestRect) : SDL_Rect{0, 0, (int) m_virtualWidth, (int) m_virtualHeight}; + float scaleX = (float) dstRect.w / (float) srcRect.w; + float scaleY = (float) dstRect.h / (float) srcRect.h; + Uint32 textureId = DDRenderer->GetTextureId(surface->ToTexture(), true, scaleX, scaleY); DDRenderer->Draw2DImage(textureId, srcRect, dstRect, {1.0f, 1.0f, 1.0f, 1.0f}); return DD_OK; diff --git a/miniwin/src/internal/d3drmrenderer.h b/miniwin/src/internal/d3drmrenderer.h index d6464eb7..7cfe14b2 100644 --- a/miniwin/src/internal/d3drmrenderer.h +++ b/miniwin/src/internal/d3drmrenderer.h @@ -31,7 +31,7 @@ class Direct3DRMRenderer : public IDirect3DDevice2 { virtual void PushLights(const SceneLight* vertices, size_t count) = 0; virtual void SetProjection(const D3DRMMATRIX4D& projection, D3DVALUE front, D3DVALUE back) = 0; virtual void SetFrustumPlanes(const Plane* frustumPlanes) = 0; - virtual Uint32 GetTextureId(IDirect3DRMTexture* texture, bool isUi = false) = 0; + virtual Uint32 GetTextureId(IDirect3DRMTexture* texture, bool isUI = false, float scaleX = 0, float scaleY = 0) = 0; virtual Uint32 GetMeshId(IDirect3DRMMesh* mesh, const MeshGroup* meshGroup) = 0; int GetWidth() { return m_width; } int GetHeight() { return m_height; } diff --git a/miniwin/src/internal/d3drmrenderer_citro3d.h b/miniwin/src/internal/d3drmrenderer_citro3d.h index 34863497..2359a88e 100644 --- a/miniwin/src/internal/d3drmrenderer_citro3d.h +++ b/miniwin/src/internal/d3drmrenderer_citro3d.h @@ -32,7 +32,7 @@ class Citro3DRenderer : public Direct3DRMRenderer { void PushLights(const SceneLight* lightsArray, size_t count) override; void SetProjection(const D3DRMMATRIX4D& projection, D3DVALUE front, D3DVALUE back) override; void SetFrustumPlanes(const Plane* frustumPlanes) override; - Uint32 GetTextureId(IDirect3DRMTexture* texture, bool isUi) override; + Uint32 GetTextureId(IDirect3DRMTexture* texture, bool isUI, float scaleX, float scaleY) override; Uint32 GetMeshId(IDirect3DRMMesh* mesh, const MeshGroup* meshGroup) override; HRESULT BeginFrame() override; void EnableTransparency() override; diff --git a/miniwin/src/internal/d3drmrenderer_directx9.h b/miniwin/src/internal/d3drmrenderer_directx9.h index d1b0a581..5e87aed3 100644 --- a/miniwin/src/internal/d3drmrenderer_directx9.h +++ b/miniwin/src/internal/d3drmrenderer_directx9.h @@ -18,7 +18,7 @@ class DirectX9Renderer : public Direct3DRMRenderer { void PushLights(const SceneLight* lightsArray, size_t count) override; void SetProjection(const D3DRMMATRIX4D& projection, D3DVALUE front, D3DVALUE back) override; void SetFrustumPlanes(const Plane* frustumPlanes) override; - Uint32 GetTextureId(IDirect3DRMTexture* texture, bool isUi) override; + Uint32 GetTextureId(IDirect3DRMTexture* texture, bool isUI, float scaleX, float scaleY) override; Uint32 GetMeshId(IDirect3DRMMesh* mesh, const MeshGroup* meshGroup) override; HRESULT BeginFrame() override; void EnableTransparency() override; diff --git a/miniwin/src/internal/d3drmrenderer_opengl1.h b/miniwin/src/internal/d3drmrenderer_opengl1.h index bd0fa003..2f1c8173 100644 --- a/miniwin/src/internal/d3drmrenderer_opengl1.h +++ b/miniwin/src/internal/d3drmrenderer_opengl1.h @@ -18,7 +18,7 @@ class OpenGL1Renderer : public Direct3DRMRenderer { void PushLights(const SceneLight* lightsArray, size_t count) override; void SetProjection(const D3DRMMATRIX4D& projection, D3DVALUE front, D3DVALUE back) override; void SetFrustumPlanes(const Plane* frustumPlanes) override; - Uint32 GetTextureId(IDirect3DRMTexture* texture, bool isUi) override; + Uint32 GetTextureId(IDirect3DRMTexture* texture, bool isUI, float scaleX, float scaleY) override; Uint32 GetMeshId(IDirect3DRMMesh* mesh, const MeshGroup* meshGroup) override; HRESULT BeginFrame() override; void EnableTransparency() override; diff --git a/miniwin/src/internal/d3drmrenderer_opengles2.h b/miniwin/src/internal/d3drmrenderer_opengles2.h index 7dbb1f4b..0472ff80 100644 --- a/miniwin/src/internal/d3drmrenderer_opengles2.h +++ b/miniwin/src/internal/d3drmrenderer_opengles2.h @@ -39,7 +39,7 @@ class OpenGLES2Renderer : public Direct3DRMRenderer { void PushLights(const SceneLight* lightsArray, size_t count) override; void SetProjection(const D3DRMMATRIX4D& projection, D3DVALUE front, D3DVALUE back) override; void SetFrustumPlanes(const Plane* frustumPlanes) override; - Uint32 GetTextureId(IDirect3DRMTexture* texture, bool isUi) override; + Uint32 GetTextureId(IDirect3DRMTexture* texture, bool isUI, float scaleX, float scaleY) override; Uint32 GetMeshId(IDirect3DRMMesh* mesh, const MeshGroup* meshGroup) override; HRESULT BeginFrame() override; void EnableTransparency() override; diff --git a/miniwin/src/internal/d3drmrenderer_sdl3gpu.h b/miniwin/src/internal/d3drmrenderer_sdl3gpu.h index b3afd674..7f948a37 100644 --- a/miniwin/src/internal/d3drmrenderer_sdl3gpu.h +++ b/miniwin/src/internal/d3drmrenderer_sdl3gpu.h @@ -47,7 +47,7 @@ class Direct3DRMSDL3GPURenderer : public Direct3DRMRenderer { static Direct3DRMRenderer* Create(DWORD width, DWORD height); ~Direct3DRMSDL3GPURenderer() override; void PushLights(const SceneLight* vertices, size_t count) override; - Uint32 GetTextureId(IDirect3DRMTexture* texture, bool isUi) override; + Uint32 GetTextureId(IDirect3DRMTexture* texture, bool isUI, float scaleX, float scaleY) override; Uint32 GetMeshId(IDirect3DRMMesh* mesh, const MeshGroup* meshGroup) override; void SetProjection(const D3DRMMATRIX4D& projection, D3DVALUE front, D3DVALUE back) override; void SetFrustumPlanes(const Plane* frustumPlanes) override; diff --git a/miniwin/src/internal/d3drmrenderer_software.h b/miniwin/src/internal/d3drmrenderer_software.h index 8b8c0dde..0c422597 100644 --- a/miniwin/src/internal/d3drmrenderer_software.h +++ b/miniwin/src/internal/d3drmrenderer_software.h @@ -29,7 +29,7 @@ class Direct3DRMSoftwareRenderer : public Direct3DRMRenderer { Direct3DRMSoftwareRenderer(DWORD width, DWORD height); ~Direct3DRMSoftwareRenderer() override; void PushLights(const SceneLight* vertices, size_t count) override; - Uint32 GetTextureId(IDirect3DRMTexture* texture, bool isUi) override; + Uint32 GetTextureId(IDirect3DRMTexture* texture, bool isUI, float scaleX, float scaleY) override; Uint32 GetMeshId(IDirect3DRMMesh* mesh, const MeshGroup* meshGroup) override; void SetProjection(const D3DRMMATRIX4D& projection, D3DVALUE front, D3DVALUE back) override; void SetFrustumPlanes(const Plane* frustumPlanes) override; From b2156c94bf6170cacd22f167199912720777d470 Mon Sep 17 00:00:00 2001 From: Anders Jenbo Date: Sat, 5 Jul 2025 05:21:32 +0200 Subject: [PATCH 073/188] Fix uninitialized presenter (#525) --- LEGO1/lego/legoomni/src/worlds/registrationbook.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/LEGO1/lego/legoomni/src/worlds/registrationbook.cpp b/LEGO1/lego/legoomni/src/worlds/registrationbook.cpp index adaf45d4..709b338d 100644 --- a/LEGO1/lego/legoomni/src/worlds/registrationbook.cpp +++ b/LEGO1/lego/legoomni/src/worlds/registrationbook.cpp @@ -671,14 +671,14 @@ MxLong RegistrationBook::HandlePathStruct(LegoPathStructNotificationParam& p_par MxBool RegistrationBook::CreateSurface() { MxCompositePresenterList* presenters = m_checkmark[0]->GetList(); - MxStillPresenter *presenter, *uninitialized; + MxStillPresenter* presenter; if (presenters) { if (presenters->begin() != presenters->end()) { presenter = (MxStillPresenter*) presenters->front(); } else { - presenter = uninitialized; // intentionally uninitialized variable + presenter = NULL; } if (presenter) { From 761dc31ac04433a4019c57908c32aa50e7fa5f1e Mon Sep 17 00:00:00 2001 From: Anders Jenbo Date: Sat, 5 Jul 2025 07:39:51 +0200 Subject: [PATCH 074/188] Render buttons as sprites (#488) --- LEGO1/omni/src/video/mxdisplaysurface.cpp | 49 +++++++++++++++++------ 1 file changed, 37 insertions(+), 12 deletions(-) diff --git a/LEGO1/omni/src/video/mxdisplaysurface.cpp b/LEGO1/omni/src/video/mxdisplaysurface.cpp index 5d08a64b..612caba4 100644 --- a/LEGO1/omni/src/video/mxdisplaysurface.cpp +++ b/LEGO1/omni/src/video/mxdisplaysurface.cpp @@ -409,9 +409,16 @@ void MxDisplaySurface::VTable0x28( return; } - DDCOLORKEY colorKey; - colorKey.dwColorSpaceLowValue = colorKey.dwColorSpaceHighValue = RGB555_CREATE(0x1f, 0, 0x1f); - tempSurface->SetColorKey(DDCKEY_SRCBLT, &colorKey); + if (m_surfaceDesc.ddpfPixelFormat.dwRGBBitCount != 32) { + DDCOLORKEY colorKey; + if (m_surfaceDesc.ddpfPixelFormat.dwRGBBitCount == 8) { + colorKey.dwColorSpaceLowValue = colorKey.dwColorSpaceHighValue = 0x10; + } + else { + colorKey.dwColorSpaceLowValue = colorKey.dwColorSpaceHighValue = RGB555_CREATE(0x1f, 0, 0x1f); + } + tempSurface->SetColorKey(DDCKEY_SRCBLT, &colorKey); + } DDSURFACEDESC tempDesc; memset(&tempDesc, 0, sizeof(tempDesc)); @@ -503,29 +510,43 @@ void MxDisplaySurface::VTable0x30( DDSURFACEDESC ddsd; memset(&ddsd, 0, sizeof(ddsd)); ddsd.dwSize = sizeof(ddsd); + ddsd.dwFlags = DDSD_WIDTH | DDSD_HEIGHT | DDSD_PIXELFORMAT | DDSD_CAPS; + ddsd.dwWidth = p_width; + ddsd.dwHeight = p_height; + ddsd.ddpfPixelFormat = m_surfaceDesc.ddpfPixelFormat; + ddsd.ddsCaps.dwCaps = DDSCAPS_OFFSCREENPLAIN; - HRESULT hr = m_ddSurface2->Lock(NULL, &ddsd, DDLOCK_WAIT | DDLOCK_WRITEONLY, NULL); - if (hr == DDERR_SURFACELOST) { - m_ddSurface2->Restore(); - hr = m_ddSurface2->Lock(NULL, &ddsd, DDLOCK_WAIT | DDLOCK_WRITEONLY, NULL); + LPDIRECTDRAW draw = MVideoManager()->GetDirectDraw(); + LPDIRECTDRAWSURFACE tempSurface = nullptr; + if (draw->CreateSurface(&ddsd, &tempSurface, nullptr) != DD_OK || !tempSurface) { + return; } - if (hr != DD_OK) { + DDCOLORKEY colorKey; + colorKey.dwColorSpaceLowValue = colorKey.dwColorSpaceHighValue = 0; + tempSurface->SetColorKey(DDCKEY_SRCBLT, &colorKey); + + DDSURFACEDESC tempDesc; + memset(&tempDesc, 0, sizeof(tempDesc)); + tempDesc.dwSize = sizeof(tempDesc); + + if (tempSurface->Lock(NULL, &tempDesc, DDLOCK_WAIT | DDLOCK_WRITEONLY, NULL) != DD_OK) { + tempSurface->Release(); return; } MxU8* data = p_bitmap->GetStart(p_left, p_top); MxS32 bytesPerPixel = m_surfaceDesc.ddpfPixelFormat.dwRGBBitCount / 8; - MxU8* surface = (MxU8*) ddsd.lpSurface + (bytesPerPixel * p_right) + (p_bottom * ddsd.lPitch); + MxU8* surface = (MxU8*) tempDesc.lpSurface; if (p_RLE) { MxS32 size = p_bitmap->GetBmiHeader()->biSizeImage; - DrawTransparentRLE(data, surface, size, p_width, p_height, ddsd.lPitch, bytesPerPixel * 8); + DrawTransparentRLE(data, surface, size, p_width, p_height, tempDesc.lPitch, bytesPerPixel * 8); } else { MxLong stride = -p_width + GetAdjustedStride(p_bitmap); - MxLong length = -bytesPerPixel * p_width + ddsd.lPitch; + MxLong length = -bytesPerPixel * p_width + tempDesc.lPitch; for (MxS32 i = 0; i < p_height; i++) { for (MxS32 j = 0; j < p_width; j++) { @@ -550,7 +571,11 @@ void MxDisplaySurface::VTable0x30( } } - m_ddSurface2->Unlock(ddsd.lpSurface); + tempSurface->Unlock(NULL); + + m_ddSurface2->BltFast(p_right, p_bottom, tempSurface, NULL, DDBLTFAST_WAIT | DDBLTFAST_SRCCOLORKEY); + + tempSurface->Release(); } // FUNCTION: LEGO1 0x100bb500 From 80514e2af7bec24da2c607de0a25d016f5e12bd5 Mon Sep 17 00:00:00 2001 From: Helloyunho Date: Sun, 6 Jul 2025 04:27:24 +0900 Subject: [PATCH 075/188] Support gamepad (#527) --- ISLE/isleapp.cpp | 94 ++++++++++++++++++- ISLE/isleapp.h | 3 + .../lego/legoomni/include/legoinputmanager.h | 2 +- .../legoomni/src/input/legoinputmanager.cpp | 60 ++++-------- 4 files changed, 115 insertions(+), 44 deletions(-) diff --git a/ISLE/isleapp.cpp b/ISLE/isleapp.cpp index 6cb0375c..cd0a6849 100644 --- a/ISLE/isleapp.cpp +++ b/ISLE/isleapp.cpp @@ -90,6 +90,11 @@ MxS32 g_targetDepth = 16; // GLOBAL: ISLE 0x410064 MxS32 g_reqEnableRMDevice = FALSE; +MxFloat g_lastJoystickMouseX = 0; +MxFloat g_lastJoystickMouseY = 0; +MxFloat g_lastMouseX = 0; +MxFloat g_lastMouseY = 0; + // STRING: ISLE 0x4101dc #define WINDOW_TITLE "LEGO®" @@ -149,6 +154,7 @@ IsleApp::IsleApp() m_maxLod = RealtimeView::GetUserMaxLOD(); m_maxAllowedExtras = m_islandQuality <= 1 ? 10 : 20; m_transitionType = MxTransitionManager::e_mosaic; + m_cursorSensitivity = 4; } // FUNCTION: ISLE 0x4011a0 @@ -270,7 +276,7 @@ SDL_AppResult SDL_AppInit(void** appstate, int argc, char** argv) SDL_SetHint(SDL_HINT_MOUSE_TOUCH_EVENTS, "0"); SDL_SetHint(SDL_HINT_TOUCH_MOUSE_EVENTS, "0"); - if (!SDL_Init(SDL_INIT_VIDEO | SDL_INIT_AUDIO | SDL_INIT_JOYSTICK)) { + if (!SDL_Init(SDL_INIT_VIDEO | SDL_INIT_AUDIO | SDL_INIT_GAMEPAD)) { char buffer[256]; SDL_snprintf( buffer, @@ -379,6 +385,8 @@ SDL_AppResult SDL_AppIterate(void* appstate) if (g_mousemoved) { g_mousemoved = FALSE; } + + g_isle->MoveVirtualMouseViaJoystick(); } return SDL_APP_CONTINUE; @@ -457,6 +465,63 @@ SDL_AppResult SDL_AppEvent(void* appstate, SDL_Event* event) } break; } + case SDL_EVENT_GAMEPAD_BUTTON_DOWN: { + { + if (event->gbutton.button == SDL_GAMEPAD_BUTTON_SOUTH) { + if (InputManager()) { + InputManager()->QueueEvent(c_notificationKeyPress, SDLK_SPACE, 0, 0, SDLK_SPACE); + } + } + if (event->gbutton.button == SDL_GAMEPAD_BUTTON_START) { + if (InputManager()) { + InputManager()->QueueEvent(c_notificationKeyPress, SDLK_ESCAPE, 0, 0, SDLK_ESCAPE); + } + } + } + break; + } + case SDL_EVENT_GAMEPAD_AXIS_MOTION: { + MxS16 axisValue = 0; + if (event->gaxis.value < -8000 || event->gaxis.value > 8000) { + // Ignore small axis values + axisValue = event->gaxis.value; + } + if (event->gaxis.axis == SDL_GAMEPAD_AXIS_RIGHTX) { + g_lastJoystickMouseX = ((MxFloat) axisValue) / SDL_JOYSTICK_AXIS_MAX * g_isle->GetCursorSensitivity(); + } + else if (event->gaxis.axis == SDL_GAMEPAD_AXIS_RIGHTY) { + g_lastJoystickMouseY = ((MxFloat) axisValue) / SDL_JOYSTICK_AXIS_MAX * g_isle->GetCursorSensitivity(); + } + else if (event->gaxis.axis == SDL_GAMEPAD_AXIS_RIGHT_TRIGGER) { + if (axisValue != 0) { + g_mousedown = TRUE; + + if (InputManager()) { + InputManager()->QueueEvent( + c_notificationButtonDown, + LegoEventNotificationParam::c_lButtonState, + g_lastMouseX, + g_lastMouseY, + 0 + ); + } + } + else { + g_mousedown = FALSE; + + if (InputManager()) { + InputManager()->QueueEvent( + c_notificationButtonUp, + LegoEventNotificationParam::c_lButtonState, + g_lastMouseX, + g_lastMouseY, + 0 + ); + } + } + } + break; + } case SDL_EVENT_MOUSE_MOTION: #ifdef __EMSCRIPTEN__ if (detectedTouchEvents) { @@ -842,6 +907,8 @@ bool IsleApp::LoadConfig() iniparser_set(dict, "isle:UseJoystick", m_useJoystick ? "true" : "false"); iniparser_set(dict, "isle:JoystickIndex", SDL_itoa(m_joystickIndex, buf, 10)); iniparser_set(dict, "isle:Draw Cursor", m_drawCursor ? "true" : "false"); + SDL_snprintf(buf, sizeof(buf), "%f", m_cursorSensitivity); + iniparser_set(dict, "isle:Cursor Sensitivity", buf); iniparser_set(dict, "isle:Back Buffers in Video RAM", "-1"); @@ -896,6 +963,7 @@ bool IsleApp::LoadConfig() m_useJoystick = iniparser_getboolean(dict, "isle:UseJoystick", m_useJoystick); m_joystickIndex = iniparser_getint(dict, "isle:JoystickIndex", m_joystickIndex); m_drawCursor = iniparser_getboolean(dict, "isle:Draw Cursor", m_drawCursor); + m_cursorSensitivity = iniparser_getdouble(dict, "isle:Cursor Sensitivity", m_cursorSensitivity); MxS32 backBuffersInVRAM = iniparser_getboolean(dict, "isle:Back Buffers in Video RAM", -1); if (backBuffersInVRAM != -1) { @@ -1207,3 +1275,27 @@ IDirect3DRMMiniwinDevice* GetD3DRMMiniwinDevice() } return d3drmMiniwinDev; } + +void IsleApp::MoveVirtualMouseViaJoystick() +{ + if (g_lastJoystickMouseX != 0 || g_lastJoystickMouseY != 0) { + g_mousemoved = TRUE; + + g_lastMouseX = SDL_clamp(g_lastMouseX + g_lastJoystickMouseX, 0, 640); + g_lastMouseY = SDL_clamp(g_lastMouseY + g_lastJoystickMouseY, 0, 480); + + if (InputManager()) { + InputManager()->QueueEvent( + c_notificationMouseMove, + g_mousedown ? LegoEventNotificationParam::c_lButtonState : 0, + g_lastMouseX, + g_lastMouseY, + 0 + ); + } + + if (g_isle->GetDrawCursor()) { + VideoManager()->MoveCursor(Min((MxS32) g_lastMouseX, 639), Min((MxS32) g_lastMouseY, 479)); + } + } +} diff --git a/ISLE/isleapp.h b/ISLE/isleapp.h index 4064dc25..70445d05 100644 --- a/ISLE/isleapp.h +++ b/ISLE/isleapp.h @@ -52,6 +52,7 @@ class IsleApp { SDL_Cursor* GetCursorNo() { return m_cursorNo; } MxS32 GetDrawCursor() { return m_drawCursor; } MxS32 GetGameStarted() { return m_gameStarted; } + MxFloat GetCursorSensitivity() { return m_cursorSensitivity; } void SetWindowActive(MxS32 p_windowActive) { m_windowActive = p_windowActive; } void SetGameStarted(MxS32 p_gameStarted) { m_gameStarted = p_gameStarted; } @@ -59,6 +60,7 @@ class IsleApp { MxResult ParseArguments(int argc, char** argv); MxResult VerifyFilesystem(); void DetectGameVersion(); + void MoveVirtualMouseViaJoystick(); private: char* m_hdPath; // 0x00 @@ -93,6 +95,7 @@ class IsleApp { const CursorBitmap* m_cursorNoBitmap; const CursorBitmap* m_cursorCurrentBitmap; char* m_mediaPath; + MxFloat m_cursorSensitivity; char* m_iniPath; MxFloat m_maxLod; diff --git a/LEGO1/lego/legoomni/include/legoinputmanager.h b/LEGO1/lego/legoomni/include/legoinputmanager.h index 7ccad404..ece1cbd1 100644 --- a/LEGO1/lego/legoomni/include/legoinputmanager.h +++ b/LEGO1/lego/legoomni/include/legoinputmanager.h @@ -166,7 +166,7 @@ class LegoInputManager : public MxPresenter { const bool* m_keyboardState; MxBool m_unk0x195; // 0x195 SDL_JoystickID* m_joyids; - SDL_Joystick* m_joystick; + SDL_Gamepad* m_joystick; MxS32 m_joystickIndex; // 0x19c MxBool m_useJoystick; // 0x334 MxBool m_unk0x335; // 0x335 diff --git a/LEGO1/lego/legoomni/src/input/legoinputmanager.cpp b/LEGO1/lego/legoomni/src/input/legoinputmanager.cpp index 3e9622c2..eca8078b 100644 --- a/LEGO1/lego/legoomni/src/input/legoinputmanager.cpp +++ b/LEGO1/lego/legoomni/src/input/legoinputmanager.cpp @@ -148,26 +148,28 @@ MxResult LegoInputManager::GetNavigationKeyStates(MxU32& p_keyFlags) // FUNCTION: LEGO1 0x1005c240 MxResult LegoInputManager::GetJoystick() { - if (m_joystick != NULL && SDL_JoystickConnected(m_joystick) == TRUE) { + if (m_joystick != NULL && SDL_GamepadConnected(m_joystick) == TRUE) { return SUCCESS; } MxS32 numJoysticks = 0; - if (m_joyids == NULL) { - m_joyids = SDL_GetJoysticks(&numJoysticks); + if (m_joyids != NULL) { + SDL_free(m_joyids); + m_joyids = NULL; } + m_joyids = SDL_GetGamepads(&numJoysticks); if (m_useJoystick != FALSE && numJoysticks != 0) { MxS32 joyid = m_joystickIndex; if (joyid >= 0) { - m_joystick = SDL_OpenJoystick(m_joyids[joyid]); + m_joystick = SDL_OpenGamepad(m_joyids[joyid]); if (m_joystick != NULL) { return SUCCESS; } } for (joyid = 0; joyid < numJoysticks; joyid++) { - m_joystick = SDL_OpenJoystick(m_joyids[joyid]); + m_joystick = SDL_OpenGamepad(m_joyids[joyid]); if (m_joystick != NULL) { return SUCCESS; } @@ -184,54 +186,28 @@ MxResult LegoInputManager::GetJoystickState(MxU32* p_joystickX, MxU32* p_joystic if (GetJoystick() == -1) { if (m_joystick != NULL) { // GetJoystick() failed but handle to joystick is still open, close it - SDL_CloseJoystick(m_joystick); + SDL_CloseGamepad(m_joystick); m_joystick = NULL; } return FAILURE; } - MxS16 xPos = SDL_GetJoystickAxis(m_joystick, 0); - MxS16 yPos = SDL_GetJoystickAxis(m_joystick, 1); - MxU8 hatPos = SDL_GetJoystickHat(m_joystick, 0); + MxS16 xPos = SDL_GetGamepadAxis(m_joystick, SDL_GAMEPAD_AXIS_LEFTX); + MxS16 yPos = SDL_GetGamepadAxis(m_joystick, SDL_GAMEPAD_AXIS_LEFTY); + if (xPos > -8000 && xPos < 8000) { + // Ignore small axis values + xPos = 0; + } + if (yPos > -8000 && yPos < 8000) { + // Ignore small axis values + yPos = 0; + } // normalize values acquired from joystick axes *p_joystickX = ((xPos + 32768) * 100) / 65535; *p_joystickY = ((yPos + 32768) * 100) / 65535; - switch (hatPos) { - case SDL_HAT_CENTERED: - *p_povPosition = (MxU32) -1; - break; - case SDL_HAT_UP: - *p_povPosition = (MxU32) 0; - break; - case SDL_HAT_RIGHT: - *p_povPosition = (MxU32) 9000 / 100; - break; - case SDL_HAT_DOWN: - *p_povPosition = (MxU32) 18000 / 100; - break; - case SDL_HAT_LEFT: - *p_povPosition = (MxU32) 27000 / 100; - break; - case SDL_HAT_RIGHTUP: - *p_povPosition = (MxU32) 4500 / 100; - break; - case SDL_HAT_RIGHTDOWN: - *p_povPosition = (MxU32) 13500 / 100; - break; - case SDL_HAT_LEFTUP: - *p_povPosition = (MxU32) 31500 / 100; - break; - case SDL_HAT_LEFTDOWN: - *p_povPosition = (MxU32) 22500 / 100; - break; - default: - *p_povPosition = (MxU32) -1; - break; - } - return SUCCESS; } From 9862705232ee85a11365bd7ed8e8ad04dd9a396a Mon Sep 17 00:00:00 2001 From: BagelSketches <112355083+BagelSketches@users.noreply.github.com> Date: Sun, 6 Jul 2025 05:33:19 +1000 Subject: [PATCH 076/188] Added building config.exe with qt dlls on msvc 64-bit builds (#520) --- .github/workflows/ci.yml | 7 ++++++- CMakeLists.txt | 19 +++++++++++++++++++ 2 files changed, 25 insertions(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 57d6e539..97b338c8 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -36,7 +36,7 @@ jobs: include: - { name: 'Linux', os: 'ubuntu-latest', dx5: false, config: true, linux: true, werror: true, clang-tidy: true } - { name: 'MSVC (x86)', os: 'windows-latest', dx5: true, config: false, msvc: true, werror: false, clang-tidy: false, vc-arch: 'amd64_x86' } - - { name: 'MSVC (x64)', os: 'windows-latest', dx5: false, config: false, msvc: true, werror: false, clang-tidy: false, vc-arch: 'amd64' } + - { name: 'MSVC (x64)', os: 'windows-latest', dx5: false, config: true, msvc: true, werror: false, clang-tidy: false, vc-arch: 'amd64' } - { name: 'MSVC (arm64)', os: 'windows-latest', dx5: false, config: false, msvc: true, werror: false, clang-tidy: false, vc-arch: 'amd64_arm64' } - { name: 'msys2 mingw32', os: 'windows-latest', dx5: false, config: false, mingw: true, werror: true, clang-tidy: true, msystem: 'mingw32', msys-env: 'mingw-w64-i686', shell: 'msys2 {0}' } - { name: 'msys2 mingw64', os: 'windows-latest', dx5: false, config: true, mingw: true, werror: true, clang-tidy: true, msystem: 'mingw64', msys-env: 'mingw-w64-x86_64', shell: 'msys2 {0}' } @@ -61,6 +61,11 @@ jobs: ${{ matrix.msys-env }}-ninja ${{ matrix.msys-env }}-clang-tools-extra ${{ (matrix.config && format('{0}-qt6-base', matrix.msys-env)) || '' }} + - name: Install Qt + if: ${{ !!matrix.msvc && matrix.config }} + uses: jurplel/install-qt-action@v4 + with: + cache: 'true' - name: Install Linux dependencies (apt-get) if: ${{ matrix.linux }} diff --git a/CMakeLists.txt b/CMakeLists.txt index 00ba25ea..00d087df 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -680,6 +680,25 @@ install(TARGETS isle ${install_extra_targets} LIBRARY DESTINATION "${CMAKE_INSTALL_LIBDIR}" ) if (ISLE_BUILD_CONFIG) + if(WIN32) + find_program(WINDEPLOYQT_EXECUTABLE windeployqt) + if(WINDEPLOYQT_EXECUTABLE) + install(CODE "message(STATUS \"Running windeployqt with minimal dependencies\") + execute_process(COMMAND \"${WINDEPLOYQT_EXECUTABLE}\" + \"$\" + --dir QTLibs + --no-compiler-runtime + --no-opengl-sw + --no-system-d3d-compiler + --no-translations + --no-quick-import + )" + ) + install(DIRECTORY "Build/QTLibs/" DESTINATION "${CMAKE_INSTALL_BINDIR}") + else() + message(STATUS "windeployqt not found: Qt binaries will not be installed") + endif() + endif() install(TARGETS isle-config RUNTIME DESTINATION "${CMAKE_INSTALL_BINDIR}" ) From 6b446d242f6dddc7f4b8f6d6d04c978a8f9670f1 Mon Sep 17 00:00:00 2001 From: Anders Jenbo Date: Sun, 6 Jul 2025 00:01:21 +0200 Subject: [PATCH 077/188] SDL_GPU: Fix transitions on scenes with 2D overlay (#529) --- miniwin/src/d3drm/backends/sdl3gpu/renderer.cpp | 2 +- miniwin/src/d3drm/backends/software/renderer.cpp | 3 +-- miniwin/src/ddraw/framebuffer.cpp | 3 +-- 3 files changed, 3 insertions(+), 5 deletions(-) diff --git a/miniwin/src/d3drm/backends/sdl3gpu/renderer.cpp b/miniwin/src/d3drm/backends/sdl3gpu/renderer.cpp index 0609bbdc..7a6589f0 100644 --- a/miniwin/src/d3drm/backends/sdl3gpu/renderer.cpp +++ b/miniwin/src/d3drm/backends/sdl3gpu/renderer.cpp @@ -1009,7 +1009,7 @@ void Direct3DRMSDL3GPURenderer::Download(SDL_Surface* target) } SDL_Surface* renderedImage = - SDL_CreateSurfaceFrom(width, height, SDL_PIXELFORMAT_ARGB8888, downloadedData, width * 4); + SDL_CreateSurfaceFrom(width, height, SDL_PIXELFORMAT_XRGB8888, downloadedData, width * 4); SDL_BlitSurfaceScaled(renderedImage, nullptr, target, nullptr, SDL_SCALEMODE_NEAREST); SDL_DestroySurface(renderedImage); diff --git a/miniwin/src/d3drm/backends/software/renderer.cpp b/miniwin/src/d3drm/backends/software/renderer.cpp index bd99d2aa..48c9c3ef 100644 --- a/miniwin/src/d3drm/backends/software/renderer.cpp +++ b/miniwin/src/d3drm/backends/software/renderer.cpp @@ -761,10 +761,9 @@ void Direct3DRMSoftwareRenderer::Resize(int width, int height, const ViewportTra void Direct3DRMSoftwareRenderer::Clear(float r, float g, float b) { - SDL_Rect rect = {0, 0, m_renderedImage->w, m_renderedImage->h}; const SDL_PixelFormatDetails* details = SDL_GetPixelFormatDetails(m_renderedImage->format); Uint32 color = SDL_MapRGB(details, m_palette, r * 255, g * 255, b * 255); - SDL_FillSurfaceRect(m_renderedImage, &rect, color); + SDL_FillSurfaceRect(m_renderedImage, nullptr, color); } void Direct3DRMSoftwareRenderer::Flip() diff --git a/miniwin/src/ddraw/framebuffer.cpp b/miniwin/src/ddraw/framebuffer.cpp index 843c36ec..ea66e96b 100644 --- a/miniwin/src/ddraw/framebuffer.cpp +++ b/miniwin/src/ddraw/framebuffer.cpp @@ -172,11 +172,10 @@ HRESULT FrameBufferImpl::Lock(LPRECT lpDestRect, DDSURFACEDESC* lpDDSurfaceDesc, return DDERR_GENERIC; } if ((dwFlags & DDLOCK_WRITEONLY) == DDLOCK_WRITEONLY) { - SDL_Rect rect = {0, 0, m_transferBuffer->m_surface->w, m_transferBuffer->m_surface->h}; const SDL_PixelFormatDetails* details = SDL_GetPixelFormatDetails(m_transferBuffer->m_surface->format); SDL_Palette* palette = m_palette ? static_cast(m_palette)->m_palette : nullptr; Uint32 color = SDL_MapRGBA(details, palette, 0, 0, 0, 0); - SDL_FillSurfaceRect(m_transferBuffer->m_surface, &rect, color); + SDL_FillSurfaceRect(m_transferBuffer->m_surface, nullptr, color); } else { DDRenderer->Download(m_transferBuffer->m_surface); From d3aecadb07c042e4650867d77e396c363f332f10 Mon Sep 17 00:00:00 2001 From: Fabian Neundorf Date: Sun, 6 Jul 2025 01:55:59 +0200 Subject: [PATCH 078/188] Clear unknown in `LegoModelPresenter` (#1608) --- LEGO1/lego/legoomni/include/legomodelpresenter.h | 2 +- LEGO1/lego/legoomni/src/entity/legoworldpresenter.cpp | 2 +- LEGO1/lego/legoomni/src/video/legomodelpresenter.cpp | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/LEGO1/lego/legoomni/include/legomodelpresenter.h b/LEGO1/lego/legoomni/include/legomodelpresenter.h index 21e47afd..62117841 100644 --- a/LEGO1/lego/legoomni/include/legomodelpresenter.h +++ b/LEGO1/lego/legoomni/include/legomodelpresenter.h @@ -46,7 +46,7 @@ class LegoModelPresenter : public MxVideoPresenter { void ReadyTickle() override; // vtable+0x18 void ParseExtra() override; // vtable+0x30 - MxResult FUN_1007ff70(MxDSChunk& p_chunk, LegoEntity* p_entity, MxBool p_roiVisible, LegoWorld* p_world); + MxResult CreateROI(MxDSChunk& p_chunk, LegoEntity* p_entity, MxBool p_roiVisible, LegoWorld* p_world); void Reset() { diff --git a/LEGO1/lego/legoomni/src/entity/legoworldpresenter.cpp b/LEGO1/lego/legoomni/src/entity/legoworldpresenter.cpp index 91c413e1..6bd3ca51 100644 --- a/LEGO1/lego/legoomni/src/entity/legoworldpresenter.cpp +++ b/LEGO1/lego/legoomni/src/entity/legoworldpresenter.cpp @@ -393,7 +393,7 @@ MxResult LegoWorldPresenter::LoadWorldModel(ModelDbModel& p_model, FILE* p_wdbFi } modelPresenter.SetAction(&action); - modelPresenter.FUN_1007ff70(chunk, createdEntity, p_model.m_visible, p_world); + modelPresenter.CreateROI(chunk, createdEntity, p_model.m_visible, p_world); delete[] buff; return SUCCESS; diff --git a/LEGO1/lego/legoomni/src/video/legomodelpresenter.cpp b/LEGO1/lego/legoomni/src/video/legomodelpresenter.cpp index 3f66c840..dcaab81d 100644 --- a/LEGO1/lego/legoomni/src/video/legomodelpresenter.cpp +++ b/LEGO1/lego/legoomni/src/video/legomodelpresenter.cpp @@ -194,7 +194,7 @@ MxResult LegoModelPresenter::CreateROI(MxDSChunk* p_chunk) // FUNCTION: LEGO1 0x1007ff70 // FUNCTION: BETA10 0x10099061 -MxResult LegoModelPresenter::FUN_1007ff70( +MxResult LegoModelPresenter::CreateROI( MxDSChunk& p_chunk, LegoEntity* p_entity, MxBool p_roiVisible, From 8ffe7b776ab80564fd0ff9edc35d3455d5988806 Mon Sep 17 00:00:00 2001 From: Fabian Neundorf Date: Sun, 6 Jul 2025 03:23:00 +0200 Subject: [PATCH 079/188] Clear unknowns in `LegoControlManager` (#1607) --- .../legoomni/include/legocontrolmanager.h | 32 ++++--- .../lego/legoomni/include/legoinputmanager.h | 4 +- .../src/control/legocontrolmanager.cpp | 94 +++++++++---------- LEGO1/lego/legoomni/src/entity/legoworld.cpp | 8 +- .../legoomni/src/input/legoinputmanager.cpp | 18 ++-- LEGO1/lego/legoomni/src/worlds/infocenter.cpp | 12 +-- 6 files changed, 87 insertions(+), 81 deletions(-) diff --git a/LEGO1/lego/legoomni/include/legocontrolmanager.h b/LEGO1/lego/legoomni/include/legocontrolmanager.h index 828a1f6e..f962ee0c 100644 --- a/LEGO1/lego/legoomni/include/legocontrolmanager.h +++ b/LEGO1/lego/legoomni/include/legocontrolmanager.h @@ -66,27 +66,33 @@ class LegoControlManager : public MxCore { return !strcmp(p_name, LegoControlManager::ClassName()) || MxCore::IsA(p_name); } - void FUN_10028df0(MxPresenterList* p_presenterList); + void SetPresenterList(MxPresenterList* p_presenterList); void Register(MxCore* p_listener); void Unregister(MxCore* p_listener); - MxBool FUN_10029210(LegoEventNotificationParam& p_param, MxPresenter* p_presenter); - void FUN_100293c0(MxU32 p_objectId, const char* p_atom, MxS16 p_unk0x4e); - MxControlPresenter* FUN_100294e0(MxS32 p_x, MxS32 p_y); - MxBool FUN_10029630(); - MxBool FUN_10029750(); - void FUN_100292e0(); + MxBool HandleButtonDown(LegoEventNotificationParam& p_param, MxPresenter* p_presenter); + void UpdateEnabledChild(MxU32 p_objectId, const char* p_atom, MxS16 p_enabledChild); + MxControlPresenter* GetControlAt(MxS32 p_x, MxS32 p_y); + MxBool HandleButtonDown(); + MxBool HandleButtonUp(); + void Notify(); - undefined4 GetUnknown0x0c() { return m_unk0x0c; } - undefined GetUnknown0x10() { return m_unk0x10; } + MxU32 HandleUpNextTickle() { return m_handleUpNextTickle; } + MxBool IsSecondButtonDown() { return m_secondButtonDown; } // SYNTHETIC: LEGO1 0x10028d40 // LegoControlManager::`scalar deleting destructor' private: - undefined4 m_unk0x08; // 0x08 - undefined4 m_unk0x0c; // 0x0c - MxBool m_unk0x10; // 0x10 - MxPresenter* m_unk0x14; // 0x14 + enum { + e_idle = 0, + e_waitNextTickle = 1, + e_tickled = 2, + }; + + MxU32 m_buttonDownState; // 0x08 + MxU32 m_handleUpNextTickle; // 0x0c + MxBool m_secondButtonDown; // 0x10 + MxPresenter* m_handledPresenter; // 0x14 LegoControlManagerNotificationParam m_event; // 0x18 MxPresenterList* m_presenterList; // 0x44 LegoNotifyList m_notifyList; // 0x48 diff --git a/LEGO1/lego/legoomni/include/legoinputmanager.h b/LEGO1/lego/legoomni/include/legoinputmanager.h index a199d7d7..27e53fcb 100644 --- a/LEGO1/lego/legoomni/include/legoinputmanager.h +++ b/LEGO1/lego/legoomni/include/legoinputmanager.h @@ -13,8 +13,8 @@ class LegoCameraController; class LegoControlManager; class LegoWorld; -extern MxS32 g_unk0x100f31b0; -extern const char* g_unk0x100f31b4; +extern MxS32 g_clickedObjectId; +extern const char* g_clickedAtom; // VTABLE: LEGO1 0x100d87b8 // class MxCollection diff --git a/LEGO1/lego/legoomni/src/control/legocontrolmanager.cpp b/LEGO1/lego/legoomni/src/control/legocontrolmanager.cpp index dc355be7..7851a52a 100644 --- a/LEGO1/lego/legoomni/src/control/legocontrolmanager.cpp +++ b/LEGO1/lego/legoomni/src/control/legocontrolmanager.cpp @@ -17,10 +17,10 @@ DECOMP_SIZE_ASSERT(LegoEventNotificationParam, 0x20) LegoControlManager::LegoControlManager() { m_presenterList = NULL; - m_unk0x08 = 0; - m_unk0x0c = 0; - m_unk0x10 = FALSE; - m_unk0x14 = NULL; + m_buttonDownState = e_idle; + m_handleUpNextTickle = 0; + m_secondButtonDown = FALSE; + m_handledPresenter = NULL; TickleManager()->RegisterClient(this, 10); } @@ -31,11 +31,11 @@ LegoControlManager::~LegoControlManager() } // FUNCTION: LEGO1 0x10028df0 -void LegoControlManager::FUN_10028df0(MxPresenterList* p_presenterList) +void LegoControlManager::SetPresenterList(MxPresenterList* p_presenterList) { m_presenterList = p_presenterList; - g_unk0x100f31b0 = -1; - g_unk0x100f31b4 = NULL; + g_clickedObjectId = -1; + g_clickedAtom = NULL; } // FUNCTION: LEGO1 0x10028e10 @@ -56,10 +56,10 @@ void LegoControlManager::Unregister(MxCore* p_listener) } // FUNCTION: LEGO1 0x10029210 -MxBool LegoControlManager::FUN_10029210(LegoEventNotificationParam& p_param, MxPresenter* p_presenter) +MxBool LegoControlManager::HandleButtonDown(LegoEventNotificationParam& p_param, MxPresenter* p_presenter) { if (m_presenterList != NULL && m_presenterList->GetNumElements() != 0) { - m_unk0x14 = p_presenter; + m_handledPresenter = p_presenter; if (p_param.GetNotification() == c_notificationButtonUp || p_param.GetNotification() == c_notificationButtonDown) { @@ -71,28 +71,28 @@ MxBool LegoControlManager::FUN_10029210(LegoEventNotificationParam& p_param, MxP m_event.SetKey(p_param.GetKey()); if (p_param.GetNotification() == c_notificationButtonUp) { - if (m_unk0x10 == TRUE) { - m_unk0x10 = FALSE; + if (m_secondButtonDown == TRUE) { + m_secondButtonDown = FALSE; return TRUE; } - if (g_unk0x100f31b0 != -1 && g_unk0x100f31b4 != NULL) { - if (m_unk0x08 == 2) { - return FUN_10029750(); + if (g_clickedObjectId != -1 && g_clickedAtom != NULL) { + if (m_buttonDownState == e_tickled) { + return HandleButtonUp(); } else { - m_unk0x0c = 1; + m_handleUpNextTickle = 1; return TRUE; } } } else if (p_param.GetNotification() == c_notificationButtonDown) { - if (m_unk0x0c == 1) { - m_unk0x10 = TRUE; + if (m_handleUpNextTickle == 1) { + m_secondButtonDown = TRUE; return TRUE; } else { - return FUN_10029630(); + return HandleButtonDown(); } } } @@ -100,15 +100,15 @@ MxBool LegoControlManager::FUN_10029210(LegoEventNotificationParam& p_param, MxP return FALSE; } else { - g_unk0x100f31b0 = -1; - g_unk0x100f31b4 = NULL; + g_clickedObjectId = -1; + g_clickedAtom = NULL; return FALSE; } } // FUNCTION: LEGO1 0x100292e0 -void LegoControlManager::FUN_100292e0() +void LegoControlManager::Notify() { LegoNotifyListCursor cursor(&m_notifyList); MxCore* target; @@ -121,7 +121,7 @@ void LegoControlManager::FUN_100292e0() } // FUNCTION: LEGO1 0x100293c0 -void LegoControlManager::FUN_100293c0(MxU32 p_objectId, const char* p_atom, MxS16 p_unk0x4e) +void LegoControlManager::UpdateEnabledChild(MxU32 p_objectId, const char* p_atom, MxS16 p_enabledChild) { if (m_presenterList) { MxPresenterListCursor cursor(m_presenterList); @@ -131,11 +131,11 @@ void LegoControlManager::FUN_100293c0(MxU32 p_objectId, const char* p_atom, MxS1 MxDSAction* action = control->GetAction(); if (action->GetObjectId() == p_objectId && action->GetAtomId().GetInternal() == p_atom) { - ((MxControlPresenter*) control)->UpdateEnabledChild(p_unk0x4e); + ((MxControlPresenter*) control)->UpdateEnabledChild(p_enabledChild); if (((MxControlPresenter*) control)->GetEnabledChild() == 0) { - g_unk0x100f31b0 = -1; - g_unk0x100f31b4 = NULL; + g_clickedObjectId = -1; + g_clickedAtom = NULL; break; } } @@ -145,7 +145,7 @@ void LegoControlManager::FUN_100293c0(MxU32 p_objectId, const char* p_atom, MxS1 // FUNCTION: LEGO1 0x100294e0 // FUNCTION: BETA10 0x1007c92f -MxControlPresenter* LegoControlManager::FUN_100294e0(MxS32 p_x, MxS32 p_y) +MxControlPresenter* LegoControlManager::GetControlAt(MxS32 p_x, MxS32 p_y) { if (m_presenterList) { MxPresenterListCursor cursor(m_presenterList); @@ -167,29 +167,29 @@ MxControlPresenter* LegoControlManager::FUN_100294e0(MxS32 p_x, MxS32 p_y) // FUNCTION: LEGO1 0x10029600 MxResult LegoControlManager::Tickle() { - if (m_unk0x08 == 2 && m_unk0x0c == 1) { + if (m_buttonDownState == e_tickled && m_handleUpNextTickle == 1) { m_event.SetNotification(c_notificationButtonUp); - FUN_10029750(); + HandleButtonUp(); return 0; } - else if (m_unk0x08 == 1) { - m_unk0x08 = 2; + else if (m_buttonDownState == e_waitNextTickle) { + m_buttonDownState = e_tickled; } return 0; } // FUNCTION: LEGO1 0x10029630 -MxBool LegoControlManager::FUN_10029630() +MxBool LegoControlManager::HandleButtonDown() { MxPresenterListCursor cursor(m_presenterList); MxPresenter* presenter; while (cursor.Next(presenter)) { - if (((MxControlPresenter*) presenter)->Notify(&m_event, m_unk0x14)) { - g_unk0x100f31b0 = m_event.m_clickedObjectId; - g_unk0x100f31b4 = m_event.GetClickedAtom(); - FUN_100292e0(); - m_unk0x08 = 1; + if (((MxControlPresenter*) presenter)->Notify(&m_event, m_handledPresenter)) { + g_clickedObjectId = m_event.m_clickedObjectId; + g_clickedAtom = m_event.GetClickedAtom(); + Notify(); + m_buttonDownState = e_waitNextTickle; return TRUE; } } @@ -198,29 +198,29 @@ MxBool LegoControlManager::FUN_10029630() } // FUNCTION: LEGO1 0x10029750 -MxBool LegoControlManager::FUN_10029750() +MxBool LegoControlManager::HandleButtonUp() { MxPresenterListCursor cursor(m_presenterList); MxPresenter* presenter; while (cursor.Next(presenter)) { - if (presenter->GetAction() && presenter->GetAction()->GetObjectId() == g_unk0x100f31b0 && - presenter->GetAction()->GetAtomId().GetInternal() == g_unk0x100f31b4) { - if (((MxControlPresenter*) presenter)->Notify(&m_event, m_unk0x14)) { - FUN_100292e0(); + if (presenter->GetAction() && presenter->GetAction()->GetObjectId() == g_clickedObjectId && + presenter->GetAction()->GetAtomId().GetInternal() == g_clickedAtom) { + if (((MxControlPresenter*) presenter)->Notify(&m_event, m_handledPresenter)) { + Notify(); } - g_unk0x100f31b0 = -1; - g_unk0x100f31b4 = NULL; + g_clickedObjectId = -1; + g_clickedAtom = NULL; - m_unk0x08 = 0; - m_unk0x0c = 0; + m_buttonDownState = e_idle; + m_handleUpNextTickle = 0; return TRUE; } } - g_unk0x100f31b0 = -1; - g_unk0x100f31b4 = NULL; + g_clickedObjectId = -1; + g_clickedAtom = NULL; return FALSE; } diff --git a/LEGO1/lego/legoomni/src/entity/legoworld.cpp b/LEGO1/lego/legoomni/src/entity/legoworld.cpp index c12d32e4..c34a985b 100644 --- a/LEGO1/lego/legoomni/src/entity/legoworld.cpp +++ b/LEGO1/lego/legoomni/src/entity/legoworld.cpp @@ -81,7 +81,7 @@ MxResult LegoWorld::Create(MxDSAction& p_dsAction) } SetCurrentWorld(this); - ControlManager()->FUN_10028df0(&m_controlPresenters); + ControlManager()->SetPresenterList(&m_controlPresenters); } SetIsWorldActive(TRUE); @@ -96,7 +96,7 @@ void LegoWorld::Destroy(MxBool p_fromDestructor) m_destroyed = TRUE; if (CurrentWorld() == this) { - ControlManager()->FUN_10028df0(NULL); + ControlManager()->SetPresenterList(NULL); SetCurrentWorld(NULL); } @@ -722,7 +722,7 @@ void LegoWorld::Enable(MxBool p_enable) } SetCurrentWorld(this); - ControlManager()->FUN_10028df0(&m_controlPresenters); + ControlManager()->SetPresenterList(&m_controlPresenters); InputManager()->SetCamera(m_cameraController); if (m_cameraController) { @@ -779,7 +779,7 @@ void LegoWorld::Enable(MxBool p_enable) } if (CurrentWorld() && CurrentWorld() == this) { - ControlManager()->FUN_10028df0(NULL); + ControlManager()->SetPresenterList(NULL); Lego()->SetCurrentWorld(NULL); } diff --git a/LEGO1/lego/legoomni/src/input/legoinputmanager.cpp b/LEGO1/lego/legoomni/src/input/legoinputmanager.cpp index f3c7a9d3..756d51aa 100644 --- a/LEGO1/lego/legoomni/src/input/legoinputmanager.cpp +++ b/LEGO1/lego/legoomni/src/input/legoinputmanager.cpp @@ -17,10 +17,10 @@ DECOMP_SIZE_ASSERT(LegoNotifyListCursor, 0x10) DECOMP_SIZE_ASSERT(LegoEventQueue, 0x18) // GLOBAL: LEGO1 0x100f31b0 -MxS32 g_unk0x100f31b0 = -1; +MxS32 g_clickedObjectId = -1; // GLOBAL: LEGO1 0x100f31b4 -const char* g_unk0x100f31b4 = NULL; +const char* g_clickedAtom = NULL; // GLOBAL: LEGO1 0x100f67b8 MxBool g_unk0x100f67b8 = TRUE; @@ -429,23 +429,23 @@ MxBool LegoInputManager::ProcessOneEvent(LegoEventNotificationParam& p_param) if (presenter->GetDisplayZ() < 0) { processRoi = FALSE; - if (m_controlManager->FUN_10029210(p_param, presenter)) { + if (m_controlManager->HandleButtonDown(p_param, presenter)) { return TRUE; } } else { LegoROI* roi = PickROI(p_param.GetX(), p_param.GetY()); - if (roi == NULL && m_controlManager->FUN_10029210(p_param, presenter)) { + if (roi == NULL && m_controlManager->HandleButtonDown(p_param, presenter)) { return TRUE; } } } } else if (p_param.GetNotification() == c_notificationButtonUp) { - if (g_unk0x100f31b0 != -1 || m_controlManager->GetUnknown0x10() || - m_controlManager->GetUnknown0x0c() == 1) { - MxBool result = m_controlManager->FUN_10029210(p_param, NULL); + if (g_clickedObjectId != -1 || m_controlManager->IsSecondButtonDown() || + m_controlManager->HandleUpNextTickle() == 1) { + MxBool result = m_controlManager->HandleButtonDown(p_param, NULL); StopAutoDragTimer(); m_unk0x80 = FALSE; @@ -585,6 +585,6 @@ void LegoInputManager::StopAutoDragTimer() void LegoInputManager::EnableInputProcessing() { m_unk0x88 = FALSE; - g_unk0x100f31b0 = -1; - g_unk0x100f31b4 = NULL; + g_clickedObjectId = -1; + g_clickedAtom = NULL; } diff --git a/LEGO1/lego/legoomni/src/worlds/infocenter.cpp b/LEGO1/lego/legoomni/src/worlds/infocenter.cpp index 55171729..40463c97 100644 --- a/LEGO1/lego/legoomni/src/worlds/infocenter.cpp +++ b/LEGO1/lego/legoomni/src/worlds/infocenter.cpp @@ -335,7 +335,7 @@ MxLong Infocenter::HandleEndAction(MxEndActionNotificationParam& p_param) } if (action->GetObjectId() == InfomainScript::c_iicx26in_RunAnim) { - ControlManager()->FUN_100293c0(InfomainScript::c_BigInfo_Ctl, action->GetAtomId().GetInternal(), 0); + ControlManager()->UpdateEnabledChild(InfomainScript::c_BigInfo_Ctl, action->GetAtomId().GetInternal(), 0); m_unk0x1d6 = 0; } @@ -747,7 +747,7 @@ MxLong Infocenter::HandleKeyPress(MxS8 p_key) MxU8 Infocenter::HandleButtonUp(MxS32 p_x, MxS32 p_y) { if (m_dragPresenter) { - MxControlPresenter* control = InputManager()->GetControlManager()->FUN_100294e0(p_x - 1, p_y - 1); + MxControlPresenter* control = InputManager()->GetControlManager()->GetControlAt(p_x - 1, p_y - 1); switch (m_dragPresenter->GetAction()->GetObjectId()) { case InfomainScript::c_PepperHot_Bitmap: @@ -1236,16 +1236,16 @@ MxResult Infocenter::Tickle() m_unk0x1d6 += 100; if (m_unk0x1d6 > 3400 && m_unk0x1d6 < 3650) { - ControlManager()->FUN_100293c0(InfomainScript::c_BigInfo_Ctl, m_atomId.GetInternal(), 1); + ControlManager()->UpdateEnabledChild(InfomainScript::c_BigInfo_Ctl, m_atomId.GetInternal(), 1); } else if (m_unk0x1d6 > 3650 && m_unk0x1d6 < 3900) { - ControlManager()->FUN_100293c0(InfomainScript::c_BigInfo_Ctl, m_atomId.GetInternal(), 0); + ControlManager()->UpdateEnabledChild(InfomainScript::c_BigInfo_Ctl, m_atomId.GetInternal(), 0); } else if (m_unk0x1d6 > 3900 && m_unk0x1d6 < 4150) { - ControlManager()->FUN_100293c0(InfomainScript::c_BigInfo_Ctl, m_atomId.GetInternal(), 1); + ControlManager()->UpdateEnabledChild(InfomainScript::c_BigInfo_Ctl, m_atomId.GetInternal(), 1); } else if (m_unk0x1d6 > 4400) { - ControlManager()->FUN_100293c0(InfomainScript::c_BigInfo_Ctl, m_atomId.GetInternal(), 0); + ControlManager()->UpdateEnabledChild(InfomainScript::c_BigInfo_Ctl, m_atomId.GetInternal(), 0); m_unk0x1d6 = 0; } } From 004b88e02e50cb703aa7ec8a54f5e2081161cae8 Mon Sep 17 00:00:00 2001 From: Anders Jenbo Date: Sun, 6 Jul 2025 07:06:14 +0200 Subject: [PATCH 080/188] Start virtual mouse at center of screen (#532) --- ISLE/isleapp.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ISLE/isleapp.cpp b/ISLE/isleapp.cpp index cd0a6849..585134a0 100644 --- a/ISLE/isleapp.cpp +++ b/ISLE/isleapp.cpp @@ -92,8 +92,8 @@ MxS32 g_reqEnableRMDevice = FALSE; MxFloat g_lastJoystickMouseX = 0; MxFloat g_lastJoystickMouseY = 0; -MxFloat g_lastMouseX = 0; -MxFloat g_lastMouseY = 0; +MxFloat g_lastMouseX = 320; +MxFloat g_lastMouseY = 240; // STRING: ISLE 0x4101dc #define WINDOW_TITLE "LEGO®" From 1b8f6620904ee6a707b501ae18e636cc16b1cf6c Mon Sep 17 00:00:00 2001 From: Anders Jenbo Date: Sun, 6 Jul 2025 07:12:20 +0200 Subject: [PATCH 081/188] Correctly init p_povPosition (#531) --- LEGO1/lego/legoomni/src/input/legoinputmanager.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/LEGO1/lego/legoomni/src/input/legoinputmanager.cpp b/LEGO1/lego/legoomni/src/input/legoinputmanager.cpp index eca8078b..b773a63e 100644 --- a/LEGO1/lego/legoomni/src/input/legoinputmanager.cpp +++ b/LEGO1/lego/legoomni/src/input/legoinputmanager.cpp @@ -207,6 +207,7 @@ MxResult LegoInputManager::GetJoystickState(MxU32* p_joystickX, MxU32* p_joystic // normalize values acquired from joystick axes *p_joystickX = ((xPos + 32768) * 100) / 65535; *p_joystickY = ((yPos + 32768) * 100) / 65535; + *p_povPosition = -1; return SUCCESS; } From 58338fecfc39c72426019fb1402a96e0e3f41c91 Mon Sep 17 00:00:00 2001 From: Anders Jenbo Date: Sun, 6 Jul 2025 07:17:06 +0200 Subject: [PATCH 082/188] Faster and cleaner mosaic transition (#530) --- .../src/common/mxtransitionmanager.cpp | 152 ++++++++++++------ miniwin/include/miniwin/ddraw.h | 2 + miniwin/src/ddraw/ddsurface.cpp | 18 +++ miniwin/src/ddraw/framebuffer.cpp | 7 +- miniwin/src/internal/framebuffer_impl.h | 1 + 5 files changed, 127 insertions(+), 53 deletions(-) diff --git a/LEGO1/lego/legoomni/src/common/mxtransitionmanager.cpp b/LEGO1/lego/legoomni/src/common/mxtransitionmanager.cpp index 27086eb3..b66b26e4 100644 --- a/LEGO1/lego/legoomni/src/common/mxtransitionmanager.cpp +++ b/LEGO1/lego/legoomni/src/common/mxtransitionmanager.cpp @@ -271,8 +271,15 @@ void MxTransitionManager::DissolveTransition() // FUNCTION: LEGO1 0x1004bed0 void MxTransitionManager::MosaicTransition() { + static LPDIRECTDRAWSURFACE g_transitionSurface = nullptr; + static MxU32 g_colors[64][48]; + if (m_animationTimer == 16) { m_animationTimer = 0; + if (g_transitionSurface) { + g_transitionSurface->Release(); + g_transitionSurface = nullptr; + } EndTransition(TRUE); return; } @@ -297,6 +304,84 @@ void MxTransitionManager::MosaicTransition() for (i = 0; i < 48; i++) { m_randomShift[i] = SDL_rand(64); } + + DDSURFACEDESC srcDesc = {}; + srcDesc.dwSize = sizeof(srcDesc); + HRESULT lockRes = m_ddSurface->Lock(NULL, &srcDesc, DDLOCK_WAIT | DDLOCK_READONLY, NULL); + if (lockRes == DDERR_SURFACELOST) { + m_ddSurface->Restore(); + lockRes = m_ddSurface->Lock(NULL, &srcDesc, DDLOCK_WAIT | DDLOCK_READONLY, NULL); + } + + if (lockRes != DD_OK) { + return; + } + + MxS32 bytesPerPixel = srcDesc.ddpfPixelFormat.dwRGBBitCount / 8; + + for (MxS32 col = 0; col < 64; col++) { + for (MxS32 row = 0; row < 48; row++) { + MxS32 xBlock = (m_randomShift[row] + col) % 64; + MxS32 y = row * 10; + MxS32 x = xBlock * 10; + + MxU8* pixelPtr = (MxU8*) srcDesc.lpSurface + y * srcDesc.lPitch + x * bytesPerPixel; + + MxU32 pixel; + switch (bytesPerPixel) { + case 1: + pixel = *pixelPtr; + break; + case 2: + pixel = *(MxU16*) pixelPtr; + break; + default: + pixel = *(MxU32*) pixelPtr; + break; + } + + g_colors[col][row] = pixel; + } + } + + m_ddSurface->Unlock(srcDesc.lpSurface); + + DDSURFACEDESC mainDesc = {}; + mainDesc.dwSize = sizeof(mainDesc); + if (m_ddSurface->GetSurfaceDesc(&mainDesc) != DD_OK) { + return; + } + + DDSURFACEDESC tempDesc = {}; + tempDesc.dwSize = sizeof(tempDesc); + tempDesc.dwFlags = DDSD_WIDTH | DDSD_HEIGHT | DDSD_PIXELFORMAT | DDSD_CAPS; + tempDesc.dwWidth = 64; + tempDesc.dwHeight = 48; + tempDesc.ddpfPixelFormat = mainDesc.ddpfPixelFormat; + tempDesc.ddsCaps.dwCaps = DDSCAPS_OFFSCREENPLAIN; + + if (MVideoManager()->GetDirectDraw()->CreateSurface(&tempDesc, &g_transitionSurface, nullptr) != DD_OK) { + return; + } + + DWORD fillColor = 0x00000000; + switch (mainDesc.ddpfPixelFormat.dwRGBBitCount) { + case 8: + fillColor = 0x10; + break; + case 16: + fillColor = RGB555_CREATE(0x1f, 0, 0x1f); + break; + } + + DDBLTFX bltFx = {}; + bltFx.dwSize = sizeof(bltFx); + bltFx.dwFillColor = fillColor; + g_transitionSurface->Blt(NULL, NULL, NULL, DDBLT_COLORFILL | DDBLT_WAIT, &bltFx); + + DDCOLORKEY key = {}; + key.dwColorSpaceLowValue = key.dwColorSpaceHighValue = fillColor; + g_transitionSurface->SetColorKey(DDCKEY_SRCBLT, &key); } // Run one tick of the animation @@ -304,15 +389,18 @@ void MxTransitionManager::MosaicTransition() memset(&ddsd, 0, sizeof(ddsd)); ddsd.dwSize = sizeof(ddsd); - HRESULT res = m_ddSurface->Lock(NULL, &ddsd, DDLOCK_WAIT, NULL); + HRESULT res = g_transitionSurface->Lock(NULL, &ddsd, DDLOCK_WAIT | DDLOCK_WRITEONLY, NULL); if (res == DDERR_SURFACELOST) { - m_ddSurface->Restore(); - res = m_ddSurface->Lock(NULL, &ddsd, DDLOCK_WAIT, NULL); + g_transitionSurface->Restore(); + res = g_transitionSurface->Lock(NULL, &ddsd, DDLOCK_WAIT | DDLOCK_WRITEONLY, NULL); } if (res == DD_OK) { SubmitCopyRect(&ddsd); + // Combine xShift with this value to target the correct location in the buffer. + MxS32 bytesPerPixel = ddsd.ddpfPixelFormat.dwRGBBitCount / 8; + for (MxS32 col = 0; col < 64; col++) { // Select 4 columns on each tick if (m_animationTimer * 4 > m_columnOrder[col]) { @@ -324,69 +412,29 @@ void MxTransitionManager::MosaicTransition() } for (MxS32 row = 0; row < 48; row++) { - // To do the mosaic effect, we subdivide the 640x480 surface into - // 10x10 pixel blocks. At the chosen block, we sample the top-leftmost - // color and set the other 99 pixels to that value. + MxS32 x = (m_randomShift[row] + col) % 64; + MxU8* dest = (MxU8*) ddsd.lpSurface + row * ddsd.lPitch + x * bytesPerPixel; - // First, get the offset of the 10x10 block that we will sample for this row. - MxS32 xShift = 10 * ((m_randomShift[row] + col) % 64); - - // Combine xShift with this value to target the correct location in the buffer. - MxS32 bytesPerPixel = ddsd.ddpfPixelFormat.dwRGBBitCount / 8; - - // Seek to the sample position. - MxU8* source = (MxU8*) ddsd.lpSurface + 10 * row * ddsd.lPitch + bytesPerPixel * xShift; - - // Sample byte or word depending on display mode. - MxU32 sample; + MxU32 source = g_colors[col][row]; switch (bytesPerPixel) { case 1: - sample = *source; + *dest = (MxU8) source; break; case 2: - sample = *(MxU16*) source; + *((MxU16*) dest) = (MxU16) source; break; default: - sample = *(MxU32*) source; + *((MxU32*) dest) = source; break; } - - // For each of the 10 rows in the 10x10 square: - for (MxS32 k = 10 * row; k < 10 * row + 10; k++) { - void* pos = (MxU8*) ddsd.lpSurface + k * ddsd.lPitch + bytesPerPixel * xShift; - - switch (bytesPerPixel) { - case 1: { - // Optimization: If the pixel is only one byte, we can use memset - memset(pos, sample, 10); - break; - } - case 2: { - MxU16* p = (MxU16*) pos; - for (MxS32 tt = 0; tt < 10; tt++) { - p[tt] = (MxU16) sample; - } - break; - } - default: { - MxU32* p = (MxU32*) pos; - for (MxS32 tt = 0; tt < 10; tt++) { - p[tt] = (MxU32) sample; - } - break; - } - } - } } } SetupCopyRect(&ddsd); - m_ddSurface->Unlock(ddsd.lpSurface); + g_transitionSurface->Unlock(ddsd.lpSurface); - if (VideoManager()->GetVideoParam().Flags().GetFlipSurfaces()) { - LPDIRECTDRAWSURFACE surf = VideoManager()->GetDisplaySurface()->GetDirectDrawSurface1(); - surf->BltFast(0, 0, m_ddSurface, &g_fullScreenRect, DDBLTFAST_WAIT); - } + RECT srcRect = {0, 0, 64, 48}; + m_ddSurface->Blt(&g_fullScreenRect, g_transitionSurface, &srcRect, DDBLT_WAIT | DDBLT_KEYSRC, NULL); m_animationTimer++; } diff --git a/miniwin/include/miniwin/ddraw.h b/miniwin/include/miniwin/ddraw.h index 2a8932a2..0250c099 100644 --- a/miniwin/include/miniwin/ddraw.h +++ b/miniwin/include/miniwin/ddraw.h @@ -162,9 +162,11 @@ ENABLE_BITMASK_OPERATORS(DDBltFastFlags) #define DDLOCK_SURFACEMEMORYPTR DDLockFlags::SURFACEMEMORYPTR #define DDLOCK_WAIT DDLockFlags::WAIT #define DDLOCK_WRITEONLY DDLockFlags::WRITEONLY +#define DDLOCK_READONLY DDLockFlags::READONLY enum class DDLockFlags : uint32_t { SURFACEMEMORYPTR = 0, WAIT = 1 << 0, + READONLY = 1 << 4, WRITEONLY = 1 << 5, }; ENABLE_BITMASK_OPERATORS(DDLockFlags) diff --git a/miniwin/src/ddraw/ddsurface.cpp b/miniwin/src/ddraw/ddsurface.cpp index de250dea..056912fd 100644 --- a/miniwin/src/ddraw/ddsurface.cpp +++ b/miniwin/src/ddraw/ddsurface.cpp @@ -52,6 +52,24 @@ HRESULT DirectDrawSurfaceImpl::Blt( LPDDBLTFX lpDDBltFx ) { + if ((dwFlags & DDBLT_COLORFILL) == DDBLT_COLORFILL) { + Uint8 a = (lpDDBltFx->dwFillColor >> 24) & 0xFF; + Uint8 r = (lpDDBltFx->dwFillColor >> 16) & 0xFF; + Uint8 g = (lpDDBltFx->dwFillColor >> 8) & 0xFF; + Uint8 b = lpDDBltFx->dwFillColor & 0xFF; + + const SDL_PixelFormatDetails* details = SDL_GetPixelFormatDetails(m_surface->format); + Uint32 color = SDL_MapRGBA(details, nullptr, r, g, b, a); + if (lpDestRect) { + SDL_Rect dstRect = ConvertRect(lpDestRect); + SDL_FillSurfaceRect(m_surface, &dstRect, color); + } + else { + SDL_FillSurfaceRect(m_surface, nullptr, color); + } + return DD_OK; + } + auto other = static_cast(lpDDSrcSurface); SDL_Rect srcRect = lpSrcRect ? ConvertRect(lpSrcRect) : SDL_Rect{0, 0, other->m_surface->w, other->m_surface->h}; diff --git a/miniwin/src/ddraw/framebuffer.cpp b/miniwin/src/ddraw/framebuffer.cpp index ea66e96b..13caa434 100644 --- a/miniwin/src/ddraw/framebuffer.cpp +++ b/miniwin/src/ddraw/framebuffer.cpp @@ -171,6 +171,9 @@ HRESULT FrameBufferImpl::Lock(LPRECT lpDestRect, DDSURFACEDESC* lpDDSurfaceDesc, if (!DDRenderer) { return DDERR_GENERIC; } + + m_readOnlyLock = (dwFlags & DDLOCK_READONLY) == DDLOCK_READONLY; + if ((dwFlags & DDLOCK_WRITEONLY) == DDLOCK_WRITEONLY) { const SDL_PixelFormatDetails* details = SDL_GetPixelFormatDetails(m_transferBuffer->m_surface->format); SDL_Palette* palette = m_palette ? static_cast(m_palette)->m_palette : nullptr; @@ -223,7 +226,9 @@ HRESULT FrameBufferImpl::SetPalette(LPDIRECTDRAWPALETTE lpDDPalette) HRESULT FrameBufferImpl::Unlock(LPVOID lpSurfaceData) { m_transferBuffer->Unlock(lpSurfaceData); - BltFast(0, 0, m_transferBuffer, nullptr, DDBLTFAST_WAIT); + if (!m_readOnlyLock) { + BltFast(0, 0, m_transferBuffer, nullptr, DDBLTFAST_WAIT); + } return DD_OK; } diff --git a/miniwin/src/internal/framebuffer_impl.h b/miniwin/src/internal/framebuffer_impl.h index 5e40f7e8..47805e69 100644 --- a/miniwin/src/internal/framebuffer_impl.h +++ b/miniwin/src/internal/framebuffer_impl.h @@ -39,6 +39,7 @@ struct FrameBufferImpl : public IDirectDrawSurface3 { private: uint32_t m_virtualWidth; uint32_t m_virtualHeight; + bool m_readOnlyLock; DirectDrawSurfaceImpl* m_transferBuffer; IDirectDrawPalette* m_palette = nullptr; }; From fd0b6bcacaedb556c129feb21491ce5b0c005a67 Mon Sep 17 00:00:00 2001 From: Anders Jenbo Date: Sun, 6 Jul 2025 07:45:27 +0200 Subject: [PATCH 083/188] Some quick dpad mouse control (#533) --- ISLE/isleapp.cpp | 105 +++++++++++++++++++++++++++++++++++++++++------ 1 file changed, 93 insertions(+), 12 deletions(-) diff --git a/ISLE/isleapp.cpp b/ISLE/isleapp.cpp index 585134a0..6d1c0a99 100644 --- a/ISLE/isleapp.cpp +++ b/ISLE/isleapp.cpp @@ -95,6 +95,11 @@ MxFloat g_lastJoystickMouseY = 0; MxFloat g_lastMouseX = 320; MxFloat g_lastMouseY = 240; +bool g_dpadUp = false; +bool g_dpadDown = false; +bool g_dpadLeft = false; +bool g_dpadRight = false; + // STRING: ISLE 0x4101dc #define WINDOW_TITLE "LEGO®" @@ -466,17 +471,73 @@ SDL_AppResult SDL_AppEvent(void* appstate, SDL_Event* event) break; } case SDL_EVENT_GAMEPAD_BUTTON_DOWN: { - { - if (event->gbutton.button == SDL_GAMEPAD_BUTTON_SOUTH) { - if (InputManager()) { - InputManager()->QueueEvent(c_notificationKeyPress, SDLK_SPACE, 0, 0, SDLK_SPACE); - } + switch (event->gbutton.button) { + case SDL_GAMEPAD_BUTTON_DPAD_UP: + g_dpadUp = true; + break; + case SDL_GAMEPAD_BUTTON_DPAD_DOWN: + g_dpadDown = true; + break; + case SDL_GAMEPAD_BUTTON_DPAD_LEFT: + g_dpadLeft = true; + break; + case SDL_GAMEPAD_BUTTON_DPAD_RIGHT: + g_dpadRight = true; + break; + case SDL_GAMEPAD_BUTTON_EAST: + g_mousedown = TRUE; + if (InputManager()) { + InputManager()->QueueEvent( + c_notificationButtonDown, + LegoEventNotificationParam::c_lButtonState, + g_lastMouseX, + g_lastMouseY, + 0 + ); } - if (event->gbutton.button == SDL_GAMEPAD_BUTTON_START) { - if (InputManager()) { - InputManager()->QueueEvent(c_notificationKeyPress, SDLK_ESCAPE, 0, 0, SDLK_ESCAPE); - } + break; + + case SDL_GAMEPAD_BUTTON_SOUTH: + if (InputManager()) { + InputManager()->QueueEvent(c_notificationKeyPress, SDLK_SPACE, 0, 0, SDLK_SPACE); } + break; + + case SDL_GAMEPAD_BUTTON_START: + if (InputManager()) { + InputManager()->QueueEvent(c_notificationKeyPress, SDLK_ESCAPE, 0, 0, SDLK_ESCAPE); + } + break; + } + break; + } + + case SDL_EVENT_GAMEPAD_BUTTON_UP: { + switch (event->gbutton.button) { + case SDL_GAMEPAD_BUTTON_DPAD_UP: + g_dpadUp = false; + break; + case SDL_GAMEPAD_BUTTON_DPAD_DOWN: + g_dpadDown = false; + break; + case SDL_GAMEPAD_BUTTON_DPAD_LEFT: + g_dpadLeft = false; + break; + case SDL_GAMEPAD_BUTTON_DPAD_RIGHT: + g_dpadRight = false; + break; + case SDL_GAMEPAD_BUTTON_EAST: + g_mousedown = FALSE; + if (InputManager()) { + InputManager()->QueueEvent( + c_notificationButtonUp, + LegoEventNotificationParam::c_lButtonState, + g_lastMouseX, + g_lastMouseY, + 0 + ); + } + break; } break; } @@ -1278,11 +1339,31 @@ IDirect3DRMMiniwinDevice* GetD3DRMMiniwinDevice() void IsleApp::MoveVirtualMouseViaJoystick() { - if (g_lastJoystickMouseX != 0 || g_lastJoystickMouseY != 0) { + float dpadX = 0.0f; + float dpadY = 0.0f; + + if (g_dpadLeft) { + dpadX -= m_cursorSensitivity; + } + if (g_dpadRight) { + dpadX += m_cursorSensitivity; + } + if (g_dpadUp) { + dpadY -= m_cursorSensitivity; + } + if (g_dpadDown) { + dpadY += m_cursorSensitivity; + } + + // Use joystick axis if non-zero, else fall back to dpad + float moveX = (g_lastJoystickMouseX != 0) ? g_lastJoystickMouseX : dpadX; + float moveY = (g_lastJoystickMouseY != 0) ? g_lastJoystickMouseY : dpadY; + + if (moveX != 0 || moveY != 0) { g_mousemoved = TRUE; - g_lastMouseX = SDL_clamp(g_lastMouseX + g_lastJoystickMouseX, 0, 640); - g_lastMouseY = SDL_clamp(g_lastMouseY + g_lastJoystickMouseY, 0, 480); + g_lastMouseX = SDL_clamp(g_lastMouseX + moveX, 0, 640); + g_lastMouseY = SDL_clamp(g_lastMouseY + moveY, 0, 480); if (InputManager()) { InputManager()->QueueEvent( From dbff98f354f776ca0149f81f00bb0260d62eee58 Mon Sep 17 00:00:00 2001 From: Helloyunho Date: Sun, 6 Jul 2025 22:22:21 +0900 Subject: [PATCH 084/188] =?UTF-8?q?=F0=9F=A9=B9=20fix:=20click=20trigger?= =?UTF-8?q?=20spam=20clicks=20(#537)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ISLE/isleapp.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ISLE/isleapp.cpp b/ISLE/isleapp.cpp index 6d1c0a99..2478f2b1 100644 --- a/ISLE/isleapp.cpp +++ b/ISLE/isleapp.cpp @@ -554,7 +554,7 @@ SDL_AppResult SDL_AppEvent(void* appstate, SDL_Event* event) g_lastJoystickMouseY = ((MxFloat) axisValue) / SDL_JOYSTICK_AXIS_MAX * g_isle->GetCursorSensitivity(); } else if (event->gaxis.axis == SDL_GAMEPAD_AXIS_RIGHT_TRIGGER) { - if (axisValue != 0) { + if (axisValue != 0 && !g_mousedown) { g_mousedown = TRUE; if (InputManager()) { @@ -567,7 +567,7 @@ SDL_AppResult SDL_AppEvent(void* appstate, SDL_Event* event) ); } } - else { + else if (axisValue == 0 && g_mousedown) { g_mousedown = FALSE; if (InputManager()) { From 04bcfde38b1a45537967776d60b20bb62d36efe7 Mon Sep 17 00:00:00 2001 From: Helloyunho Date: Sun, 6 Jul 2025 23:28:00 +0900 Subject: [PATCH 085/188] Add Xbox One/Series X|S port (#526) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * chore: update README.md * ✨ feat: got the audio working * 🔧 feat: add appxmanifest * 🩹 fix: remove opengl stuff * ⚗️ feat: add button support * ⚗️ chore: try to make less modification from upstream * 🩹 fix: doesnt compile * ✨ feat: forcibly draw cursor and map right joystick to cursor * 🩹 fix: remap joystick * 🎨 fix: formatting * 🎨 fix: cmakelists formatting * 🩹 fix: 3ds build * 👷 feat: add xbox one ci build * 💚 fix: github hates xbox series naming * 💚 fix: dont run cpack for xbox one, vs does that for you * 💚 fix: xbox one need to use vs as generator * 💚 fix: please * 💚 fix: it could be msix not bundle * 💚 fix: try recursive directory search when upload release * 🔨 feat: support cpack * 👷 feat: better packaging * 💚 fix: ignore if mv fails * 🔧 feat: add mouse sensitivity config * 🔥 chore: dont need you * ⚗️ chore: use d3d11 * ✨ feat: hardware acceleration!! * 🚸 chore: change default cd path so user can easily copy the assets to xbox * 🧑‍💻 chore: improve reusability * 🚨 fix: formatting error * 🚸 chore: draw cursor by default * 🎨 chore: cmakelists formatting * chore: match with upstream * chore: more matching with upstream * chore: don't need you * 🩹 fix: apply changes * 🔥 fix: we don't need controller map anymore * 🩹 fix: use isle style include guard * 🩹 fix: last newline fix * 👷 chore: use cpack * 💚 fix: try to fix ci * 💚 fix: i tried my best... --- .github/workflows/ci.yml | 23 +- CMakeLists.txt | 137 +++++++----- ISLE/isleapp.cpp | 9 +- ISLE/xbox_one_series/config.cpp | 18 ++ ISLE/xbox_one_series/config.h | 8 + LEGO1/lego/legoomni/src/race/carrace.cpp | 2 +- LEGO1/lego/legoomni/src/race/jetskirace.cpp | 2 +- LEGO1/omni/src/stream/mxramstreamprovider.cpp | 2 +- miniwin/CMakeLists.txt | 10 +- .../src/d3drm/backends/sdl3gpu/renderer.cpp | 2 +- .../shaders/generated/PositionColor.vert.h | 102 ++++++++- .../sdl3gpu/shaders/generated/ShaderIndex.cpp | 36 ++++ .../shaders/generated/SolidColor.frag.h | 204 +++++++++++++++++- .../backends/sdl3gpu/shaders/src/Common.hlsl | 6 + .../shaders/src/PositionColor.vert.hlsl | 2 +- .../sdl3gpu/shaders/src/SolidColor.frag.hlsl | 6 +- miniwin/src/d3drm/d3drm.cpp | 2 +- miniwin/src/ddraw/ddraw.cpp | 6 +- packaging/CMakeLists.txt | 4 + packaging/UWP/CMakeLists.txt | 15 ++ packaging/UWP/Package.appxmanifest | 47 ++++ packaging/UWP/assets/LargeTile.scale-100.png | Bin 0 -> 2046 bytes packaging/UWP/assets/LargeTile.scale-125.png | Bin 0 -> 2648 bytes packaging/UWP/assets/LargeTile.scale-150.png | Bin 0 -> 3354 bytes packaging/UWP/assets/LargeTile.scale-200.png | Bin 0 -> 5425 bytes packaging/UWP/assets/LargeTile.scale-400.png | Bin 0 -> 14676 bytes .../UWP/assets/SplashScreen.scale-100.png | Bin 0 -> 2498 bytes .../UWP/assets/SplashScreen.scale-125.png | Bin 0 -> 3167 bytes .../UWP/assets/SplashScreen.scale-150.png | Bin 0 -> 4174 bytes .../UWP/assets/SplashScreen.scale-200.png | Bin 0 -> 6410 bytes .../UWP/assets/SplashScreen.scale-400.png | Bin 0 -> 19945 bytes .../assets/Square150x150Logo.scale-100.png | Bin 0 -> 944 bytes .../assets/Square150x150Logo.scale-125.png | Bin 0 -> 1172 bytes .../assets/Square150x150Logo.scale-150.png | Bin 0 -> 1363 bytes .../assets/Square150x150Logo.scale-200.png | Bin 0 -> 2079 bytes .../assets/Square150x150Logo.scale-400.png | Bin 0 -> 4825 bytes ...go.altform-lightunplated_targetsize-16.png | Bin 0 -> 229 bytes ...go.altform-lightunplated_targetsize-24.png | Bin 0 -> 305 bytes ...o.altform-lightunplated_targetsize-256.png | Bin 0 -> 3676 bytes ...go.altform-lightunplated_targetsize-32.png | Bin 0 -> 371 bytes ...go.altform-lightunplated_targetsize-48.png | Bin 0 -> 585 bytes ...x44Logo.altform-unplated_targetsize-16.png | Bin 0 -> 229 bytes ...x44Logo.altform-unplated_targetsize-24.png | Bin 0 -> 305 bytes ...44Logo.altform-unplated_targetsize-256.png | Bin 0 -> 3676 bytes ...x44Logo.altform-unplated_targetsize-32.png | Bin 0 -> 371 bytes ...x44Logo.altform-unplated_targetsize-48.png | Bin 0 -> 585 bytes .../UWP/assets/Square44x44Logo.scale-100.png | Bin 0 -> 407 bytes .../UWP/assets/Square44x44Logo.scale-125.png | Bin 0 -> 514 bytes .../UWP/assets/Square44x44Logo.scale-150.png | Bin 0 -> 627 bytes .../UWP/assets/Square44x44Logo.scale-200.png | Bin 0 -> 842 bytes .../UWP/assets/Square44x44Logo.scale-400.png | Bin 0 -> 1805 bytes .../assets/Square44x44Logo.targetsize-16.png | Bin 0 -> 203 bytes .../assets/Square44x44Logo.targetsize-24.png | Bin 0 -> 279 bytes .../assets/Square44x44Logo.targetsize-256.png | Bin 0 -> 2884 bytes .../assets/Square44x44Logo.targetsize-32.png | Bin 0 -> 309 bytes .../assets/Square44x44Logo.targetsize-48.png | Bin 0 -> 436 bytes .../UWP/assets/Square71x71Logo.scale-100.png | Bin 0 -> 578 bytes .../UWP/assets/Square71x71Logo.scale-125.png | Bin 0 -> 683 bytes .../UWP/assets/Square71x71Logo.scale-150.png | Bin 0 -> 808 bytes .../UWP/assets/Square71x71Logo.scale-200.png | Bin 0 -> 1057 bytes .../UWP/assets/Square71x71Logo.scale-400.png | Bin 0 -> 2488 bytes packaging/UWP/assets/StoreLogo.scale-100.png | Bin 0 -> 541 bytes packaging/UWP/assets/StoreLogo.scale-125.png | Bin 0 -> 710 bytes packaging/UWP/assets/StoreLogo.scale-150.png | Bin 0 -> 809 bytes packaging/UWP/assets/StoreLogo.scale-200.png | Bin 0 -> 1190 bytes packaging/UWP/assets/StoreLogo.scale-400.png | Bin 0 -> 2823 bytes .../UWP/assets/Wide310x150Logo.scale-100.png | Bin 0 -> 1043 bytes .../UWP/assets/Wide310x150Logo.scale-125.png | Bin 0 -> 1320 bytes .../UWP/assets/Wide310x150Logo.scale-150.png | Bin 0 -> 1595 bytes .../UWP/assets/Wide310x150Logo.scale-200.png | Bin 0 -> 2498 bytes .../UWP/assets/Wide310x150Logo.scale-400.png | Bin 0 -> 6410 bytes 71 files changed, 563 insertions(+), 80 deletions(-) create mode 100644 ISLE/xbox_one_series/config.cpp create mode 100644 ISLE/xbox_one_series/config.h create mode 100644 packaging/UWP/CMakeLists.txt create mode 100644 packaging/UWP/Package.appxmanifest create mode 100644 packaging/UWP/assets/LargeTile.scale-100.png create mode 100644 packaging/UWP/assets/LargeTile.scale-125.png create mode 100644 packaging/UWP/assets/LargeTile.scale-150.png create mode 100644 packaging/UWP/assets/LargeTile.scale-200.png create mode 100644 packaging/UWP/assets/LargeTile.scale-400.png create mode 100644 packaging/UWP/assets/SplashScreen.scale-100.png create mode 100644 packaging/UWP/assets/SplashScreen.scale-125.png create mode 100644 packaging/UWP/assets/SplashScreen.scale-150.png create mode 100644 packaging/UWP/assets/SplashScreen.scale-200.png create mode 100644 packaging/UWP/assets/SplashScreen.scale-400.png create mode 100644 packaging/UWP/assets/Square150x150Logo.scale-100.png create mode 100644 packaging/UWP/assets/Square150x150Logo.scale-125.png create mode 100644 packaging/UWP/assets/Square150x150Logo.scale-150.png create mode 100644 packaging/UWP/assets/Square150x150Logo.scale-200.png create mode 100644 packaging/UWP/assets/Square150x150Logo.scale-400.png create mode 100644 packaging/UWP/assets/Square44x44Logo.altform-lightunplated_targetsize-16.png create mode 100644 packaging/UWP/assets/Square44x44Logo.altform-lightunplated_targetsize-24.png create mode 100644 packaging/UWP/assets/Square44x44Logo.altform-lightunplated_targetsize-256.png create mode 100644 packaging/UWP/assets/Square44x44Logo.altform-lightunplated_targetsize-32.png create mode 100644 packaging/UWP/assets/Square44x44Logo.altform-lightunplated_targetsize-48.png create mode 100644 packaging/UWP/assets/Square44x44Logo.altform-unplated_targetsize-16.png create mode 100644 packaging/UWP/assets/Square44x44Logo.altform-unplated_targetsize-24.png create mode 100644 packaging/UWP/assets/Square44x44Logo.altform-unplated_targetsize-256.png create mode 100644 packaging/UWP/assets/Square44x44Logo.altform-unplated_targetsize-32.png create mode 100644 packaging/UWP/assets/Square44x44Logo.altform-unplated_targetsize-48.png create mode 100644 packaging/UWP/assets/Square44x44Logo.scale-100.png create mode 100644 packaging/UWP/assets/Square44x44Logo.scale-125.png create mode 100644 packaging/UWP/assets/Square44x44Logo.scale-150.png create mode 100644 packaging/UWP/assets/Square44x44Logo.scale-200.png create mode 100644 packaging/UWP/assets/Square44x44Logo.scale-400.png create mode 100644 packaging/UWP/assets/Square44x44Logo.targetsize-16.png create mode 100644 packaging/UWP/assets/Square44x44Logo.targetsize-24.png create mode 100644 packaging/UWP/assets/Square44x44Logo.targetsize-256.png create mode 100644 packaging/UWP/assets/Square44x44Logo.targetsize-32.png create mode 100644 packaging/UWP/assets/Square44x44Logo.targetsize-48.png create mode 100644 packaging/UWP/assets/Square71x71Logo.scale-100.png create mode 100644 packaging/UWP/assets/Square71x71Logo.scale-125.png create mode 100644 packaging/UWP/assets/Square71x71Logo.scale-150.png create mode 100644 packaging/UWP/assets/Square71x71Logo.scale-200.png create mode 100644 packaging/UWP/assets/Square71x71Logo.scale-400.png create mode 100644 packaging/UWP/assets/StoreLogo.scale-100.png create mode 100644 packaging/UWP/assets/StoreLogo.scale-125.png create mode 100644 packaging/UWP/assets/StoreLogo.scale-150.png create mode 100644 packaging/UWP/assets/StoreLogo.scale-200.png create mode 100644 packaging/UWP/assets/StoreLogo.scale-400.png create mode 100644 packaging/UWP/assets/Wide310x150Logo.scale-100.png create mode 100644 packaging/UWP/assets/Wide310x150Logo.scale-125.png create mode 100644 packaging/UWP/assets/Wide310x150Logo.scale-150.png create mode 100644 packaging/UWP/assets/Wide310x150Logo.scale-200.png create mode 100644 packaging/UWP/assets/Wide310x150Logo.scale-400.png diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 97b338c8..26d7fe64 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -34,15 +34,16 @@ jobs: fail-fast: false matrix: include: - - { name: 'Linux', os: 'ubuntu-latest', dx5: false, config: true, linux: true, werror: true, clang-tidy: true } - - { name: 'MSVC (x86)', os: 'windows-latest', dx5: true, config: false, msvc: true, werror: false, clang-tidy: false, vc-arch: 'amd64_x86' } - - { name: 'MSVC (x64)', os: 'windows-latest', dx5: false, config: true, msvc: true, werror: false, clang-tidy: false, vc-arch: 'amd64' } - - { name: 'MSVC (arm64)', os: 'windows-latest', dx5: false, config: false, msvc: true, werror: false, clang-tidy: false, vc-arch: 'amd64_arm64' } - - { name: 'msys2 mingw32', os: 'windows-latest', dx5: false, config: false, mingw: true, werror: true, clang-tidy: true, msystem: 'mingw32', msys-env: 'mingw-w64-i686', shell: 'msys2 {0}' } - - { name: 'msys2 mingw64', os: 'windows-latest', dx5: false, config: true, mingw: true, werror: true, clang-tidy: true, msystem: 'mingw64', msys-env: 'mingw-w64-x86_64', shell: 'msys2 {0}' } - - { name: 'macOS', os: 'macos-latest', dx5: false, config: true, brew: true, werror: true, clang-tidy: false } - - { name: 'Emscripten', os: 'ubuntu-latest', dx5: false, config: false, emsdk: true, werror: true, clang-tidy: false, cmake-wrapper: 'emcmake' } - - { name: 'Nintendo 3DS', os: 'ubuntu-latest', dx5: false, config: false, n3ds: true, werror: true, clang-tidy: false, container: 'devkitpro/devkitarm:latest', cmake-args: '-DCMAKE_TOOLCHAIN_FILE=/opt/devkitpro/cmake/3DS.cmake' } + - { name: 'Linux', os: 'ubuntu-latest', generator: 'Ninja', dx5: false, config: true, linux: true, werror: true, clang-tidy: true } + - { name: 'MSVC (x86)', os: 'windows-latest', generator: 'Ninja', dx5: true, config: false, msvc: true, werror: false, clang-tidy: false, vc-arch: 'amd64_x86' } + - { name: 'MSVC (x64)', os: 'windows-latest', generator: 'Ninja', dx5: false, config: true, msvc: true, werror: false, clang-tidy: false, vc-arch: 'amd64' } + - { name: 'MSVC (arm64)', os: 'windows-latest', generator: 'Ninja', dx5: false, config: false, msvc: true, werror: false, clang-tidy: false, vc-arch: 'amd64_arm64' } + - { name: 'msys2 mingw32', os: 'windows-latest', generator: 'Ninja', dx5: false, config: false, mingw: true, werror: true, clang-tidy: true, msystem: 'mingw32', msys-env: 'mingw-w64-i686', shell: 'msys2 {0}' } + - { name: 'msys2 mingw64', os: 'windows-latest', generator: 'Ninja', dx5: false, config: true, mingw: true, werror: true, clang-tidy: true, msystem: 'mingw64', msys-env: 'mingw-w64-x86_64', shell: 'msys2 {0}' } + - { name: 'macOS', os: 'macos-latest', generator: 'Ninja', dx5: false, config: true, brew: true, werror: true, clang-tidy: false } + - { name: 'Emscripten', os: 'ubuntu-latest', generator: 'Ninja', dx5: false, config: false, emsdk: true, werror: true, clang-tidy: false, cmake-wrapper: 'emcmake' } + - { name: 'Nintendo 3DS', os: 'ubuntu-latest', generator: 'Ninja', dx5: false, config: false, n3ds: true, werror: true, clang-tidy: false, container: 'devkitpro/devkitarm:latest', cmake-args: '-DCMAKE_TOOLCHAIN_FILE=/opt/devkitpro/cmake/3DS.cmake' } + - { name: 'Xbox One', os: 'windows-latest', generator: 'Visual Studio 17 2022', dx5: false, config: false, msvc: true, werror: false, clang-tidy: false, vc-arch: 'amd64', cmake-args: '-DCMAKE_SYSTEM_NAME=WindowsStore -DCMAKE_SYSTEM_VERSION=10.0.26100.0', xbox-one: true} steps: - name: Setup vcvars if: ${{ !!matrix.msvc }} @@ -95,7 +96,7 @@ jobs: - name: Configure (CMake) run: | - ${{ matrix.cmake-wrapper || '' }} cmake -S . -B build -GNinja \ + ${{ matrix.cmake-wrapper || '' }} cmake -S . -B build -G "${{ matrix.generator }}" \ ${{ matrix.cmake-args || '' }} \ -DCMAKE_BUILD_TYPE=Release \ -DISLE_USE_DX5=${{ !!matrix.dx5 }} \ @@ -106,7 +107,7 @@ jobs: -Werror=dev - name: Build (CMake) - run: cmake --build build --verbose + run: cmake --build build --verbose --config Release - name: Package (CPack) if: ${{ !matrix.n3ds }} diff --git a/CMakeLists.txt b/CMakeLists.txt index 00d087df..f9669bad 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -2,6 +2,10 @@ cmake_minimum_required(VERSION 3.25...4.0 FATAL_ERROR) project(isle LANGUAGES CXX C VERSION 0.1) +if (WINDOWS_STORE) + add_compile_definitions(WINDOWS_STORE) +endif() + if (EMSCRIPTEN) add_compile_options(-pthread) add_link_options(-sALLOW_MEMORY_GROWTH=1 -sMAXIMUM_MEMORY=2gb -sUSE_PTHREADS=1 -sPROXY_TO_PTHREAD=1 -sOFFSCREENCANVAS_SUPPORT=1 -sPTHREAD_POOL_SIZE_STRICT=0 -sFORCE_FILESYSTEM=1 -sWASMFS=1 -sEXIT_RUNTIME=1) @@ -38,7 +42,7 @@ option(ISLE_WERROR "Treat warnings as errors" OFF) option(ISLE_DEBUG "Enable imgui debug" ON) cmake_dependent_option(ISLE_USE_DX5 "Build with internal DirectX 5 SDK" "${NOT_MINGW}" "WIN32;CMAKE_SIZEOF_VOID_P EQUAL 4" OFF) cmake_dependent_option(ISLE_MINIWIN "Use miniwin" ON "NOT ISLE_USE_DX5" OFF) -cmake_dependent_option(ISLE_BUILD_CONFIG "Build CONFIG.EXE application" ON "MSVC OR ISLE_MINIWIN;NOT NINTENDO_3DS" OFF) +cmake_dependent_option(ISLE_BUILD_CONFIG "Build CONFIG.EXE application" ON "MSVC OR ISLE_MINIWIN;NOT NINTENDO_3DS;NOT WINDOWS_STORE" OFF) cmake_dependent_option(ISLE_COMPILE_SHADERS "Compile shaders" ON "SDL_SHADERCROSS_BIN;TARGET Python3::Interpreter" OFF) option(CMAKE_POSITION_INDEPENDENT_CODE "Build with -fPIC" ON) option(ENABLE_CLANG_TIDY "Enable clang-tidy") @@ -57,44 +61,53 @@ message(STATUS "Compile shaders: ${ISLE_COMPILE_SHADERS}") add_library(Isle::iniparser INTERFACE IMPORTED) if (DOWNLOAD_DEPENDENCIES) - # FetchContent downloads and configures dependencies - message(STATUS "Fetching SDL3 and iniparser. This might take a while...") - include(FetchContent) + # FetchContent downloads and configures dependencies + message(STATUS "Fetching SDL3 and iniparser. This might take a while...") + include(FetchContent) + if (WINDOWS_STORE) + FetchContent_Declare( + SDL3 + GIT_REPOSITORY "https://github.com/Helloyunho/SDL3-uwp.git" + GIT_TAG "main" + EXCLUDE_FROM_ALL + ) + else() FetchContent_Declare( SDL3 GIT_REPOSITORY "https://github.com/libsdl-org/SDL.git" GIT_TAG "main" EXCLUDE_FROM_ALL ) - FetchContent_MakeAvailable(SDL3) + endif() + FetchContent_MakeAvailable(SDL3) - FetchContent_Declare( - iniparser - GIT_REPOSITORY "https://gitlab.com/iniparser/iniparser.git" - GIT_TAG "main" - EXCLUDE_FROM_ALL - ) - block() - set(BUILD_DOCS off) - set(BUILD_SHARED_LIBS off) - FetchContent_MakeAvailable(iniparser) - target_link_libraries(Isle::iniparser INTERFACE iniparser-static) - endblock() + FetchContent_Declare( + iniparser + GIT_REPOSITORY "https://gitlab.com/iniparser/iniparser.git" + GIT_TAG "main" + EXCLUDE_FROM_ALL + ) + block() + set(BUILD_DOCS off) + set(BUILD_SHARED_LIBS off) + FetchContent_MakeAvailable(iniparser) + target_link_libraries(Isle::iniparser INTERFACE iniparser-static) + endblock() else() - # find_package looks for already-installed system packages. - # Configure with `-DCMAKE_PREFIX_PATH="/path/to/package1;/path/to/package2"` - # to add search paths. - find_package(SDL3 CONFIG REQUIRED) + # find_package looks for already-installed system packages. + # Configure with `-DCMAKE_PREFIX_PATH="/path/to/package1;/path/to/package2"` + # to add search paths. + find_package(SDL3 CONFIG REQUIRED) - find_package(iniparser REQUIRED CONFIG COMPONENTS shared) - target_link_libraries(Isle::iniparser INTERFACE iniparser-shared) + find_package(iniparser REQUIRED CONFIG COMPONENTS shared) + target_link_libraries(Isle::iniparser INTERFACE iniparser-shared) endif() set(CMAKE_EXPORT_COMPILE_COMMANDS ON) if (ENABLE_CLANG_TIDY) - find_program(CLANG_TIDY_BIN NAMES "clang-tidy" ENV "LLVM_ROOT" REQUIRED) - set(CMAKE_C_CLANG_TIDY "${CLANG_TIDY_BIN}") - set(CMAKE_CXX_CLANG_TIDY "${CLANG_TIDY_BIN}") + find_program(CLANG_TIDY_BIN NAMES "clang-tidy" ENV "LLVM_ROOT" REQUIRED) + set(CMAKE_C_CLANG_TIDY "${CLANG_TIDY_BIN}") + set(CMAKE_CXX_CLANG_TIDY "${CLANG_TIDY_BIN}") endif() if (ISLE_ASAN) @@ -112,26 +125,26 @@ add_subdirectory(miniwin EXCLUDE_FROM_ALL) set(isle_targets) function(add_cxx_warning WARNING) - if (ISLE_WERROR) - set(compiler_option "-Werror=${WARNING}") - else() - set(compiler_option "-W${WARNING}") - endif() - string(MAKE_C_IDENTIFIER "COMPILER_SUPPORTS${compiler_option}" varname) + if (ISLE_WERROR) + set(compiler_option "-Werror=${WARNING}") + else() + set(compiler_option "-W${WARNING}") + endif() + string(MAKE_C_IDENTIFIER "COMPILER_SUPPORTS${compiler_option}" varname) - cmake_push_check_state(RESET) - set(CMAKE_REQUIRED_FLAGS "${compiler_option} ") - if (MSVC) - string(APPEND CMAKE_REQUIRED_FLAGS "/WX") - else() - string(APPEND CMAKE_REQUIRED_FLAGS "-Werror") - endif() - check_cxx_source_compiles("int main() { return 0; }" ${varname}) - cmake_pop_check_state() + cmake_push_check_state(RESET) + set(CMAKE_REQUIRED_FLAGS "${compiler_option} ") + if (MSVC) + string(APPEND CMAKE_REQUIRED_FLAGS "/WX") + else() + string(APPEND CMAKE_REQUIRED_FLAGS "-Werror") + endif() + check_cxx_source_compiles("int main() { return 0; }" ${varname}) + cmake_pop_check_state() - if (${varname}) - add_compile_options(${compiler_option}) - endif() + if (${varname}) + add_compile_options(${compiler_option}) + endif() endfunction() add_subdirectory(3rdparty EXCLUDE_FROM_ALL SYSTEM) @@ -532,6 +545,11 @@ if (ISLE_BUILD_APP) ISLE/3ds/config.cpp ) endif() + if(WINDOWS_STORE) + target_sources(isle PRIVATE + ISLE/xbox_one_series/config.cpp + ) + endif() if(Python3_FOUND) if(NOT DEFINED PYTHON_PIL_AVAILABLE) execute_process( @@ -612,6 +630,12 @@ if (MSVC) if (TARGET isle-config) target_compile_definitions(isle-config PRIVATE "_CRT_SECURE_NO_WARNINGS") endif() + if (TARGET iniparser-static) + target_compile_definitions(iniparser-static PRIVATE "_CRT_SECURE_NO_WARNINGS") + endif() + if (TARGET libsmacker) + target_compile_definitions(libsmacker PRIVATE "_CRT_SECURE_NO_WARNINGS") + endif() endif() # Visual Studio 2017 version 15.7 needs "/Zc:__cplusplus" for __cplusplus if (CMAKE_CXX_COMPILER_VERSION VERSION_GREATER_EQUAL "19.14.26428") @@ -671,14 +695,20 @@ endif() string(REPLACE ";" "-" ISLE_CPUS_STRING "${ISLE_CPUS}") string(TOLOWER "${ISLE_CPUS_STRING}" ISLE_CPUS_STRING) -set(ISLE_PACKAGE_NAME "${CMAKE_SYSTEM_NAME}-${ISLE_CPUS_STRING}" CACHE STRING "Platform name of the package") +if (WINDOWS_STORE) + set(ISLE_PACKAGE_NAME "Xbox_One_Series_XS-${ISLE_CPUS_STRING}" CACHE STRING "Platform name of the package") +else() + set(ISLE_PACKAGE_NAME "${CMAKE_SYSTEM_NAME}-${ISLE_CPUS_STRING}" CACHE STRING "Platform name of the package") +endif() if(BUILD_SHARED_LIBS) list(APPEND install_extra_targets lego1) endif() -install(TARGETS isle ${install_extra_targets} - RUNTIME DESTINATION "${CMAKE_INSTALL_BINDIR}" - LIBRARY DESTINATION "${CMAKE_INSTALL_LIBDIR}" -) +if (NOT (NINTENDO_3DS OR WINDOWS_STORE)) + install(TARGETS isle ${install_extra_targets} + RUNTIME DESTINATION "${CMAKE_INSTALL_BINDIR}" + LIBRARY DESTINATION "${CMAKE_INSTALL_LIBDIR}" + ) +endif() if (ISLE_BUILD_CONFIG) if(WIN32) find_program(WINDEPLOYQT_EXECUTABLE windeployqt) @@ -726,6 +756,15 @@ if(NINTENDO_3DS) ctr_create_3dsx(isle SMDH isle.smdh) install(FILES "$/isle.3dsx" DESTINATION "${CMAKE_INSTALL_BINDIR}") endif() +if(WINDOWS_STORE) + install( + DIRECTORY + "${CMAKE_BINARY_DIR}/AppPackages/isle/" + DESTINATION "${CMAKE_INSTALL_BINDIR}" + FILES_MATCHING PATTERN "*/Dependencies/x64/Microsoft.VCLibs.x64*.14.00.appx" + PATTERN "*/*.msix" + PATTERN "*/*.msixbundle") +endif() if(MSVC) set(CPACK_GENERATOR ZIP) else() diff --git a/ISLE/isleapp.cpp b/ISLE/isleapp.cpp index 2478f2b1..09dcaab2 100644 --- a/ISLE/isleapp.cpp +++ b/ISLE/isleapp.cpp @@ -58,6 +58,10 @@ #include "3ds/config.h" #endif +#ifdef WINDOWS_STORE +#include "xbox_one_series/config.h" +#endif + DECOMP_SIZE_ASSERT(IsleApp, 0x8c) // GLOBAL: ISLE 0x410030 @@ -801,7 +805,7 @@ MxResult IsleApp::SetupWindow() SDL_SetNumberProperty(props, SDL_PROP_WINDOW_CREATE_HEIGHT_NUMBER, g_targetHeight); SDL_SetBooleanProperty(props, SDL_PROP_WINDOW_CREATE_FULLSCREEN_BOOLEAN, m_fullScreen); SDL_SetStringProperty(props, SDL_PROP_WINDOW_CREATE_TITLE_STRING, WINDOW_TITLE); -#if defined(MINIWIN) && !defined(__3DS__) +#if defined(MINIWIN) && !defined(__3DS__) && !defined(WINDOWS_STORE) SDL_SetBooleanProperty(props, SDL_PROP_WINDOW_CREATE_OPENGL_BOOLEAN, true); SDL_GL_SetAttribute(SDL_GL_DOUBLEBUFFER, 1); SDL_GL_SetAttribute(SDL_GL_DEPTH_SIZE, 24); @@ -982,6 +986,9 @@ bool IsleApp::LoadConfig() #ifdef __3DS__ N3DS_SetupDefaultConfigOverrides(dict); +#endif +#ifdef WINDOWS_STORE + XBONE_SetupDefaultConfigOverrides(dict); #endif iniparser_dump_ini(dict, iniFP); SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION, "New config written at '%s'", iniConfig); diff --git a/ISLE/xbox_one_series/config.cpp b/ISLE/xbox_one_series/config.cpp new file mode 100644 index 00000000..72ea32af --- /dev/null +++ b/ISLE/xbox_one_series/config.cpp @@ -0,0 +1,18 @@ +#include "config.h" + +#include +#include + +void XBONE_SetupDefaultConfigOverrides(dictionary* p_dictionary) +{ + SDL_Log("Overriding default config for Xbox One/Series"); + + // Use DevelopmentFiles path for disk and cd paths + // It's good to use that path since user can easily + // connect through SMB and copy the files + iniparser_set(p_dictionary, "isle:diskpath", "D:\\DevelopmentFiles\\isle\\"); + iniparser_set(p_dictionary, "isle:cdpath", "D:\\DevelopmentFiles\\isle\\"); + + // Enable cursor by default + iniparser_set(p_dictionary, "isle:Draw Cursor", "true"); +} diff --git a/ISLE/xbox_one_series/config.h b/ISLE/xbox_one_series/config.h new file mode 100644 index 00000000..bdfcb010 --- /dev/null +++ b/ISLE/xbox_one_series/config.h @@ -0,0 +1,8 @@ +#ifndef XBOX_ONE_SERIES_CONFIG_H +#define XBOX_ONE_SERIES_CONFIG_H + +#include "dictionary.h" + +void XBONE_SetupDefaultConfigOverrides(dictionary* p_dictionary); + +#endif // XBOX_ONE_SERIES_CONFIG_H diff --git a/LEGO1/lego/legoomni/src/race/carrace.cpp b/LEGO1/lego/legoomni/src/race/carrace.cpp index 62701294..c59cd6de 100644 --- a/LEGO1/lego/legoomni/src/race/carrace.cpp +++ b/LEGO1/lego/legoomni/src/race/carrace.cpp @@ -386,7 +386,7 @@ MxLong CarRace::HandleType0Notification(MxNotificationParam&) void CarRace::FUN_10017820(MxS32 p_param1, MxS16 p_param2) { MxS32 local4; - MxStillPresenter* presenter; + MxStillPresenter* presenter = NULL; MxS32 x, y; if (p_param1 == 11) { diff --git a/LEGO1/lego/legoomni/src/race/jetskirace.cpp b/LEGO1/lego/legoomni/src/race/jetskirace.cpp index 7081d8eb..6ea02f86 100644 --- a/LEGO1/lego/legoomni/src/race/jetskirace.cpp +++ b/LEGO1/lego/legoomni/src/race/jetskirace.cpp @@ -263,7 +263,7 @@ MxLong JetskiRace::HandlePathStruct(LegoPathStructNotificationParam& p_param) void JetskiRace::FUN_10016930(MxS32 p_param1, MxS16 p_param2) { MxS32 local4; - MxStillPresenter* presenter; + MxStillPresenter* presenter = NULL; MxS32 x, y; if (p_param1 == 11) { diff --git a/LEGO1/omni/src/stream/mxramstreamprovider.cpp b/LEGO1/omni/src/stream/mxramstreamprovider.cpp index 71fa256c..966bec95 100644 --- a/LEGO1/omni/src/stream/mxramstreamprovider.cpp +++ b/LEGO1/omni/src/stream/mxramstreamprovider.cpp @@ -108,7 +108,7 @@ MxU32 ReadData(MxU8* p_buffer, MxU32 p_size) { MxU32 id; MxU8* data = p_buffer; - MxU8* data2; + MxU8* data2 = NULL; while (data < p_buffer + p_size) { if (data + sizeof(MxU32) <= p_buffer + p_size && UnalignedRead(data) == FOURCC('M', 'x', 'O', 'b')) { diff --git a/miniwin/CMakeLists.txt b/miniwin/CMakeLists.txt index c2aa0bd6..1ed5e909 100644 --- a/miniwin/CMakeLists.txt +++ b/miniwin/CMakeLists.txt @@ -32,7 +32,7 @@ target_compile_definitions(miniwin PRIVATE ) find_package(OpenGL) -if(OpenGL_FOUND) +if(OpenGL_FOUND AND NOT WINDOWS_STORE) message(STATUS "Found OpenGL: enabling OpenGL 1.x renderer") target_sources(miniwin PRIVATE src/d3drm/backends/opengl1/actual.cpp @@ -45,7 +45,7 @@ else() endif() find_library(OPENGL_ES2_LIBRARY NAMES GLESv2) -if(EMSCRIPTEN OR OPENGL_ES2_LIBRARY) +if(EMSCRIPTEN OR OPENGL_ES2_LIBRARY AND NOT WINDOWS_STORE) message(STATUS "Found OpenGL: enabling OpenGL ES 2.x renderer") target_sources(miniwin PRIVATE src/d3drm/backends/opengles2/renderer.cpp) target_compile_definitions(miniwin PRIVATE USE_OPENGLES2) @@ -73,7 +73,7 @@ if(NINTENDO_3DS) endif() endif() -if(WIN32) +if(WIN32 AND NOT WINDOWS_STORE) target_sources(miniwin PRIVATE src/d3drm/backends/directx9/actual.cpp src/d3drm/backends/directx9/renderer.cpp @@ -81,6 +81,10 @@ if(WIN32) target_link_libraries(miniwin PRIVATE d3d9) endif() +if(WINDOWS_STORE) + add_compile_definitions(WINDOWS_STORE) +endif() + target_compile_definitions(miniwin PUBLIC MINIWIN) target_include_directories(miniwin diff --git a/miniwin/src/d3drm/backends/sdl3gpu/renderer.cpp b/miniwin/src/d3drm/backends/sdl3gpu/renderer.cpp index 7a6589f0..8039c147 100644 --- a/miniwin/src/d3drm/backends/sdl3gpu/renderer.cpp +++ b/miniwin/src/d3drm/backends/sdl3gpu/renderer.cpp @@ -190,7 +190,7 @@ static SDL_GPUGraphicsPipeline* InitializeGraphicsPipeline( Direct3DRMRenderer* Direct3DRMSDL3GPURenderer::Create(DWORD width, DWORD height) { ScopedDevice device{SDL_CreateGPUDevice( - SDL_GPU_SHADERFORMAT_SPIRV | SDL_GPU_SHADERFORMAT_DXIL | SDL_GPU_SHADERFORMAT_MSL, + SDL_GPU_SHADERFORMAT_SPIRV | SDL_GPU_SHADERFORMAT_DXBC | SDL_GPU_SHADERFORMAT_DXIL | SDL_GPU_SHADERFORMAT_MSL, #ifdef DEBUG true, #else diff --git a/miniwin/src/d3drm/backends/sdl3gpu/shaders/generated/PositionColor.vert.h b/miniwin/src/d3drm/backends/sdl3gpu/shaders/generated/PositionColor.vert.h index 46e7964d..08204e9e 100644 --- a/miniwin/src/d3drm/backends/sdl3gpu/shaders/generated/PositionColor.vert.h +++ b/miniwin/src/d3drm/backends/sdl3gpu/shaders/generated/PositionColor.vert.h @@ -8,7 +8,7 @@ #include -// DXIL only makes sense on Windows platforms +// DX only makes sense on Windows platforms #if defined(SDL_PLATFORM_WINDOWS) static const Uint8 PositionColor_vert_dxil[5132] = { 0x44, 0x58, 0x42, 0x43, 0x4a, 0x28, 0x54, 0x85, 0x4b, 0xb0, 0x04, 0x11, 0x56, 0x33, 0x94, 0x85, @@ -333,6 +333,104 @@ static const Uint8 PositionColor_vert_dxil[5132] = { 0x83, 0x3f, 0xcc, 0xc3, 0x37, 0x62, 0x90, 0x00, 0x20, 0x08, 0x06, 0x48, 0x49, 0xb8, 0x83, 0x3e, 0xf8, 0x03, 0x3d, 0x6c, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, }; +static const Uint8 PositionColor_vert_dxbc[1528] = { + 0x44, 0x58, 0x42, 0x43, 0x26, 0x81, 0xf8, 0x51, 0x4d, 0x47, 0xfd, 0xfd, 0xa8, 0xbd, 0xce, 0x70, + 0x72, 0xe8, 0x3f, 0x4e, 0x01, 0x00, 0x00, 0x00, 0xf8, 0x05, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, + 0x34, 0x00, 0x00, 0x00, 0xf4, 0x01, 0x00, 0x00, 0x58, 0x02, 0x00, 0x00, 0xe0, 0x02, 0x00, 0x00, + 0x5c, 0x05, 0x00, 0x00, 0x52, 0x44, 0x45, 0x46, 0xb8, 0x01, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, + 0x74, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x3c, 0x00, 0x00, 0x00, 0x00, 0x05, 0xfe, 0xff, + 0x00, 0x01, 0x00, 0x00, 0x8f, 0x01, 0x00, 0x00, 0x52, 0x44, 0x31, 0x31, 0x3c, 0x00, 0x00, 0x00, + 0x18, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0x28, 0x00, 0x00, 0x00, 0x24, 0x00, 0x00, 0x00, + 0x0c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x5c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x74, 0x79, 0x70, 0x65, 0x5f, 0x56, 0x69, 0x65, + 0x77, 0x70, 0x6f, 0x72, 0x74, 0x55, 0x6e, 0x69, 0x66, 0x6f, 0x72, 0x6d, 0x73, 0x00, 0xab, 0xab, + 0x5c, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x8c, 0x00, 0x00, 0x00, 0xc0, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x40, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x2c, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, + 0x50, 0x01, 0x00, 0x00, 0x40, 0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, + 0x2c, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, + 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x71, 0x01, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00, + 0x40, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x2c, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, + 0x56, 0x69, 0x65, 0x77, 0x70, 0x6f, 0x72, 0x74, 0x55, 0x6e, 0x69, 0x66, 0x6f, 0x72, 0x6d, 0x73, + 0x5f, 0x70, 0x72, 0x6f, 0x6a, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x00, 0x66, 0x6c, 0x6f, 0x61, + 0x74, 0x34, 0x78, 0x34, 0x00, 0xab, 0xab, 0xab, 0x03, 0x00, 0x03, 0x00, 0x04, 0x00, 0x04, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x20, 0x01, 0x00, 0x00, 0x56, 0x69, 0x65, 0x77, + 0x70, 0x6f, 0x72, 0x74, 0x55, 0x6e, 0x69, 0x66, 0x6f, 0x72, 0x6d, 0x73, 0x5f, 0x77, 0x6f, 0x72, + 0x6c, 0x64, 0x56, 0x69, 0x65, 0x77, 0x4d, 0x61, 0x74, 0x72, 0x69, 0x78, 0x00, 0x56, 0x69, 0x65, + 0x77, 0x70, 0x6f, 0x72, 0x74, 0x55, 0x6e, 0x69, 0x66, 0x6f, 0x72, 0x6d, 0x73, 0x5f, 0x6e, 0x6f, + 0x72, 0x6d, 0x61, 0x6c, 0x4d, 0x61, 0x74, 0x72, 0x69, 0x78, 0x00, 0x4d, 0x69, 0x63, 0x72, 0x6f, + 0x73, 0x6f, 0x66, 0x74, 0x20, 0x28, 0x52, 0x29, 0x20, 0x48, 0x4c, 0x53, 0x4c, 0x20, 0x53, 0x68, + 0x61, 0x64, 0x65, 0x72, 0x20, 0x43, 0x6f, 0x6d, 0x70, 0x69, 0x6c, 0x65, 0x72, 0x20, 0x31, 0x30, + 0x2e, 0x31, 0x00, 0xab, 0x49, 0x53, 0x47, 0x4e, 0x5c, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, + 0x08, 0x00, 0x00, 0x00, 0x50, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0x07, 0x00, 0x00, 0x50, 0x00, 0x00, 0x00, + 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, + 0x07, 0x07, 0x00, 0x00, 0x50, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x03, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x03, 0x03, 0x00, 0x00, 0x54, 0x45, 0x58, 0x43, + 0x4f, 0x4f, 0x52, 0x44, 0x00, 0xab, 0xab, 0xab, 0x4f, 0x53, 0x47, 0x4e, 0x80, 0x00, 0x00, 0x00, + 0x04, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x68, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0x08, 0x00, 0x00, + 0x68, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, + 0x01, 0x00, 0x00, 0x00, 0x03, 0x0c, 0x00, 0x00, 0x68, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x07, 0x08, 0x00, 0x00, + 0x71, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, + 0x03, 0x00, 0x00, 0x00, 0x0f, 0x00, 0x00, 0x00, 0x54, 0x45, 0x58, 0x43, 0x4f, 0x4f, 0x52, 0x44, + 0x00, 0x53, 0x56, 0x5f, 0x50, 0x6f, 0x73, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x00, 0xab, 0xab, 0xab, + 0x53, 0x48, 0x45, 0x58, 0x74, 0x02, 0x00, 0x00, 0x50, 0x00, 0x01, 0x00, 0x9d, 0x00, 0x00, 0x00, + 0x6a, 0x08, 0x00, 0x01, 0x59, 0x00, 0x00, 0x04, 0x46, 0x8e, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x0b, 0x00, 0x00, 0x00, 0x5f, 0x00, 0x00, 0x03, 0x72, 0x10, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x5f, 0x00, 0x00, 0x03, 0x72, 0x10, 0x10, 0x00, 0x01, 0x00, 0x00, 0x00, 0x5f, 0x00, 0x00, 0x03, + 0x32, 0x10, 0x10, 0x00, 0x02, 0x00, 0x00, 0x00, 0x65, 0x00, 0x00, 0x03, 0x72, 0x20, 0x10, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x65, 0x00, 0x00, 0x03, 0x32, 0x20, 0x10, 0x00, 0x01, 0x00, 0x00, 0x00, + 0x65, 0x00, 0x00, 0x03, 0x72, 0x20, 0x10, 0x00, 0x02, 0x00, 0x00, 0x00, 0x67, 0x00, 0x00, 0x04, + 0xf2, 0x20, 0x10, 0x00, 0x03, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x68, 0x00, 0x00, 0x02, + 0x02, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x08, 0x12, 0x00, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x46, 0x12, 0x10, 0x00, 0x01, 0x00, 0x00, 0x00, 0x46, 0x82, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x08, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x08, 0x22, 0x00, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x46, 0x12, 0x10, 0x00, 0x01, 0x00, 0x00, 0x00, 0x46, 0x82, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x09, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x08, 0x42, 0x00, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x46, 0x12, 0x10, 0x00, 0x01, 0x00, 0x00, 0x00, 0x46, 0x82, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x0a, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x07, 0x82, 0x00, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x46, 0x02, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x46, 0x02, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x44, 0x00, 0x00, 0x05, 0x82, 0x00, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3a, 0x00, 0x10, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x38, 0x00, 0x00, 0x07, 0x72, 0x20, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, + 0xf6, 0x0f, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x46, 0x02, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x36, 0x00, 0x00, 0x05, 0x32, 0x20, 0x10, 0x00, 0x01, 0x00, 0x00, 0x00, 0x46, 0x10, 0x10, 0x00, + 0x02, 0x00, 0x00, 0x00, 0x38, 0x00, 0x00, 0x08, 0x72, 0x00, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x56, 0x15, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x46, 0x82, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x05, 0x00, 0x00, 0x00, 0x32, 0x00, 0x00, 0x0a, 0x72, 0x00, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x46, 0x82, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x06, 0x10, 0x10, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x46, 0x02, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x32, 0x00, 0x00, 0x0a, + 0x72, 0x00, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x46, 0x82, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x06, 0x00, 0x00, 0x00, 0xa6, 0x1a, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x46, 0x02, 0x10, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x08, 0x72, 0x00, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x46, 0x02, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x46, 0x82, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x07, 0x00, 0x00, 0x00, 0x36, 0x00, 0x00, 0x05, 0x72, 0x20, 0x10, 0x00, 0x02, 0x00, 0x00, 0x00, + 0x46, 0x02, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x38, 0x00, 0x00, 0x08, 0xf2, 0x00, 0x10, 0x00, + 0x01, 0x00, 0x00, 0x00, 0x56, 0x05, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x46, 0x8e, 0x20, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x32, 0x00, 0x00, 0x0a, 0xf2, 0x00, 0x10, 0x00, + 0x01, 0x00, 0x00, 0x00, 0x46, 0x8e, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x06, 0x00, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x46, 0x0e, 0x10, 0x00, 0x01, 0x00, 0x00, 0x00, + 0x32, 0x00, 0x00, 0x0a, 0xf2, 0x00, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x46, 0x8e, 0x20, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0xa6, 0x0a, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x46, 0x0e, 0x10, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x08, 0xf2, 0x20, 0x10, 0x00, + 0x03, 0x00, 0x00, 0x00, 0x46, 0x0e, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x46, 0x8e, 0x20, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x3e, 0x00, 0x00, 0x01, 0x53, 0x54, 0x41, 0x54, + 0x94, 0x00, 0x00, 0x00, 0x11, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x07, 0x00, 0x00, 0x00, 0x0e, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +}; #endif // MSL only makes sense on Apple platforms @@ -557,4 +655,4 @@ static const Uint8 PositionColor_vert_spirv[1924] = { 0x3d, 0x00, 0x00, 0x00, 0x3e, 0x00, 0x03, 0x00, 0x08, 0x00, 0x00, 0x00, 0x23, 0x00, 0x00, 0x00, 0x3e, 0x00, 0x03, 0x00, 0x09, 0x00, 0x00, 0x00, 0x2b, 0x00, 0x00, 0x00, 0xfd, 0x00, 0x01, 0x00, 0x38, 0x00, 0x01, 0x00, -}; +}; \ No newline at end of file diff --git a/miniwin/src/d3drm/backends/sdl3gpu/shaders/generated/ShaderIndex.cpp b/miniwin/src/d3drm/backends/sdl3gpu/shaders/generated/ShaderIndex.cpp index a092dd6d..fefcfa80 100644 --- a/miniwin/src/d3drm/backends/sdl3gpu/shaders/generated/ShaderIndex.cpp +++ b/miniwin/src/d3drm/backends/sdl3gpu/shaders/generated/ShaderIndex.cpp @@ -24,6 +24,20 @@ static const SDL_GPUShaderCreateInfo VertexShaderDXILCodes[] = { /* num_uniform_buffers */ 1, }, }; +static const SDL_GPUShaderCreateInfo VertexShaderDXBCCodes[] = { + // VertexShaderId::PositionColor + { + /* code_size */ sizeof(PositionColor_vert_dxbc), + /* code */ PositionColor_vert_dxbc, + /* entrypoint */ "main", + /* format */ SDL_GPU_SHADERFORMAT_DXBC, + /* stage */ SDL_GPU_SHADERSTAGE_VERTEX, + /* num_samplers */ 0, + /* num_storage_textures */ 0, + /* num_storage_buffers */ 0, + /* num_uniform_buffers */ 1, + }, +}; #endif #if defined(SDL_PLATFORM_APPLE) @@ -71,6 +85,20 @@ static const SDL_GPUShaderCreateInfo FragmentShaderDXILCodes[] = { /* num_uniform_buffers */ 1, }, }; +static const SDL_GPUShaderCreateInfo FragmentShaderDXBCCodes[] = { + // FragmentShaderId::SolidColor + { + /* code_size */ sizeof(SolidColor_frag_dxbc), + /* code */ SolidColor_frag_dxbc, + /* entrypoint */ "main", + /* format */ SDL_GPU_SHADERFORMAT_DXBC, + /* stage */ SDL_GPU_SHADERSTAGE_FRAGMENT, + /* num_samplers */ 1, + /* num_storage_textures */ 0, + /* num_storage_buffers */ 0, + /* num_uniform_buffers */ 1, + }, +}; #endif #if defined(SDL_PLATFORM_APPLE) @@ -109,6 +137,10 @@ const SDL_GPUShaderCreateInfo* GetVertexShaderCode(VertexShaderId id, SDL_GPUSha SDL_assert(id < SDL_arraysize(VertexShaderDXILCodes)); return &VertexShaderDXILCodes[id]; } + if (formats & SDL_GPU_SHADERFORMAT_DXBC) { + SDL_assert(id < SDL_arraysize(VertexShaderDXBCCodes)); + return &VertexShaderDXBCCodes[id]; + } #endif #if defined(SDL_PLATFORM_APPLE) if (formats & SDL_GPU_SHADERFORMAT_MSL) { @@ -130,6 +162,10 @@ const SDL_GPUShaderCreateInfo* GetFragmentShaderCode(FragmentShaderId id, SDL_GP SDL_assert(id < SDL_arraysize(FragmentShaderDXILCodes)); return &FragmentShaderDXILCodes[id]; } + if (formats & SDL_GPU_SHADERFORMAT_DXBC) { + SDL_assert(id < SDL_arraysize(FragmentShaderDXBCCodes)); + return &FragmentShaderDXBCCodes[id]; + } #endif #if defined(SDL_PLATFORM_APPLE) if (formats & SDL_GPU_SHADERFORMAT_MSL) { diff --git a/miniwin/src/d3drm/backends/sdl3gpu/shaders/generated/SolidColor.frag.h b/miniwin/src/d3drm/backends/sdl3gpu/shaders/generated/SolidColor.frag.h index ebdbe79d..beb316f4 100644 --- a/miniwin/src/d3drm/backends/sdl3gpu/shaders/generated/SolidColor.frag.h +++ b/miniwin/src/d3drm/backends/sdl3gpu/shaders/generated/SolidColor.frag.h @@ -8,7 +8,7 @@ #include -// DXIL only makes sense on Windows platforms +// DX only makes sense on Windows platforms #if defined(SDL_PLATFORM_WINDOWS) static const Uint8 SolidColor_frag_dxil[6648] = { 0x44, 0x58, 0x42, 0x43, 0xd8, 0xfe, 0x4c, 0xec, 0x17, 0x1b, 0x52, 0xee, 0xf7, 0xc8, 0x8d, 0xcf, @@ -428,6 +428,206 @@ static const Uint8 SolidColor_frag_dxil[6648] = { 0xc1, 0x88, 0x41, 0x02, 0x80, 0x20, 0x18, 0x20, 0xe9, 0xe1, 0x17, 0xe1, 0x01, 0x1e, 0xb5, 0x81, 0x16, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, }; +static const Uint8 SolidColor_frag_dxbc[3160] = { + 0x44, 0x58, 0x42, 0x43, 0xe8, 0x49, 0xcf, 0x45, 0xf9, 0x5a, 0x8f, 0xd1, 0xe0, 0x69, 0x77, 0x8f, + 0x38, 0xc7, 0x7b, 0xc1, 0x01, 0x00, 0x00, 0x00, 0x58, 0x0c, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, + 0x34, 0x00, 0x00, 0x00, 0xb8, 0x03, 0x00, 0x00, 0x40, 0x04, 0x00, 0x00, 0x94, 0x04, 0x00, 0x00, + 0xbc, 0x0b, 0x00, 0x00, 0x52, 0x44, 0x45, 0x46, 0x7c, 0x03, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, + 0xc8, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x3c, 0x00, 0x00, 0x00, 0x00, 0x05, 0xff, 0xff, + 0x00, 0x01, 0x00, 0x00, 0x53, 0x03, 0x00, 0x00, 0x52, 0x44, 0x31, 0x31, 0x3c, 0x00, 0x00, 0x00, + 0x18, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0x28, 0x00, 0x00, 0x00, 0x24, 0x00, 0x00, 0x00, + 0x0c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x9c, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0xa4, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, + 0x05, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, + 0x01, 0x00, 0x00, 0x00, 0x0d, 0x00, 0x00, 0x00, 0xac, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x53, 0x61, 0x6d, 0x70, 0x6c, 0x65, 0x72, 0x00, + 0x54, 0x65, 0x78, 0x74, 0x75, 0x72, 0x65, 0x00, 0x74, 0x79, 0x70, 0x65, 0x5f, 0x46, 0x72, 0x61, + 0x67, 0x6d, 0x65, 0x6e, 0x74, 0x53, 0x68, 0x61, 0x64, 0x69, 0x6e, 0x67, 0x44, 0x61, 0x74, 0x61, + 0x00, 0xab, 0xab, 0xab, 0xac, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0xe0, 0x00, 0x00, 0x00, + 0xa0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xa8, 0x01, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x90, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x38, 0x02, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, + 0x00, 0x00, 0x00, 0x00, 0x5c, 0x02, 0x00, 0x00, 0x90, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, + 0x02, 0x00, 0x00, 0x00, 0x80, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, + 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0xa4, 0x02, 0x00, 0x00, + 0x94, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0xc8, 0x02, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, + 0x00, 0x00, 0x00, 0x00, 0xec, 0x02, 0x00, 0x00, 0x98, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, + 0x02, 0x00, 0x00, 0x00, 0x10, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, + 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x34, 0x03, 0x00, 0x00, + 0x9c, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x80, 0x02, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, + 0x00, 0x00, 0x00, 0x00, 0x46, 0x72, 0x61, 0x67, 0x6d, 0x65, 0x6e, 0x74, 0x53, 0x68, 0x61, 0x64, + 0x69, 0x6e, 0x67, 0x44, 0x61, 0x74, 0x61, 0x5f, 0x6c, 0x69, 0x67, 0x68, 0x74, 0x73, 0x00, 0x53, + 0x63, 0x65, 0x6e, 0x65, 0x4c, 0x69, 0x67, 0x68, 0x74, 0x00, 0x63, 0x6f, 0x6c, 0x6f, 0x72, 0x00, + 0x66, 0x6c, 0x6f, 0x61, 0x74, 0x34, 0x00, 0xab, 0x01, 0x00, 0x03, 0x00, 0x01, 0x00, 0x04, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xd4, 0x01, 0x00, 0x00, 0x70, 0x6f, 0x73, 0x69, + 0x74, 0x69, 0x6f, 0x6e, 0x00, 0x64, 0x69, 0x72, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x00, 0xab, + 0xce, 0x01, 0x00, 0x00, 0xdc, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, + 0xdc, 0x01, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x09, 0x02, 0x00, 0x00, 0xdc, 0x01, 0x00, 0x00, + 0x20, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0x01, 0x00, 0x0c, 0x00, 0x03, 0x00, 0x03, 0x00, + 0x14, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0xc3, 0x01, 0x00, 0x00, 0x46, 0x72, 0x61, 0x67, 0x6d, 0x65, 0x6e, 0x74, + 0x53, 0x68, 0x61, 0x64, 0x69, 0x6e, 0x67, 0x44, 0x61, 0x74, 0x61, 0x5f, 0x6c, 0x69, 0x67, 0x68, + 0x74, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x00, 0x69, 0x6e, 0x74, 0x00, 0xab, 0x00, 0x00, 0x02, 0x00, + 0x01, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7b, 0x02, 0x00, 0x00, + 0x46, 0x72, 0x61, 0x67, 0x6d, 0x65, 0x6e, 0x74, 0x53, 0x68, 0x61, 0x64, 0x69, 0x6e, 0x67, 0x44, + 0x61, 0x74, 0x61, 0x5f, 0x53, 0x68, 0x69, 0x6e, 0x69, 0x6e, 0x65, 0x73, 0x73, 0x00, 0x66, 0x6c, + 0x6f, 0x61, 0x74, 0x00, 0x00, 0x00, 0x03, 0x00, 0x01, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0xc2, 0x02, 0x00, 0x00, 0x46, 0x72, 0x61, 0x67, 0x6d, 0x65, 0x6e, 0x74, + 0x53, 0x68, 0x61, 0x64, 0x69, 0x6e, 0x67, 0x44, 0x61, 0x74, 0x61, 0x5f, 0x43, 0x6f, 0x6c, 0x6f, + 0x72, 0x52, 0x61, 0x77, 0x00, 0x64, 0x77, 0x6f, 0x72, 0x64, 0x00, 0xab, 0x00, 0x00, 0x13, 0x00, + 0x01, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x09, 0x03, 0x00, 0x00, + 0x46, 0x72, 0x61, 0x67, 0x6d, 0x65, 0x6e, 0x74, 0x53, 0x68, 0x61, 0x64, 0x69, 0x6e, 0x67, 0x44, + 0x61, 0x74, 0x61, 0x5f, 0x55, 0x73, 0x65, 0x54, 0x65, 0x78, 0x74, 0x75, 0x72, 0x65, 0x00, 0x4d, + 0x69, 0x63, 0x72, 0x6f, 0x73, 0x6f, 0x66, 0x74, 0x20, 0x28, 0x52, 0x29, 0x20, 0x48, 0x4c, 0x53, + 0x4c, 0x20, 0x53, 0x68, 0x61, 0x64, 0x65, 0x72, 0x20, 0x43, 0x6f, 0x6d, 0x70, 0x69, 0x6c, 0x65, + 0x72, 0x20, 0x31, 0x30, 0x2e, 0x31, 0x00, 0xab, 0x49, 0x53, 0x47, 0x4e, 0x80, 0x00, 0x00, 0x00, + 0x04, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x68, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0x07, 0x00, 0x00, + 0x68, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, + 0x01, 0x00, 0x00, 0x00, 0x03, 0x03, 0x00, 0x00, 0x68, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x07, 0x07, 0x00, 0x00, + 0x71, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, + 0x03, 0x00, 0x00, 0x00, 0x0f, 0x08, 0x00, 0x00, 0x54, 0x45, 0x58, 0x43, 0x4f, 0x4f, 0x52, 0x44, + 0x00, 0x53, 0x56, 0x5f, 0x50, 0x6f, 0x73, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x00, 0xab, 0xab, 0xab, + 0x4f, 0x53, 0x47, 0x4e, 0x4c, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, + 0x38, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x0f, 0x00, 0x00, 0x00, 0x42, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0x01, 0x0e, 0x00, 0x00, + 0x53, 0x56, 0x5f, 0x54, 0x61, 0x72, 0x67, 0x65, 0x74, 0x00, 0x53, 0x56, 0x5f, 0x44, 0x65, 0x70, + 0x74, 0x68, 0x00, 0xab, 0x53, 0x48, 0x45, 0x58, 0x20, 0x07, 0x00, 0x00, 0x50, 0x00, 0x00, 0x00, + 0xc8, 0x01, 0x00, 0x00, 0x6a, 0x08, 0x00, 0x01, 0x59, 0x08, 0x00, 0x04, 0x46, 0x8e, 0x20, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x5a, 0x00, 0x00, 0x03, 0x00, 0x60, 0x10, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x58, 0x18, 0x00, 0x04, 0x00, 0x70, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x55, 0x55, 0x00, 0x00, 0x62, 0x10, 0x00, 0x03, 0x72, 0x10, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x62, 0x10, 0x00, 0x03, 0x32, 0x10, 0x10, 0x00, 0x01, 0x00, 0x00, 0x00, 0x62, 0x10, 0x00, 0x03, + 0x72, 0x10, 0x10, 0x00, 0x02, 0x00, 0x00, 0x00, 0x64, 0x20, 0x00, 0x04, 0x82, 0x10, 0x10, 0x00, + 0x03, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x65, 0x00, 0x00, 0x03, 0xf2, 0x20, 0x10, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x65, 0x00, 0x00, 0x02, 0x01, 0xc0, 0x00, 0x00, 0x68, 0x00, 0x00, 0x02, + 0x04, 0x00, 0x00, 0x00, 0x0e, 0x00, 0x00, 0x09, 0x01, 0xc0, 0x00, 0x00, 0x02, 0x40, 0x00, 0x00, + 0x00, 0x00, 0x80, 0x3f, 0x00, 0x00, 0x80, 0x3f, 0x00, 0x00, 0x80, 0x3f, 0x00, 0x00, 0x80, 0x3f, + 0x3a, 0x10, 0x10, 0x00, 0x03, 0x00, 0x00, 0x00, 0x31, 0x00, 0x00, 0x08, 0x12, 0x00, 0x10, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x01, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1a, 0x80, 0x20, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x09, 0x22, 0x00, 0x10, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x46, 0x12, 0x10, 0x80, 0x41, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, + 0x46, 0x12, 0x10, 0x80, 0x41, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x44, 0x00, 0x00, 0x05, + 0x22, 0x00, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1a, 0x00, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x36, 0x00, 0x00, 0x08, 0x72, 0x00, 0x10, 0x00, 0x01, 0x00, 0x00, 0x00, 0x02, 0x40, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x36, 0x00, 0x00, 0x08, 0x72, 0x00, 0x10, 0x00, 0x02, 0x00, 0x00, 0x00, 0x02, 0x40, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x36, 0x00, 0x00, 0x05, 0x42, 0x00, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x40, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x30, 0x00, 0x00, 0x01, 0x21, 0x00, 0x00, 0x08, 0x82, 0x00, 0x10, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x2a, 0x00, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0a, 0x80, 0x20, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, 0x03, 0x00, 0x04, 0x03, 0x3a, 0x00, 0x10, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x26, 0x00, 0x00, 0x08, 0x00, 0xd0, 0x00, 0x00, 0x82, 0x00, 0x10, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x2a, 0x00, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x40, 0x00, 0x00, + 0x03, 0x00, 0x00, 0x00, 0x18, 0x00, 0x00, 0x0a, 0x82, 0x00, 0x10, 0x00, 0x01, 0x00, 0x00, 0x00, + 0x01, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3a, 0x80, 0x20, 0x06, 0x00, 0x00, 0x00, 0x00, + 0x01, 0x00, 0x00, 0x00, 0x3a, 0x00, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1f, 0x00, 0x04, 0x03, + 0x3a, 0x00, 0x10, 0x00, 0x01, 0x00, 0x00, 0x00, 0x18, 0x00, 0x00, 0x0a, 0x82, 0x00, 0x10, 0x00, + 0x01, 0x00, 0x00, 0x00, 0x01, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3a, 0x80, 0x20, 0x06, + 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x3a, 0x00, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x12, 0x00, 0x00, 0x01, 0x36, 0x00, 0x00, 0x05, 0x82, 0x00, 0x10, 0x00, 0x01, 0x00, 0x00, 0x00, + 0x01, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x15, 0x00, 0x00, 0x01, 0x1f, 0x00, 0x04, 0x03, + 0x3a, 0x00, 0x10, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x09, 0x72, 0x00, 0x10, 0x00, + 0x03, 0x00, 0x00, 0x00, 0x46, 0x02, 0x10, 0x00, 0x02, 0x00, 0x00, 0x00, 0x46, 0x82, 0x20, 0x04, + 0x00, 0x00, 0x00, 0x00, 0x3a, 0x00, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1e, 0x00, 0x00, 0x07, + 0x82, 0x00, 0x10, 0x00, 0x01, 0x00, 0x00, 0x00, 0x2a, 0x00, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x01, 0x40, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x36, 0x00, 0x00, 0x05, 0x72, 0x00, 0x10, 0x00, + 0x02, 0x00, 0x00, 0x00, 0x46, 0x02, 0x10, 0x00, 0x03, 0x00, 0x00, 0x00, 0x36, 0x00, 0x00, 0x05, + 0x42, 0x00, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3a, 0x00, 0x10, 0x00, 0x01, 0x00, 0x00, 0x00, + 0x07, 0x00, 0x00, 0x01, 0x15, 0x00, 0x00, 0x01, 0x18, 0x00, 0x00, 0x0a, 0x82, 0x00, 0x10, 0x00, + 0x01, 0x00, 0x00, 0x00, 0x01, 0x40, 0x00, 0x00, 0x00, 0x00, 0x80, 0x3f, 0x3a, 0x80, 0x20, 0x06, + 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x3a, 0x00, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x1f, 0x00, 0x04, 0x03, 0x3a, 0x00, 0x10, 0x00, 0x01, 0x00, 0x00, 0x00, 0x36, 0x00, 0x00, 0x09, + 0x72, 0x00, 0x10, 0x00, 0x03, 0x00, 0x00, 0x00, 0x46, 0x82, 0x20, 0x86, 0x41, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x3a, 0x00, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x12, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x0b, 0x72, 0x00, 0x10, 0x00, 0x03, 0x00, 0x00, 0x00, + 0x46, 0x12, 0x10, 0x80, 0x41, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x46, 0x82, 0x20, 0x06, + 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x3a, 0x00, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x15, 0x00, 0x00, 0x01, 0x10, 0x00, 0x00, 0x07, 0x82, 0x00, 0x10, 0x00, 0x02, 0x00, 0x00, 0x00, + 0x46, 0x02, 0x10, 0x00, 0x03, 0x00, 0x00, 0x00, 0x46, 0x02, 0x10, 0x00, 0x03, 0x00, 0x00, 0x00, + 0x44, 0x00, 0x00, 0x05, 0x82, 0x00, 0x10, 0x00, 0x02, 0x00, 0x00, 0x00, 0x3a, 0x00, 0x10, 0x00, + 0x02, 0x00, 0x00, 0x00, 0x38, 0x00, 0x00, 0x07, 0x72, 0x00, 0x10, 0x00, 0x03, 0x00, 0x00, 0x00, + 0xf6, 0x0f, 0x10, 0x00, 0x02, 0x00, 0x00, 0x00, 0x46, 0x02, 0x10, 0x00, 0x03, 0x00, 0x00, 0x00, + 0x10, 0x00, 0x00, 0x07, 0x82, 0x00, 0x10, 0x00, 0x02, 0x00, 0x00, 0x00, 0x46, 0x12, 0x10, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x46, 0x02, 0x10, 0x00, 0x03, 0x00, 0x00, 0x00, 0x31, 0x00, 0x00, 0x07, + 0x82, 0x00, 0x10, 0x00, 0x03, 0x00, 0x00, 0x00, 0x01, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x3a, 0x00, 0x10, 0x00, 0x02, 0x00, 0x00, 0x00, 0x1f, 0x00, 0x04, 0x03, 0x3a, 0x00, 0x10, 0x00, + 0x03, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x07, 0x82, 0x00, 0x10, 0x00, 0x01, 0x00, 0x00, 0x00, + 0x0a, 0x00, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3a, 0x00, 0x10, 0x00, 0x01, 0x00, 0x00, 0x00, + 0x1f, 0x00, 0x04, 0x03, 0x3a, 0x00, 0x10, 0x00, 0x01, 0x00, 0x00, 0x00, 0x32, 0x00, 0x00, 0x0a, + 0x72, 0x00, 0x10, 0x00, 0x03, 0x00, 0x00, 0x00, 0x46, 0x12, 0x10, 0x80, 0x41, 0x00, 0x00, 0x00, + 0x02, 0x00, 0x00, 0x00, 0x56, 0x05, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x46, 0x02, 0x10, 0x00, + 0x03, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x07, 0x82, 0x00, 0x10, 0x00, 0x01, 0x00, 0x00, 0x00, + 0x46, 0x02, 0x10, 0x00, 0x03, 0x00, 0x00, 0x00, 0x46, 0x02, 0x10, 0x00, 0x03, 0x00, 0x00, 0x00, + 0x44, 0x00, 0x00, 0x05, 0x82, 0x00, 0x10, 0x00, 0x01, 0x00, 0x00, 0x00, 0x3a, 0x00, 0x10, 0x00, + 0x01, 0x00, 0x00, 0x00, 0x38, 0x00, 0x00, 0x07, 0x72, 0x00, 0x10, 0x00, 0x03, 0x00, 0x00, 0x00, + 0xf6, 0x0f, 0x10, 0x00, 0x01, 0x00, 0x00, 0x00, 0x46, 0x02, 0x10, 0x00, 0x03, 0x00, 0x00, 0x00, + 0x10, 0x00, 0x00, 0x07, 0x82, 0x00, 0x10, 0x00, 0x01, 0x00, 0x00, 0x00, 0x46, 0x12, 0x10, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x46, 0x02, 0x10, 0x00, 0x03, 0x00, 0x00, 0x00, 0x34, 0x00, 0x00, 0x07, + 0x82, 0x00, 0x10, 0x00, 0x01, 0x00, 0x00, 0x00, 0x3a, 0x00, 0x10, 0x00, 0x01, 0x00, 0x00, 0x00, + 0x01, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x2f, 0x00, 0x00, 0x05, 0x82, 0x00, 0x10, 0x00, + 0x01, 0x00, 0x00, 0x00, 0x3a, 0x00, 0x10, 0x00, 0x01, 0x00, 0x00, 0x00, 0x38, 0x00, 0x00, 0x08, + 0x82, 0x00, 0x10, 0x00, 0x01, 0x00, 0x00, 0x00, 0x3a, 0x00, 0x10, 0x00, 0x01, 0x00, 0x00, 0x00, + 0x1a, 0x80, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, 0x19, 0x00, 0x00, 0x05, + 0x82, 0x00, 0x10, 0x00, 0x01, 0x00, 0x00, 0x00, 0x3a, 0x00, 0x10, 0x00, 0x01, 0x00, 0x00, 0x00, + 0x32, 0x00, 0x00, 0x0b, 0x72, 0x00, 0x10, 0x00, 0x01, 0x00, 0x00, 0x00, 0x46, 0x82, 0x20, 0x04, + 0x00, 0x00, 0x00, 0x00, 0x3a, 0x00, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0xf6, 0x0f, 0x10, 0x00, + 0x01, 0x00, 0x00, 0x00, 0x46, 0x02, 0x10, 0x00, 0x01, 0x00, 0x00, 0x00, 0x15, 0x00, 0x00, 0x01, + 0x32, 0x00, 0x00, 0x0b, 0x72, 0x00, 0x10, 0x00, 0x02, 0x00, 0x00, 0x00, 0x46, 0x82, 0x20, 0x04, + 0x00, 0x00, 0x00, 0x00, 0x3a, 0x00, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0xf6, 0x0f, 0x10, 0x00, + 0x02, 0x00, 0x00, 0x00, 0x46, 0x02, 0x10, 0x00, 0x02, 0x00, 0x00, 0x00, 0x15, 0x00, 0x00, 0x01, + 0x1e, 0x00, 0x00, 0x07, 0x42, 0x00, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x2a, 0x00, 0x10, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x01, 0x40, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x16, 0x00, 0x00, 0x01, + 0x01, 0x00, 0x00, 0x08, 0x12, 0x00, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x40, 0x00, 0x00, + 0xff, 0x00, 0x00, 0x00, 0x2a, 0x80, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, + 0x56, 0x00, 0x00, 0x05, 0x12, 0x00, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x10, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x38, 0x00, 0x00, 0x07, 0x12, 0x00, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x0a, 0x00, 0x10, 0x00, 0x02, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x8a, 0x00, 0x00, 0x10, 0x92, 0x00, 0x10, 0x00, 0x02, 0x00, 0x00, 0x00, 0x02, 0x40, 0x00, 0x00, + 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, + 0x02, 0x40, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x10, 0x00, 0x00, 0x00, 0xa6, 0x8a, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, + 0x56, 0x00, 0x00, 0x05, 0x92, 0x00, 0x10, 0x00, 0x02, 0x00, 0x00, 0x00, 0x06, 0x0c, 0x10, 0x00, + 0x02, 0x00, 0x00, 0x00, 0x38, 0x00, 0x00, 0x07, 0x62, 0x00, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x56, 0x06, 0x10, 0x00, 0x02, 0x00, 0x00, 0x00, 0x06, 0x03, 0x10, 0x00, 0x02, 0x00, 0x00, 0x00, + 0x32, 0x20, 0x00, 0x0c, 0x72, 0x00, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x46, 0x02, 0x10, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x02, 0x40, 0x00, 0x00, 0x81, 0x80, 0x80, 0x3b, 0x81, 0x80, 0x80, 0x3b, + 0x81, 0x80, 0x80, 0x3b, 0x00, 0x00, 0x00, 0x00, 0x46, 0x02, 0x10, 0x00, 0x01, 0x00, 0x00, 0x00, + 0x1f, 0x00, 0x04, 0x04, 0x3a, 0x80, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, + 0x45, 0x00, 0x00, 0x8b, 0xc2, 0x00, 0x00, 0x80, 0x43, 0x55, 0x15, 0x00, 0xf2, 0x00, 0x10, 0x00, + 0x01, 0x00, 0x00, 0x00, 0x46, 0x10, 0x10, 0x00, 0x01, 0x00, 0x00, 0x00, 0x46, 0x7e, 0x10, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x60, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x38, 0x20, 0x00, 0x07, + 0x72, 0x20, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x46, 0x02, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x46, 0x02, 0x10, 0x00, 0x01, 0x00, 0x00, 0x00, 0x36, 0x00, 0x00, 0x05, 0x82, 0x20, 0x10, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x3a, 0x00, 0x10, 0x00, 0x01, 0x00, 0x00, 0x00, 0x12, 0x00, 0x00, 0x01, + 0x55, 0x00, 0x00, 0x08, 0x82, 0x00, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x2a, 0x80, 0x20, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, 0x01, 0x40, 0x00, 0x00, 0x18, 0x00, 0x00, 0x00, + 0x56, 0x00, 0x00, 0x05, 0x82, 0x00, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3a, 0x00, 0x10, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x38, 0x00, 0x00, 0x07, 0x82, 0x20, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x3a, 0x00, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x40, 0x00, 0x00, 0x81, 0x80, 0x80, 0x3b, + 0x36, 0x00, 0x00, 0x05, 0x72, 0x20, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x46, 0x02, 0x10, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x15, 0x00, 0x00, 0x01, 0x3e, 0x00, 0x00, 0x01, 0x53, 0x54, 0x41, 0x54, + 0x94, 0x00, 0x00, 0x00, 0x47, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x06, 0x00, 0x00, 0x00, 0x1e, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, + 0x06, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x09, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +}; #endif // MSL only makes sense on Apple platforms @@ -938,4 +1138,4 @@ static const Uint8 SolidColor_frag_spirv[4616] = { 0xb0, 0x00, 0x00, 0x00, 0xaf, 0x00, 0x00, 0x00, 0x3e, 0x00, 0x03, 0x00, 0x07, 0x00, 0x00, 0x00, 0xae, 0x00, 0x00, 0x00, 0x3e, 0x00, 0x03, 0x00, 0x08, 0x00, 0x00, 0x00, 0xb0, 0x00, 0x00, 0x00, 0xfd, 0x00, 0x01, 0x00, 0x38, 0x00, 0x01, 0x00, -}; +}; \ No newline at end of file diff --git a/miniwin/src/d3drm/backends/sdl3gpu/shaders/src/Common.hlsl b/miniwin/src/d3drm/backends/sdl3gpu/shaders/src/Common.hlsl index c4d9d361..6330aa9b 100644 --- a/miniwin/src/d3drm/backends/sdl3gpu/shaders/src/Common.hlsl +++ b/miniwin/src/d3drm/backends/sdl3gpu/shaders/src/Common.hlsl @@ -18,3 +18,9 @@ struct SceneLight { float4 position; float4 direction; }; + +#ifdef NO_REGISTER_SPACE + #define REGISTER_SPACE(REG, SPACE) register(REG) +#else + #define REGISTER_SPACE(REG, SPACE) register(REG, SPACE) +#endif \ No newline at end of file diff --git a/miniwin/src/d3drm/backends/sdl3gpu/shaders/src/PositionColor.vert.hlsl b/miniwin/src/d3drm/backends/sdl3gpu/shaders/src/PositionColor.vert.hlsl index cbf14c6b..598b8f09 100644 --- a/miniwin/src/d3drm/backends/sdl3gpu/shaders/src/PositionColor.vert.hlsl +++ b/miniwin/src/d3drm/backends/sdl3gpu/shaders/src/PositionColor.vert.hlsl @@ -1,6 +1,6 @@ #include "Common.hlsl" -cbuffer ViewportUniforms : register(b0, space1) +cbuffer ViewportUniforms : REGISTER_SPACE(b0, space1) { float4x4 projection; float4x4 worldViewMatrix; diff --git a/miniwin/src/d3drm/backends/sdl3gpu/shaders/src/SolidColor.frag.hlsl b/miniwin/src/d3drm/backends/sdl3gpu/shaders/src/SolidColor.frag.hlsl index 6a6608ba..8f9a269e 100644 --- a/miniwin/src/d3drm/backends/sdl3gpu/shaders/src/SolidColor.frag.hlsl +++ b/miniwin/src/d3drm/backends/sdl3gpu/shaders/src/SolidColor.frag.hlsl @@ -5,7 +5,7 @@ struct FS_Output { float Depth : SV_Depth; }; -cbuffer FragmentShadingData : register(b0, space3) +cbuffer FragmentShadingData : REGISTER_SPACE(b0, space3) { SceneLight lights[3]; int lightCount; @@ -24,8 +24,8 @@ float4 unpackColor(uint packed) return color; } -Texture2D Texture : register(t0, space2); -SamplerState Sampler : register(s0, space2); +Texture2D Texture : REGISTER_SPACE(t0, space2); +SamplerState Sampler : REGISTER_SPACE(s0, space2); FS_Output main(FS_Input input) { diff --git a/miniwin/src/d3drm/d3drm.cpp b/miniwin/src/d3drm/d3drm.cpp index 45a4e3ad..1088a943 100644 --- a/miniwin/src/d3drm/d3drm.cpp +++ b/miniwin/src/d3drm/d3drm.cpp @@ -167,7 +167,7 @@ HRESULT Direct3DRMImpl::CreateDeviceFromSurface( DDRenderer = new Citro3DRenderer(DDSDesc.dwWidth, DDSDesc.dwHeight); } #endif -#ifdef _WIN32 +#if defined(_WIN32) && !defined(WINDOWS_STORE) else if (SDL_memcmp(&guid, &DirectX9_GUID, sizeof(GUID)) == 0) { DDRenderer = DirectX9Renderer::Create(DDSDesc.dwWidth, DDSDesc.dwHeight); } diff --git a/miniwin/src/ddraw/ddraw.cpp b/miniwin/src/ddraw/ddraw.cpp index a3bc5d4d..f9dabada 100644 --- a/miniwin/src/ddraw/ddraw.cpp +++ b/miniwin/src/ddraw/ddraw.cpp @@ -7,7 +7,7 @@ #ifdef __3DS__ #include "d3drmrenderer_citro3d.h" #endif -#ifdef _WIN32 +#if defined(_WIN32) && !defined(WINDOWS_STORE) #include "d3drmrenderer_directx9.h" #endif #include "d3drmrenderer_sdl3gpu.h" @@ -238,7 +238,7 @@ HRESULT DirectDrawImpl::EnumDevices(LPD3DENUMDEVICESCALLBACK cb, void* ctx) #ifdef __3DS__ Citro3DRenderer_EnumDevice(cb, ctx); #endif -#ifdef _WIN32 +#if defined(_WIN32) && !defined(WINDOWS_STORE) DirectX9Renderer_EnumDevice(cb, ctx); #endif Direct3DRMSoftware_EnumDevice(cb, ctx); @@ -357,7 +357,7 @@ HRESULT DirectDrawImpl::CreateDevice( DDRenderer = new Citro3DRenderer(DDSDesc.dwWidth, DDSDesc.dwHeight); } #endif -#ifdef _WIN32 +#if defined(_WIN32) && !defined(WINDOWS_STORE) else if (SDL_memcmp(&guid, &DirectX9_GUID, sizeof(GUID)) == 0) { DDRenderer = DirectX9Renderer::Create(DDSDesc.dwWidth, DDSDesc.dwHeight); } diff --git a/packaging/CMakeLists.txt b/packaging/CMakeLists.txt index d6fc19f0..3ec9d174 100644 --- a/packaging/CMakeLists.txt +++ b/packaging/CMakeLists.txt @@ -20,3 +20,7 @@ endif() if(LINUX) add_subdirectory(linux) endif() + +if(WINDOWS_STORE) + add_subdirectory(UWP) +endif() diff --git a/packaging/UWP/CMakeLists.txt b/packaging/UWP/CMakeLists.txt new file mode 100644 index 00000000..dedad8d8 --- /dev/null +++ b/packaging/UWP/CMakeLists.txt @@ -0,0 +1,15 @@ +file(GLOB_RECURSE GENERATED_ASSETS + "assets/*" +) +set(ASSET_FILES ${GENERATED_ASSETS}) +set_source_files_properties(${ASSET_FILES} PROPERTIES + VS_DEPLOYMENT_CONTENT 1 + VS_DEPLOYMENT_LOCATION "assets" +) + +set(MANIFEST_FILE Package.appxmanifest) +set_source_files_properties(${MANIFEST_FILE} PROPERTIES + VS_DEPLOYMENT_CONTENT 1 +) + +target_sources(isle PRIVATE ${ASSET_FILES} ${MANIFEST_FILE}) diff --git a/packaging/UWP/Package.appxmanifest b/packaging/UWP/Package.appxmanifest new file mode 100644 index 00000000..c0023051 --- /dev/null +++ b/packaging/UWP/Package.appxmanifest @@ -0,0 +1,47 @@ + + + + + + LEGO® Island + Helloyunho + StoreLogo.png + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/packaging/UWP/assets/LargeTile.scale-100.png b/packaging/UWP/assets/LargeTile.scale-100.png new file mode 100644 index 0000000000000000000000000000000000000000..94b280c2e82370c200eae83d38a87e296d5ebc35 GIT binary patch literal 2046 zcmeHIYgE!_7zZuIoL5p?VzDwkYgWz_yd}~~x@lfIX9i}`oSOTi5sIcFwUOP_oTt*# z)M;v47aS#*L`ZX?X?mQf)QA(`%Y-nYgh3t$&zMJz`E#4Phg9AJH)1h5Q z=DnoMj-Nxj1F>Du?!sI6-LNZyQe$5bEKMyKobU{B4H;0D>~vCpm9Hc{}(MqHxsUcevY)B3VYV218@ zWD^_0WZo4D`1cl63Jtf(hipvDpmK_Q6LA1twy?$r@QWaWgO*3F+2>vc?@y{LQ$i!8 z`ibC`mH}|~%C%`_pln8AAwUA=tKU5+=8_9)x3s_ZoSl9T$4wpu+t9*jOtdH>qC1YZ zEz_Dv$meM9?;sBge7FH$&*7X5}^zda;#Z- zh+7>*Fb+ygNbuE>gObdVz)vrCD>NyZ8fB-jK~tpH423qJZz0@HQG{)=36I^IQ00Y% zJyzg)Oq3cy>3B~@@LbI5;)H9)`q`!>n?PXWD z)D!U|RRiq=MccMxCjJdRDOU!AvoCNe(CEZDkt}HohM_+J1H;?(3;3AX(Yx!+T>+v2 z^g*EkbaEOf?EU-i0d|AwWxV@lDEl!^U}&8r^OAU$RztlvNPLKShqsEH{4?Lfghfw(c*izY{9&-2{A1si~yku%M|vwti%BtY4E}-PJ!c)O;T&u~)Q5&d(LF{E;#6 zZ^wky+&UPz^9{zS!4u(bOEmZOaQ6IU zD`1X&ycpBI8Q$|wTHcfZj*>Y8fqAu1pC*%Gm}CCN4;36#&a;DT_<9~QCcS3p`ef7v;Y0;9?XDM6t2RHYX#H-RydvEyA zxgl($^ZNR{0}@pLsl2#H8FFM6Ak2?G`{7ZRfYWk=v1Rf)mT+lc@PPE03*e4jT0fp@ zR9tFvFE8#%${Lqc7a(e?Kh(?#{f|N83em;9V2PaSsEDP2+cVwd$zQNgp2W-bDdOwL zb*TjA2p5vDfo}C_}K;|We9}C=e%l#O5%UCam@uzDaJUTUIh3@vIG|!^r RhJg;*yE|mpgPq5I{2Niso_7EM literal 0 HcmV?d00001 diff --git a/packaging/UWP/assets/LargeTile.scale-125.png b/packaging/UWP/assets/LargeTile.scale-125.png new file mode 100644 index 0000000000000000000000000000000000000000..e8531486bbf41c57e8d8436fd8e18c74cd416bad GIT binary patch literal 2648 zcmeHJc~H}58vYR@IfRfxiV+Z!QY0O&AXhj;AaV+bESG>up$KRMN+}``NFpeQ9J^ae z3?N7gRHY^imP^91>PA~2SQZj6G)jR8%C!)MK*)xf-I?u7JG=kw{?R|)`R4t;Z{Fj1 z-kIkuITh@y4L5)T0HE#fM?MVz8io4?)l}a&@*~I92Q1w$`ZEAPJlr=8LE1N;0012B zPbU2#yKZ@gUty7?A$Yld4EYLM%;JDxAEmquZY;bnfy*_%p9^?pO7af6f|vvOF&yj) zCLtzV!AtYN3!1mg3UX+IC^26pNfblsJ40s^{C<(xTH5k7;|A!d3 zI2+$hc)p6bwuG1U0f|tM(+qrQKEc>tOV%JX?Jvk8BfIYeLZ@$TG=7|1=h5LySJCP) zYu{I)V?73};6mX<+H0xu-0gAI|JU5dBB=%!LkUVmL`U?C*uZ3a?oLa;QZbufTO|gQsF4vo@12gCS|yxT zfk8HRt`don(t?~jd!g~u7NSnrVyk>vxFJMH%%P}on|2jHC;GUD6~Cr9#C=5gdTlpj zX|W>H3SMv~-ly>piJE00ZnJA0&$Uu$Td-Le{DY!PyVmp5)1bCe48C?F=!8rqjW$YM zc1tZ|9UW~`|0r_-h`s9%_>W#@zukyHjhf`{NvsAgWqBfTOEiY+LXBV*kD&i_1;@jY zFr1;T&D&;*X{u2Rf&0g=YNM~K1GEoKoib844f}i4LDGx*+ag_S$ja@O0*;SzgAx)l z(Ip7xGO#iS-Gln-CZ2Vo3i4HA!r#UflWf^x$4_MKdUNeK(hK)6%f}+2DoG@A$sKJJ zNd|4TyEL~u1(e+D-yJN~1D`#jR}>@i>~=^G=~>`;c_RWkU?o#9P^7l=>KOmfY$pHF zxduX8-tr4cRalg`@KGC>SsQ#{W#cnBU!mfTo|$&UNDZFs&=#g%a=fKxs)aq`WLHCV z-ABn#P`#UbI3--qx;iT^hR}8EJ7%uJdRw(SYg=wI*)a)|UJa8{EWMCy37;vM_ARtI*JP?meQZ2iB~eC1=OAt`F8$8dNI!Z`{C~dQ>$Y78S!tgn{mUdg0=&z}N~W z$8U{XtP{D320Zm(e)04kE6aeavnsuuQ$8weh&P6xKbt^{A{U(oGvj+x9nT*v)*08% zr)$pIj=$2au341OhuPZp%zSn=)sd5nw019k3k!Q5 zdQKQFU-jUc!%zAnad$3ugi4zgokM9Qbckr7+4#wTfvZV*PLs*?`orh|BztCzlo#Y$ zVa}dn4Jy6C*xXTc%1t-s%VRpY%=PT3G1GrI|CVTgjg`c~V$|n_8s1SokIu+V|tz z_kZjOJ}$VwM0^5~o2(E6g^<_g(Adw99JCeRU&usH>UIYrlP6^G#CTq+mn_(kI>We!8#_qGj-K{GJW8xC%){@4(J4vI{J~-F7v~WLEqVkl&CH zMs8hqH3SQN6H;R5;aX;|xw(kE8g)pG#jSS~O?&H)*9i}c zGCB!!BfLsQx_Hj~Aq&j>;2dm7MJco33@~wH*D!+>CHoluI@14<13$#SI6z;g#{5@} h{JtstFSn&F8;313!>r-71L{=~@IMhuzUM_{{R3#jYA*l) literal 0 HcmV?d00001 diff --git a/packaging/UWP/assets/LargeTile.scale-150.png b/packaging/UWP/assets/LargeTile.scale-150.png new file mode 100644 index 0000000000000000000000000000000000000000..b73fc6b574ebeb5d3a8bea1ad96cc25294d3bcbd GIT binary patch literal 3354 zcmeHKdsNcd7RF37&5Xn>y*YuF!PQKDH! z7>2JDXY3*=VrfkjRw`*$iulNkYhaBSV~jGi?ppKD+_mnyv*yqK*z2sb&sl4K`<(Bb z{p|ujUr&8q6I}=dqVMemU?C8Vfv={ct@hYII9H_}R>ymVCP5%;AAB_pKC%8d1hVF- zH?Ti2?YdOt+A?)S<58q#n1yak8^E9x8{FSOh4s7rh*E`FAs2P}<2(!34RW>gwLsm6 z*(+H&9#< zf`5kVoe)p2B>gbYe|b#X5bD1nGR95GvaAikyR`9DinQ*P0CiGGiC`V@@`Pe5DIj(b zrhz&-%r5m!#~rXn{+lmNlXP)T%oxBs=yf&M>>uoi*wVbv)C}+GN z>+B>zMPPs;%V7`Zq>gz3(!ep*&DN8$^<38Xd&J_oDtbl7l39SyT+h1#O@_2Zu)OHrpgL(vrWu%bO$h8bo~G zhcwFokyWidcTqwEy75464gkA5=*ZIxU+3>I#cT*Se-SZ^K2>L)wvBNbEhgVIMIoN{ zn|J)Oan0x!MqnTOCCg;}gIMz1awo3KdBLMQOdXV6(RZ3)U9ZTbPCNGjBU`lQV6vl6_6H_$Lftr1^1n{n7Hs~5V@(BF(8+b$xA?p4N$j5&8cSC7s6xOYwK zm_V6mW!Aj3S4=}VhIAUIeCW3;9_gH`P~D3kYre$frM}(=WrFP;_Az#K@W%c!au>LqHWtF2 zb~JBUbsNsGel*elYm?rH!Yl!Ymv<%fjs45WbAGELSI)5wSDi78Glp5J;kY!;E-U3x z@02?NYs6)y+#2Bp*NRq)1FqWhk(|!ms(1INK%_f<^_b*Np`+~?;!6I9WS|8;SEh>` zQd4v`k(ws0te&|-HY+ky5$<)&e11P&bz%G}h-L1`=PN`WXS9dvlVRlX)^r5M=!?fC zzIgN6XSZd^gJM#RusbsVTD6T4NW4dMA?>rbqJ7Q(An^L4{O{UDm{q%Zz3)Ke(8l## z^_*_2s*!zhZ9itn8GmyEb8|03aOzO{;FWm1t84?5Cw(K|5$F!QZaN-#>QpMM#Bj*a zzwt6aU}@%RE*Sa;N3q8BH50fEFT))bmmu#=AE8sGeZa zUL~@jr!Oz`c;4hIcar3nNfCufsgFjKwTr_`IPKmojHc&<0UaTp$LX{axv3qar6dEq zcwa%ny_c@`$WxyiCwky*lJ-BE?6V zOtbghh%W+LofH=zvUnd4Z!U9co4-No64`vm43WWaHPJw9^Of44L{m57Hob73)FvPyZ0IyeS;9cNRMF6!CRTRjxZpklgbwCIjMc0K3iJg1V zf@>gh;pbhQVQ7K+*)1#va55X@9`UyDD1CrBM2^EKzAK^xzwO_1Z_M%8@Im4#In=-L zBMeI(53V!K&u-4j$Ut$d%fQwn$JU~@RR_&#->M~rZi6!GyLho&G0&u9twu9l;TdWL zW!`-O&cg!C{UQ*ztS^DB8;?zTf@TTVZk6Q51D5c~Pi-G9zPv(lCj9?Uupnkt_d~OkwVlhFptQ zD22FgYNk@NcPk?>T6JD#aNfQlpVE37fg!!Aqwi|rvAZTY)3Q+RZABz=1p*V~vKncB zGLNcd_$Osiq%@^?|5_%9d>W^q9Efz8bf7qbzsoCY(Hw>d`O2hL@|Tm>K18p(3wJ9ghw;y>k*1&pi3P}@?VJ4GC1m>MQJH_C2*U)WaHEIYd~g}gmCrvp+3@uH sJ%NT|WBm9Z3H40@eiO!jK||2bzZMV5e|5vAtD7B&x4SRELjU9JZ@Wpw7ytkO literal 0 HcmV?d00001 diff --git a/packaging/UWP/assets/LargeTile.scale-200.png b/packaging/UWP/assets/LargeTile.scale-200.png new file mode 100644 index 0000000000000000000000000000000000000000..0230084965d628ae8e7f6c3e8f1aa44a7601ca4c GIT binary patch literal 5425 zcmeHLdpuP87T-e+V!}}pG9+~zlIjd1Nhqc0(1{w%lv2!$p~;&eQ(h6BQ<2xiIq7!t z7!6Zsun{HCqIqmXlE=Z!oDvhmxZ6E_?&o~&=ibk`f86`Wy`TQrpS}0`t-XKi@m*{E z)=D|*dH}UbdldixsDsXq?f@W1T)q^MFlFz}B_sHz6zqJ00syOh1*)`1M3X=%TcDsy%d&XCvqn%zX0flLTOG zZ9Fn|3VtdbESM;x9zg*5e8Q+6ELE$L1rQ&cfCBXM@bZA-7A^oZej$Ca#1~Bb4~GJ6 z{gmWv+}p8jRisGjFYkfOBMi{d)rntNY@9LiAtQ(reQa3h*{ZYtoOO&|E^j}An2M)e z&({ca^B5N14QeT+&h1;Ae|jnLskx{CJA_O-!~mVcYCnj~Sjhf&%9#F|^BM}%kzd!< zilcqwdlW6Sz$#|oJ=Rvf|MdGPDPMuQng2I3H@hP2aR#NR)NILSMpUhy)C)EGO;5Hp zF^PAroK~edGQjxaDMF6zFr`94cfIt{z3#F43b^Yq zAGb{iOMgs5dZ9q1Nl4tSQmM7=W+?3D63TilY0#&gg*bO|IL;I?y1d8NAkx#B4jw8v z3q2!<=JPfR8ixVzgAA~3Lt;;mr;O5BQAwI>+a?huKCW&z> z0aez`8ewCCj;|voHACM!>MWbpWV_Shlt=d_D2JwPCWK@KKHcMIhCR%C%y4uI;mN{$ z|8O?{)RVq)9EUZsG^*Rx?&<^$>Y7@vf&Xc+%;zxDgz*|(%|bt3N;kGUP$`<+IY5e< zj$ZO&FTTEhwll>Pt*^R!GZznFjt_V`jEs%(;{=ie=ufhDXj$F+SH<3JgiU0-bs&9+Fs#E9WepM!NIC^xXjruz7~G!O%1@De zS!c@6HB$oO(MP4?lJL0D4z0+W+lz#LGqWti7jP(;7;y2M{2U<%#B4X!ET8ww27nO>V74QZgt$EU3;DNop~vyM6?@pzj0hP9;bS9K*xMPpg} zJ)i;a9dFGB>MCnT`9{!?QmjAJ8S9Xsnwhj#gJ^!qrHSGiHEAbKo#i)wk+0xM)-MarxE=cV44SO_Azp7w8GiiJ1&lK_g?AF z<`IW>)>cw~Ad@Hs{aU+IF8)xYz_6y-mqN%WE(=>ZI<> ziAT91gn|pndOP?N!kr{+0Xq!q-Jwt?Z@EIpw2P)NUTD^feKc*&Bz-sUt;M0NG=b4} zC2ymT5#1lqfuAqIIgbe4azkT_l}l^-p~*$MH**`=Ir*inLcKdV`-3w7L?0m}77 z%pdzT@Zup@LZ@GoJ#g=bK|7PoKKAnoe<9s zL`ht1H5aFAn#Ai*(;MdDRP=cJKDfcf=2Bx*EWGqVB#-)Jfa;fq!fC9h=zhG?vaU~ID4kOxuDCn`JE|n0jVzKZ zCJ6Ih6*;trSnB`3_HK`GT|E0X_GdXlK<)nQm}hNx{skk(O)vj+pP+i_&V76ijD@ z$;bo9T}Bdx0Oc@p5jn*Rt<~##))Mj_7?0qtp0G%UR= zOR*eV(s3fOF)`U9n~tYlsjfcLDhs>SVQ?b_rexu1w|mFupSo~D^D(3PeEc4`iqAHy zaMejz>g>zdWX$MBK3*>YrMbUkyJZjZ6`Yt7G?>J$Ub@(fSLS8tv?wpT$GSnzi`HeV zO+f8`=>%m$8iY;vh$)fQv*;nzjry8Wg_9bB63Z0UMx-)1O=7B7bqNoc)c4*desp0W#`QhQZ-di6Q z`u#3#h2tH6I^7s^clS35D1X@ah_Q6> zlhhe4-=Lm=%Es+$-;{=UgZLvz#!IW`6tZ)EZ?*AY4ewg4v0*)1-S<=&YIIp8(dwdF zpELt{6tZeW4SWGMxg{un#cO#-llzL>q)O4tJxO#_#inu z1y5Uv-B(hfCm75CL!P7?Ipp&a(**<*2UjdJsPsRWM literal 0 HcmV?d00001 diff --git a/packaging/UWP/assets/LargeTile.scale-400.png b/packaging/UWP/assets/LargeTile.scale-400.png new file mode 100644 index 0000000000000000000000000000000000000000..e447517671b5aca3525c56889dedd703f4a2ff0d GIT binary patch literal 14676 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 PC5w@PxjyBH%kTdNyb?JQ literal 0 HcmV?d00001 diff --git a/packaging/UWP/assets/SplashScreen.scale-100.png b/packaging/UWP/assets/SplashScreen.scale-100.png new file mode 100644 index 0000000000000000000000000000000000000000..ba958235fc0f45c4a8a400db0d5735660cd5dd9c GIT binary patch literal 2498 zcmeHJX;4#F7=0n(u|`%Ah*(A0Lw&eD zyJ%e;-Xv)R+8r{`Ho`!9XKU`AeJ@xmMUOt zY~(hPk-y4gA6~L(90{7)HBn8eA#qf+Hr}$Mw45lB_DS!qBR#Z)ZowhByU*RrZ`N%b zxi)F_8LfqX*{WqcR+%{zqY;irb%Ik_Skx-{f(o5GfE@q>ONq!$KDOC!%zKlL4{r=R zI^UJUB};!!U2Y#-L6oUccp;IFlj|A#aX81NpCh9((?1T1i1OA~2hX)Ed$Y zidtQbY-1FP2=lE**{78hyaJAI>0jO`+?D9Q71C3V?Mtfk^^>lY zO0<6oT-3s1WhLlXc;=Rxn!O#grOdhKOQVwc*`9?iw0{;6Syp7(o1_~MAC7C?Wk1{H zN-LKJ%rsRz2EyUJS$b7cjT!9Aq9ndpBs}ZdtfNi6`b#dhe0wMOQdfi8O^^I)u0gUo zS|d)Tw(z^VSIwyQ4_ybjyAR&o>1Uh1heuwMZqS5Z=2hOnX*eq^Rcd%9y=IixP#>zv zM=kI|P?(s3sdJ1%uVO@1#KrSvO9@p$<+gBoLEF5jTKrif1Am z*;S!NUIZLBc!ASm8tm+ur-X)l6tKkRA+jCUnsqXH9Ahv6-IK=>)yzU!{CgE^WHF02 zd_LiuB2P-oZ zx2zp0bw67AneYx7j6spEX8KR_nk!yONCYbyc#u_HZ%M*I25|jCldWMMA@uLX|H>?J;9IsvtzFy;XR{8%T4(ewCX-9*a~;T}Z&0oYmrky&$EI$_8s zsuX;pN)>JJcbx&y4c=owibtMnVUq*K95$VxP=7-%mbjx6yd$ycHQaX^o+x*Mnr$|j z=`Xe2S%|ABOn=lJH;@6A5?BZWSP$Qx_VN(wUfA;bV^Gh{Be+VmHvHyt^GIqX+Qx~A zsLHj165)Sdvg7rC? z@Xb^tKVN3ig^BpV?b4zGr2bwyNcwo@7_U^7!V?u?h%)P15C6SL>prh^m{S!QKPuO% z{=o~wJMMKNclIaX!sao6aod27*Xabko1z60rz89+=^`>t0SlRqw|s+{1~p;k*Go^? z{~7zQJ*~9~O>i6=R9U@Q)JU;z)2{>?daY0Y_1j8~^|S literal 0 HcmV?d00001 diff --git a/packaging/UWP/assets/SplashScreen.scale-125.png b/packaging/UWP/assets/SplashScreen.scale-125.png new file mode 100644 index 0000000000000000000000000000000000000000..f63eb9e8a392978a7d6f5af2982e27d99f1b67ae GIT binary patch literal 3167 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 literal 0 HcmV?d00001 diff --git a/packaging/UWP/assets/SplashScreen.scale-200.png b/packaging/UWP/assets/SplashScreen.scale-200.png new file mode 100644 index 0000000000000000000000000000000000000000..24263c2446710eceb49cea80fca63476c0be1f9c GIT binary patch literal 6410 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 literal 0 HcmV?d00001 diff --git a/packaging/UWP/assets/SplashScreen.scale-400.png b/packaging/UWP/assets/SplashScreen.scale-400.png new file mode 100644 index 0000000000000000000000000000000000000000..f622b917af1f95dc4c6b677f16929d4c536f9792 GIT binary patch literal 19945 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 literal 0 HcmV?d00001 diff --git a/packaging/UWP/assets/Square150x150Logo.scale-100.png b/packaging/UWP/assets/Square150x150Logo.scale-100.png new file mode 100644 index 0000000000000000000000000000000000000000..2bce23c58bdbd316dfef03912905ee2c0c1cc665 GIT binary patch literal 944 zcmeAS@N?(olHy`uVBq!ia0vp^(?FPm4M^HB7Cr(}oCO|{#S9F5he4R}c>anM1_tIN zPZ!6KiaBrZyv@61z|eLv<<-}m@AZRXc3){~%;{wR%MT2%?&5qsDLZLr;gMC3rS2Id ziaz`qX8G+yA+LB?^~suqjIXH$KfE6sB<8Y)w3nr)>|fSDan;--+i&l>?i=APa(&O^ z=;}ATlfT{iUNqxnvCrY&IG_a)-D%m;vKN%^2l~lRKe#pb@R6&b5#6?n{eI6l(6X<# z-NOI%!P3>MH=H^Vd`R>AX^zl-pYphG-?H3SDY~t84NuI~6kL0B>m9ibEQVK73k+Q% zwr;h&&bauxfum?_d+EEmk9O8B&(bq751dl__M7)_x67pwTgyVK`XAiiyI1kbLEZZ6 z=c8|K|FtLk-1k#$oU5<>Tm0JN+}mjDbDP%Jp4(ZhTjziIeE#JxkGF;Ym)gbu`f^LH za#Qx>8-;sbEvxjE?_PQLmED?KJ01GU@>Jd&6iD3LALTC+da*brwsq@I$IrrUpQM*6)i3 z-=);n?2li#)8>EWoqVac)0QXboFyt=akR{0Gx)GmH+?% literal 0 HcmV?d00001 diff --git a/packaging/UWP/assets/Square150x150Logo.scale-125.png b/packaging/UWP/assets/Square150x150Logo.scale-125.png new file mode 100644 index 0000000000000000000000000000000000000000..906a3f487cbe81b5413bf5dfd7c580f9f4091816 GIT binary patch literal 1172 zcmeAS@N?(olHy`uVBq!ia0vp^dq9|j4M-mHJhB={aTa()7BevL9R^{>sYkdjiK%0w9`9}zu9z>chckM95Y=v&1&{>Wpr2R$n;U>RQ|j_eDNkn ziOoOmT+K^7o873ezB-jxJ>n?M$Sk-^Ore_m|q|9!6C(=Erhmi)@T`MO$u z)&pjldH##~veu=?`Mv*_K0lp&LvLzsSQpiNbm zEc+*__Di<=&+7e8pFXbG@|>?JK6AO{UkEGFYFEC@|B`)qU7s16_8zr=E60u;ve;657hI_P8GR4x$m2aP>t(%?w8j;AA0qN@B7scK!04^`QO~9 zRBQ2@%1wE8wrRg=P8%BQZ=dIWUiz&#N1dB_RsDOnkJnE>55GJ$v*Yi#pd8)fKK<+8 z?6x|ndwrtc2jg9tXZ^Qodg~tf{%yVGvX3&0`M(x-)XjSkp27J^62y?MopytL(R$%O z*8ieAe=fP%@z1?Ue&xEw^`Bx~>U}??{tLZy-mG_~@~{7#b;kmeBm~rJFVMZS-exKSi~_;qe&es z*t}V#()RjD3{`{ErdH5F4u|djX@-O a%Kos-r{7UZem{W441=eupUXO@geCyXWg-y( literal 0 HcmV?d00001 diff --git a/packaging/UWP/assets/Square150x150Logo.scale-150.png b/packaging/UWP/assets/Square150x150Logo.scale-150.png new file mode 100644 index 0000000000000000000000000000000000000000..10cd54dcf8abd87d4729647ebabf4226faf1e744 GIT binary patch literal 1363 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 literal 0 HcmV?d00001 diff --git a/packaging/UWP/assets/Square44x44Logo.altform-lightunplated_targetsize-256.png b/packaging/UWP/assets/Square44x44Logo.altform-lightunplated_targetsize-256.png new file mode 100644 index 0000000000000000000000000000000000000000..05e20da7bd4e4443c2363f9312118b136700a901 GIT binary patch literal 3676 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 literal 0 HcmV?d00001 diff --git a/packaging/UWP/assets/Square44x44Logo.altform-lightunplated_targetsize-32.png b/packaging/UWP/assets/Square44x44Logo.altform-lightunplated_targetsize-32.png new file mode 100644 index 0000000000000000000000000000000000000000..34f380b781b31245930c59ebf19509882cff7d46 GIT binary patch literal 371 zcmV-(0gV2MP)pGB}qgozHAe^YkofAxgkmFnqB+Y|-Zw3@q3mL+iXo%)@18@NdqF9`Rp{OYXHh`9H za!BLsdcR!4&9NyR^Cf8k$RRnA_r=P#ELSfg;S4+XH2^O>o(*0LZ`1@|l16F`@Fmc) zF)6bfDgapzUOP@gs509S6SpG`$f3{qJamJbssiaD6B?h>raN`d$rhA!g}Qfg~cp z4nbb)5Xr}x)kIXf92QXpRX}E~Ph6xit3;>*@a*GNXdp&%nN0!it?ohlG!Rpod9#S( z3SeTTY?R`}VvsWfxd*Y*=^gF^ XgnZlPKJyl+00000NkvXXu0mjfanMpx|^* z7srr_Icq0BHo!@we?llCZN(!Q9*n-sQ$D+?P3 zEEL)+@s(xvBZov5B{9XQW}ATB7DsRIf98Jne8Pf7{V$vrYqaeyd9-8Z1kw19-CG07 zCw$K2Ygty;v-@anMpx|Rq z7srr_IeVwv}yYV}p~g8VzuVdjPZ8j{khg)V2c@b_)y^Alur6j4&RA^K?7 z@7Q@ZuA$ko8g3l>w=?wSylJt{=#pXT+4l6L>+TrYGo9+^niO}rmTpayJNBfpnZ--2 zFp0BBPw1FK*R&^}b&|6u2C;n%^Yggs_^en<&VC2S3gOtmdmq^+?3q`3he6YdgZt9a z!;CXp=IzY*Dkt(uF5$M7^cSw22Kz5I41oq$_V4mMG9zR5+UARzk literal 0 HcmV?d00001 diff --git a/packaging/UWP/assets/Square44x44Logo.altform-unplated_targetsize-256.png b/packaging/UWP/assets/Square44x44Logo.altform-unplated_targetsize-256.png new file mode 100644 index 0000000000000000000000000000000000000000..05e20da7bd4e4443c2363f9312118b136700a901 GIT binary patch literal 3676 zcmb7Hc{~(a`#-Z7<7F5kld+Z**D~BLA%f)L+C8KWCB<3<) z*{`ugmP=8#Y+Z{n(vf^15)`6Bmb!(?V{=d zL$o-Fln6z??O#bTN`#+Anh3Op(|PRsO-}C1FYn*g&E(zXaUWR@Z|3FcQp0zfb$z@k zvqfoM?9gsnF}8*}lrPJ3z|5a5gRdSOJsO`Bbt4CqS}Ih2j^;lkLTi%+SjQ0{D=Amy{EN)EO&Eiz zxz2Q>@u+7WL!M&96iq}32a=9ETrj)Q7XbATZUU*zlEirU^n`zZvj$IckH43b9)J_6VBz*UOQk7=U}TIN9zNldl2;b3zBiJYY~z zv!)Pi&3spgwRtqgw>MC77y3eKr1eFv<9lHlGSGF>7 zq-U|A+?82wXQNJH3;6C~#b0Z|Bg1y3l9d?b0u@ z6Ad-pKYhI$YbBdAe@I}2yt(l7k&R`iUsII&;a2&Q<*xOw_aDTLz8|x_w}`USg?DA( z%$8>3)|U4qJtp%YT>FhiX4b?8Pfd&>0*pA##rsCs_bL!{eei!GKh=Iz^G+WS~d zsKB7JFq)@4dHwkeMl5%p*%!9$%5=zZ%v5pUBMjD2y$;#$0|RvK*~llYxD`3qyce>H zRS${rw-+M{?-`Ok-f4m}M2J9vd%H8e71t81`vl(p_+7J>D;7r{QYhb$XC$G`blubs zAST}UYVjHxoS1mU^PLv;oqe*MrkA#?>pv9qQ};9MkX?FHN2t4h-X^l>dv>WD0fY=Ke6=C$IsgdOS!672WJN#c`VCiGtt3i zl?sL{VL&$u=9$V{IiY2s(UiP+u)2c_KX+leAwk2^Rq591au)vF`cnJiiuZ;LEI}Q9 zdI&pOV=7&F(rLJ2)HA?|j+d`)ZmbPEGPBIPUK4O??f#NxsLR(f%kXr^-qJEz>9E7o zthxfs=1lL4t}qRonDXBRGsZ8;%wAM?h?3<0ebq~5q`U2B;(aSFp4|uCNxVmS)2EsQ z&r;{3Up!v98?KyU`u>gWy${(Bn$CTQ^?SZ$5@9)fuXN6f-CHl&HZAdaB~xatWD@PI zG%}P>GVAcuf)Au#e>3+w+j9O=WAuX7m0U{Jd*0w5fo3Ha(oV!!#xGxc9_IPzp+azi zWv`N3rrrWxNU>vW@lv8YdC7luY}~=;m)jNwiloEGL9I+mNg#3dp!7%FCA0GRfSU_} zYDa-lP@T1{_0Xud*Du;|;g|b|F8YAP`|(*7TX_SnczsG_bjO_qDR>lY&bs4rV{sJB zWBMzX{H$#l+@KxTSevNzcV#zkBy#|T6fH$&1@J?@rN-vLJ4OnQgiO&dDK<#m0$^!M@%@ z#3YxQRijkxuJvjm)1dUGfTEc{{|tY=eyXLhN&u1#!6k~%iOeXNlL_(fht?66UkkC? ziYpH=29^FMFIXb9>@uBTYuU_BH_8Y$BDq;bbF=cHjW6<8*=G*zHNaHOWzb0n+9~fimSv4QjZbS{MpJv%uEn)aOm~W( zPyMcyYd;9X~II5O0&hzpPNhH+yLFIqtH1#lF_yjIFcHk*i zf9`H$u-{%$%wXzuX2v=-Y8B>nJLlX+?vZMarO4UbTT`9d_8Z_L3da0+;bPEC#b&7Oc){h&l47;{bXjK=dp?4YrZ#R~ zY}cfoLQdYNw?8n zO`Z-U`)1fQo5qH`A(O6ONMQytpUV>FaPlK}u5@8phi{Yy&h>VMwq1|BBAd|nxuJM( z|IM6a9GitX+WKDZ-dJKKiIa1Z&_`u~auUQ@ThAkBK!J1u{PjBOkWH#ww(D-;HG`Fn((evn$49Y+HU|eBYgLXL%k?Lk8Q+w0*)&Wb_`^Ifo@SK3+ge8 zV|@8Fi{MsFB-n%X{4W>mo})>U088ETnQkEgU4XVU9E2Z@@o-N?r$_;tYHqvpe#tMu zAx#Eq4P#uMXse(bufpzi2*q*04TCLyFRSe=%uiDBZ~3=sUgibmQbQN6M8OD~b9h$- zJ)f@s9_%-($O|imdBhq+a^`Hd2{D>UP{U4ej*nBj^wZ~A4cT2TX3t?=u>x*Ro*^V4 zdn1|uud)DasvL%V8Y$i^Onb}E#A2bwus!hotGJ%cZqqA#mWIF z$R4A3PG}-Q3l0i!=>P~F0-)_bXthctUw4+3UigCDXyf7WXA<^VqCm=kdwy(VD%5uL zTA@bO+Tszok^(rU7g-r9cvSt+f7t)f-QS|L=XdH2>S8N)DP4u*ke-`cU{d>*LpQVP=#P@;cpTR=qZ7N1Rts_A1x_Qtmk~z5jgb&3q z+-|6`Lj!UAN`9RJ$q;}#n?|(!kMaI6z+<0BB5pqcTDzBKNhzR@l>{w)m@#JzK$0k+ zU4R1*3iqUB@1 z3KqK1gpeTP*aTvA*rmHn&It{i3)zxh=YOddZTdf3gP~VX#v(Ug@|PHeuG40 zT=nr(vxo+vK{@jkdq?=R%SWE1l>|io@t533M?jlbTWr(EuRLig+aBH$clS2W(hZ?? zD{uP2Ol<$-M%F<9 z`o9(Ve^;h$oe=dm{CA9FQ^M>}eV|sL#F6-S*jZpSOES73RG&7mEv)(BGYd&$vM0_) jLCc>n!yUrVgFJ{th_e85Ev0Jjy9msU&z>qX@`(QzjHWwy literal 0 HcmV?d00001 diff --git a/packaging/UWP/assets/Square44x44Logo.altform-unplated_targetsize-32.png b/packaging/UWP/assets/Square44x44Logo.altform-unplated_targetsize-32.png new file mode 100644 index 0000000000000000000000000000000000000000..34f380b781b31245930c59ebf19509882cff7d46 GIT binary patch literal 371 zcmV-(0gV2MP)pGB}qgozHAe^YkofAxgkmFnqB+Y|-Zw3@q3mL+iXo%)@18@NdqF9`Rp{OYXHh`9H za!BLsdcR!4&9NyR^Cf8k$RRnA_r=P#ELSfg;S4+XH2^O>o(*0LZ`1@|l16F`@Fmc) zF)6bfDgapzUOP@gs509S6SpG`$f3{qJamJbssiaD6B?h>raN`d$rhA!g}Qfg~cp z4nbb)5Xr}x)kIXf92QXpRX}E~Ph6xit3;>*@a*GNXdp&%nN0!it?ohlG!Rpod9#S( z3SeTTY?R`}VvsWfxd*Y*=^gF^ XgnZlPKJyl+00000NkvXXu0mjfpGNl8RORCt{2n9&ZyAP7Y@>Hq&Si&<$IQ3TZ08t=>0RXtKb?Gaw4 z<7oh^Ht=X|;L#F+0Rmt-#t_Hj)Kvtguvkyfyq&7DDC`%{vdaJ_mq2LGMBxT3E?6V8 z4>!08S`{tqQV}@!`VkP@jqn_lx=1@PLLd|`U$Y86of{In70PYEG|(s*_2mYirY@qE z$%m;+002ovPDHLkV1go> BxCHpGv`IukRCt{2nbCH_APhjk}YNiLf4(cKj!@I0A}KHR&hD2xLishf#cih6h0MW$a6=<`Bu7eP%IN;_{d|+XRN=g z$>$Ja$mc*oHQA88%?)V*}>JCwaBvpM#iN z!OZoZm%HCzb}8S) zHxSy%)Q&^EFTUoi;&N7Txfh($ literal 0 HcmV?d00001 diff --git a/packaging/UWP/assets/Square44x44Logo.scale-150.png b/packaging/UWP/assets/Square44x44Logo.scale-150.png new file mode 100644 index 0000000000000000000000000000000000000000..8f5e6d9ebe11fd2ac2de029828f7e5c0041ea1b6 GIT binary patch literal 627 zcmeAS@N?(olHy`uVBq!ia0vp^P9V&|1|%PcFuVX#oCO|{#S9F5he4R}c>anM1_mZo zPZ!6KiaBp*Mduw>5O6J=di2ZcyZ?=T-8_3m^qQ-rpq#NmK=>~Ash1>f8yshzXHm&g z^zp}Q#yhNC3?hsgOaUwlI2;5WbRXFqo|$jmSMY=H`PSPv&OWl!3sLeCy7WzD@wWby zfEc~x&N=Hid>f*cSg01>H_kcH^V0N?(47P~56RO7(K_Pm#b4ezowB3z#PSJ?B`kHP z`A*Fg+}f~ohNtHHhch{UE}EGyy-Zt)Epeyvly9;TDysJ%md>3Jd{ossL^67nW zH9Z)2k)7ILMg{W0+u>l{yl~9(MX>FwWd?J3aTxZd&c+_DEj0e|A< z!ZWnx(qFD`dHuPgCc$V$SoBMqjk@;Bu7*ERZ&|-xz|Q@cozA1w#x*>*qM^W&+b*6y-_MBL)O;{{%K-|1onZ4n%lnlz1n^iq;L;#~7uY=$LN0&2^Z`~LEHu^8}``7F9@%!&{yf~Ngwm+B|VnD-G)daVO zO2JcHN!1GzeRR^maoC{5;L6a&aJsJh@BIWfAPW(_55%p7&Y5{G<9rKDMGT&-`m8(}kLzx2|90&} z>$+Xyg0qC)bIwlq^zf1U8yn3-$N!pd7fQ}dX_9s-<}XtG#l2B8Ul?7y z(fy25WwAn2@}nX(t@MR6ABa@GK0SXjV{__U^I7_h8rnI+@6I1A%z9a-f5?93g_V0U zKi_yZufkRO|GVE03!@r1|E+K7zB1zo=h1uG+kLfXaq%tfTs%2bRb^xE<}Uf=PhH)E zUCw)CdU~In^C0qBcI%c&cVa$d&#G8H@%3e=qK|jCM69hA+c_=j*a^6G7|dY$?LBF`J45A>WkY`4TN}%mp?F7Jba_3=*qM+)|_WF zf4CsxSfS~^=yT1b2WH#eE`N6LZQ0FVp$3;%yb%^Ts9JQsGl&J4AkJu7q<=H5SXOpq zPLpY<*h;WwhoGx(Dyx1~(%nX@tMUpi-MNq_FNcvtOWDdEuY!kv+9tJ$$?r!P-wp+k( r1IN4<$NSqBi7+Z31bPOOsAv3T#vd;*H@Os;IT$=${an^LB{Ts5`>u=! literal 0 HcmV?d00001 diff --git a/packaging/UWP/assets/Square44x44Logo.scale-400.png b/packaging/UWP/assets/Square44x44Logo.scale-400.png new file mode 100644 index 0000000000000000000000000000000000000000..5c7d9c85a5e9f3a9caf65d3f64f68340a141f9fe GIT binary patch literal 1805 zcmbW2X;c!37RNzSQd88@)WI!Ns!_}>6*4k+r^EqE3yvabxu9S+E~x|7WG#- zKWM(3wZII|A+>5eSR4E07zIc%=H=UkH!W1?RTBE^s-EfrYNC$X9bwWejwM#@6*BBA z%H(5~uUr*W={)S`6N3p**nA(OP`Wn*YP}RHH3s}e2cbE z;oV7%wskXCV0b4(^iNqvyNf*|TCtx}ff1Mv$ti0mhtc;ZD8PPvg6@Va4me-E>- zgqIuRCbD2X9mqV3EmG<scHBf0U zYk$vjuomMc-x|M;>b(eqI?gY+hh4tcEo#ED?yz=@zLW>vh%OH zJsq)V2~#;4r`MW1(OCj{yZ-mBW>LzU6U@NrwOKoX|I)|Eq9tsBLp>drp#6c&D~kV|Ha5vE7JNI{8!Y<^zQ+O}2^Jlu_N% zJ6Zk9eyzD>pBIL@`*Wn|snssHhxzUaopMC9w5+YXGsQ~uxsp433)RlRqzu*e2VV;L zHqP~o!+m#!XgF&zQmCPry5-2m1WCXnY!KP3-@SV{P6v#PjYs1THdh{d?}1^_&@g2? zWKmDV6;pe&Yz ze7a~XhsUlnd_CB*yfLl$wP6j!v$b&T9(S%D3EC9$sbTz6Zz3FTEASk(yLUu6?z(Y)sn5^Nvk4Vzby0!#0Rp2qUJ8~Pwz z^3AT$G~mw0mX}!VG@x&EUxBviOQGiBjRS?3oyy6NWM|JT<1>v;DF$MMno#~Gl2CA* z$1~8#chd-ErC;!JV~JQokj*qF1m#-&_2bbzTl@cgg|IqncGL~_#}J+gomTVwZ;zJ; zyq}1zcJ1Fu;ek!X806&91Jjhb5YFtWM_VM5q-tWB{P#ekLd_l7p;n%wsxyn?s~YFD zNLWQN@>zB*(97W9eM2=X_}6_cJqc@h(V@ExC`C{f^f0e^yXnjAnM}tzh*#7pE8Mcr ze{%m_@h`8fnWGYL-d2i}fVdwtQlgtVPRe(lcfIpQKt`pHRF=CQ(xP+n@$}8d8|ZUa z(>Tqj=k+5XPVY7$EK?k=xc)ncPRp~|wj<&HaX5l$3tqG;-&aDjQFFq6n$l}|EnLh8 zO0^5dZi?kv7~s^t8%|{v2_E2I6ke$cxmoJF>)N+6lXXy4)wNCk9k+Voe+He#x5anMpkS@1 zi(^Q|oUK8Qds%xL$1|>NupCi*Y zJ+fjJRyL1%URgFJ?5@;?9X9N4_S!#H#Nrh{?>+zC?8w>U8C=ChJX$}PtB$#{YQNjg yqVX=U`p8$#0}H-?sPLa|U%2p)>n)>-D0ZnP)lgltKrf&}7(8A5T-G@yGywq4iAg8` literal 0 HcmV?d00001 diff --git a/packaging/UWP/assets/Square44x44Logo.targetsize-24.png b/packaging/UWP/assets/Square44x44Logo.targetsize-24.png new file mode 100644 index 0000000000000000000000000000000000000000..24711949132b88ecf7fdae78771a43f4215f90a1 GIT binary patch literal 279 zcmeAS@N?(olHy`uVBq!ia0vp^5+KaM1|%Pp+x`GjoCO|{#S9F5he4R}c>anMpx{YQ z7srr_Id`YtbHOWyKV>Fb?bHy*>uC?<<6iGySi1eFH&^BGj&XOF=M90 z^8&N=3)k{HSo(E8nW9XFHHRVQefXNA1yW=dPhyQ1pMv2*X_jJ%T%sKi$C|NGD0!mzo- bDuJ0j!lwLy56d>7rx-k4{an^LB{Ts5c*Jrq literal 0 HcmV?d00001 diff --git a/packaging/UWP/assets/Square44x44Logo.targetsize-256.png b/packaging/UWP/assets/Square44x44Logo.targetsize-256.png new file mode 100644 index 0000000000000000000000000000000000000000..e93cd58dd234b9f2ddeb41a6027fece29aa35ffa GIT binary patch literal 2884 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 literal 0 HcmV?d00001 diff --git a/packaging/UWP/assets/Square44x44Logo.targetsize-48.png b/packaging/UWP/assets/Square44x44Logo.targetsize-48.png new file mode 100644 index 0000000000000000000000000000000000000000..1cc66b30bd9ce3cc6692a9a88bdb5be571b5ca18 GIT binary patch literal 436 zcmV;l0ZaagP)pGW=TXrRCt{2m^&F83abhHr!4nc5Zm+itfeUAOWzk#~H?LxKv6t`%$7Czz_f+44s_Rxu8zstNlPT zq^6S^AjF?f)NR&yW?i-~M{EF?@paY-!xeR?*q-A;$URW(D~1*qLI&_7<0WG611a}^ zTO2R&bHMGF&*U=-4+UacILq^YA81rcO=)yZuHLBOf!Y!Scuy@x@{q8O*PjP2l}pS5 z37gVbg=(umfYY}$k|rRm+@4qjIv#(}x~YkKe3+ za4LYWp@?A$^8y)$rwkhG4wek37_kVgIK_F8wesEG&+gNEPRqnR&b{*FNz$bVk&s#5 zCM?HxPWbyy?e&#SRfd}%naZEL${Kw!^xW*!X`5Wr0>yWon)r09b4Z-urZYZ)lY?LB zX6kfK_+0d7>eIXb?4>4XYmLpX7;brKgB&`#F0`2;3pFd8Y6321H$Hh}x?p+#Rmu&bZsNa;+<9RIegzhv3?T1HpYAAQOmuBW{zSbqb z^tngMvL}q&nAY{So_+XUO55?-%wVotkL1Lft~|||m3!RZjoUHpy``;@ntSww*z<)y zvTJsxrAsVa82wN*@$k>tv6V+oM&#~MetvqV!4zhLsh4lDUfDPO+pJ?Vg_-h1e?R&t zSH*X$w=~(hZt!rjI_!yWd7(8A5T-G@yGywpOGy@p` literal 0 HcmV?d00001 diff --git a/packaging/UWP/assets/Square71x71Logo.scale-125.png b/packaging/UWP/assets/Square71x71Logo.scale-125.png new file mode 100644 index 0000000000000000000000000000000000000000..076c070376a051998b5821aa8fdcc2f10b62e5a7 GIT binary patch literal 683 zcmeAS@N?(olHy`uVBq!ia0vp^ks!>$1|&ndJJtg!&H|6fVg?4j!ywFfJby(B0|Qg6 zr;B4q#hkaZ63Y%L2(Wq1|Ni1+)y{isY(0WE@_QR9n7w;r_xD-2v%sSrdfi_pv{({BC-B&{@D;94oPt@8e8gr}b#oFJ#_peW%{-yHtl$YTp z5--yxxie*7RPL*_yR>J;oqXTMCt@+hn;c)QSZu+wZj$#F>63iIR~1)vTWx(K5_c?e zmRb14v`L%S#Kq|;ZU6PA=bcs4=~AzAi+q(Uy_4=v`@b$VZnat3a^LBeJM#{wD!*JL zJ~j8sJwx++*Dup*Sl0OEea@>spE3E8UW1<)eB@#}@@l8o&h^Ljl_|J1y=<4Ldy`)|hMQZ>iLJq<+9KAZR_{kDupL8HJ~ zy_(E-E>EtMKffKPZtr*a@do2}s&|Y9;*0cEidXc%V^!MsgdH5<2V6g$@2boFdw+WP z^wed05+-dARyi*gaqY?T4dQD{7zN@#X gpu+4y8galpTWITytJ<=CftT-05Jn!s->VfNbIvPj=*?pgQ^F4r8G zLTk3g+~hrw`E$#?_S4sQF8}=a`h#7|o@bfQI9Rpd)Khlz$p@!>;QS`j@-=@ihwv?? z;`ys5PMuxy=hW>FpBQ(|@?O*IZ}(|e^2Z*DRK1PYrdC)V*jfaw(FTy_uRRyyx}e(~~;+xJY_^XrsJ`QkGv<`L4OlBu>;d~LhUPTgsknXuOI z)WwjboLG0+nq@oKil_dR?I|+3df?LMmdii4{JXv&`C0_$vl1@*bLaCbZ}paIyZxSN zEEOi*LLmC z&pN()=Cs(wU(T$~`yBNC{Mi$%*RHKA_`B@tvajd%8?CSS^{Y7UKup5a+66l+vh6-I zJ>xt5Pxt0I<{8p!`ePS{Q@6Z`68 zukSZIy?Bnc&Q(8n(&*}6dfV6RsKJNz4SjWnTY2urCR+X~choCxU3!a?d&&j}7@@6m qdjAE5CtTiJg+ZHK7*&LpUXO@geCwvh;|_W literal 0 HcmV?d00001 diff --git a/packaging/UWP/assets/Square71x71Logo.scale-200.png b/packaging/UWP/assets/Square71x71Logo.scale-200.png new file mode 100644 index 0000000000000000000000000000000000000000..3805d16b095425c7f06747bbef559de74fe5d63a GIT binary patch literal 1057 zcmeAS@N?(olHy`uVBq!ia0vp^eIU%i1|*;VHQ)eJoCO|{#S9F5he4R}c>anM1_tKK zo-U3d6?5L+Nt`se>2fNUr#+CvFMHvt3GgWFVP8;byE&gnCHa*AN_0~3x+eIrjJiO9$ zGG2LJaVyVqQQv(&a{lM5z2*zVSN;F=G2(yi-6f}r4|7QRtv8iZo%QFhwRv)H`mDH& zFP!3iRz-769>s*4?v1c{cqow7;#p*UyLy-?gGlz~;}D zRC&MCj)GgytywqwQpMSz9R*XE@6P*fW4Y)Whw+ma!g_O-n9s9m(7j$VuY_?U&yugR zYHbt`d&(W%{O-ps$B*~8o2{RuhpoN-<}s(VUl3bBpU~FkM@2KET-aqyzWc;p``E&} z+(zx?-J5GOUmOYkdaba3DbQzEl2-Y;Hm-ZA@{{B9^G`pf+2)_tnltNDgu3{P=r5-8 z)=&N^vUL;h@}5PezhdO}-@CVL{_(try$jE+$vM1or}?j#dH)N47M59bmg-opF4z`x z-|~mapQjP6!Mn(9mdYb`wA_w}67l{;PVZ}z$zyH^_{+KNphO#Bx2u}x4PgIf8qJ>fuARrAK#?>QDJ+NNn~rY1t?9uoEdzr zGWYbRW@VH#w&t+p$7Ln$-SwTN7BBxq*#EA5_itwU&gVxT&benJGp7}pCguwf5E0RTb@J90`uUc{aY5u%5W{-c^8=H89ZJ6T-G@yGywo1 CSlO5W literal 0 HcmV?d00001 diff --git a/packaging/UWP/assets/Square71x71Logo.scale-400.png b/packaging/UWP/assets/Square71x71Logo.scale-400.png new file mode 100644 index 0000000000000000000000000000000000000000..020b9307e8410217786c94a7dd6638029291355b GIT binary patch literal 2488 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 literal 0 HcmV?d00001 diff --git a/packaging/UWP/assets/StoreLogo.scale-125.png b/packaging/UWP/assets/StoreLogo.scale-125.png new file mode 100644 index 0000000000000000000000000000000000000000..24dcfe0550806d47b462a14a825f7ca2885965d1 GIT binary patch literal 710 zcmV;%0y+JOP)j{00009a7bBm000id z000id0mpBsWB>pHcu7P-RCt{2T3u4aAPlBXUw!mV58*ZR9_yVvh}ZBE-ay}xc4Q}} z352x3?uKt>XJ;ut`Mv}QWoHNAz4H!Xn%soT8e*}h+W7N%6oPmx?QYdO_Z)|JTLGdCyF{6#WI9H`GG-JY{yqeAz6!D=2vFkvg9M8LIPkq2#pG8@ z`bEHke&n&&8wi2tJB|YN_$tpsKrt!`k(%#n0cPnOj>J;+D+!VEG+Yde$6GzGUBpst z4&EK$e8*!#0lvcLN!sZ->^T7_qA&_#nP&f207d~-{^$y#;NP5=dW!OD!ySN|2AnV~ z$n+L_n?F=S-cgeV`822Zbd0K=?io`evU)qln?K(Go0epz)GE za2?zSha~yCJNM(6p(&#zgTgu*asJxDfTL4wfh|RIK8AkEXpN_oeSj~%pOgSu@`3Ps zAbeVXJm|gE3n>Az`&Q{Al)kH7-( z2b&us9svd5b9k!Q^#aWmQF)VkAcD$_ s=c(&=Gvp}@@>Vg(Tg4!60n~NXFQP62R@Z|N;{X5v07*qoM6N<$g3&8SoB#j- literal 0 HcmV?d00001 diff --git a/packaging/UWP/assets/StoreLogo.scale-150.png b/packaging/UWP/assets/StoreLogo.scale-150.png new file mode 100644 index 0000000000000000000000000000000000000000..db730a3191d925df60a8772eeff17a56fb513414 GIT binary patch literal 809 zcmeAS@N?(olHy`uVBq!ia0vp^-XP4u1|%)~s$Kyp&H|6fVg?4j!ywFfJby(B0|V1- zPZ!6KiaBp*-!8muAmGZ#|NoNPsSUderu#|0bjnrPzs%L`fy3;hj~}Ua>`OnUJd?%o zLxwnuL#AOk?&@s|pI^)nK&e!+>}z9m;UvOT;aj31oHa%|ss{_6AP zsl}^Od~ZDb9e8byR-_THyo&#_IP=7#o3{Krb>GY*X2tqRe`gr-{@S!O+}+kgMrrA; z&+W>mV|P@a6}(#+@VflnJ5#aLqq#RypSn#i+o;R7viHIwoo)Qn%3Q*{W{7zP-D7!_ zG`CudY4V+=inG)GF95Tev3|kU!}Cp@5foM^H;@8 zTN9wAWTf`I|76FyPe=EE?znZ*^SDu@aqk?dNkY?fUwa&#+*Nd>W#y)(#qTC}7qzt9 z)%~|r>&3+lkr!K1mp&{PemddMq)q!)Yqaa@*xsGE_(RvpO|mzhubnEt@tUsflihsh zxl$Qz{KEX^{ivKhJ6yJS?nG9HNQNnJqV@iHEm`?~=|j=cRxR=M(w{#qd#l;8=2qvC zH#e@=U;pvHp*NEQsH1;!tDm=to;1` zqMzF|r%#AxpziNe283V)^ g&zopr0GpO^Z2$lO literal 0 HcmV?d00001 diff --git a/packaging/UWP/assets/StoreLogo.scale-200.png b/packaging/UWP/assets/StoreLogo.scale-200.png new file mode 100644 index 0000000000000000000000000000000000000000..d9f30bf41752d3834b15b618c95177c85627784c GIT binary patch literal 1190 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 literal 0 HcmV?d00001 diff --git a/packaging/UWP/assets/Wide310x150Logo.scale-100.png b/packaging/UWP/assets/Wide310x150Logo.scale-100.png new file mode 100644 index 0000000000000000000000000000000000000000..82e3a2e62640438a52f4ccd25175ada95efa4f63 GIT binary patch literal 1043 zcmeAS@N?(olHy`uVBq!ia0y~yU^D}=r*W_W$&l+yJ_9Mv0*}aI1_r*vAk26?e?r^ou9!Uh(BU6>v>RY@WMk&gaPzIUlaeHnzmG zH5llh|Hs5oEo`RHw}G*+g||aF$3Z5M`4NY-fcuRGi$iQj1P6=MJa8ogWtJNZ#+5{+tTa%%KMtEv-y6W(#%spuAVpJ?Dlu(PVD8&SKeAB zwD2C=>XzQknqJXqHqr0*CSFQ-mu`9HgJGImevVR3%$7x4x8%xguuTtX-+ubSHM7FS z-5WmoonNuK?mVL~*H@mG7F(LxeSt2Ntejdf^?GWHnf%PVl{as$mRl*g_M+>J9jjc{ zh;D!Ro_Sis4xUuu#N^4>Y`3%b++*qWZRvH*HeQzPfNmdF!_IrE@al|IXZ6?E8=B@O$~g zA6{?EKVPnpv*wpU;s$|*YYSgl+y8}hD4jo&xUcfX%&`fM^bo#ZQ+;Wfo9xn?{50d|| zM=O59hQgkuMpHk<-sUeCKus@;Pg{Ij_vgm*Xa5(UcNc@DEQ@1Xzh+!pU8s=wSVHW@ zCl^>cyO_Dl?M^$!qFFw6j?^$>M|E<@2H&=f4cz5EW5^B;sW@uA+K$sJhP+wf$ z+4S}y_g$Vp8EDo+Q}PR=KC!S5rGqe4W9GZ(`WKzgx6Wo?;Rwv-44$rjF6*2Ung9!U BzkdJ# literal 0 HcmV?d00001 diff --git a/packaging/UWP/assets/Wide310x150Logo.scale-125.png b/packaging/UWP/assets/Wide310x150Logo.scale-125.png new file mode 100644 index 0000000000000000000000000000000000000000..4a3315fc9490b0cf8c8897785855536276121868 GIT binary patch literal 1320 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 literal 0 HcmV?d00001 diff --git a/packaging/UWP/assets/Wide310x150Logo.scale-400.png b/packaging/UWP/assets/Wide310x150Logo.scale-400.png new file mode 100644 index 0000000000000000000000000000000000000000..24263c2446710eceb49cea80fca63476c0be1f9c GIT binary patch literal 6410 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 literal 0 HcmV?d00001 From 54832590da38993381c6f764c4d1fe6c7a8d24ae Mon Sep 17 00:00:00 2001 From: Christian Semmler Date: Sun, 6 Jul 2025 07:30:37 -0700 Subject: [PATCH 086/188] Update README.md [skip ci] --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index fe4a285f..765c74d2 100644 --- a/README.md +++ b/README.md @@ -15,6 +15,7 @@ Please note: this project is dedicated to achieving platform independence withou | macOS | [![CI](https://github.com/isledecomp/isle-portable/actions/workflows/ci.yml/badge.svg)](https://github.com/isledecomp/isle-portable/actions/workflows/ci.yml) | | [Web](https://isle.pizza) | [![CI](https://github.com/isledecomp/isle-portable/actions/workflows/ci.yml/badge.svg)](https://github.com/isledecomp/isle-portable/actions/workflows/ci.yml) | | Nintendo 3DS | [![CI](https://github.com/isledecomp/isle-portable/actions/workflows/ci.yml/badge.svg)](https://github.com/isledecomp/isle-portable/actions/workflows/ci.yml) | +| Xbox One | [![CI](https://github.com/isledecomp/isle-portable/actions/workflows/ci.yml/badge.svg)](https://github.com/isledecomp/isle-portable/actions/workflows/ci.yml) | We are actively working to support more platforms. If you have experience with a particular platform, we encourage you to contribute to `isle-portable`. You can find a [list of ongoing efforts](https://github.com/isledecomp/isle-portable/wiki/Work%E2%80%90in%E2%80%90progress-ports) in our Wiki. From 9e14aac98a90bd9341802d15c1ff4d52383cf503 Mon Sep 17 00:00:00 2001 From: Anders Jenbo Date: Sun, 6 Jul 2025 17:00:39 +0200 Subject: [PATCH 087/188] Implement real transition for 3DS (#538) --- ISLE/3ds/config.cpp | 3 -- .../src/d3drm/backends/citro3d/renderer.cpp | 48 ++++++++++++++++++- 2 files changed, 47 insertions(+), 4 deletions(-) diff --git a/ISLE/3ds/config.cpp b/ISLE/3ds/config.cpp index e70ec4dd..88318565 100644 --- a/ISLE/3ds/config.cpp +++ b/ISLE/3ds/config.cpp @@ -16,7 +16,4 @@ void N3DS_SetupDefaultConfigOverrides(dictionary* p_dictionary) // TODO: Save path: can we use libctru FS save data functions? Would be neat, especially for CIA install // Extra / at the end causes some issues iniparser_set(p_dictionary, "isle:savepath", "sdmc:/3ds/isle"); - - // Use e_noAnimation/cut transition - iniparser_set(p_dictionary, "isle:Transition Type", "7"); } diff --git a/miniwin/src/d3drm/backends/citro3d/renderer.cpp b/miniwin/src/d3drm/backends/citro3d/renderer.cpp index ab9088bc..a82f1238 100644 --- a/miniwin/src/d3drm/backends/citro3d/renderer.cpp +++ b/miniwin/src/d3drm/backends/citro3d/renderer.cpp @@ -637,5 +637,51 @@ void Citro3DRenderer::SetDither(bool dither) void Citro3DRenderer::Download(SDL_Surface* target) { - MINIWIN_NOT_IMPLEMENTED(); + u16 width, height; + u8* fb = gfxGetFramebuffer(GFX_BOTTOM, GFX_LEFT, &width, &height); + if (!fb) { + SDL_Log("Failed to get framebuffer"); + return; + } + + SDL_Surface* srcSurface = SDL_CreateSurfaceFrom(width, height, SDL_PIXELFORMAT_BGR24, fb, width * 3); + if (!srcSurface) { + SDL_Log("SDL_CreateSurfaceFrom failed: %s", SDL_GetError()); + return; + } + + SDL_Surface* convertedSurface = SDL_ConvertSurface(srcSurface, target->format); + SDL_DestroySurface(srcSurface); + if (!convertedSurface) { + SDL_Log("SDL_ConvertSurface failed: %s", SDL_GetError()); + return; + } + + int rotatedWidth = height; + int rotatedHeight = width; + SDL_Surface* rotatedSurface = SDL_CreateSurface(rotatedWidth, rotatedHeight, target->format); + if (!rotatedSurface) { + SDL_Log("SDL_CreateSurface failed: %s", SDL_GetError()); + SDL_DestroySurface(convertedSurface); + return; + } + + Uint32* srcPixels = (Uint32*) convertedSurface->pixels; + Uint32* dstPixels = (Uint32*) rotatedSurface->pixels; + for (int y = 0; y < height; ++y) { + for (int x = 0; x < width; ++x) { + Uint32 pixel = srcPixels[y * width + x]; + int newX = y; + int newY = width - 1 - x; + dstPixels[newY * rotatedWidth + newX] = pixel; + } + } + + SDL_DestroySurface(convertedSurface); + + SDL_Rect srcRect = {0, 0, rotatedSurface->w, rotatedSurface->h}; + SDL_Rect dstRect = {0, 0, target->w, target->h}; + SDL_BlitSurfaceScaled(rotatedSurface, &srcRect, target, &dstRect, SDL_SCALEMODE_NEAREST); + + SDL_DestroySurface(rotatedSurface); } From d95946abfe40093ab76b5e870b968f4fb7430184 Mon Sep 17 00:00:00 2001 From: Helloyunho Date: Mon, 7 Jul 2025 07:40:30 +0900 Subject: [PATCH 088/188] =?UTF-8?q?=F0=9F=94=A8=20fix:=20use=20custom=20ce?= =?UTF-8?q?rtificate=20(#540)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- packaging/UWP/CMakeLists.txt | 7 ++++++- packaging/UWP/IslePortable.pfx | Bin 0 -> 2547 bytes packaging/UWP/Package.appxmanifest | 4 ++-- 3 files changed, 8 insertions(+), 3 deletions(-) create mode 100644 packaging/UWP/IslePortable.pfx diff --git a/packaging/UWP/CMakeLists.txt b/packaging/UWP/CMakeLists.txt index dedad8d8..95018c49 100644 --- a/packaging/UWP/CMakeLists.txt +++ b/packaging/UWP/CMakeLists.txt @@ -12,4 +12,9 @@ set_source_files_properties(${MANIFEST_FILE} PROPERTIES VS_DEPLOYMENT_CONTENT 1 ) -target_sources(isle PRIVATE ${ASSET_FILES} ${MANIFEST_FILE}) +set(SIGNING_KEY_FILE IslePortable.pfx) +set_source_files_properties(${SIGNING_KEY_FILE} PROPERTIES + VS_DEPLOYMENT_CONTENT 1 +) + +target_sources(isle PRIVATE ${ASSET_FILES} ${MANIFEST_FILE} ${SIGNING_KEY_FILE}) diff --git a/packaging/UWP/IslePortable.pfx b/packaging/UWP/IslePortable.pfx new file mode 100644 index 0000000000000000000000000000000000000000..3b85ffca4267642899c4b29f063c0a4283371db7 GIT binary patch literal 2547 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 - + LEGO® Island - Helloyunho + isledecomp/isle-portable StoreLogo.png From 6df6ae407c7cb7461ff85ade92859db494893bdf Mon Sep 17 00:00:00 2001 From: Anders Jenbo Date: Mon, 7 Jul 2025 02:43:25 +0200 Subject: [PATCH 089/188] Fix transition on software rendere (#543) --- miniwin/src/d3drm/backends/software/renderer.cpp | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/miniwin/src/d3drm/backends/software/renderer.cpp b/miniwin/src/d3drm/backends/software/renderer.cpp index 48c9c3ef..93a80a2e 100644 --- a/miniwin/src/d3drm/backends/software/renderer.cpp +++ b/miniwin/src/d3drm/backends/software/renderer.cpp @@ -800,9 +800,17 @@ void Direct3DRMSoftwareRenderer::Draw2DImage( return; } + bool isUpscaling = centeredRect.w > srcRect.w || centeredRect.h > srcRect.h; + SDL_Surface* surface = m_textures[textureId].cached; SDL_UnlockSurface(surface); - SDL_BlitSurfaceScaled(surface, &srcRect, m_renderedImage, ¢eredRect, SDL_SCALEMODE_LINEAR); + SDL_BlitSurfaceScaled( + surface, + &srcRect, + m_renderedImage, + ¢eredRect, + isUpscaling ? SDL_SCALEMODE_NEAREST : SDL_SCALEMODE_LINEAR + ); SDL_LockSurface(surface); } From bb86ee317230fdffe54e0b3315ce9e9841dd5c85 Mon Sep 17 00:00:00 2001 From: Anders Jenbo Date: Mon, 7 Jul 2025 03:40:53 +0200 Subject: [PATCH 090/188] Fix clear screen on buffered deevices (#544) --- LEGO1/omni/src/video/mxdisplaysurface.cpp | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/LEGO1/omni/src/video/mxdisplaysurface.cpp b/LEGO1/omni/src/video/mxdisplaysurface.cpp index 612caba4..b36cbd2a 100644 --- a/LEGO1/omni/src/video/mxdisplaysurface.cpp +++ b/LEGO1/omni/src/video/mxdisplaysurface.cpp @@ -52,7 +52,7 @@ void MxDisplaySurface::ClearScreen() DDSURFACEDESC desc; if (!m_videoParam.Flags().GetFlipSurfaces()) { - backBuffers = 1; + backBuffers = 2; } else { backBuffers = m_videoParam.GetBackBuffers() + 1; @@ -80,6 +80,17 @@ void MxDisplaySurface::ClearScreen() if (m_videoParam.Flags().GetFlipSurfaces()) { m_ddSurface1->Flip(NULL, DDFLIP_WAIT); } + else { + DDBLTFX data; + memset(&data, 0, sizeof(data)); + data.dwSize = sizeof(data); + data.dwDDFX = DDBLTFX_NOTEARING; + + if (m_ddSurface1->Blt(NULL, m_ddSurface2, NULL, DDBLT_NONE, &data) == DDERR_SURFACELOST) { + m_ddSurface1->Restore(); + m_ddSurface1->Blt(NULL, m_ddSurface2, NULL, DDBLT_NONE, &data); + } + } } } From b82cfc4b36445fedcc8e0d0ab5a921ac5e71420b Mon Sep 17 00:00:00 2001 From: Helloyunho Date: Mon, 7 Jul 2025 11:36:37 +0900 Subject: [PATCH 091/188] Recheck through gamepads when gamepad is added or removed (#546) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * 🩹 fix: recheck through gamepads when gamepad is added or removed * 🚑️ fix: oops i forgot to put lego1_export --- ISLE/isleapp.cpp | 7 +++++++ LEGO1/lego/legoomni/include/legoinputmanager.h | 2 +- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/ISLE/isleapp.cpp b/ISLE/isleapp.cpp index 09dcaab2..5cc6c9c2 100644 --- a/ISLE/isleapp.cpp +++ b/ISLE/isleapp.cpp @@ -474,6 +474,13 @@ SDL_AppResult SDL_AppEvent(void* appstate, SDL_Event* event) } break; } + case SDL_EVENT_GAMEPAD_ADDED: + case SDL_EVENT_GAMEPAD_REMOVED: { + if (InputManager()) { + InputManager()->GetJoystick(); + } + break; + } case SDL_EVENT_GAMEPAD_BUTTON_DOWN: { switch (event->gbutton.button) { case SDL_GAMEPAD_BUTTON_DPAD_UP: diff --git a/LEGO1/lego/legoomni/include/legoinputmanager.h b/LEGO1/lego/legoomni/include/legoinputmanager.h index 202f8a55..5f3052c1 100644 --- a/LEGO1/lego/legoomni/include/legoinputmanager.h +++ b/LEGO1/lego/legoomni/include/legoinputmanager.h @@ -108,7 +108,7 @@ class LegoInputManager : public MxPresenter { MxResult Create(HWND p_hwnd); void Destroy() override; - MxResult GetJoystick(); + LEGO1_EXPORT MxResult GetJoystick(); MxResult GetJoystickState(MxU32* p_joystickX, MxU32* p_joystickY, MxU32* p_povPosition); void StartAutoDragTimer(); void StopAutoDragTimer(); From c6b94dabcf1a75c1b757547e2325a6b4c7ba0f48 Mon Sep 17 00:00:00 2001 From: Anders Jenbo Date: Mon, 7 Jul 2025 04:59:44 +0200 Subject: [PATCH 092/188] Align FakeMosaicTransition with MosaicTransition (#545) The palette is a bit off but I have no strong urge to fix it --- .../src/common/mxtransitionmanager.cpp | 202 ++++++++++-------- 1 file changed, 115 insertions(+), 87 deletions(-) diff --git a/LEGO1/lego/legoomni/src/common/mxtransitionmanager.cpp b/LEGO1/lego/legoomni/src/common/mxtransitionmanager.cpp index b66b26e4..04805092 100644 --- a/LEGO1/lego/legoomni/src/common/mxtransitionmanager.cpp +++ b/LEGO1/lego/legoomni/src/common/mxtransitionmanager.cpp @@ -701,101 +701,129 @@ void MxTransitionManager::FakeMosaicTransition() EndTransition(TRUE); return; } + else { + if (m_animationTimer == 0) { + g_colorOffset = SDL_rand(32); - if (m_animationTimer == 0) { - g_colorOffset = SDL_rand(32); - for (MxS32 i = 0; i < 64; i++) { - m_columnOrder[i] = i; - } - for (MxS32 i = 0; i < 64; i++) { - MxS32 swap = SDL_rand(64); - std::swap(m_columnOrder[i], m_columnOrder[swap]); - } - for (MxS32 i = 0; i < 48; i++) { - m_randomShift[i] = SDL_rand(64); - } - } - - if (!g_fakeTranstionSurface) { - DDSURFACEDESC mainDesc = {}; - mainDesc.dwSize = sizeof(mainDesc); - if (m_ddSurface->GetSurfaceDesc(&mainDesc) != DD_OK) { - return; - } - - DDSURFACEDESC tempDesc = {}; - tempDesc.dwSize = sizeof(tempDesc); - tempDesc.dwFlags = DDSD_WIDTH | DDSD_HEIGHT | DDSD_PIXELFORMAT | DDSD_CAPS; - tempDesc.dwWidth = 64; - tempDesc.dwHeight = 48; - tempDesc.ddpfPixelFormat = mainDesc.ddpfPixelFormat; - tempDesc.ddsCaps.dwCaps = DDSCAPS_OFFSCREENPLAIN; - - HRESULT hr = MVideoManager()->GetDirectDraw()->CreateSurface(&tempDesc, &g_fakeTranstionSurface, nullptr); - if (hr != DD_OK || !g_fakeTranstionSurface) { - return; - } - } - - DDSURFACEDESC ddsd = {}; - ddsd.dwSize = sizeof(ddsd); - HRESULT res = g_fakeTranstionSurface->Lock(NULL, &ddsd, DDLOCK_WAIT | DDLOCK_WRITEONLY, NULL); - if (res == DDERR_SURFACELOST) { - g_fakeTranstionSurface->Restore(); - res = g_fakeTranstionSurface->Lock(NULL, &ddsd, DDLOCK_WAIT | DDLOCK_WRITEONLY, NULL); - } - - if (res == DD_OK) { - SubmitCopyRect(&ddsd); - - static const MxU8 g_palette[32][3] = { - {0x00, 0x00, 0x00}, {0x12, 0x1e, 0x50}, {0x00, 0x22, 0x6c}, {0x14, 0x2d, 0x9f}, {0x0e, 0x36, 0xb0}, - {0x0e, 0x39, 0xd0}, {0x47, 0x96, 0xe2}, {0x79, 0xaa, 0xca}, {0xff, 0xff, 0xff}, {0xc9, 0xcd, 0xcb}, - {0xad, 0xad, 0xab}, {0xa6, 0x91, 0x8e}, {0xaf, 0x59, 0x49}, {0xc0, 0x00, 0x00}, {0xab, 0x18, 0x18}, - {0x61, 0x0c, 0x0c}, {0x04, 0x38, 0x12}, {0x2c, 0x67, 0x28}, {0x4a, 0xb4, 0x6b}, {0x94, 0xb7, 0x7c}, - {0xb6, 0xb9, 0x87}, {0x52, 0x4a, 0x67}, {0x87, 0x8d, 0x8a}, {0xa6, 0x91, 0x8e}, {0xf8, 0xee, 0xdc}, - {0xf4, 0xe2, 0xc3}, {0x87, 0x8d, 0x8a}, {0xba, 0x9f, 0x12}, {0xb5, 0x83, 0x00}, {0x6a, 0x44, 0x27}, - {0x36, 0x37, 0x34}, {0x2b, 0x23, 0x0f} - }; - - MxS32 bytesPerPixel = ddsd.ddpfPixelFormat.dwRGBBitCount / 8; - - for (MxS32 col = 0; col < 64; col++) { - if (m_animationTimer * 4 > m_columnOrder[col]) { - continue; - } - if (m_animationTimer * 4 + 3 < m_columnOrder[col]) { - continue; + // Same init/shuffle steps as the dissolve transition, except that + // we are using big blocky pixels and only need 64 columns. + MxS32 i; + for (i = 0; i < 64; i++) { + m_columnOrder[i] = i; } - for (MxS32 row = 0; row < 48; row++) { - int paletteIndex = GetColorIndexWithLocality(col, row); + for (i = 0; i < 64; i++) { + MxS32 swap = SDL_rand(64); + MxU16 t = m_columnOrder[i]; + m_columnOrder[i] = m_columnOrder[swap]; + m_columnOrder[swap] = t; + } - const MxU8* color = g_palette[paletteIndex]; + // The same is true here. We only need 48 rows. + for (i = 0; i < 48; i++) { + m_randomShift[i] = SDL_rand(64); + } + DDSURFACEDESC mainDesc = {}; + mainDesc.dwSize = sizeof(mainDesc); + if (m_ddSurface->GetSurfaceDesc(&mainDesc) != DD_OK) { + return; + } - MxS32 xShift = (m_randomShift[row] + col) % 64; - MxU8* dest = (MxU8*) ddsd.lpSurface + row * ddsd.lPitch + xShift * bytesPerPixel; + DDSURFACEDESC tempDesc = {}; + tempDesc.dwSize = sizeof(tempDesc); + tempDesc.dwFlags = DDSD_WIDTH | DDSD_HEIGHT | DDSD_PIXELFORMAT | DDSD_CAPS; + tempDesc.dwWidth = 64; + tempDesc.dwHeight = 48; + tempDesc.ddpfPixelFormat = mainDesc.ddpfPixelFormat; + tempDesc.ddsCaps.dwCaps = DDSCAPS_OFFSCREENPLAIN; - switch (bytesPerPixel) { - case 1: - *dest = paletteIndex; - break; - case 2: - *((MxU16*) dest) = RGB555_CREATE(color[2], color[1], color[0]); - break; - default: - *((MxU32*) dest) = RGB8888_CREATE(color[2], color[1], color[0], 255); - break; + if (MVideoManager()->GetDirectDraw()->CreateSurface(&tempDesc, &g_fakeTranstionSurface, nullptr) != DD_OK) { + return; + } + + DWORD fillColor = 0x00000000; + switch (mainDesc.ddpfPixelFormat.dwRGBBitCount) { + case 8: + fillColor = 0x10; + break; + case 16: + fillColor = RGB555_CREATE(0x1f, 0, 0x1f); + break; + } + + DDBLTFX bltFx = {}; + bltFx.dwSize = sizeof(bltFx); + bltFx.dwFillColor = fillColor; + g_fakeTranstionSurface->Blt(NULL, NULL, NULL, DDBLT_COLORFILL | DDBLT_WAIT, &bltFx); + + DDCOLORKEY key = {}; + key.dwColorSpaceLowValue = key.dwColorSpaceHighValue = fillColor; + g_fakeTranstionSurface->SetColorKey(DDCKEY_SRCBLT, &key); + } + + // Run one tick of the animation + DDSURFACEDESC ddsd; + memset(&ddsd, 0, sizeof(ddsd)); + ddsd.dwSize = sizeof(ddsd); + + HRESULT res = g_fakeTranstionSurface->Lock(NULL, &ddsd, DDLOCK_WAIT | DDLOCK_WRITEONLY, NULL); + if (res == DDERR_SURFACELOST) { + g_fakeTranstionSurface->Restore(); + res = g_fakeTranstionSurface->Lock(NULL, &ddsd, DDLOCK_WAIT | DDLOCK_WRITEONLY, NULL); + } + + if (res == DD_OK) { + SubmitCopyRect(&ddsd); + + static const MxU8 g_palette[32][3] = { + {0x00, 0x00, 0x00}, {0x12, 0x1e, 0x50}, {0x00, 0x22, 0x6c}, {0x14, 0x2d, 0x9f}, {0x0e, 0x36, 0xb0}, + {0x0e, 0x39, 0xd0}, {0x47, 0x96, 0xe2}, {0x79, 0xaa, 0xca}, {0xff, 0xff, 0xff}, {0xc9, 0xcd, 0xcb}, + {0xad, 0xad, 0xab}, {0xa6, 0x91, 0x8e}, {0xaf, 0x59, 0x49}, {0xc0, 0x00, 0x00}, {0xab, 0x18, 0x18}, + {0x61, 0x0c, 0x0c}, {0x04, 0x38, 0x12}, {0x2c, 0x67, 0x28}, {0x4a, 0xb4, 0x6b}, {0x94, 0xb7, 0x7c}, + {0xb6, 0xb9, 0x87}, {0x52, 0x4a, 0x67}, {0x87, 0x8d, 0x8a}, {0xa6, 0x91, 0x8e}, {0xf8, 0xee, 0xdc}, + {0xf4, 0xe2, 0xc3}, {0x87, 0x8d, 0x8a}, {0xba, 0x9f, 0x12}, {0xb5, 0x83, 0x00}, {0x6a, 0x44, 0x27}, + {0x36, 0x37, 0x34}, {0x2b, 0x23, 0x0f} + }; + + MxS32 bytesPerPixel = ddsd.ddpfPixelFormat.dwRGBBitCount / 8; + + for (MxS32 col = 0; col < 64; col++) { + // Select 4 columns on each tick + if (m_animationTimer * 4 > m_columnOrder[col]) { + continue; + } + + if (m_animationTimer * 4 + 3 < m_columnOrder[col]) { + continue; + } + + for (MxS32 row = 0; row < 48; row++) { + MxS32 x = (m_randomShift[row] + col) % 64; + MxU8* dest = (MxU8*) ddsd.lpSurface + row * ddsd.lPitch + x * bytesPerPixel; + + const MxU8 paletteIndex = GetColorIndexWithLocality(col, row); + const MxU8* color = g_palette[paletteIndex]; + switch (bytesPerPixel) { + case 1: + *dest = paletteIndex; + break; + case 2: + *((MxU16*) dest) = RGB555_CREATE(color[2], color[1], color[0]); + break; + default: + *((MxU32*) dest) = RGB8888_CREATE(color[2], color[1], color[0], 255); + break; + } } } + + SetupCopyRect(&ddsd); + g_fakeTranstionSurface->Unlock(ddsd.lpSurface); + + RECT srcRect = {0, 0, 64, 48}; + m_ddSurface->Blt(&g_fullScreenRect, g_fakeTranstionSurface, &srcRect, DDBLT_WAIT | DDBLT_KEYSRC, NULL); + + m_animationTimer++; } - - SetupCopyRect(&ddsd); - g_fakeTranstionSurface->Unlock(ddsd.lpSurface); - - RECT srcRect = {0, 0, 64, 48}; - m_ddSurface->Blt(&g_fullScreenRect, g_fakeTranstionSurface, &srcRect, DDBLT_WAIT, NULL); } - - m_animationTimer++; } From 7045607c6c4e3892d18a60d66c5e129b599cef6d Mon Sep 17 00:00:00 2001 From: Anders Jenbo Date: Tue, 8 Jul 2025 00:47:49 +0200 Subject: [PATCH 093/188] Remove fake transition (#550) --- CONFIG/res/maindialog.ui | 5 - .../legoomni/include/mxtransitionmanager.h | 4 +- .../src/common/mxtransitionmanager.cpp | 166 ------------------ 3 files changed, 1 insertion(+), 174 deletions(-) diff --git a/CONFIG/res/maindialog.ui b/CONFIG/res/maindialog.ui index 12739f27..66e2b52e 100644 --- a/CONFIG/res/maindialog.ui +++ b/CONFIG/res/maindialog.ui @@ -455,11 +455,6 @@ Unknown - Broken - - - Fake Mosaic - - diff --git a/LEGO1/lego/legoomni/include/mxtransitionmanager.h b/LEGO1/lego/legoomni/include/mxtransitionmanager.h index 7c31daf7..dd9dc9ed 100644 --- a/LEGO1/lego/legoomni/include/mxtransitionmanager.h +++ b/LEGO1/lego/legoomni/include/mxtransitionmanager.h @@ -48,8 +48,7 @@ class MxTransitionManager : public MxCore { e_mosaic, e_wipeDown, e_windows, - e_broken, // Unknown what this is supposed to be, it locks the game up - e_fakeMosaic + e_broken // Unknown what this is supposed to be, it locks the game up }; MxResult StartTransition(TransitionType p_animationType, MxS32 p_speed, MxBool p_doCopy, MxBool p_playMusicInAnim); @@ -69,7 +68,6 @@ class MxTransitionManager : public MxCore { void WipeDownTransition(); void WindowsTransition(); void BrokenTransition(); - void FakeMosaicTransition(); void SubmitCopyRect(LPDDSURFACEDESC p_ddsc); void SetupCopyRect(LPDDSURFACEDESC p_ddsc); diff --git a/LEGO1/lego/legoomni/src/common/mxtransitionmanager.cpp b/LEGO1/lego/legoomni/src/common/mxtransitionmanager.cpp index 04805092..7431033e 100644 --- a/LEGO1/lego/legoomni/src/common/mxtransitionmanager.cpp +++ b/LEGO1/lego/legoomni/src/common/mxtransitionmanager.cpp @@ -84,9 +84,6 @@ MxResult MxTransitionManager::Tickle() case e_broken: BrokenTransition(); break; - case e_fakeMosaic: - FakeMosaicTransition(); - break; } return SUCCESS; } @@ -664,166 +661,3 @@ void MxTransitionManager::configureMxTransitionManager(TransitionType p_transiti { g_transitionManagerConfig = p_transitionManagerConfig; } - -int g_colorOffset; -int GetColorIndexWithLocality(int p_col, int p_row) -{ - int islandX = p_col / 8; - int islandY = p_row / 8; // Dvide screen in 8x6 tiles - - int island = islandY * 8 + islandX; // tile id - - if (SDL_rand(3) > island / 8) { - return 6 + SDL_rand(2); // emulate sky - } - - if (SDL_rand(16) > 2) { - island += SDL_rand(3) - 1 + (SDL_rand(3) - 1) * 8; // blure tiles - } - - int hash = (island + g_colorOffset) * 2654435761u; - int scrambled = (hash >> 16) % 32; - - int finalIndex = scrambled + SDL_rand(3) - 1; - return abs(finalIndex) % 32; -} - -void MxTransitionManager::FakeMosaicTransition() -{ - static LPDIRECTDRAWSURFACE g_fakeTranstionSurface = nullptr; - - if (m_animationTimer == 16) { - m_animationTimer = 0; - if (g_fakeTranstionSurface) { - g_fakeTranstionSurface->Release(); - g_fakeTranstionSurface = nullptr; - } - EndTransition(TRUE); - return; - } - else { - if (m_animationTimer == 0) { - g_colorOffset = SDL_rand(32); - - // Same init/shuffle steps as the dissolve transition, except that - // we are using big blocky pixels and only need 64 columns. - MxS32 i; - for (i = 0; i < 64; i++) { - m_columnOrder[i] = i; - } - - for (i = 0; i < 64; i++) { - MxS32 swap = SDL_rand(64); - MxU16 t = m_columnOrder[i]; - m_columnOrder[i] = m_columnOrder[swap]; - m_columnOrder[swap] = t; - } - - // The same is true here. We only need 48 rows. - for (i = 0; i < 48; i++) { - m_randomShift[i] = SDL_rand(64); - } - DDSURFACEDESC mainDesc = {}; - mainDesc.dwSize = sizeof(mainDesc); - if (m_ddSurface->GetSurfaceDesc(&mainDesc) != DD_OK) { - return; - } - - DDSURFACEDESC tempDesc = {}; - tempDesc.dwSize = sizeof(tempDesc); - tempDesc.dwFlags = DDSD_WIDTH | DDSD_HEIGHT | DDSD_PIXELFORMAT | DDSD_CAPS; - tempDesc.dwWidth = 64; - tempDesc.dwHeight = 48; - tempDesc.ddpfPixelFormat = mainDesc.ddpfPixelFormat; - tempDesc.ddsCaps.dwCaps = DDSCAPS_OFFSCREENPLAIN; - - if (MVideoManager()->GetDirectDraw()->CreateSurface(&tempDesc, &g_fakeTranstionSurface, nullptr) != DD_OK) { - return; - } - - DWORD fillColor = 0x00000000; - switch (mainDesc.ddpfPixelFormat.dwRGBBitCount) { - case 8: - fillColor = 0x10; - break; - case 16: - fillColor = RGB555_CREATE(0x1f, 0, 0x1f); - break; - } - - DDBLTFX bltFx = {}; - bltFx.dwSize = sizeof(bltFx); - bltFx.dwFillColor = fillColor; - g_fakeTranstionSurface->Blt(NULL, NULL, NULL, DDBLT_COLORFILL | DDBLT_WAIT, &bltFx); - - DDCOLORKEY key = {}; - key.dwColorSpaceLowValue = key.dwColorSpaceHighValue = fillColor; - g_fakeTranstionSurface->SetColorKey(DDCKEY_SRCBLT, &key); - } - - // Run one tick of the animation - DDSURFACEDESC ddsd; - memset(&ddsd, 0, sizeof(ddsd)); - ddsd.dwSize = sizeof(ddsd); - - HRESULT res = g_fakeTranstionSurface->Lock(NULL, &ddsd, DDLOCK_WAIT | DDLOCK_WRITEONLY, NULL); - if (res == DDERR_SURFACELOST) { - g_fakeTranstionSurface->Restore(); - res = g_fakeTranstionSurface->Lock(NULL, &ddsd, DDLOCK_WAIT | DDLOCK_WRITEONLY, NULL); - } - - if (res == DD_OK) { - SubmitCopyRect(&ddsd); - - static const MxU8 g_palette[32][3] = { - {0x00, 0x00, 0x00}, {0x12, 0x1e, 0x50}, {0x00, 0x22, 0x6c}, {0x14, 0x2d, 0x9f}, {0x0e, 0x36, 0xb0}, - {0x0e, 0x39, 0xd0}, {0x47, 0x96, 0xe2}, {0x79, 0xaa, 0xca}, {0xff, 0xff, 0xff}, {0xc9, 0xcd, 0xcb}, - {0xad, 0xad, 0xab}, {0xa6, 0x91, 0x8e}, {0xaf, 0x59, 0x49}, {0xc0, 0x00, 0x00}, {0xab, 0x18, 0x18}, - {0x61, 0x0c, 0x0c}, {0x04, 0x38, 0x12}, {0x2c, 0x67, 0x28}, {0x4a, 0xb4, 0x6b}, {0x94, 0xb7, 0x7c}, - {0xb6, 0xb9, 0x87}, {0x52, 0x4a, 0x67}, {0x87, 0x8d, 0x8a}, {0xa6, 0x91, 0x8e}, {0xf8, 0xee, 0xdc}, - {0xf4, 0xe2, 0xc3}, {0x87, 0x8d, 0x8a}, {0xba, 0x9f, 0x12}, {0xb5, 0x83, 0x00}, {0x6a, 0x44, 0x27}, - {0x36, 0x37, 0x34}, {0x2b, 0x23, 0x0f} - }; - - MxS32 bytesPerPixel = ddsd.ddpfPixelFormat.dwRGBBitCount / 8; - - for (MxS32 col = 0; col < 64; col++) { - // Select 4 columns on each tick - if (m_animationTimer * 4 > m_columnOrder[col]) { - continue; - } - - if (m_animationTimer * 4 + 3 < m_columnOrder[col]) { - continue; - } - - for (MxS32 row = 0; row < 48; row++) { - MxS32 x = (m_randomShift[row] + col) % 64; - MxU8* dest = (MxU8*) ddsd.lpSurface + row * ddsd.lPitch + x * bytesPerPixel; - - const MxU8 paletteIndex = GetColorIndexWithLocality(col, row); - const MxU8* color = g_palette[paletteIndex]; - switch (bytesPerPixel) { - case 1: - *dest = paletteIndex; - break; - case 2: - *((MxU16*) dest) = RGB555_CREATE(color[2], color[1], color[0]); - break; - default: - *((MxU32*) dest) = RGB8888_CREATE(color[2], color[1], color[0], 255); - break; - } - } - } - - SetupCopyRect(&ddsd); - g_fakeTranstionSurface->Unlock(ddsd.lpSurface); - - RECT srcRect = {0, 0, 64, 48}; - m_ddSurface->Blt(&g_fullScreenRect, g_fakeTranstionSurface, &srcRect, DDBLT_WAIT | DDBLT_KEYSRC, NULL); - - m_animationTimer++; - } - } -} From f8cc5b9651f208fd98925739a7326a67237b6d7a Mon Sep 17 00:00:00 2001 From: Anders Jenbo Date: Tue, 8 Jul 2025 00:48:00 +0200 Subject: [PATCH 094/188] Use FBO in WebGL (#547) --- .../src/d3drm/backends/opengles2/renderer.cpp | 219 ++++++++++++++---- .../src/internal/d3drmrenderer_opengles2.h | 4 + 2 files changed, 173 insertions(+), 50 deletions(-) diff --git a/miniwin/src/d3drm/backends/opengles2/renderer.cpp b/miniwin/src/d3drm/backends/opengles2/renderer.cpp index ebcb38e6..50e43d02 100644 --- a/miniwin/src/d3drm/backends/opengles2/renderer.cpp +++ b/miniwin/src/d3drm/backends/opengles2/renderer.cpp @@ -237,14 +237,79 @@ GLES2MeshCacheEntry GLES2UploadMesh(const MeshGroup& meshGroup, bool forceUV = f return cache; } +bool UploadTexture(SDL_Surface* source, GLuint& outTexId, bool isUI) +{ + SDL_Surface* surf = source; + if (source->format != SDL_PIXELFORMAT_RGBA32) { + surf = SDL_ConvertSurface(source, SDL_PIXELFORMAT_RGBA32); + if (!surf) { + return false; + } + } + + glGenTextures(1, &outTexId); + glBindTexture(GL_TEXTURE_2D, outTexId); + glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, surf->w, surf->h, 0, GL_RGBA, GL_UNSIGNED_BYTE, surf->pixels); + + if (isUI) { + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); + } + else { + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); + if (strstr((const char*) glGetString(GL_EXTENSIONS), "GL_EXT_texture_filter_anisotropic")) { + GLfloat maxAniso = 0.0f; + glGetFloatv(GL_MAX_TEXTURE_MAX_ANISOTROPY_EXT, &maxAniso); + GLfloat desiredAniso = fminf(8.0f, maxAniso); + glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAX_ANISOTROPY_EXT, desiredAniso); + } + glGenerateMipmap(GL_TEXTURE_2D); + } + + if (surf != source) { + SDL_DestroySurface(surf); + } + + return true; +} + OpenGLES2Renderer::OpenGLES2Renderer(DWORD width, DWORD height, SDL_GLContext context, GLuint shaderProgram) : m_context(context), m_shaderProgram(shaderProgram) { + glGenFramebuffers(1, &m_fbo); + glBindFramebuffer(GL_FRAMEBUFFER, m_fbo); + m_virtualWidth = width; m_virtualHeight = height; ViewportTransform viewportTransform = {1.0f, 0.0f, 0.0f}; Resize(width, height, viewportTransform); + SDL_Surface* dummySurface = SDL_CreateSurface(1, 1, SDL_PIXELFORMAT_RGBA32); + if (!dummySurface) { + SDL_Log("Failed to create surface: %s", SDL_GetError()); + return; + } + if (!SDL_LockSurface(dummySurface)) { + SDL_Log("Failed to lock surface: %s", SDL_GetError()); + SDL_DestroySurface(dummySurface); + return; + } + ((Uint32*) dummySurface->pixels)[0] = 0xFFFFFFFF; + SDL_UnlockSurface(dummySurface); + + UploadTexture(dummySurface, m_dummyTexture, false); + if (!m_dummyTexture) { + SDL_DestroySurface(dummySurface); + SDL_Log("Failed to create surface: %s", SDL_GetError()); + return; + } + SDL_DestroySurface(dummySurface); + m_uiMesh.vertices = { {{0.0f, 0.0f, 0.0f}, {0, 0, -1}, {0.0f, 0.0f}}, {{1.0f, 0.0f, 0.0f}, {0, 0, -1}, {1.0f, 0.0f}}, @@ -270,6 +335,8 @@ OpenGLES2Renderer::OpenGLES2Renderer(DWORD width, DWORD height, SDL_GLContext co m_modelViewMatrixLoc = glGetUniformLocation(m_shaderProgram, "u_modelViewMatrix"); m_normalMatrixLoc = glGetUniformLocation(m_shaderProgram, "u_normalMatrix"); m_projectionMatrixLoc = glGetUniformLocation(m_shaderProgram, "u_projectionMatrix"); + + glUseProgram(m_shaderProgram); } OpenGLES2Renderer::~OpenGLES2Renderer() @@ -321,47 +388,6 @@ void OpenGLES2Renderer::AddTextureDestroyCallback(Uint32 id, IDirect3DRMTexture* ); } -bool UploadTexture(SDL_Surface* source, GLuint& outTexId, bool isUI) -{ - SDL_Surface* surf = source; - if (source->format != SDL_PIXELFORMAT_RGBA32) { - surf = SDL_ConvertSurface(source, SDL_PIXELFORMAT_RGBA32); - if (!surf) { - return false; - } - } - - glGenTextures(1, &outTexId); - glBindTexture(GL_TEXTURE_2D, outTexId); - glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, surf->w, surf->h, 0, GL_RGBA, GL_UNSIGNED_BYTE, surf->pixels); - - if (isUI) { - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); - } - else { - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); - if (strstr((const char*) glGetString(GL_EXTENSIONS), "GL_EXT_texture_filter_anisotropic")) { - GLfloat maxAniso = 0.0f; - glGetFloatv(GL_MAX_TEXTURE_MAX_ANISOTROPY_EXT, &maxAniso); - GLfloat desiredAniso = fminf(8.0f, maxAniso); - glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAX_ANISOTROPY_EXT, desiredAniso); - } - glGenerateMipmap(GL_TEXTURE_2D); - } - - if (surf != source) { - SDL_DestroySurface(surf); - } - - return true; -} - Uint32 OpenGLES2Renderer::GetTextureId(IDirect3DRMTexture* iTexture, bool isUI, float scaleX, float scaleY) { auto texture = static_cast(iTexture); @@ -460,13 +486,13 @@ HRESULT OpenGLES2Renderer::BeginFrame() { m_dirty = true; + glBindFramebuffer(GL_FRAMEBUFFER, m_fbo); + glEnable(GL_CULL_FACE); glDisable(GL_BLEND); glEnable(GL_DEPTH_TEST); glDepthMask(GL_TRUE); - glUseProgram(m_shaderProgram); - SceneLightGLES2 lightData[3]; int lightCount = std::min(static_cast(m_lights.size()), 3); @@ -537,6 +563,9 @@ void OpenGLES2Renderer::SubmitDraw( } else { glUniform1i(m_useTextureLoc, 0); + glActiveTexture(GL_TEXTURE0); + glBindTexture(GL_TEXTURE_2D, m_dummyTexture); + glUniform1i(m_textureLoc, 0); } glBindBuffer(GL_ARRAY_BUFFER, mesh.vboPositions); @@ -564,7 +593,6 @@ HRESULT OpenGLES2Renderer::FinalizeFrame() { glBindBuffer(GL_ARRAY_BUFFER, 0); glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0); - glUseProgram(0); return DD_OK; } @@ -578,12 +606,43 @@ void OpenGLES2Renderer::Resize(int width, int height, const ViewportTransform& v SDL_DestroySurface(m_renderedImage); } m_renderedImage = SDL_CreateSurface(m_width, m_height, SDL_PIXELFORMAT_RGBA32); + + glBindFramebuffer(GL_FRAMEBUFFER, m_fbo); + + // Create color texture + glGenTextures(1, &m_colorTarget); + glBindTexture(GL_TEXTURE_2D, m_colorTarget); + glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, nullptr); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); + glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, m_colorTarget, 0); + + // Create depth renderbuffer + glGenRenderbuffers(1, &m_depthTarget); + glBindRenderbuffer(GL_RENDERBUFFER, m_depthTarget); + + if (SDL_GL_ExtensionSupported("GL_OES_depth24")) { + glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH_COMPONENT24_OES, width, height); + } + else if (SDL_GL_ExtensionSupported("GL_OES_depth32")) { + glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH_COMPONENT32_OES, width, height); + } + else { + glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH_COMPONENT16, width, height); + } + glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_RENDERBUFFER, m_depthTarget); + glViewport(0, 0, m_width, m_height); } void OpenGLES2Renderer::Clear(float r, float g, float b) { m_dirty = true; + + glBindFramebuffer(GL_FRAMEBUFFER, m_fbo); + glEnable(GL_DEPTH_TEST); glDepthMask(GL_TRUE); glClearColor(r, g, b, 1.0f); @@ -592,21 +651,76 @@ void OpenGLES2Renderer::Clear(float r, float g, float b) void OpenGLES2Renderer::Flip() { - if (m_dirty) { - SDL_GL_SwapWindow(DDWindow); - m_dirty = false; + if (!m_dirty) { + return; } + + glBindFramebuffer(GL_FRAMEBUFFER, 0); + + glDisable(GL_DEPTH_TEST); + glFrontFace(GL_CCW); + glDepthMask(GL_FALSE); + + glUniform4f(m_colorLoc, 1.0f, 1.0f, 1.0f, 1.0f); + glUniform1f(m_shinLoc, 0.0f); + + float ambient[] = {1.0f, 1.0f, 1.0f, 1.0f}; + float blank[] = {0.0f, 0.0f, 0.0f, 0.0f}; + glUniform4fv(u_lightLocs[0][0], 1, ambient); + glUniform4fv(u_lightLocs[0][1], 1, blank); + glUniform4fv(u_lightLocs[0][2], 1, blank); + glUniform1i(m_lightCountLoc, 1); + + glActiveTexture(GL_TEXTURE0); + glBindTexture(GL_TEXTURE_2D, m_colorTarget); + glUniform1i(m_textureLoc, 0); + glUniform1i(m_useTextureLoc, 1); + + D3DRMMATRIX4D projection; + D3DRMMATRIX4D modelViewMatrix = { + {(float) m_width, 0.0f, 0.0f, 0.0f}, + {0.0f, (float) -m_height, 0.0f, 0.0f}, + {0.0f, 0.0f, 1.0f, 0.0f}, + {0.0f, (float) m_height, 0.0f, 1.0f} + }; + glUniformMatrix4fv(m_modelViewMatrixLoc, 1, GL_FALSE, &modelViewMatrix[0][0]); + Matrix3x3 identity = {{1.f, 0.f, 0.f}, {0.f, 1.f, 0.f}, {0.f, 0.f, 1.f}}; + glUniformMatrix3fv(m_normalMatrixLoc, 1, GL_FALSE, &identity[0][0]); + CreateOrthographicProjection((float) m_width, (float) m_height, projection); + glUniformMatrix4fv(m_projectionMatrixLoc, 1, GL_FALSE, &projection[0][0]); + + glDisable(GL_SCISSOR_TEST); + + glEnable(GL_BLEND); + glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); + + glBindBuffer(GL_ARRAY_BUFFER, m_uiMeshCache.vboPositions); + glEnableVertexAttribArray(m_posLoc); + glVertexAttribPointer(m_posLoc, 3, GL_FLOAT, GL_FALSE, 0, nullptr); + + glBindBuffer(GL_ARRAY_BUFFER, m_uiMeshCache.vboTexcoords); + glEnableVertexAttribArray(m_texLoc); + glVertexAttribPointer(m_texLoc, 2, GL_FLOAT, GL_FALSE, 0, nullptr); + + glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, m_uiMeshCache.ibo); + glDrawElements(GL_TRIANGLES, static_cast(m_uiMeshCache.indices.size()), GL_UNSIGNED_SHORT, nullptr); + + glDisableVertexAttribArray(m_texLoc); + + SDL_GL_SwapWindow(DDWindow); + glFrontFace(GL_CW); + m_dirty = false; } void OpenGLES2Renderer::Draw2DImage(Uint32 textureId, const SDL_Rect& srcRect, const SDL_Rect& dstRect, FColor color) { m_dirty = true; + glBindFramebuffer(GL_FRAMEBUFFER, m_fbo); + glDisable(GL_DEPTH_TEST); glDepthMask(GL_FALSE); - glUseProgram(m_shaderProgram); - float ambient[] = {1.0f, 1.0f, 1.0f, 1.0f}; float blank[] = {0.0f, 0.0f, 0.0f, 0.0f}; glUniform4fv(u_lightLocs[0][0], 1, ambient); @@ -629,14 +743,17 @@ void OpenGLES2Renderer::Draw2DImage(Uint32 textureId, const SDL_Rect& srcRect, c static_cast(std::round(texture.height * scaleY)) }; - glActiveTexture(GL_TEXTURE0); glUniform1i(m_useTextureLoc, 1); + glActiveTexture(GL_TEXTURE0); glBindTexture(GL_TEXTURE_2D, texture.glTextureId); glUniform1i(m_textureLoc, 0); } else { expandedDstRect = dstRect; glUniform1i(m_useTextureLoc, 0); + glActiveTexture(GL_TEXTURE0); + glBindTexture(GL_TEXTURE_2D, m_dummyTexture); + glUniform1i(m_textureLoc, 0); } D3DRMMATRIX4D modelView, projection; @@ -685,6 +802,8 @@ void OpenGLES2Renderer::Draw2DImage(Uint32 textureId, const SDL_Rect& srcRect, c void OpenGLES2Renderer::Download(SDL_Surface* target) { glFinish(); + + glBindFramebuffer(GL_FRAMEBUFFER, m_fbo); glReadPixels(0, 0, m_width, m_height, GL_RGBA, GL_UNSIGNED_BYTE, m_renderedImage->pixels); SDL_Rect srcRect = { diff --git a/miniwin/src/internal/d3drmrenderer_opengles2.h b/miniwin/src/internal/d3drmrenderer_opengles2.h index 0472ff80..d4985f32 100644 --- a/miniwin/src/internal/d3drmrenderer_opengles2.h +++ b/miniwin/src/internal/d3drmrenderer_opengles2.h @@ -72,7 +72,11 @@ class OpenGLES2Renderer : public Direct3DRMRenderer { bool m_dirty = false; std::vector m_lights; SDL_GLContext m_context; + GLuint m_fbo; + GLuint m_colorTarget; + GLuint m_depthTarget; GLuint m_shaderProgram; + GLuint m_dummyTexture; GLint m_posLoc; GLint m_normLoc; GLint m_texLoc; From 33f3bb29cf55a89a1f30d176c28d80f9a52b7cbd Mon Sep 17 00:00:00 2001 From: Anders Jenbo Date: Tue, 8 Jul 2025 01:23:58 +0200 Subject: [PATCH 095/188] Clear screen when resizing (#551) --- miniwin/src/d3drm/d3drmdevice.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/miniwin/src/d3drm/d3drmdevice.cpp b/miniwin/src/d3drm/d3drmdevice.cpp index 40dd1137..bae67e94 100644 --- a/miniwin/src/d3drm/d3drmdevice.cpp +++ b/miniwin/src/d3drm/d3drmdevice.cpp @@ -158,6 +158,7 @@ void Direct3DRMDevice2Impl::Resize() #endif m_viewportTransform = CalculateViewportTransform(m_virtualWidth, m_virtualHeight, width, height); m_renderer->Resize(width, height, m_viewportTransform); + m_renderer->Clear(0, 0, 0); for (int i = 0; i < m_viewports->GetSize(); i++) { IDirect3DRMViewport* viewport; m_viewports->GetElement(i, &viewport); From 6aeeb520c41f98adb13e312f8ceac9af8b6514aa Mon Sep 17 00:00:00 2001 From: Korbo Date: Mon, 7 Jul 2025 18:45:49 -0500 Subject: [PATCH 096/188] 3DS CIA support (#542) --- .github/workflows/ci.yml | 11 ++ CMakeLists.txt | 37 +++- ISLE/res/3ds/banner.png | Bin 0 -> 18083 bytes ISLE/res/3ds/banner.wav | Bin 0 -> 497206 bytes ISLE/res/3ds/{isle.png => icon.png} | Bin ISLE/res/3ds/logo.bcma.lz | Bin 0 -> 8192 bytes ISLE/res/3ds/template.rsf | 283 ++++++++++++++++++++++++++++ 7 files changed, 330 insertions(+), 1 deletion(-) create mode 100644 ISLE/res/3ds/banner.png create mode 100644 ISLE/res/3ds/banner.wav rename ISLE/res/3ds/{isle.png => icon.png} (100%) create mode 100644 ISLE/res/3ds/logo.bcma.lz create mode 100644 ISLE/res/3ds/template.rsf diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 26d7fe64..0c28ea84 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -68,6 +68,15 @@ jobs: with: cache: 'true' + - name: Install 3DS dependencies + if: ${{ matrix.n3ds }} + run: | + wget https://github.com/diasurgical/bannertool/releases/download/1.2.0/bannertool.zip + unzip -j "bannertool.zip" "linux-x86_64/bannertool" -d "/opt/devkitpro/tools/bin" + wget https://github.com/3DSGuy/Project_CTR/releases/download/makerom-v0.18/makerom-v0.18-ubuntu_x86_64.zip + unzip "makerom-v0.18-ubuntu_x86_64.zip" "makerom" -d "/opt/devkitpro/tools/bin" + chmod a+x /opt/devkitpro/tools/bin/makerom + - name: Install Linux dependencies (apt-get) if: ${{ matrix.linux }} run: | @@ -144,6 +153,7 @@ jobs: cd build mkdir dist mv *.3dsx dist/ + mv *.cia dist/ - name: Upload Build Artifacts uses: actions/upload-artifact@v4 @@ -153,6 +163,7 @@ jobs: build/dist/isle-* build/dist/*.AppImage build/dist/*.3dsx + build/dist/*.cia flatpak: name: "Flatpak (${{ matrix.arch }})" diff --git a/CMakeLists.txt b/CMakeLists.txt index f9669bad..d5f77cde 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -744,16 +744,51 @@ add_subdirectory(packaging) set(CPACK_PACKAGE_DIRECTORY "dist") set(CPACK_PACKAGE_FILE_NAME "isle-${PROJECT_VERSION}-${ISLE_PACKAGE_NAME}") if(NINTENDO_3DS) + find_program(BANNERTOOL bannertool) + find_program(MAKEROM makerom) + ctr_generate_smdh(isle.smdh NAME "LEGO Island" TITLE "LEGO Island" DESCRIPTION "LEGO Island for the Nintendo 3DS" AUTHOR "isledecomp/isle-portable" VERSION "${PROJECT_VERSION}" - ICON "ISLE/res/3ds/isle.png" + ICON "ISLE/res/3ds/icon.png" ) ctr_create_3dsx(isle SMDH isle.smdh) + if(BANNERTOOL AND MAKEROM) + add_custom_command( + OUTPUT "isle.bnr" + COMMAND "${BANNERTOOL}" makebanner + -i "${CMAKE_SOURCE_DIR}/ISLE/res/3ds/banner.png" + -a "${CMAKE_SOURCE_DIR}/ISLE/res/3ds/banner.wav" + -o "isle.bnr" + DEPENDS "${CMAKE_SOURCE_DIR}/ISLE/res/3ds/banner.png" "${CMAKE_SOURCE_DIR}/ISLE/res/3ds/banner.wav" + VERBATIM + ) + + add_custom_command( + OUTPUT "isle.cia" + COMMAND "${MAKEROM}" + -f cia + -exefslogo + -o "isle.cia" + -rsf "${CMAKE_SOURCE_DIR}/ISLE/res/3ds/template.rsf" + -major "${CMAKE_PROJECT_VERSION_MAJOR}" + -minor "${CMAKE_PROJECT_VERSION_MINOR}" + -micro 0 + -icon "isle.smdh" + -banner "isle.bnr" + -elf "isle.elf" + DEPENDS "${CMAKE_SOURCE_DIR}/ISLE/res/3ds/template.rsf" "isle.smdh" "isle.bnr" + COMMENT "Building CIA executable target isle.cia" + VERBATIM + ) + + add_custom_target("isle_cia" ALL DEPENDS "isle.cia" isle) + install(FILES "$/isle.cia" DESTINATION "${CMAKE_INSTALL_BINDIR}") + endif() install(FILES "$/isle.3dsx" DESTINATION "${CMAKE_INSTALL_BINDIR}") endif() if(WINDOWS_STORE) diff --git a/ISLE/res/3ds/banner.png b/ISLE/res/3ds/banner.png new file mode 100644 index 0000000000000000000000000000000000000000..550b53ead5f24889a951a2aca7a6ba86b392fece GIT binary patch literal 18083 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 literal 0 HcmV?d00001 diff --git a/ISLE/res/3ds/banner.wav b/ISLE/res/3ds/banner.wav new file mode 100644 index 0000000000000000000000000000000000000000..ce7849e72be18f2a1a2ca2ea92c92773ce35701a GIT binary patch literal 497206 zcmW(-1z4J08%7WTl}5q>MFg=s=5(_by6*1o?(X~+ZcgXA!>QZsZm}B^FfmXtP>}l1 ze(wvXTrUG&&U>Ecj>o0*!or-MBOt40uMFLJ;5Y*Zfk5;iP{`#B@WTicq7T7Awr@GU zrTG~Gf&nmqAi#Yvh$(6Yt00f+(e z00IEUwPRYO&QLdBH%CX;S?fM(-)YxtH)=BgHtN>vF6yr6vb3q%*P0I+tj1UqqzTa^ zY5r=m08RpY1IX7FXqQ5kK<4Pp)ssP_kX7K>v}&Dt|MdQ8e}%t-JH?$U1gKD!D;KI4 ztHZQG+S$4=-3Hw{UA4AKE7EjprqvVbAoX1JS=C7ucA7BFQrIY>C!;5ACYh6wlg}sb zOx>7zs*G0hGzFT2u;VZqf`|x)&xI4USZ#&8T23Fe7!4SmHOiS_PjpNPrtoT~q`1$d^iJpn~Q=g`Wru(O(G|`%)y2HAEj~UY&)$0Mc0=Wu7=@7bl zZIc!YK|{9bZPRPjYu9_D7prHYN7ehG`>NZZ-mI2RNv8DV`f`U!yUC&Hk!hVytvdic z2>lHG3QdLmg(V}>5g4>Fx&_;cU4vPT88jR+EJWp_yp4U0$Lzpx%$&^pY&>m*Hr+P8^e*}q)9t2gGK*Y|ug3pI z|3P0xUqNe#8e*8`9Lp|NCoA8!z;@hv%-WmoP2X*~$5KX@(vMmnw_e6t#QM!nVMnqb zu@^9xF#FAV%~DAzq@}p!xW$;o7`2hg=m6pnVyoUJy#<DZ%-{{`9qNT)q~SZmPljR0V5B}20X;l@aJqQ1WOB7~r4p+pXo=wV zVlrxSd~jlLg=m@Rc=M@dw>p=)UzPEd&#EG;n9a=Qjs2VYmnfDf44?*3H@F-89{etx z4d=kGLa#zg)Fo=Rf}^leF;!{27LkY!3U^!50s7TqP3{nOvAu5=Ptz;>~ z$0NoYh8u=$BsP-k!&iq3MoLFqM(jpDN#Z0?lK&*Dr5mLCCiYBRR$f*vRWDVaS6@`; ztFl#HDzWM^_zkDRt7cCIO^T#K>F=RGL&X3YBWWWRqx8`&vdyxtq3$7CFQvCiP$Rg~ zez~0>!i&C(CUSm`N~s#0C~h4-1) zSk_p!x^i{pO8~tJy$bJg?{d2eZiPpsdu4V-c13J?Y*bEW4?SMgTxI!cA5ktI({V8!~yy9;&}_~!WK%*&Xc@iIL+eMi>rEWcduT;p7H z?(OV5*|T$J`Nph-!nr)tCK4KxY_+tLqJd2i1OEURuvIoBhzX`JiBS5#KZLln?4UUQX ziH*Z%VA3#mFb^<=m>f(ST7)JVV~w+nvW>biJ(zi7#SIQaTM027z=5`Fb4|cEYjJO0Y)In{(&-IY&7QfAYGlKns zi^7V-{Db_1*gh?GU^yBEmvFKu)JoO&d6YVxBhN@%lf8u6{DKbK(D9IwVG!ou<5Y* z&iTP%+q3ND_EYwG_Br;(_85Ctt}}Oz-F&;7oU0ry8_GG!KE{r;eP%nGHJin@VcYBk zNU%+`J;pu4t#zz(T4WnJ=f$o|T>o=>=!S8}xOcdV+-Y6}uXVoL zeK-2A^d|+F1{4S61(XF;1(<`MdH!YoA7^}=@omP}8R7mB{+TnfW(a-SeOf&P9u2Ow zu4-qE^8v>Lj>DW`&QY7AHb0o(nZMWx><5_E)DMW?jfEbo>F(Idy8wE zYqM*k>r;>C9tGYx-WPqY_}uim>2=iOh{r6?*`AiZ=DyA|-DXN=_RpN{7wT8%QR89f zX6MHBbnvY7FZF*K5*_kr?)|y6xn^^}hJFdX7JN0hdUoCH)WD>`+L;YAVF8eUH?!W( zQU_^+F3-L&`{|75Gd_8I@SwO-Tz9(db_@2J<(2J|>$BK}Bj>Hi1Lp z6xbKo_qcSq2s}GIm-#L7>+$dRf9QALZ^}jC;>Gl2%4jlLf_Z|uIoE<4<{j#N!smpK zzP-NvcFJ~27pfbz6}1^vO{^vEw%Td6hP#$~oqK~DY(2|*i^+DAY*Z#HM?Y6z1+I|+ zVn4Cgvd(gq^-Aj)vsklvm@rHesG`PdgnG4Rqh>L5KGYb2N4!SGpd3*ysBiGE@PGZ! z@U&$5o#KPymh!sNPVJx;>w0z8a3*|((F&t=*mYP2hJiVYJd0F96;QUGot`(;3wjLn z52y7{>YE@ah#bRgLpeYPOb8<@2#P(@gHjKfvuurWy|M;Y1AB&uL_C2!fy^HbAN}6- zrE7z5n=oQ1WXMVDrENylq6`p5h)z|9%5&Uz94Uj#wn(;1*u6HrHKH2PUh#hMBKbmj zhALBaS$0u2+#+p3@en+IX-lb7lT*`z-tgWpl5Y}^0r!E=9iKZs)O@VjQMSE|U)5Z7 zujNjQT%-`8L~xN`Ye4I0<4EIc4`Go#N{!07$;_tJ+xAJc1^_7ZC>r3lO&*fdnGf$=^ zqkb9v`kL@10g;8wvf?p#Zsi{3o&|0NX{lMM8xz+iVt?X(&ifMnW%lR5&t`GvajR3; zq*`W~W?B5T{=4!0+V{DU`H>M%Bc48qc^vcb-NScxKHU3Y_yO@@@`K_7>@)1M{U^Im zUT=Kgu%ehzwNGlF+Y{2_j@2|YO^zQk~7cb+V#XYNdTKV+ivy0DYQPik; z&*weg5xFz+Smcq&%Td>&j=Vhda>=WuuYzNPVl87VV^Gnk=vUEiqHo9Cj5!!{I3^(4 zKe{=pIjZ7$)pOaik!RM=n9mkHTm0;6)aNMeOGxahw=3Sxd_VJj!Q0}u)K@02jz^!2 z#=pS7SQoQ8X5`KIn}eVCd_MX8%=dhNhuhw@kEug;Inf06$#|5N_1{2lq{3N92BmlTyeuKcf(Ttlsy zET1g*Dex}%n~|JRlU9{>KI?o|OhIhHU}=A;Q@L|_Jnt88ylA4Rsi3hyuLN2WUL9Wj ztocdv1;G^oL4*}O6+RbU5S|k@i<(7$yOX-FiLZ*I2BQa)2LBE&5HAo*yGFVOL_?yC z&eYE8uKKS0&YVurG7I*#?{7cTcBbu5dt!S7cQ| zdE*k%Vv(`TNM-|io7I!6Cx^h-!>)&2xuRTAsyIc=oUobL4%rSli8z7yiujJuKvhs< zz#S$Oazza2;W>I-y-D;qx{1f&K(ZLnc}P;U=T1P z!YAU|(wj>ai{y(`b2M{cfzZHlpD~}KKF56`W`@s<42cY>nO{9WcEPI!cjf~G-U-|Z z_H=!g9!u;n=wLFJG`D?c`%dE%=o7B{o%bJEeQdQ?xO=#3xK}uTY2DJawb^U;ZP~Hq z&h7`hS07w?@a2Kn1Mb^Bwm(|+a8>b=k|k%>oLaMH-}-&TbGUP#&V4<%fA5~XkCr}J zN(-fi{+|DP{=5zIH&`EH9NK^C(5dUEuAVw};`oWE3TBxc=B<(opw7NmN_g#%tOrEwqWZ5>oqoO zj%_}?xqYK>qh^hI&Du4q*XTCDHg<0nY+bf()wYUFWt(2Ef4Tnph8r84wmNPl>?H2o zxPAS0gAK?H-YdOU)~&2x$=}$t@%GL;J0I-4yVG*B^=7?wy0xp;tzLI}{i*fZmAaLS z!Xv_02CWOCg^)rV7rHDQT`{`i{nB?!(?ZfhoIPAT8aOqaC9E~9Vs<8*=TPs^1L`!) z)6g^ABh+KuY1)a+rn4)o>aBvA0Zg(z)jr2P+nw%h?R~-hjJv0UuR}cR2dlw~Z{^Bx zVi+;73^ikl@y+U&)hfnv#sl+*=C7!+)OnP76mOajZHM`G^WT<#EuYh$(xd6I^wsng zbOSnyZbi4G-=jaIuLbv*TbNt4QroG{BnOfLr@}em8~`;?mNjUhzv|rZLxK=%wq)p;OR7NI#@nTcf?NyQfn?r=c?V2>b;+ z27V8E7kUtK5F*!;>m7w3gde0S6iVI`s#&(YF9(ywRcydBHq0Ck0DQy9_ z=K(F?AmHF}$ONQ2+6Dc@DAEXHfHts!*gy)W3#TCydJ|souYa3g+x+T?YAsL0 zJ5znS`c~`RR_jj7&TAbvJG6D0I{h+O8Hq>c-L1J@h6@>DLI>YE|Zu+%(zr?zQn4{wr#p! z-oL7UUH|^}z3p~2b~TmdRpl?LqN@JZCDrj7N*k^*HnArq>2E1~yLCPu6=?dsc5M+gw&uT38xX7F_nA{9*Z;vXy0Z zr46OoW!Yu(Yv$D?HvDM_sQ0h`Q=U|An2*SBNN-NRkajw)AcvQ8sp4!!a7$Q=f5(iD z`ue*1+PsFmpNU@+Ro@{$EPhdcy-I$UJe)I_Q&m)3w5)JRpKYfdE^PH_@ohWcU!d=-D1e~o{GzrT5J^NQwG&4(KfG)z`1D_8KA@z(Ix@nR}o zR76$3sP?Mys(D}azN(-+zq}rNwrM44B}>belzG?q)XJ!w7LdbjpQcfRa2 zZ#Qk<(7dtv5dR>*kYB`~$DhkjZ%l6-Z;&_K=ilb96Kob_ic`hAWP4;wCKpfglsx4l z^<%XLXj(Sq*+G_OLAl9H#Q>*zr@owU?^oJBPYB<_AH86Ek?}nb* zP;IylzaHO4XdyJ<_&6z6f;~e#M-)+oR1M&=I;{&riy|V491D)cVfJBmn|-^zmaF2n+jZI%bMv{|xjVQLn_-*#W_Qh&5|-IP7Q_#fZe)2)$e&PcBV*D=r z4*Yz=d;++na6yJahI94i>77xXR{3gtH39HII1gQd#uD&^MT7<5s0hL$lZ7TP%%aV< zGq*7#xDniAPRE=^oTN^r4kZq;+*jPAfF~ccKWu-4dzJei>j`VnV!+}*$^%L$c|Mt9 zhBsTzT+2+f`)QZP$>f;PP3gt>BD_1w8x?2x)o=lEF;Q+hVY1)l`n6ENfZSolN2t&k}U~0&f>ku^6bVLxl|#xP`+;7%On<5Vd)Rwes9vZZAJPbUum4Fu#wf<_59 zg>s%^VPb89$6;_8sBBcc{!jfsup}56NkX#lws?#=)_j6F#@ueb(>jfqLUd8Ps@L|f z@AvL;?^!F`D7yjl+?NKg47_z-x*20L#?JJg?Pqpdb+-!yg0{BSwqRkf&}+zhNT6s} z{M5v0#FGP)fg>|UD5K`1?_seplDVll+8$-E$ANJcQWj7a!5701s}HJUw6WUH2A>Re zWAL47AuRCWtvLOR_!Kj`c&4`zLDJ{$RWcaAiT@=$oI&Ydg5xGl1+N6;x) zHN1NGiRy`pq({;_raPgF1A6_Kc2Zjfl<)PBjgSOwg7zptA^5xwLJmWEbzQo6$REf8 z_xCtuDd>gh`RL7{LmM$ZZ+!RQ-oXQa z@0Jag4?dSg%3$O0aiSD2?e81vYZf*L%R8z&N<~E?mp->X{19#^XEa2fCz=ot_WboYzXz3n5H1#0jh2qKLA#)Mj3MSV*f%VQG-3`RpHNBQ z5dsOp1Oc`Us{op}BZ7;tgV;m<@%j_}C;EvZlIU^g<4)!fW9ZYwci=rVPlbd1Fn;p; z>$tTGxDJv)|i7Sb*sF$clHDBE|DjL-vHW-ePMoTGE zV_&;|$0s@(;>4N;9d26od=LZ8BPKWT9`RPf-vR>jyRs z)CejC4+M_|yZX2Gx5x$ZOkJvOHY^0D0)1KtEEsmx;JQJQ(H|o<7J}P>-;95Xdx7)B zx#GgXPGU}`kZ(}$QJcuMWPLmmUyG_jIq5s=)Abm7QMzY3BQ08Mt!Ahedb8DL`&b8ANv?lfTRqx5c5!!cH`6xLLeL>-j1k)C z1m+~>HtrT~9(F$V75Wu=1U-yqVQsMi*qK-bMu~ZbkHnt_`}QfuNk%KPjafynqF9~z64*KFW;H({N4Je^;+sGYOP6w2@^OR^O^IQM>t10 zNIQhx3eE~nyPeR^!O_<7fperY%a!ix>gnP6#s8~+=j@Kzk7h>BoNyj>mRnC+2U^Xt zdSm~_e#mFQCoV7{@N2+_fHgBV%_#KC@hkDK@HY)02VC>N?%(9o0PqWvZRA={bRo8>cd7rwL|^Vft=1UG~|;*0K=?uc%m4r@R(7{ZFN zd}nW3wnT2z}ZTW`(lq zu@YLPGBO#R<^uDRlv9*kLM}mum13vR)950jd?Pa$c2Gh>gPQz7mZ?dkl=oZ!%d~zfC5a|f%3h5HbgXBeehkt{21sZ&g zezyK$&0$TTa+dPS^yTR?P){BpA0Q8+51|v$@#qUimyJ#t9y3H5qm9pFPGNko{@CB> z1oRK1A4W>UX~U03AB~P09X48xScf>QIiR6R&7{$t&pX$*ZEQmejf83a8U2>Phh$DL zCrZal$4^h3n%FS4aVk}rs&rI3D6aypb$9&EIFNj$hx(56#W(+G-VF4ri-m|U8r_Z;~u8-5_)$iSJGh#C`FwsB3nX;R*8@C(3Be^Z97dMD0 z!1wtjiW51tIJfxL`P5N?e#EbBuKfm}D3_Nb3XKc-xplc)i*^=uR(4d%>L==NHC%6? z)|%EbDi{^-i(eOC&%K-bHa#Z&c=Fj~mn8cngA`QCaE2t~c)`(v)bgzIvf85B=K8k! zxAm{z(PGCS=$$s(L+t!NWae=e zrCCG8(qdWpaQXi7L*@Gcp9v}qD?D7ZzvxWaxw0MAJF4qyYHJQuAFMtEKx`m1)V9~R z6Z>#|XC)UT|7xNEz^0KcpjsUp=^YV|{2GoQR`o0T8Q^SwY5vswt2VCIy~4X8t~XuQz;kqwI!g}?9~>?NJo$v=u;k$I@!?U)uw+mok<`j+WbdZlO_{4K zRN<5JCo8})Ha#{yM!kl;GbMqN9b?yz|JR`ssx{U-P-Fl{w!b!~QQe%A7& z<&PjmAnzOPg8}V3UY;NinV3D1KU6gIw)1u8CE!Iq6+970x`(?zi9d+F27CwFhgyf0 zjV>GAHnnZaL*=PzR5yS;z#Oekr_mjT9EI%B?belP%e0GiOLQ=;o_6olfho+e(QtTg zL~nXede7W}xdW4uDamEYC5hL7_kf~jvS*8MyKqLcce87~S3S1LsEO6Z>AE?3ebfLL z3u8*DQZ3WT_K0_hzjS}?7Knvn+9+jowsN-eoa%y#t}s`WOq5ONPeG?JI;>6)2|=Dg zpF__Am0}mbBzg*6iY!4Ify}@P#R|pVsXbGH+Cc4kDlB= z@=dclW=0lBi~FXJOc#S*TZj|k)_~sY2<0Fpj6RPZ#eBvrur9DRW}q3L=pX6VtZ!J~ zx4mP#({8WbM(!H!e!IhV@wRcctC(w;Z>`=~owPn-ZN+3V9|P@Zm@Fe-LSI0mkVeSu zh^>fE$j`{7hD!~%VYXpl6c~kN&9Z*RjpT|Px*Se&Pja2u_G}u^V7#{FwBFw2Qoxyv%I5Spg%T@zUm{%`Ns#cDsYfq1Y?eYdQ!L>=)q~(YJ75p-+T& z#K@fCIp0Elgj@-_7<6OSomu>u^)oAcYJB>gd!1vMubIy&k(BG$8`yfI2BTSqfrbYG z=7IBAWm;ys+HQkgnR~JOd9RCJ0^rB}_4woA;qB>t*XNE8-=o>%qQeD;ZPr_@>!=OX z<@lxegP6mZc#u5^wg|Qu;tX+ax!iI|bWd>K=C#uc<8SOg6g(6>HD58`Ws&ou%*9!Y zWs78s=n)nX*FtWDnEKKDc02EN-p$_0zQ(-H#4)kp)xG)uzr|KOtH;(4tUt29uwgDx z7at#QAFBXXfOn8@Fj18+t&>n3&* zYp}IgYn(OiHsLm*)uh!V#Vpy(h-J*`aO!YM^hor0;rYVzhsQUMWRG-@YL9Xc8{omQ z9jqMQ+I+HsT4>GTCMXju8jY4Bq=;0&y)+mN2DFLfV;09OK3jjb4(CL0wm5BcN^(ng z)A!K0`??3Yd%HQgwYzk?G&eWQ7=X;L?;1sZ|orRJ69y85#E3g8C4@J=`b_}7(`N{TDZnN~`d240Ocv2ImZ- zFj1HcQU-9j=a8As1+ z31hjj{EqUDazZnq*`wQ~s|7igE80t1p|%rzzo=~j`wv`&QcWu-mG2ZE6*uMA?3v`^d9V!c1c_09r7&D3;0e4Oy^8xOf`+SjAzTT zWW~}V=~3AUS-kAK?8V6Ik)T2U!4ij7DCX5z57d+{B+Oe#CdAobNYx_Fk z2H`l^bHB>I$tEV{lM(WH^6!#glBn*d-8)2kL`S<1c5}oI;@`b-y&eKT0iqGsxUqV3 z_5JF5)zW%t{aEvOGqfGre!k;;hpWg%6x0{ecYNgNh@aeF{!Q^kF?%{_dZ~JuI$x8k z(SmG)d~AH|<-qHKc|D;$4c*P%Wj!T5XL`=}Z0K3rLl)D--9w_GW73n-B-tO?g|SOx z`@tUh2IR$lL*gI`tyX(WbybxGbl;Q8W6H*e<|}q2or=?WPBH_v+ee+iGj->gwF;o$H^~y{KDSzo@>AFW?^*9uW5TN_u_zJ^HJ} zwc_Ufrv3wxgOX5bs8l*UG7KAp4Mz4q?+=j7kRZp5#!zwt`K9qIegwFxR;HG{lfIQc9(g`uHexd3C~=dNi;Kl8yVrHQcR6+Gx?w%N zJzYK4T{d0O9WOfKTjN_noTP=`OmB{B_}TEL_Fb)Stxv69onDaW; z#OZhJ_Xa&8b;5K4J&B#9OcEwr0jFbuEZo`2bCVLeL>@n$Fy1s$KOzSFn%qI`2xtgs z@Tm5xHmM?1ZLHc-C9RZJ9;rN78C@M){iN}~Msf$WBdY6pmqo8-FGWli>kq*|8^e=s z99=VNKFl0O^+J1(cAV}oYB6XTX&P&4X>4q~)O5Y6thK1st;@SBeIRK71~#C5~ zcTq=aM^S5jE36gX`k?K88;EC!jQfrIg|bfBz@&Kcmm(fG`R^67Y1#BQ$alzbgX0E& z5PuMJVRK;#>IC%~#cH4d8cm&&pOlkjRM{)>OL3j3UPK1C*?YaWX}D$hJUBxybT4%I zr~=e%VjwY{nn@j}j!|)B92rZ*65Da@xTlz>m{FrqqhF{v)F;FjgsZ-j{$1@u?GeR6 z#p#LD6GdaCV@pOOMz2b5OEEH(tV!M~uh5n1j-XDWPGOH?w-I&|DoLfJFiJROA9WYC z3h?F$!We;yp<%Xwyh9nh9PXj-sefGmsJ=+QQ$I#OTK^dQIK0fT+;E%8W|J1H4y#M{ zXYHe0Ub%dBd+WB`eT_TSo#Yu}!gvfXRDXuEZe8yzujST}c1XU|D5wU>>rrSIU3kr@U7Fp%J> z4NwQF0&Rk=gR8=7!xqj7p92p@1PlBH{t>?6zB@qXr`os9x6!N4%iYD(g#q$T&ut=Y zh)gn*!(=fHnP_GOsO?<3Bi+y(VJ@YO(UzGmHoa%@(87dCWLj`7xvyLSykor${E&Xz zeYX1ac=UPPb-V5MpW9=%N%sl&^`09%-M!qrraTp%vF@?%y-qz&5Uw7#iQULf;3RUQ z?Oxc0a6&mZY;W4`NK77R1S18_b(0rJ7~6BtAW1;7@d*s9p7 z7p%){W)3rvk-+%ZNBpq-X8F@P&f3)06ujdbwneOBmK)oR?QidIf7j`bQ>i1*QOnVA z(yTMAXIjj#SU_7yi=;fE&}e2fp@qm|A#*-c$m(Gow%KcQ$m)buG~+pADRU{aoL$Zy zy<`Q)rxTOh!>q2)F_K0`xMp z4aSH4GE6kAGpRMPw6d@wb4)q5U@iQz{cD?M^T*}~E0JXivRYNZ-#rbq^D>7@hj;9^ zY%=JH=u`$3L4XtXVE1Dm;O^pTi4DZVn&<6 z9+^Kj7gPJF2ot2q8~khhY0Oznrcs8`7u09e3q%a!y#6`;4u}A^SWtEs2^$eNA~qVNq?VL$pEKTGO?rwiGty9PTWx z23d>z2>l5Csr{+#R&}ZtDVHe^g4%m%>i869+Hm@i_K3CuS`M87_lGaiU#MTKE7mcU zHp<82|BXA3xs7S$({hzstuBHU!ImJFAp8vc45lDckV()BQRHO#9MD&YmA%TtKp%G1 zx@e<-pNkqpjR~b9Y1g=DTmmx8XuykjI-ZWNMM&*2>p44kYS2!`l{Jhtj-4GlJ@!`iLH24SW@L0| zY=|x9h($u7u$o`Z@2Tyr{ZpL)%A>G)MfH;E8x=PzR`6Ewj`0riC{-3!7aPttIDtUk zOMWchxDnNOy8e9qx%$)fJL`AV)9V;@6rdR!RH3T0<yb> zRZ7)^ipLdid9QizOFx!sc+CQtfvBX8!h$T^;|tXA1#CaIW=CtC-)< zpWigE=}7&tdVWJ$K^Tlx=T7sjuo6JxR!M}D>^MUEio-Vtu3=XvleJD z;)33Svqk5N5Jd(>)O<>QWL8wxh4gdjeHr46+`OE;{*wNZOkO7MbMe>Wy!`C^b@?0f zlZ*ZqZR2g@rIe+XMF8jl(W;79!*eLLE3Gc9D-6jG0#4$eeBUC!qW_8?78e#46*BS| zd5CO%Kr2l%1JeD|<5Ln+h)IMbhd&N~@)GkB7bYx9xEg;oemG$$;b78{Bvd9mlb_#~ zf4%f-X&CTIAA%fG4$yth0H3-pzdCk!ewMS#;UF%DI(aD!x@{%G71` z<@M!{Y97_BYFg3swK=Yt)kJSPR(GauMctyhxlIe2T3ef14Li^sT!EdSN>nLY*t59j zM$eU=KEaUSYSYE0O|{!r@h$k4*KM!cE(k6PZV0albwWKr zow;2%dT;iIibKVkUUl#3?sMH{U6d{>&@=jUd3B9-j(2jwF{0Qw*75G@DcpB zrnaV{wxYHTof|uoy8d=;7H$?6widSj{ z$hl(`I0XYB8el*|5Dw(w>oABjcd&?r0# zUkCQdbX*p0kI^n8q5;WZi~eT)7l>#?A-Vv4iFlb1M?1c8Gy!JiR{9Ql7&(Z{09y47j1Q(8(~Bv^mSAN#8Ey+{E2#u5 z>S(K%R=0poZD4C?`dXoqdD6XM4YQf9D?T z6zZg8PqF`HufrH~88@x2StZ!~wJ~BN*`>BTTRj$pb<*afO{;CY?Oyxc_Pbp6xQu#? zc&zYQ@bPK$KXM10_%ct#>5!CG+K;W zjJkxph`fQkiA+NLMS#N%AO?B{Gf;jgQ>-~Qm6Sv}N4ZG(NPbV=LEK3!#1>*H7&2xp z$Q>U5(>a?-n@L0>iRgp%#0DG9F&alok;cH2bV52H$5G>`MsyQejPJ#NB77kn1^QJk zq5vUprz^}^-D#pf(i0dOI3?i;kpI7yG;}4?FY>#&AjQbX@^Od$t4pDCeBY@o(xdW zP_KfmhJlC~s?f05u-c%)AV?dkwUJxN+oT;*SDA|}0v!7ZXd+LhBd2#OcPJT?){`&B zqQ-`0BeKA;z_CubP@ba9Q1Vsvs>iBG)l=nTW$I+wBpqlbjbqJY$HxzkljIcnJNa9A z^mz36G3in1!l5NYlK%ewu>SD=;QpEYVzEU0FGu1AwA_^8-^1|}zbBkk4k~Ajo5oOy zRECc(7_$bFM|^@xvXNUVrq+ei+UE&Y-j0a>E=PgA=$c2-DTAk)#HiB69prABe{}7 z$=Tr(!;BH;h-!FxIBO((#BbDRv}e3;93@A}e*$b!Y*4J%tk--HzA&QJNkMwRgxjOIdFYocwl7U&Cu(i(!r9!oW8uiFI}Iz3V}aACX@+F zJIXrJT2osWw=8bKwc=aPwVrKNHY=OwH-|UJwnw+m1zNnVgeloPv}MSk2ibE3>ZJ>$}i@xgQFO+GXi=smr+2IAe@HRtV;#o^=1$eYxX$$LZFSt?(B8 zmM_iUnnkT0t?xyjMZ|u5zr%>*$o{dtV;iP6Pr2({b=Q#Bksq-iu-6FJ2^0dE5P+MF z+l*d^PC;fOFF?;gi&Z?;Hu)C0Xryxl-;eEY?-X@@2J0}tt+dUcUEB6U@JH~u=XH-p zqCZ?Pku#C1NLRQjot4ST6y;sbJxv1a7px3Xfmma(%HSyE805_K+3B>Y$MZ;ed?vU*1{aG9wg|M?V|T!Ao9kv*uBW3X&#TzW$J@{QvezZA&z@gA z*LbbtS~YxUYHoW6h_W))&(#j;|Vap;^tt}j>53F*WHgm!4Abmqs9cOeB~MPcVd z&xc|`aNusp!;t?%?}m;B%Yz*PxB<7l?syrwqFg7OC!LSF9&!ER`qlM{(n9mQ{dtKy*M%z^0k&XF`1peC`2{<2Ls`x0l_;PGhIDxg0L1fnCp@ zW@%W>*7ereK-2F4_4KUOC9Bo!!h%I>1+e zt6o>UG+sKdH18B|x!0su=fC+b5Wur?vUb|zwAX2^^D5`F&KI1y&Kzen02r2V@^*A{ z4CgN9PS}pxCNYzljtob}1@rUfAfjc;1qA0zyrlz@=xtRU1h_et| zb^|Ma!U9OQ=BXCR7LoKQdIV!W;|Ki*eFI}7gJr|CndYcDx!fG?P4+D|-U@5wW$J6X zl(dM1#o=&EuuHKl5|iWsw3EH8{VXb&rHW_$VNrm#G|kj7_p|r1Z`s|oV{xoF<~9r) zAG!x!Koik0Bn;^`<~F9)u+{J^>Ky6}>I7;J>JaKX@*`4gFl_Kn|E2yF*iG0IgGUC} zu-C9$b9?g$_I!3Z&@JGO`i__EFWbwx6WqP_`|LA-u2T$jKB5E8L15oyZ)Im{7i0b2 znqg{YdXM;!hz2vbhfNNd_<+|dmXocbM%myGgoEnlPL+?1%P4 zKWaZ}Q5vMiUFD&AH1%NW1enBbA8j7}Jn?m+d%9`y_dG})BQlJ5tgQQiZU{EL# zPJ&tq5g-I}+k)FFfp#wy$OL=)cK7)Y`ww4~U6Pf_c(Na|pEANIVRV&jrRkD3=%(N(2YMO9YStZL6Hmns~XIXzQ#s>-F_wSHOi(&i1V8(W=P z>|30G-;QcUw0`LL&_NYZMg2mt@I%MPj$bXmTKeky>LV*7E0cJCd852BUU_+0d2Ly3 z+25k%qSTy}90AZDDzmDy>T~OJR~M};dR_6Z;zZNYrYnHE^1J!nqg^sE*S1l(p>2KJ zMAKxGUY)jPN@UGMysXNp5q?M(Yq<_fzoV6)$ zeV%{O%%Z2fNZz8Vg;jdBu-ds*p;h~e4i>eix1<{;p_3Yt>yp#KYgm=LD%U65Cwq44 z?9>at&;LfpW8zoEt%^(hnfNm!E+ft+k(szZ>0lBj6`k6Z(v@;F`B?H`k~m3~3`rSC z?MZD-7o;D`JdpYC>tp(-^c~6Dlj*-1zfBXU2@VM^V8ZTQLR&&t!k6D~fA320^ zAJsprMZiCs5=;vMy954>4EH2;CUrUk6Z&fN)#iHO_wX7^8k?J1n+SX?e{R$KCI~>iP1-tbU0y|gMP6}U@t1uDY3vU(PEIe6wypUheUT~-2T0u`y zf00dvO+`gLufB&b=GQb#iLpI}TJ9X&K!JXkh34(8S(1<`^xq8O1knESXjd}9~` z$n_QJC28A8`$&W|T$(VFI5K;1_TbvCbzOqi*48`xNBq10W9clQqFlc&KHZ&6(j6*_ zh>Bu&cXxMpUc38x?QT&Nu)773?q-s*gOiyGw|?yU0E zcxn(ei1z4q=uzN1{)qjE&A~m#?T7u@8tAXya@=#wv(B~_*@|qhoUfb)ufe+)wIAij zdm&Hrfuf?ysTW9>NmH;BvAM{XNNB<%F(@o51=$IC+;hw`$vVY)Qgd9xlyl_et;MZL zs$|sz?H%n_{Z4&|QDWR+*=SkRwx~_#(R@rv;rj?n_ z=)#ysUrZ08bHSlS9eH_!2w~O9R5g3G6n~W>Ow7C?0`_ z6(_(8rt_xruCuPO1~G;(0^S)mIKq=D9Vh_j05?b(v}d@q`>U9xa)DOcAR!J$W~v8xe=2a`aE>Bc#L?PaI5g9 z_?Gxk$f1zJ(7&Ni#m~fXtSAbBLVpK)_apRu^!1EQkevF( z7)c*PA3*Czd&SY111 zySp79gIncc$HDD~x9=a=3bUulRS-l63LEX z$6SiI6d?%-4k?iSlAa1aA3Q|bUpi7SMxbV^*lSp8SPQrdxXFTK!7|}8VTd4DAPSNM ztq?31ycNF@hlGTMbPwwirUiCLby!(g`>>8-%R`oh9D@FukSFG))4J25@iF+%s86V2 z$YIDSu;StDKL<3Lfu8;zg+uM=V(nzDhds}nwwY}c5EBvq2Ks`ek0cg}PijYuC5Dot z$W!T)=-XL4Sh1`))@k|)Itosx(e#n@MrI>(H|rnPOL{Iu3g?jNL?RJGCX@d_Kk5ws zBL9GRx41-7E|~~E;Q_qO5FEj9dnv%jZH1p1&VfDfoHka_4Y&@OSf- z0-0cL(88c*Mgv2Jm1Aq+`tJpP(L`5!SB@vk^N)YO|33OQdKhsOkwatB(&*jjY1CBe zN%ASOnxdiHW!z>Q;T+?5`7ZuB$Ynf+eT`p=3@#G=74d?2K@0c``HKV#1$lye!C~%U zu8O9hwUJyTDMd^XFhq>=?0?y(xTm=Wo}TxS|B)Zem-3Yy73T-*Co2_J6g!fAgmaWr z&8^_x<2~m6=6>T&XU%5u;QDz^$)-$UOl7=;pw(&7Y0+xQDoM69N7_HMKUl#c!e4}B zhrATL5v+jTRAu10f<>U?=`O09+69i`Ang!sB6B=*C3`h{9%nA+Ij|^>2OSUkCHgMn zOGJ|MqBEj-&rG?onJ$%!`Pt zb02e_aYh6U4QdE(2%Z}?KPog@8vPG2E`CV9O6U@%TvP<Pp&4+Cw@@+E406?oDRXsI-^NH_ThCn=Av(K>G!Ki5N^ArU>;1H4!}p zy#u`+odnON3;xUg7oO)HzYFaqc(9(k{`>wb=*#F^*jrd6^jnAGhTwWbzds1gLl;4> zaT9nn_L%pWyW6|lXEw%5)Ui(hVF>y_FC(W7c>j+QhmA(p3rDwcn93((y zd1kn$yN@`JIS;h$YdhR_r0pR*|5UI{F0(7nAM5XiPDgiy+yfp(L1n_vFw;NRzZxjCPE1WLSDwf=nXQ72_pixGBO^XR0;5Grc!0H7+s!)fa+W>74$o@vO0@wWl?% zExyeQRHVo5C+q2WY^vURMh8yWcdB?fNx)uUA>7D+q{AZCr_?9aWy(_J zUByF1i=s~POZiucRr!=L$|U7=#T7*?IKUKIrPgLN8!wnIo8KB=8CPl7X?H5ODi?sjgaN8w3uCAxaR=M$}q)J=oWA+$BPb);gHVxe}Cc9v;|snAkn zIS*^Md8awS+}<2*3A6089AbmfOBuPQDpw#v85@3!1&dD8HtVQ&4L`skLp zmU?ZC)@}3HJ~=--2e%Ds+hyBryKcQ^oellaUABL07of*K**L|xS+_|Sqm9;n(tgqA z>T~q{O#MtJEhjDA!C&|u7Q@Q0jxvlkbX0awzG!;YByErc;-`%a8B-={6}Kk0bZoib zcn=cQ0~;%w%bNAzb$kwfJuU3RN9#uE0)d!4>b>frsv)YOs$r@^b)h;2j$dasm@^z1 zj^mDF4y8$D(kj(TUo#S9ICASI-8S7>+ezDb@T47p42Djp)*VrwQ0rA{RkAuoJw`oB zJx)DAymfrXu3zRmgtu1d>XH2y=tARQQiVoJ3}s0 zC>5(UD>OUxyY*&+(U5NJ31`#`W0|Skq&92J=gsHLhYd#!a%HP>Lc@dxYniodd(rly z1!eQgJ~e)A9HAeguX5BmHo4Zja-bKy#jCp#^LJp96=@1JXvnkUjdLrKX!v+t>R0|uNHv9 zHSU1C{r<}n-zFL+80G>i z?z#SnzNfyAzN4YNVWw%OX(`;3jTWt?z+7bR4QxP~k!IBDb^1Vm=BefhBpnJgz4RIS z52lZ%Zq{zrAFygH)fTpyV}4|KWSFO$r|YZhqpLC28HEnM0|C#bI8V6eisP1}!c<~1 z>#Vx5+OgV!n*N$h)ic#R&^WHay>g*?Hhk=Dnj%fUrcza_I;A+N*e%~F@2uzww;ctX zns(K7xYi^u2glIxTlKa1= zPO=nPXJr>9OH0!d^;G>pIHn2escN~rS$;)!9lmP33@69PrK(_6q4uwqW~3Y6nckZE zn);h`dNtfz|7gam#;V|>Q(`n24Z?sl&@FV!JlkB`cH0hHI=pwGmT=2r%OcA=+gsZu z=T)c4qXUZFZqGjZZhNw>vo62&XX``w?Yp(N- z^NxO&{+2JMPo@^gATDt&cFpq5_Nidqg4gu5dE3gZ71lGRGjK-CGR=YKv)OHS&qmHh z;?OvBKH?AJs^^MF3(uHhM}gykE7SD~k&8g%v3M*AOZtbfi%^elL?8AY@{Mwhay^B< zK!vN?l>xNP1=uCn_P8Wm4Lr9KJV~C(&I!&d&Kpjqi{|1xg-&@}b6cb*+Jix1P`1HvC&&FF{3PU%Uy>_nB{VtY=d-Dq)FNU9 zQH&45lW`Q>8)%>nMGZoo0xsPQ+*F*C?xwE^S`}mxSOmvmFR_xkirSCZk2rxafiRme zhae&d2*2@v@MgRozn^f30Ihq{BkBWc8MA`9nZ1#{nX!!_B=gA%yc++N@QKhul94K* zU+p8f2ulg82!p`e&!BKAcgWYtPf6LNACxZ?G>gF6%i96zztg->wwT=rd+v3#wY0U= zb<_fK9(gx;A9)(=^dzd^lo1*UQ!q0yH!xQ)!|`MA&BR(_Fgb!e zf;D`4~3AJ|{mJAgwll{p-~+5BlEsp&&}X67VXYUh7taCBteM@+9>^WQ?aA-O$3nj5 zE9V>M4eJf-1M59&3wIm$64c*{5OqkGsLoM+V*1AHh}s!-2=)#o(n{$?@j3BE(RNXm z@R4weaJq1uXpG1uaZ9F!P7U1_zB4>0EGx_>MF(FJ{wo{=`;X3oPJ+kKODGi=iBF2o zip&C?KnRt6Vn|#_OH^xAQ+#9m{lo`}2NL%su1Q>Pw0TA_GWW35amT@$rpzGhR>yy_fcZSUB zxu|nd5RD9f9F`e&IQ&p}c64s^rKIyor&9h+IhA%S?Nr*Cw9B0@bq3U3a%n9)}Ap%qbOQJz?D?6a8XF;^q6MBan75OTlg+yB>of5&|gji~S7O>`xG zf+n;Vw3$R$BVYk;DvpuJNSxPxUi;pVjR8zbN+8EGDQ!wx8~AT0cbeMiQu~YTOOr~I zwt#~4uEU287du_(G%0mbYKOGsv|DL6(qvs)ySz$$oqD13h0e!Qj--U9M5mnXe5A87 z&6Fli=cOm6w@(l28qzg7H8yp4>X6jSY1h)|T`65Bbe`Bbv;EWdOA;3*o=>`%bUyh^ za`(8~iqSPUxJ_rJy-c1N$hrEC_G;C4?)2p`c662%Z)E3_QnK>}<9Gw45=JTW{l8cxS{H#p}aXhG`>=5kEq|hQ1NL7shg; zIZP&t>3}THQ)U*Bi9Afe?=XMBI?Ox>RLf<|LgpXl0PyWPnGWV}AlVkv3+S7Hiphmu zz)N5?96|3#zXNE?VB}yV95ikLs9);|YY6MG8?g=OIVq=+Ouj+Bfj)s>z(le5ZtR{~D-;fHUz`R1dL3H(Y@vd>Mb;jEgY%fhO zOmMoGc3F2>qg^qsq5eVsI%E@)gJPm~Aa^45z)lwXg?=`$O^9Z^*{HIpF0`I&T_9f~ zSE*ao!;B-1r>&=~db`0s-7&>+#eN-sD`T!G#+Tg)L?7QRGzHd zQT0z%X8ptZney54WNioSTUduxhg5p-b~MQw#_U1XDl@-;L)nFmXatr*U+2y(A-O9Q^u4hEq4Op5IRmqLQJB1~G z3;sSVeOkH&{5~6Hn`OzZ9a?)d_Go-n^`$;y)BC5KBkm2N8yFAFQ1RXU^8 zUF0h||M$$_`+qb4zAk=I>@0JZT`s#)wytnpVcM_MUzRV9FCRa@{=Dbwp|29qi=@9K zzjpuL^ZQWVfjn$JF8_Ak-8}1W)9>+l6Y|mux)h|8q(W}0ssb8oRrAW{l>Y=_@I^sR zL2kjzg8hYi3O$foiYN{)9#u51s260kxBlJ!Hy75#qVYxJN+y)NFL_&%Q}nWE>fcH5 zGd=t}qGV{v=8EkV9Pq&I0$NdXX>;k2l0iTgzEVRoNN5JnM@$7Hf-CMQ2LSmD0;; z<-1G%DbW_F3wZg${MNjNypj3i^C#zz%kTZC-=B=aj6zx|wRCU!-ttuyt1C#Agv$Ra zFIC>DxK(ku^l&M&h*<=Y?Z3kdhZjZ^MHS&e_ahWhiuM%mEY2$}C>53q%8Sa1p+z7s z#g`IG_ZROi<`(h``xNvpnDTe>-z_EEN{kiyip;8~Rg_9n<>k`Lr9+B_7L5k)d4Ruk zyZBadL`4MreP&hniu8*7!o0$fd86|RKzB74+lz&D{JOH%%2t8~uh|Wbt{v)~>d~zu zTdV8q>ZeyvsWw;Hs_xX@tnCdPnXe7s8w%_5K}Dw23+mbRD!jE>u(l#l2m`6|5? zKzJ(gR(Z?4p^$YJ`vt&}d4|Xb_V*y*!Wa2V{IziB?2JxDzX2~*DW!R z#l*XyW0X>hs00dyg2MVS6FrkXQyh~VBicr^MSEktqk*Sv$J()D2%`yk#C&2RDS>o_ zc$FxDwVAk)=qI8{74SOSh`Wgf;HIymZKSsFjpb${6Z+>R)mJ znMfcIg0UfxPHn`dkUEpD(XY`BY(2X>uRCu6Zz1m|=R4;C>oK6T>zEprmK6uQ<`wK^ zz?fPIZ@-L^1(}jXq{XCSav}LO?JaFEJkyqNmU5_U3VQ%;08K~G5du8BY+Nobo0v_U zO`S1iatm_N>H!@Je4t=F&qyKHX0-!=`QgaaWr`fnGU`DN>)8<8Fw-F8t*EP z!DsQq`CY34R_|H4A^P&~STA)o|Pg0Yd*CMXC^H?HGEvi z_>dl=bWt8JpXXq=vA?svvg+9lY(9uIBZZ?ueOe|Q0=4%#&N>c*$K(;k1aYshUSZcG zu165ViQy2_3E3t2M{+`VM3@3B#C&ERvp1tRqaSE+X9Xt(&R}2gEl70F2%jI`KO`f> zE_R8xh&PEB1}_Y*4y%Dw{pT=_lq1~}v@gg7?^S{Dukd2Xg^+#`{UT08oQ!B6-Y)!b z=#kKQ!E=HKiU*6G!0j&PmGI!E!7bnva^|vUvjdI7`Jf`Hg<9du(A?0)(MzM#=+|C}m5y5{Si!hKL4> zz}zC9EuA6#8u}yjN5r>?Sy8j1ila)Sv{C9PMl>_JHA)s0AC(X_J#u;^AhaVhB6~(& zfIR+(uyVOPUax z7^)1FhYk-O8JsTYCa?mNr3MAz)6ra+b&}iWECPB6!m6^sg(~a~a)MM0TT9xc^zO!xShO>yq>&~ z+>X?aRD-X?FT<_IWdf1*DJh4vpR$L-CUeMIoE8`0S33Mo|1`v8ga_?M+lf}<9Qs^( z4kL$g6Li#iYyK_ypNk_K$Tl_h2SM@@^dV_1Ns~GN`@#sYK z7~BY4I7}M&jQoh?A$WmS3vw;grzi2p@z3F2at!(a=lvJ`KDW!g*tx>Fy=_z5ROc+` zX7@U`8kiIP5q%M7P-js4Fb6PWv7@lrm|Tni)@1Bt>;}SG!V@_5@lc;W!en83!@Q6p zFm}B4zk^;q3gG}&1si4!yZ|4=d)HeR!AtUv^^f+$$%rh670Bc+ftq`bbDh&-H`%LU zoo~C?Msj1_sorkh9=>#6z9-M~78j;^*I zHn*wGq=8=CG2KyJo8GD4Y2I#j+q||(jtP!0wr@5NLdzwR^A)G z7@ry*82amm>aulDbT`d+&B>s$CYQza+wtA;pXG{Wq<*meCNMp>DK{t& zYL02f8%G)Mnje~Dj8R5_%Ba@1tZg~mc&IVCxl?nRJXP*gx|CaBHMKScB9Sta%q%;s zIH*8D7JZOrux7btiDs^Pk@~mdo1&nlxFxxLKY29_DblIK9Ol#G%ahJ*EXMQIN5-y!PTrOUr~Oz^mwVDf>-gr_ERmX8Q=V= zKMD>z- zN1df^P3?x->(y7P^Wkfcfsfq^PPp~;YwLBDy2`YYZY7z8j|xi)%L@fX+@eDGKXI|J zII=vtoKwSrXo0?N2CyT~0ISU1;A`02w7F?#(~hRajY}F?jf_TPqp9&6FqPgkzHR(f z|D(P~T}IuT+LyJdbzSSK>nrPTHQi|v$^^1QEeBg()xWL}c+%aa&eHo84=dF5%KC|* zhs3I5)EpI8^$%zu!y1M+oUT1n+g#UJ*Qa?v^JDp4`5xsV7C;}>pbN=0(!@L`+NIs`z?Eb2a)bbcWCWu`ziZb`@fE}j_0l% z*LL4lUm>Ch!GwFySHus*MC1hI64YXp5oJadpne1S4o+88E9wFA2{PTE=3nJo>!YAZ zsE34y1RKRpc|&?lT7zDP_Ili&Qt)}s_0RF&K;J}44CwCk*FZ)NG#Bq zQ&?SCow+I8uiP)(y`24=fPWRs!mu7QGym6rD1g3$iD72I)&l%QKPfH%lBY2`GXk8j zV#x1b;9cb1=icL9;9lfDfW_gkIUSiDnFhLbu=8EO!v{+X!mK4VbaGDkSq)|9z)1zVb(LZu(z{s zv2U=qK>etvYN@lybIHSi!`aMeV#s+a9#=$%+F^_+ho8ys1JBh$Y94hSWg%rSZ6Ga> zZNdTs@-XKFryr*mXAoyFXA5T==QsO1dp>J1(Dw$hUUOb?LW07AL;|5;K5%}%f%j-O ztWms?yrH7uqG6IDl7*thqJ*H>phdi8P*HB@DR>&Fm*?^#z$+F6OoD9obEu*X?2+Ju z9?2QanE^hL>&%{duD+%_$WAnQ@~@9%&?@_6yijw)A(%uaNm63q#T+-6g&tUr-7+mAf8%N)e1GM!;|M zoSH-32cGB*b{{s5kK)G)k_0ORO9YdGrUr3&EM9kTA_JbCS_1F+F5(_y7UdBI19gN3 zW=|~Rtl+$0=dv?^8L^LjfbC;@;5bZdG#kP01AYrColfsf?LkFSG1OPIm$d23Y0Ouw zx2#Rfbvs7tJ{=Wzee&xRvo$(vvWuAx4}zC;hfOQ4hJ z&?WChYQVSP-(X(A(>MoXCs+s=8jd!Q-XB_r>6Bc;bAlOf#XG=Tc8GA8kcLacjRarv zTf#d6hK`|MVE@O?WB*}?15;K-QvtDV6YUP|CT$dLEKNa`{jbmMqSpDK2u-j}HsC+v-{IR4+QX!dO@vfJ7eW_& z7rYcJ#ePM8L6*bbGRz(7?&eN+CxBmIA}AmltPGK>r($F^eY@s0S-gk(7WY%Csy zMmd2)7KGp+q7iY3?!K zUmTwt-EHZ%7p7d(9oTn|hF)DVOb|N(bxDXb*xBN0b%lC@J%c<$Jj-0mTutC(z*~t{ zij`!YXJ23^wGrB$*|Y5nEb}Z0rbN?6SYqJ6oUosSyX@YMHtkH2D;BM{}jU%AVYo+&0`X%<%*Id_l13%W(B_ZFX;VcW}3NZ*^^RU4+%! z-OFut*g&quN~9X2zNfvVwdx$YGHtOoL>;EiRpcm^ z%9q0v8V|C$x^=32ihQJUq*9<2Y7>m{#+Rm-rZhuW!xE^g!l6e}r>Is$0Ql>g?vn0^ z?wD?tb`~hvC~di>O7m0oRrRzryEU;Trsa9-%hqe!%i1--v>R_8XFj4mq79ZyVM|Yw zDdlqcIn8-Zo8GR!AMov)Y38Gr6P8<+>y~H6T;oH{UClDZDg{A~mQR#Vl|Kc(Qty_2 zE&G6jNNS-qXLIC?+yTaQ2nfTtF!vs8~& zSYX%MR7RBsevQ%$(*)^-`bY461AIKLk!zd{jKH0`UAi&4(J+E2R`)+%5@_DT;ZLG; z!8!zNUc{Op%`EL4ZMZSq*v{I{`pEXcrncxTmBupT1N~#YOXt*K^l1G!{doNp!xRJ2 zL^K6zliQ|SrhSHehUK~yx?F9x_M7&XcD8n!mah?OQq=9$L=8=|N4Ha#0H1%2sm64} ze9hd!(#dkqa@%so@~-C ztG$Q32fR{uh+7A<`f_YJws>12P`j#a!yF?V@0@R)>E3kj3B*Z64YCTk4{-o7%s156 z*_-O!?cL>F4QbF~@GVVu&U9+Q-`c0GU)wS8n^n0gTqFfU<*smFaa?tbHjg&D!S4|8Zj?BSoMT~b<6Gog@fLw6d9S(=v<#KiOck-WvpDov)4O1KU`nMrA!34ygh`>z3{}2}t91I5& zM~EY2!*gW{W*TORZ=r9zd!n1@vZt*sF`@kJ*9OR4# zVn$)|K`Z$|$Ri9O^dl7Gig6hDuL0`4Wr)QHJ;sO`P8mk&&g{-~faX#|tR@ad4@O_{ zU-5%j~2DH1Pv` zzdiWv_|=51 zFYNDoQZgtL$m7W$NuQuIa2e>@t4JL|%l49xPTUeWc;XIAKiMNioPq0srF6s@bu9+f( zz#*6l%*@~vD4_g}`p zj9#P+5)DtouOn_CR?ti6bHH;S&5Po#Vl8LQr7foIq;8~kpr_H#GLA9G3?`!+J(b=I z_}@4-mL0>5;rux!hK28)P43UjTd~i^BvJ$1cY+`(wKlCd78Lr`UCn-*Llz@N3(5 zpqAvc?Q`sPT(n)ZK`YN%Y$>*Mv~{$J!K-R_*Z`dwaN1=vW>*j)Jb}S-jJY4(ikB{Plo5D zR4dZ1*KX7f(GAue)gRLbq)hP8y zb-B7!JzX#U(+t!M(Aed6d3tk?<{pjdjUAejn>x32ZrLT9*7BAWEk|UB zVJ=U)q7b~NPL&NbT${Y0`Fk_6k7MFFYer~>$w$dMwj{P>wY+G#2k#H3 zS=8*Qwbrhz-cXINL)AGNU5&RJZ#LR$9RJg!Tft|$sd0Vd_?8JR-J7~M5kR5aSAU>h z*dl0|sGOt>uoL?0`s-E$!}+TIvVJxAhquF=h~urNTNlaa%ZuSNdS-ZPSY}#o+Ns~6 z|E&C?tZ%7r(L;vK(rRvvQ^%>-z*$oa-n!MQO)9birI^$@tCiczY<0?=@+%w!Z zOa+hdAj1HIL*vk>)f#mU)M`O`fj%C*ftiXYiU+d$vNPZja48+iZ7>7j2e6l)YO*v* zxnp7}ZAs^U@0M=UmxB*(2qB(3127eS2Vld@_AC^@i(Ug=xL1r?D%n?na$SZ@Le4pdZW_Nl=R?fNqF3`VA_p z);iZY+j$4}0%M^T{NQ-+sIk}BZ`y9#+C#;dZp<(q((l)Q)Bn(KHEw|!5f}>>91KI9 zqn!8Mx7{5eR)L`S1_KD{p-_N0fLN1WT-Tiu5dhY%X99y^CHgK!u35GO$k(3fBW^b*V>jDRE{ zCDS|6W0}#+m-LtPz;kOLVGv;t_8*umoPzxfd*9oTVRnD!{> zwk^Z1fb<;=8%Kx%ru9~8Hscv%GJP_A8D%+TH>`s&AK?clkK^Q8xlh3p6yW*>arhiB z+t1#}TF=5VaiE~SU_x}AW8mt!`?&|W37~^IAtOGYyAU!;Y91GY;|gvAcM@k7CxR_u z_W+L12Y8m(!1~4d%?j{(2k{2-d_f37dvTI@k#vc4zhs|8AEXZ|1U3{PIq5Ns7)CG1 zwR94th?0Zb2XjKWA$_HNr8k8)gxQc~TFP0(VQ`rs(f{R#3nPVng}sFSAdH|O=x5Ls z_%}!iLULDfTaqE}E8ZYnD}=F@f{*Z={{}O5ehdBx9)p*TBqoaUA%hjj?3`y^V5LIs zgMuMt=bfpI~UeE)3N%~9Hg{%(AljcdKP$OIeCdN0)FUof6W~!X7q_1GEU`DVa zS>1qdv6r)tvyro&LtvBGb6Im(fqo?=h!nI)ut;DAZ&iQD*>48&(>3mO?s&mi!4$!C z0UM6r1-{KLP*> z&R)cR&3F&$*>mWxuHXy|8WePle}vy1*oKR!^TC7pl8R-aS%sWp&Ry0W77Ma#7byQx zHZnFcI`EQt>w?w>Wpi`5OPEWTBnFYupEZDWlXHX92l{$(v;m21AG5; zxYu;3#!j)%vBz>oaW2!Z(gS{iFmyPYi(zA$2+afw#Y$0AmDD6^2kI!O#{q9gJ3>E3 zpA2)|1N#;v89_cnIZZhYdE0x;+suK`Gkie1M|(ngN?L|nhMNZLf^D#}@LBj{pl^eH@xB`!Z-pJ!mLGv zzXUz(H^u@hejIT;k&Gwd2Vw?cx&U8l8*(c$2bqh68x6{Vbi#zdmq-?pjeO;M6&R7~ zJZ3p+u^4QIY|}H-HOCFd5bqH0T$o|h6F7AL02_QM)LbH$&~?Uf+VS1`%X$H1#A-o|^-)A+}9z|0#b*Kt>-nM|K$Uu{KMf40 zzJ^|gZ!lMN40!7MDtjxJXqRdKSo5qoo)?~NQ1_nnoc1hm%yYC@WRUDwWntLpwg-+! zj!&+SuD}ek3%(1!FmIUmgzJQBk#i9|Z|}ISdain;-e7OQ2uk))JU`)m+6i-2Id~ra zH2wtsBlatHEoKE~9(dNLfzR7QZX;i(U#71IKlcINL2y2g=2b8%88`8_@IArXjzZuN z#Xuh`hq-KM5lYkzdhSS(PngC}2f0hZJ_+-4zA(NphJ$i|;^BB~Azhd*?j>#!HHZ!g zj|n4$AwoR(3MxhABAd`Igt5Lsfvn+D?sB-YpK*_XzTcCR!6}3ZOZBWK);i8=&L{3? zZXE0pd$7B)1M_Bv!E3)~e`J$@rApwDc)j^O`P+kb1+@SGLjilp?YynLTin~+N8E?p zcD(jH4zP<%d<*{|e;0O|8C81T~fEWE~X-;;(h6d z(htS&io**c3UYtG___c4-tXOi_Wa5H&H8=+=fj_F7+_iax#aV=FJHc7e#?T%D|5be z_?+??^B(bj-J1<>ByYuU0ay6J^40cr@{b8Wx_(Xny7cXmw}-M1WXEMCX8n5h{n?+K zzc~kT59BU}IfVnWhGbbYO_|6{Or|DNk?GAsJd4eZ$sPT2^h=QTa(X}Q{dDcawGUe# zv_1f9<74wv^V4O|mOfKG(>xPB7d~H~wLUB2QRJibx7OaeeC6tu#>)+t2V5U;eZ}on zFdLe6?;y-PzV+bRgZ>W(J)8=w@?rVIIgjT*F3l{;v_Ecp9Q!clVeb8$`wJc|e)I+S zq47BhIo9Xa=Y}WRCn1j`AK!j_{c)eC{hz*kmiuh~^8?RsJ-hMj&(or(6P}EI68a?a zNq~W|K5Jdpk>@9#zj~4VqU>GeyTP9af7X4}d|di^#p@?Ik8?)88u{w;*RNkw3p*8# zDji!|1^RJ%UPfN#&qqH|Fn6;!zbL<=puAu__z(~L-TQZR(b%HYyW$H7($8&^+)($!z?Zo7K2?+T|-^N z+Q9lw`%D{x8G#w#?(Kd9*YrEoD^w!2JypxruwlR`XCm~TNE8wUgGb{7dD+vL6PQ`} zdH6TPTu_gO5YOR`;ih5cVN$Uju*1O1aglzJK7}`#Hwb(~w?wx^b-Zd`4Wk~s&vyDW z-Zb7==>#b`7$2Mm{ik2VZ$v&;h((b7q|b1@0eT72#-k+rVD{B2&>zD=q$DE9h+UA* zt)x*_kR{v+o~tjS4$D=|MqYJJlj)+F0 zTOl_hhWg~YI8XdR@>Wt0^&v!RDUG;h+-CfGJeq_jje`6}D)b?cWH0GH=`rad@jUpv zHxgzMXAuWd2T+H=bVDZa3c@%c93KP0JOi55SlT$+Zt#i+Jb3YpXoeMZ(u36f)R~l- zl*8mBdVreB9%5t~EfBr-ZO!bwun1MFjL1-2YZgf0k{g=1B6tGK7wC)i7= z%cysWcZp~!f|>$;L3S`X7#W5SBLhs4DWnP4@HX)_bJlV^@IG|{?vao!V$--(?g{QG zu8-wq1z6R0sJE$88Pge)xD&Wy=plIoUO@}YyxhXy#oq$`vuy55AX3iZdbnQh4E}Wf zV5nP9@sINZ{jg+qBHPEpuzN%HK0l~92qAWhPDoBcN3OkuE+UHx`K4f;<8Zw&OYJAr z$Zn}idN=%DxB}Lzke4Bsq*tVQAwNT2MZSsL8nZQ~PgL)y0l?f`CS5Fb1UrKp!t2A! zqROM-Q5=0K=3>m5*t4;}Vt>Yl#e~N!j$9l$FnmB@>k|qi^@6L#HR994)4~GLA5jW$ zxwl4ai^z%0j+_mw&FSG&!buU-2qJ(p`$i0mkcJ7vkfE5+FJYg;rbJDNdKdFPW>WOz zXlxiNte2>d$j9^XB7(w$s8YIgOvK2DV#t?l4c!#FRJ2m`KOGl(Yahbjg&&DN68$?W zKWcaQj_`*enIQ**_XqESMS~g3V?)P;dc%BSd&2jG(?Y4C-+^6llXshU2=*>{uou`K zvMpp|#D)k;I4%6D^s;oTc)R#XaAq(_@KN7@X*MQ)RQ#;ixv@QBx&o;j6+0gob0P7e zFsb~Q=$N%TZY;;i^N@h{?X;`%}!rX-;_p)YV(wTbFPdt6%_ zDjFFb@aO#ke)+b9O$is;U2T^HY_da;|6d-rIPQ0Be(cBC53zS(0gNo}Nc@p_S%NI# zTf)}_dAutAZo=(^o$YqEO9Sp~AlLE~nB{9jSBIW~SwQpR=f)rIaJ0jP_W!moh%1Wg z9i0(9EpA#|U5ADa?o?-LMq015K=!1qgRKKENsx3n{$TvUxI=L>VeJCG{vP17)52(B zyQMp&3DP9#^3dg>jwpN7{e%Yzo7!(|e-D;7!IyA1`d;+Fh`tenB8LHswKn!m(wn3` z?f12pCW(?B#bm}v!lmKEfQyz0?Akx!`Qd?CpsG++=snnxtro2n9R|O{W$6v+O4#@9 z5bY6l4oVIR_)a?nB?aMyB%xpE1$XCi;f|mkK}WcUxj`%;>pkra%}w@^r;sK>Kl=Ld%0f%R0i@lvKch<0Pp=BXzO{T0#XXTBVLYBAe!CH?n=;8 zLXi>3vxJj`(QxnB0&JK5pskz9R16x|=)0o56` z7T8~{Xcf8?TZ|3V&hM~qv1I^63TU}rlh5P=&l(vr)vcg$xtwmN9Q54{Z5!H-T8~@d zZ%r1n!+hO-8MLv@&T4pGd^Z6iOZ{D4C9jb$Q7lqy)$GvVLFrlsS&VD8E4IGI{>E;~ zuFAD7>sofV?rbdu#`AjPTH`PnRCEw}d~J{?E!UQ7eR{usILxNdnDyo#(3|0DgS6?g zbeXbI(Rf&XM9wlXOmjg`z5^_Env5a4Sp8r1ud3fwX$@%&lvYaXXyqv7Zq;tpHPv<1 z2Jp9~gZF7ABuaI#nqW58In71Q2i+@OrM6DHUbRNGRK85!N6|+i&k4Ze%&L1SdoNqpxUzA$e58C6(B0>`=eP&B z2DwU&#l{4AqFmf4ZoJcQr(sp|s%BfOwKY|lrd+6;uUw*7u0TL=K0(`F`^o&?d=B!R zT_IV#)wIpjRn=9+2NUwlhB*y{7Hmtkre3qny4lKt9ulDQ^|QeP2jgifORLJNx&d|j zkmji79O%_cj7yD)&=1?B+NfeGSc-k}eexlS!3wUDuS6>m%FZyOCtLnp&Q}B}W+-PU zUqGK#u2QPDDz+%pkfUv=uB+Zzw!4g3N-w4Hf2j>(2Q@fAxA1&V;-zFA}D?pZFN*9#QD@`s; zECtg%MD}N?`mF`TuW}mi-;HQ95;+Z#^$WfUXy+O`L*YFpWT0^ zd!~KH%3k1>WfYkdXAs+DjYu1;n|DIlaD$Dvm zmd*mKt+eafaR?+ph?9f_m%6*VySuwg-Q8_Eow`kR>h09MrPKv09$XXR?((g?|3CY> z#tJPt9CG%v_geR=dKmLG{^{=LJD=O0IiCITWcd^4BgZ4>WB23k&$>LbJa;^oz7)T_ z^6J{FYj3W+8TxMcyY=taz0XU_OY5ILAbn5z?sR{eKdt(G_51P<es!@2bs^nL~DtjanSDpzZVrO zDR^4^xVS}etKy3M^86BH7QW4Ym)}p`TYj|sMEPxK2jw+oHGzEDfz<=5dzbYqt1GH5 z8e24>NKz72a1ucT8k7q}99J$M*(DD@KgAM#Y{Olk=|pS}>f z>bHy+jJV*W;3x!8Fe#A~Cw9R@>0{_?vDq922G?2kX?7;|RBC9@H}KZ+da?SjT86g> zhcqE9laa+3g5L3Q@YCQM=smh{yK>h>t&dtQS}Br6#YP3hTQn|}>xghgj6qMgt2y+3F1(p^;dbXP<}czSG94Sk>w+KfB}_t}c9r)p&yBq9G|@-VYT+tj zEH{pO1T&#G(EIHYZ58E<<>J%eS!N+?1T5BIQlW+0>J%jp0|dARjhV5+wNu&iZLEoJPcqiTRZHA@N62Mv^g3 z7xzc>%II05nW9eM#0PwvXTTJj6f-p@S}K1Afv22?r9E zq%2Ljl6Wam3QpLdsD4q)_^bE__`CR%u|tg$i^WBNnXHwql8up#m(7w+mlCBEI1|Q6 zi=vC8W5n^|W`b0~cQa5wgbKh{%IDz>Vkx%5m;J@U*Lw2H%L*T}9 z;z7w$a^}OkBZj{q5nod@Cx-JG^UY=K$0IzL3+%tC3$VAhXGba0Zl6 z#Z)qdNa2y9NV^H!390_Zev`-KX@_U>Qs~f@Tb5WpK#4ZaJJx%SaE>t1H`xaxB`&V^Ubwznq$yW(f7ogLMggN*sc3fSwowPD_oVuT? zw~DRess3%a`aiPEEokt2AYXcN!_)s3q=RCcbsS9Pt2jE~*y>^sS^)20Polq~-C+U)O^R@G|C$y)u(=ch=3@#&2 z%hjGx9arg<24xpy_051z>!|6Nsk6C*c?&$T15^W5KNa5TR&CMVft9zjZwP5V7FX z0@Yw%i0j@Rzt?Y5rYYW*V#~Du#70|Ue`$SXwLy2a*s;PDgyE$(@ zXZ9fcaJOT(Bio(h&hq~9w(z&{pCbHCXiMx!wE9i{Gv2e_C-71ju+vJzel3@hN4ZPB zLk=PaV@~|b|CI2Q(2~@G^a{C(fmy{Q@-*^e%72u@)FadZD4@cqZ)tC6dqVbw1hSX{ zo}D^oJ@b0VwU9ydA@ohqz7EHnJ_b6~b7AMgV(2pZD#~g~Z{(8Cq0Xi1gLT237@ZkQ z!xn+n&?l?}G>Kl+zSN_{L&VwS`Q!m1eM4xhAl6=Jgs*^mMhu~Zj0Ar#pOMc9$1~?- z~_T6h(cB_%gS=XwX_*ehBi>_l(5#b*RxsJ z5sl%F=03&FV;?j+d!RMCAN(+QCv6+;@1PSwRnVc9BfnJ_sf`R|kUzl;a%Aw(;H6+= zh`^{gz}(OL!TiNM7J4+)5#$V7gKM#gv<27pHR&m|z&B{uXa<^z#-VbkW1tjUPWXec zi?EGgCfbQ=u#ry_&k^nLUgAjTs&&*k>cKg(=nL1;$E1y5wOZ-lRdx3hPpXQ`*V zyRW;-Sq6pMd1r%5=?ct;iEgs{w*8juoT}+91PV zwRw|S3cpRbiElcJxq27euY1iq%wb?+Ei}(F_cQf3K^|`k%&3i4la=7`+byU76NZdj znJ@ZpdY|5-FE>^g&zjGgfz!6A%_?&&AP^(1BdrWO!@d_Rw(icJNSt9gyJ43*+&<9$ zukE(2wXKD1uXUgG5mct>miLx3&}IB~W;r*5Dg(T^mkQ0_W!E)Vwll*S=S*;t9Rx?f zgTC0d$kra`g$s@gjwY_gE;Tf%pS_>GWIx4UgUrtl-cR1oo^;P6@aCp?r+LTuM)^kg z$N1I$2EQL!m5so0X@;zB9W&@jv24??vxw&uY&E?4EaEw!VnE zlsbhni872doOGFZ8B!WL`2;dTBd}+>M88UJ5z;v13H>F#BNWY*ct1ZvK1$ZYWf4pb zr9KG07hDxm9dd?#ihc|G;yEZvC@J(d>mJL&v2rehna2eKbvI)t<7D`ma4!4^B@ws20uD|(6l4?n;yp>t>)1ouQr!CU+bjFn}AWuTfY6EOHpJ|Zo-uX(R{ zql9CGM)Xe?(GT^5Hfy?Yu5b-LW`tmzptGPoww7_&(h`J*D1B60aXWD|I3imGTLgiP zq60h5KyRkSuML8J=@jQIXBSj+<)Ug)CuuurCrKAcQ$Z8KYh86W+;u-Y2Ny?g;M*Zy~3p4laVgYl=iY0dw-BFkNUCTgAho zheZF7{F3|-eHB^2gZYT8pd-A4yuSq}1vfG2q(z6oHMu9+BDRS;M74{m;n(x~@q6<- z;?;14Y~gspM8QNjX)8FD96ELgzfosUjW~@tF>EROH}c$0U?z8+cZ)ZIH=l?kWamv8}ZNx}nOr}qzH^cYx9rFY8B61P}T*#a3+w2(lV`UKu5qrXRglWK+ z*^MmQF3{PH4;zQ~u+os$wAQq_B%~;`IJB5q#QZb-&+t7F`yyUr|L?$Tt1o{Uq%mSauKgkb~$BI#fo8q83 z?$~bH#K?3y=RWPes9@*M}&AJg}i50FtTvf=Ht8`W&TW$%q zQgiL;;J@wkZ1*hpt@O3`w}nUC>!rJbT|8vHECzvWAXrT^;Z5rA@9S^wYU#pTnW?$9 zsWzZ*jzKQRKI?vKBYPt#=0D(cf66|{HrO`bGS9Ntyw{uuINCqTh3Z49Hovt=z0f&XLOKoh;Sa7_F0xWmZio;#ya?|2HE@DC%7lMU%6kp zXW3`k#~UXasbHPWFwZdGb>DN(BhDrcA&emW>%8o2W$FMH-ZwnWqkva-=!}4z&9lt5 zgj&O_JPX_M!u;ASvq@}UTt8gf;h!1>T}gzC;c`HiqSqO8O|+@nLcLrsuyJkuJOe%V zym!4F9UUDlkhflUUSlW=KFV^=27p)Og_)A@jU1x1)Z3j$;ObZNip*IdzDQK76-%2Z<2;f7-kLC_M(Ybu~yYF{gzUy)vs?*8QdVe(yh3`zJL7svGK!{9r0@B({gdId?X&Vr^%v(?-`AAy3Ex|Tuc7~{`6~J<{c8NC`9{g0XKcydkUb=4 zL{9(gUfGYcUSu`TZj^m3`&RbZ>{HpK97@iy-+%s&%S%K8;o7{Zc{B1D1tA3&i!T+g zEL~ozC{dLp6vq``1D9e;@z&yHWlPFhRkf{(tPQUn3O$+#^Cow#uXbYPxXRY0ZA+(@ zOf6YgwxR4cGM`&jwyqRa3M+?H4XX;6!b24d1#WMp2Aacjsxyeq3R5SjW$M=I4(c#X zs3r@2Wv#wO{}R54t=N^HupYPmY5xn+q^%qzJJ}w1mY%bnwq0;vcDE(9B5kB@ruIk9 z)IHZd*BSg=slhJo587rgayRkl^o{VY_^`Cb<7;bVMfsf zbZS^ugAN594m!;^#R%chIZlB`P%Eqwo&$p`3tqf{uPz#y;SZR1nV|?7o(X@wjc4Oc z<4@uL2{y8eZDn6!|I2R7N#zXT4&geX!ATUv3z~tIAcuCRJG}p2!aj!Whu$J1;v3S} zOh{jki&%~9UN(5Ebl8MxYfWL2JAjYUdPyu_xfU(MHr7XZ$t7 zugEJ8=yoH(dR_>=df@9_EnF+C#yPvUu%ECCrz0ou%s$5clYI=TU^;f;3!#qz`hnSt z*^@afaz0@zPmNj z^9D->NOs|w=!^11#X>XPL(oHzC{2=HMy9E+k-yQ)lxHcQW534|#9q-_;RfLl@gVWK z*tN00lk<}OnD>8ZlHQ~sH9yrJZ;QVjeK-1u=#WS*DU&3C2K7AUb;_aCeW|?@`zIci z9hO~|T#>+V9ep(RNNhARc0R>_h!@6+Vh2hGNTZ?=MJ$bx>SEL}>k>93kdsNtz{4j^ z0LLs?NEY^G4`eUE9A=GZz36`I-PptNN8;;cbux)KT09e{Y^IngE{T>$4~!Wca}cbs zmkBQuwt)xsJ^pKaW=wX>DcK3xo7l8idLku}ohV5BBzrG=A$To-rJv)${jfl`NH#FF zf9mR{tD8z1NgExGI~aEflc0XWzQX0GY<%w{q%0{pmKZxDZdTj}?4-KIbcvZSTPRy2 zTP>@WR!g@^wn;iib&3jbyYrB>RL`&DzvR8-o#$NSoX4!~C)f{BU{v%J^b;)L&*AUE zj%gNh{yuZQaB8?U+%f3O+Hl))1M{UT*hEf^oE#a2%#$DBBG~v2J{j{gSA-jTOiS4L zh;tD?Ilnj`1s?^0*FRf0M_A4);RQ?^C9DG0-_WViIV8>?>|u|H4vHd09MN>_1s%}h zm7+TEI`R(j5AtJqvAi61HW+qdwhX4p{m88yA2Kdv81~JB$wSD9JqWtMJkO*>1VvU3&TVhiGEuw}A***+HZjlgamOzTH0gvVh6WgUfp zzixPLcp?$5ODE6?JBhoAGQY&1>Tc>@wNz^9tbA3$Ul@=sh~SJGg(if4N~~as~WF_bhjz zmD*_f2&R_CuC?ca%N*~C_sn<9cT6!)HQzPdGLX@K3GfDb%6SHEDW5ae9%CP8o?vca zY-a2TPhK#7z0RmG+K`tw5Vgv%+Hk{g9n*$IhLwip2DMRZ9Az14IbuCx9c&qF`Gmbw zn&E@tD*Rd+IJ|z^zuVPN;J>guvlz@~vju0t6lbb4#wBx=VFun4{9l63r^Ug-@CJO) zKo8Zy)!jATF%nm3u%jv1V6A*DA&E)%ZS`*U8azhNS??L|GGyxwaQAm7;<_>MIrZK; z?R2J4n&G+M?yQoQPMF|GtAH4Q$JE|6fyR1r z+29d*=o6qD{t)&q?00xxct-fI@F}QAVUNOAGuJS;F}5=HGxsrpif8#TzkJSp#_bx} zBXTcu2lE=Vz_DSHu>FxoB0FR9SE(p6F;T$5LfSBwXo2ON-UctjSNbtUMZpdw-^&@z1TuHgN_ z%NdIp7ecRwilGs+5?#b`_!&ImIqT`_>4>C*r=Ahsq2BZGrVWO6uhvrsb=q^!22`j! z#J$75)4tWT)kKF*oB^)v4$W3gvbM3dK>b^NRe4jXhiCI+-PgK}igk*>%uj*84%Z*4 zzh8g1J`v39Kb41-(e={$=G85#`IVf?=&IN%Q4OzVMD^HeLz%klaM7`%JNf_StMm2w zqCx?3a!U(X`H}eta`)z5$-S8ypD)h8T5zYJSz$`y*Mjc_V++RPBZ>-!6pbhvSlqAp zRpFb$iMiu*Z)e`k9G)>OBP^SlJwA6*Ztm~w-%*+3%sbz2e{b@m$&Xf9tzqRg=MKsn zoEMhE%$b-uDbtu~$Xu7RE@#2-g}*;#ead?98>cwt4OIG7wJOKW`9{ddu@B=S|6L{=3=lVOhhnR2iy_%B-ra!{Cz9ag;Y;lWei0}G!Wec2VpjG z9`P6P2i~Fg5T_6)6Q96Kl!z?h4z%{Pd*lb;@9y;-fd}-L>ziwUZ;0<6@fNY3)IjP0 zpW+U92;0zF(SQ=f&-5wM-@d2_zDSy1T{%bS_j^6GMHkU zTpNLb2=j8uk>q|sgMz-(KGS}YGs&k&CrK&LU6oNwsm-9Vssx+&2=Py13Mqxe!7RQL z{0HyA%()K=TB@(H?+Nh{F`#Yg0zK17#%actkV_%6q1XD1S-y+rqHRKsnAWZkau=`=XSUp)~m>JFsn-%to`JTCpwT0D>KbYTD*ikqW zebO+-Aja#U_dz%4SLm0*u7&l+$KMLS6<)!pV(h_;Xj}M>@Xzc|>>y|>nh2T;TJc-( z&vVXk-oO(u9_)pUtgWn>$fFHtY<_~jkk2dNjpvT#1{e?}964tgZx}C0kR)ITnZg9j zfoiyQ+$EePoV(mx+>631!dB7EqWhy4`#1J#?1;Egarffx#GQ}55DU?1v=ckAiO`?b zV#hL>KN(r|34+t`*17q1{%GW{i~)=4dF->;+tArCgjC@+@D*l9PKylU(zzc5X@VSN zQTQZ&2^;;GBw7|7;89icYx$%3qxknRBPtfjMH7&F&)|k|PlcZiZyJ^ocAa&bHHhDr zzXr*H=f$VRSD<@)6ZJA`8g^sLB}*iL1WTTr@$`7 z`5Xae*eNh`Ng|49CH8VWa4!bo+){!ZzE+YJlC9Fs(g2gUGkCuZLX)skP>lVNK+v4u z1YLU&e*^M3Hb!lTB8dD^D}}3t-S}Pkx!hbX;E0?Q>@!^v-iR(JkVzu{<38rf`2~D8 z&(CX&JyAqtWaM~w0|VR%F?eVNTsgN1&NGvs;ScCtHwrci3cwqopf2#v^JcJTu%|{& zj~ve)&HjgTjkBM-le>bq78x;b_~US1=>?yu6x#b4oLQWU`1o7!SFV<2!bJ#r9>%965U*&Xnj`hyJ-@FR3%b!Q3DB##$N5R8J3_Z7%X%%Ims*Aa^uBCH60Jg7xb9@3=RgA^8E!G(dfSIMjb!D0|IB%BVab7}ZD<`-s5xPoti3-*Qf zjkcb)hUTXFsS0W}HQ?j9fM+`cc65oOarT;k-v$5v3s$5wSASHVxd|lm?oHtO@*dSmaUd| z*4NeqTe2E0ZnkW+IN+HSxrOc{ z?xXH;uCcC(@NNz8494u~mRILCy6<4Fe-PUH1;%+sghYTT6k}`)|5gI#XN7RGcei!4 zormJ+rS*k%xqX>E-j(25;#ut3ec#h+V9#g`p^39n03hD0S&i@{Ld3Q%Q4fj-MQU42-&v# zUHe=xAUcT-l7nm|S`$nuroDz8hIIW`eU2_ecUOB~TZtNN7-e`2mPfAjw{?eYr|pOR zhuw-i=UHHjb@F%d*LbQtKd@&Q2c7>s$3jd^B@U%cgEZ|Xwng^Ec9Bc$+TdC5>EZ3+ z-R;@qdEy`c-CVnF_cMoK7Mn2b=Xkn zJr_K42(t(yNFzyGN!t*mU;zJpB=FUV#NY62d?9@z4IvFB4e}52)7%vIbnI)0PM>3y zd#zjSjq=9fS@(x`sW+U!Cd7iBGC5>M$TH?aP-}WHPkI(K2Y3REHg3cpI&bW=7m7(^*_9mWYo;TnZFCfe(OmI--W6nWP-AyjXXs?sCoL8egTi@?_cR zvYe945>=U|?0EIx)v0x-9@=Dx^zld~jqapw15-+vJ^37H?V z)3ft`=lz!E%5t0iZvOj1&bger-}S!-77QquET1BGm%2+aIw^C4hdT>-*cVDJmgLLx zu zoV0}4!|Xee;UM!%|F>sQC2-0q@0BRyqZzXc!9hEMAd-cb)LQ>WP1n@1EF8R zFUX@8(Z|B8WudyMm&s?zQDiB3Cv_V&jh;^b!u-q(Fo*(Nu5#$xFNB{D-yF6rj15M@ zdDazHCr(?=drmrM0ee0>;42I4cLG|`3EWBCRCWqm6QPOt8vZR@j_k8kW(t$R2xnYo zUStM%3jr-hD^6?9OV&%)m53`5OCpy=CU9l&m9*v^;U2(~H5MKeDld#5&Og9Az?;IE z!f6@VD)I~KGwTKW1>42*@G{{xToSb+$}O-9<|5}U52X>Rg!{oI4MtyfFydgu4CM3i z(DMQR6#fx2zVo~@yfe@tEf*{iUhplpeBB0Y!#7togwMcGl=Q9ZD$dn0-cmcjy& zAc`Ah6F3DU!7reQXrc&lxcCbgp3nHt`J$Ks1S5Pr*!{LcKX{LTCzgcBYT9uz)6 zMu1gfmb8?$mf2%$G3ls5(SxI}39bnKV?Sbl#p{@Yo~$o;2u;|{*#YL+j>z4SJ=s0j za`ZE|`FHu-p>cW^{XCj34Ux)4#UjXV!B6PNwnJ<6Au=uUBQ!Cs`K|cL!W8flvV{YL zgM?-1nZ^kxg8*R?c0;Ckpf3$@*3czEBXs~-YD>66=xpM{lfw^UPMm-_tQmgA9R6?2 z)8IIb+!Gm?i4JEEV~^vF;qe7xK{Q{$|H;l_bHLEuz}(2}#puQOi3|y6s4KK3{C54r z`-ktxUVa7Uy@wcw8GA!^hp=(YGa0|oHOyyx4*nE;o$@b5gI;7o(BhzI2A}aYEIaH> z_+R01@C;nwoaa2||HpTtR~rq!M+#hg$;h1O#pug8h~529{64x6UC5k}*&zTMg|Yx7 zZ3t6^4MABLCPuH&zM&j?1U(-4hYp&JRt6?XaY$iEbFfFOGz-l~CIHE!C4Yh^t3Rzj zEtnEa8A=?6$;K$65?|B5)GO3|SpPCSx6JMaG5d=pEufTl1BB(Ct zSkRFGktL`_aI;`fkS}NtGDd5cO3=5AgBE&o(8i!Cv?(+hDTY)GO~F0#Loy%3)qwYL z9%lV5y)C`7kpI%p*4O3&6L2qSH|Z|v0p@2d{R=z`J+nP?z`LpNZuM>VVO{BqKyOq4 zZay1OHxWrly6XGa$8oV-3ml6b!@$-$;s4t|iZBj#K9#$Jt*dQ~bS)v?`M6m6aEtZkCQMN2&w(-8|wklqcsu&GFn-pIm+Zth=W}0bA zSHD-wLC~sGRjYb|9-d@Rv@0zQmJwj5jtA*#MeXw1rivB{rK(4uLT!CSU7{{kQ}OH%{Chv}7Y1ksXlOc`?jCxrBMpZeHq>sa-BY`> zw!dbe<{#Td+fvt3R}AzZ=?(81Zi0Afh1S-urRrK5niwh}O6O}hngnRD7JvuUU{PB3 zgYG)PJl327Hqr%DM`*49Ts1y2JT*Kv+%u>!qo@Nn^N#hNH5q;NTgzL^V$(v?D&tyX zOX%D-*f!hh&`AdJc>>I}P-sy4;vQ}W&eICZDocg6!m4m8fPwRQPy5dJI=I`p&)6@3 z=2UNg;ePGz;p^%<0v5qd>%Z3iV9cJd9Jh>ij&V+MPjnZezm0G*oJ0@J6AllR5~`9Q zuyHTjuGqGsJ1xb1ME3c;Gu*S?@9l5xmG&yT*dzAb#a=#;Z8(rJn4+g@s8fPw1QpQ= zXsa+QX;11vT0~p`^5S@>YeKuSKji2NV*F7+?W_`l=4!;Roa+z7uO{tkS<8SqYR!7Oq*b1HKe za}U!T3U@@XCAbcJy?K-wn5eobDq0mSH@GBNNw22w!H%yLSe|iY33)J>F7JIWd;vY; z0qOy28Cgz__au8FvC!CJ+HP8i++tuM{NIqRaRV7gvwd@X`{CUkNE%G?5k15cn9)3e zk+2y3;&$yO%&M;FFPYAo23bZ}np+xKYO(NKZd`0k!cT=AOtzESlUh4`5c&pP15?3N ztOSR5N9A^GN?TWItM%1w>svv^|FnKf&6b*l74s|d$_mS9737M$756LR%3{m1i?WM; z7yK@;p^t1-+PL&X$@`N2g#!z#aw~Gn3#tkxS4^y6)koHMuI*I2L%u`4CVOr6)Ql+^ zhjWhP)D+YdunQv!W05ED{rl(di@z-W()4@N?+4*VY@OdRe`Vp4!UfQu&%hQqDp#DV z|E>GoHMe{2liWwS;d!jQXXqcFQmIcxM%UK;%UW^#r$GVvAx(;;w?E@L#X-fqQE>oAOyQ00SZ3*`^z%I64uHLS$&d$i8EVOFCqhniH z)&Qqtgkv~})oUFmq1!m+|H~f)CHoHe+%vt|UZCF{yTR?jO5Z&JtnKZ-ExtkSA?~s6 zac(ccO*j^OBKSP!YnL#yb_YA*QXdzbPEVsh4S617g60n)55Z!(h<+vHYDi&dVJPtQ zjDN7-x(S*p!eS#!xg}hQAQ}^%-hwB*7rX|r5f)&^HJv+^TaF@NzxEKBF|zR3@S2F~ zh*WkX_Brg`IxsphmIW^i9uPV-bPIbEdlodnfxYDu!2>~n&;L^JO0Xkpd(=VUAz>P{ z;Dfn?xaTl~(Svaj(48YDTj=K#1YW*}FBXc0OW}9=H|j=IV{rZJ!M5tj>%|Ke(uDKj zziS%TG_EGTD*i#-qqrS0TVq&Kj`R!k=Fj=B_&Sc7vw*vZy8!vb4Ne$ry z#Q4Ox)Hr!eVa(sL$76w~NNAkYB&laokEGPZ)I>UJebV}*g5;v)fFI7D;7)MG+2b1H zYecL-Y>ZSY-67fyf43>>oAjqNFDWO<*2K{ywpna5Z(~p6IgREuayGIxy4LtcV|F7} zqqyYwWPml32q}b=X^o~d>XF(fb#=lY32vDm$wNk2X?$h;>g1Klqm#!ZUrD%L_!KBrF&ABq$csr z;!_fm6IP_IOzq#aU(=n9b~U2M)8o5KyGe6n@?&a~tCAl#dETUVlO9dxAaiO#%G{Kb zsb^B-8^t#2ozf@e@5JMYj}q=D{7TGDyqt6~sZCMk$^}D z`$Z3m?hh8r@Tf6S`+2*0UEz)Bi~Q%=$cO$r@;G|)-7IlrG}5WRMCP&bS%talwiFT3;xH{k%NP+dD&$engP`7E!)~Ilr>8QSfLPWa8ruJY zD@diJ5Ff*L#&gEA$G6Kj2z_w}Qfm?!ynO+nBgcuSh@&YZV9BFXXCe!)G3bKdd_R3p z2oDLH2wMm`x7NMQw%yhaOt1;q-fo9Za~^dbHNa17@G8CceE<0hNV%l`)In4Vh_r#M zk|cK`wx+r69iTW)!spzy-?TTF8ceAuvW;w0yVNeWm*Wiw!*K|52mbDZM+}|7!^;KzVH1Bc7{;Bz;vFJ_ujrz^{)2dS{pxx_I>r?9yOR9Q; z98T>k*E>?I4a8PYU*<8GV6v_4zK)A{!s2Mb`{SlpI3gY=2%U$ zx~6ruYDcvN{IZMX|CHY^zgK>!{BU^`>LO}P<(SHL_`Ro8OsRNS^04G~@$KR{C9_K+ zD>xN*DsNTxD(_RSD6TIaS2(t?3B36q{k?cCayH7#m})efy~Q`fexTW$B+9o1W_L#xB8Z`NF|Nyhi+mg1J;VEuvm zcgXhbSl*>P5nn;q^6ur4HLRL$=&u#ZdL^NO)G)STOoJH}(E6WMp1{-dZ{=~-->N)K zzUDeAz=*pJ*8FkhapeF_Kim&?{a#Q?l8mXwA()hA>9h11C_4&KYKBl$o<3h+t*_Lt zHEb}n#E$-f@u6|AX|L&`<)US`eV1Jh4P=$A-1gA))TGm^^x1}7LlaA5%xma4G5v&s zx*sHdMYe3{p=0dv_Lh!jjv=m*u1lVCo`1b}yop|!cP#Gnm7e9EvCxhN^x_}hkn_z04R5)=QQV7uwJR-iR-TPlj&DF(Vs@hR)iq+G16aDh4$g^|IkrgI=LibgJ(J zu5d_wgd#$5SaC#=SRY?MsAfn_Ze>oTr@~t?wQ?HL2VPbdSC&>*S5;QMgO{}(@>4S! zel;{#HdltiM=Pk|*EB};LH(%wS$VDGYKf#kQegk>_}zdRU`Aec9;-02@K@2-qVnRp z;*IhZ^0V@*@}=^5@)&s{rkXL>1g{2tZmoP{@y6ne!i+*?fuev?L@in@UnKvx6 zFWFwUqwH+?+49|}4&@!o{pFtW7FBJkFvza?R`mZ?AZ+c($P5yW+p%Uje^E9Z^9H@YvpiCD|SO)*i$@L=jnlsB$AM4iDx& zK|O-%vCB;TZ8XKbcyr2G-|M^J#R9u~p~xe|IkG!?w0Bg~`BIz}yHDk_KZ8}ple z%)Z$FgfeK18DN-0yufGyeIJ!gXP;spXHQ1<(JuH115C|}?DK3slvKeH!4ZKzl8BOo zM~80(hd+(`fveysI3w62;7qJ!zvI2Ze3C*dDp*KZVT z6TE@bO(c+D<~K@6it-CL3bzX7f&xLTFkW~Gbw+STVCP!7I<^VivWuMl;F4|?t`}wr zbA^8+|7cRwJ|%oi_!jpA`Awr_AH|=Y5Y7`G;VO}r)E9AAO@ ziHbnq+ApDZLREZiya)Yh>$nbaq&RijVT5zf)F<^Zcf~&*fFuAWg}&37PA*8UOsq+y#uMYG#LkQzfeg)UvAaOFAjBfU zDJJmeixQ&}v(Y23O#CCUBDOl#DAi$-%9LJ+J{LVoJX)NFUHB^gO1=`keZVWa0M{fm zmL9tvX;nc8@u?8j2yO6Yh9c*|$kXv|uy3(9f+ROnFi(&z`XPFY@4+bX5bE_=M;5Y+*iTqbSm!a%{Q@oPKFn0tg44DE+@}Oi6354NbIBY2mlR?u7LX?-x#E5m}qrJK4vg+OAG2LP{1?yZPw5rNAYK}@G-wNT3pL>97(yIE-00un zPXwQKC8*!G@k|{6uftjFx7X2@(>8+vofs4yGy}YD4t69S_=*Ay?_K^K_^5$+VjlI! z6A}nR{UiL3Joh|>u2R=M&n=IEU?S`x?IksXri1{^cA+`Xd;$)MiM|QGQep{l7GW-7 zns<_Sv}e4hE%q*%NZdOL=H*83CMZ$vddGOidR97CIG!VW>AT@8+ysr_7wis2-vGmP zQS1 z8~*@x;`5owPE)$N*nCHU-fob#OhZT)Q>Ozlmdu#;V`U#3q- z-PGUGx6!vowLwRdtG{P>Xo$o!q6fa7ak`f%YMw3=)>&olAN~e;lp=*w*kE)w0n=1Fz?W^k!hSR2otqt=v zb2X#1W3(QPQ&Xj>)6CFLMasZjZG?ucApp>puPj!6Zb)y4*Tiecm`N(MHCQE^)in(j z4G9g&4R6pN{DD8Ki>i}qLBj%Y{L_`W%4}tD1EZk=pW8v%U74f!5tx)Jl9V!~P$^Yv z6-ot7864>Plo~Ylca{Gs??M08M%6*J3+(=_*f;M$zE6Wjt!brfsdUwO>ar9W3WwUM zo~56m=R*_uO7}u%(wH?0b-j8VIKEd^msL3pc@6T0qK0U3JzNTv_3IC zH2tOfTi06CLSxieG!nf??*wB$@cqfwW$7jxrW#(GUYZt}mzdWZ*BLMAF6p*{{~wrx zn~Zj&)FQO(1w+pPeP_TI^9Syoxz>455xjsx?6NhGzcmSYXG|~CJI_B4_xK4v%})jW z`I&zlaWruW@ekr5Xt?gX9=k5Vi+C2h?tcgu2>HZ9V!6M>FZM>o{OrnR_cx3o945k{7gWFy*oA~%d~3${HmJ~5uspVn_OZZy_|YvQuHtiQ~^ z%;#|adV%l9bM14j6SKx64M!VhfC(}RbFy^RM^%4SUsa)^NHL;rWF5DbT^r~di>r#N zVk%=Qk0PV%kIH40W2(kgQL89b?US(a&9+o~SwUk&(x|DV(on1D+Y*}=Xqg98idR6zXURArM_IUlD^?xf)<0qzC@lE+dIZr)TJwrcT|I3tVT5MiumK(|p zLHJEGz!-s^o1g&BQdlq{o@~eKZf3e3zj*9%q`pDv;U!BUG z$W7%nC|a|!1XPRE~)AC125ne3_TT+F!`N&-3IO7hj@ z&ncf%s*=jTQSs`oHRA*Vd8&@#5iKyd2qrG zNe)RC$1IH5m9#r)bfYniD5&?zZ<7xs9ZtHFbR(%Wu_DnQ=Z`aEj@eh%UsfMe7ZVg0 z6jvXki0LHjC_@Vx(+ac5g(-_tI;VC@H9;I?i?PJuxFm@Zi^Mr#6UU2_#Z!>Ixf2YH z$&#^>m(usrp>c!af*^#=Ovp|6j-=9_c=cnU>RTaRDSjCBD5`>A#-GNU!CNO-BT$RA z;!J6VbZYdpXmOM%D!{SGM_Hjq_$%s9BpED@YAI|kl%UW1!vDfQD)>|ID(ZFAUy`E| zij*uBOQI$Jz=QP~DvA-Dp`6>G1{VqPz}r*`Ea(Bt_%-}L@x3VHSMZIXz}yD6wg@_m z-pI@%@~QlH!q>tz;F??#o);1X6u~R7n-9RR+X9ozK(1;)hdCCSzascqbHZ}MFiK&i zfGxd`v!62wb&_)e?y3+Df#r`F$r#OOLQkb1pdY4-7(zxR=9Np3&!9jLSRGP_l#q&$ zAs~CM2kEy_Xi6v`%^{QEPg_h|NGqTeQ}o!QXCiazHzkAenf9GFEO>D6xu6R{Cgha7 z2jd{X9vFr!Ej#8N|4=T#;oXDM82kEsum`MkGkp+wFuAvA+_Y8)+|Hk>kqGMh9H(Y#Se(#s^ZA-5!d!@uQ0wRIbrnJ@jX z{dfGg{QnUi6HG)Mu`j%<2K=)RsV{_S4)PxA9%>!(s1(Eo;w$XaI>Ec&6Pe4ypu4ww zyq<2zkl*3k=bHlGIorqa@!bM98;YB7#Q(PQcJLPa3;j*u{ipjw{JnfVd`EmoL9aAm ziW=s7<$mEVa1=QX*$&u-TSr(SRkS2piIf^EF5o27>(&^t`C z%(9Hd-uNyu76w`dSuzd33^HiDHXAk@Mwo}2t1UIy@`YQpMwM}aVTobCVHb9bW=XOx~ce>z&!S@{*Hd3Zh>wr z`pi#?4~ocoPQ3=R*i~TidktPgtXXCrVj5<8r+=-NY2$&6reLaDrwQ!qOAMuk&nUf4 zr>j7xNkh`qRm}}eh>ogTh~Bp`I`4aUtq!eQ+d%oTZqWX;bAN6(haP=UyUS(FDQJ++YYD2XmU9_&Ru9xnq z?z!%{;kjY0d6jv#Wr0OtiZsd475dOm{)PSMY0RQ8TdrHiz^{YoJmV}pPd{V7e;%2Z z7tuNDz##$B*{}nglK#kn2xJTl#2iwN=cm{xLPZ;$@B!uHzbo)Mbq0fBh;fiHpc8m( zc>r(rReLSG;LG5jdggrY)HqadKK<<&?HJ+k*@?g|g*pCo9CM7r|5*?J_!;QP)X+UH zaW8c5g0`Pd4k8o%WPg>j+?ftdC(9G&x$M8@A4eENNX9I`t-qDOKXDL|My8ObW4@nC zNG3RZZlB6u?_W$@PRvA}cr4T`{fK>u!Qd?fm^jT)Hj<4r1fHOy{v-a|*r)FxZY3Tg z93v25M4RWIYgReq&k8DNTS#<^u@!}f;x ziiHYmjkzWP52j9)?JM6_eyDt2^`vTD^}1?PwYi#s!|xyPey3HYRocs}Wk$JKKC^g6 z@f!IW`8Le^-c-D(sKyNZUdfG;_Qjoxb;X)ueW{^zV8sAX_HI{%R|Hik%Z=svg8BV@%o%r->?(Oz{-*pw)upOdRZXiNU>3PSzDizMTw9z{l33EQv~y`zX=dq( z@{{G^5d3Wf)9_CHwfYNn|JDtv?O9u1Q(F@PcA%s-8fuUowdr-A>c)WH6Q+z*hATsq zE0yb%-;vw)O!ZRL0P30&=dAPf7whHqh4pt8_Y{fB1SO?`*03Hbl)zai!1ZlqZi}m5jE^k;St9H#y8sC=2rsfhNN2l%Q|tlfF6R^1 zOP2}^$GNVBuI)IcpWZBU>5zz`^fthX7*XLYj!SFnB?*0ks8QvazIbq)z^h{%rUgr-Kt5id@x4$Va_P zx`n?#hliyNc?4xFg-Rm=O>7Ms3TDnE`egcHWIjeCFUt{Z4c-&7KjdiWVN5k|gdPt) z78;LSifl#(L&A_Tj?xd&ALIEHc$UpX{={g;1O_df5MB=Mo0_3ytVRlUBqNfM4Btdx zw>KzaNW{GGIpG_^Hik_Kn-caa?0Hy4SS9}cJdA@phRYGxBeapq$Vr^3oNRU``yP0? zufku2uSaqIkEF8zYb)KlaNH9jKp+VOrjsubZ?MBA$fwB9%g)Mz(bKG#uaVylxffC#QV?=oc2m|Tuuq`CpXW~o zyN&@S*ahhosU^^et$Jd3W;K8y%G8(eim6((Y6gxzXJ}$*r_lF`H;O*8{<7cF zpVIkY);R)gfh#f1y%TaPwAY@+%Q_hkPkPSrVHd8hW zq|`C+!GwUzk{Hq{q-j{Aussp`F^6mxIT(HJ@33ECYn2<6148?R8o*CkB3~%~4gO zh5I!W)fe?d?t@euEbfbRguBQt*$oni|Carc z?Zn4lg_qgGa52_1HZrC%C&8n&3F#PHSgWCj+|Jm}IE^~VJc&Jni?x`uj6?LJ3wg_U zQs|d6nc2);wB59J)Hc*aWFLF!ZaR^ZcN2A%agH&FIgr^89H=L(r>u^w4utKNVWL@R z=P0Kr6{I?niKHR@p=3~+BNb*h^6VBe7BOn+we+7T8{JG_NMB07M!k%A2Zwyxd&;}Q zv(h7gw$ko%`@$(o$_VNR>Nv2u@+di!p_C-bI%G85^F8!!@~rnfa6fXl@HY3ZBdsOf z#jgCD=c`BW&^vI;u=ho6NA*X>;RtJzRfwm!KXl-aypOyC(1k8>EOzt&SLLexlD)U9 zuj>&i4xU_>#ckP#nNT-NHw(>1w;Aku`x56;P^PM!UmU59Tw9iHp>>J1t2GgBmSNVf zmX8*z*^X|k+`PiF!g3P%TryM~K+|Ka5pj`x|+!-kI8H508h&#%n`b4dTD|*ftq5}U9i>$sQaq>qI;r3>vTkOSVMz9^&wQ3 z0oYkBty@?}WFd72Uu7-!^(MVRU#cn7M1noHvvx=A4NUI{25&)4UJa#=QFj8=w?^7# z+D~=w>RMv|_qFPC)#{qHHI3>T)rP{@zqM``+RzGh9%@zHs=5WLg{u3i zJF1@O;TqJ{uQRChDw#S&eN=l$8xLJ$t~N&-To+t7qk2a5tIF4vbmWOffL%8deu?YY zB-di+cB}4s-TS&v=mw9Wj)OFM0mR^S$dX@FM^?M*s?k43XhJlZb-8uvwZCg`*W9V; zQ`@K3rn0C8sfVfmfyaAAeG6Tyx9+g&xau-`kAX-6D?(1HyVh5$tJBtvP>)c*!raAD zXRgcBwtyV5uBZ)n5jkRm3oCfL|=+6b|-yD{TBTE{u=)pgH1uE5ysI*hrw!SX=-iC zu>7&K#xu5&qk)6v;NowpCJEETX zo)Qrrq@Bp_O!1xfbtEMqzitMlh*}6XmzHuGv&W;pW4>T=5E+sIYBoKG-Vz+Azx2OI zS-D5QPQO9_P5VikNtsJ=d2QZ$?gs83u5YgP-VWXukE5?X+9mC$uBh0((K1yu`fWxubKt=5@?_mj5PyDVR2m(E~hjw1pmJGxUPW%jtEyL3q$+Y%LV3G#dKssz zvZA6-Dnv6HEzsimcjB>{Bm&Cq02S}H9)%stGzP@Ak1h! z0>9{Aq2Snp+_MS%vHWYio4nOvlAYyV;7$O`;4}9dHyOFJ6@>QyOm#Asiuf@ee-Hm4 zzaBPp1kexbZvFs%KmL9GL;f1{m%tPu(VQV@2My&C?jo+4XXQ=xo8;F<*#3W+O@ra( zj1t7cL)t+!%zvbRM!>ItngC6}+koeA9^U~GekYu%ivsQj+zYS=SOeZb6W10@m45KL zGyuh!ju^w(wE$F>%X)<^kzJ=|TI@@4d!6iW0;Kijsy&Y4G2$r7YNyyU> zGxAP9gulmJj}>_$@_6J>)Ru^?5x>HJg2H{X>W96Kcoi`V``wR`?;~4w`24IddfJn|KC z4(dhJLj;^4EDs*!FQKWSO_dFmAC+H~D#uX{}$&4r#ZI;_m$w4-~zm3slu;99$&<#bG+;u><8>ZEJWJE&p!n&pO;`bRCdQ=1raYJX{klv6!i#nS+0Ituzq`st~q$JWu z(p%C%?9Za1nL3Jkfeh7RUy%=BP47(PE>FjfSx6VsOQ|JP9Dm5Yv0qL?6{A9Y3g0hx znwtq9UWu*D7GRUuDlzLrY=?7-d$OC2+~<$3cdka*=@Py@yUXSp@0sZFxSejoqwvR( z<~VClvE$;h?Xc{z1X%(shpdOJCGby7aZYsR;$Ah$HNnMpv)qJEJK7#;kAmlQfpLMc z3fWcTt&^+`ZS`%#tRt-ipO%NS_W<;oX5_TCwzjffFrPQ8Om)Z;Ka7)mThlhvM$>E1 zKJUVVT4*jZ+l)@*4zPGHjOteF)TDp)KAhM(;wIG)$i8VYSr2Y@EYw>?^Vy&&C-oGO*5^B8mk4KS!2y( z%+t(M%!|#-%w7Pbo$!t}gBDeS{rh6|BK0-RbZl9oron+*{m#J-z_ zxlXk9=mZ043FymOL60Z}>t`G_iAtg~ajMeMRmkl;NjfqMiu-d2WJf`OE$jJkJ|=myNYP zvfZ=wb@X@KbX;@Hw$HQA0}uBUbfYGt;eXeEAVerLu+3_qkKZ0tk%JghQ+Vu zn)x$q%sSIN$jva<5B zUZ{t~kBV0oEiXc7YRRawVP(yb_mNQCrubg|qx{0$yxc#z8M&MDHs(Fgf00iuq7_Yt zmUtU7iap2`UQoHX@>}`Wa!MJs3_`6M2Y#|Kphz`U^xpQ~F{ud1=g1kP0DsjAgw>R_!xyI;E( zr@cd%*!R$;fOUQYXR-F`cIp+Hm706{d-}QP@H<*NSh1Y5oPy`+mEnyc&Gf?*Z);>D zW{x3trJW4D0+9>o#erqIWtzon_TgRD546Tp=4RH$Rv&UEg5bN(b`&@kLlk?GnybJ2A^o1+ixq_Fm=WDsq23 zuLyQpmhYeM4EZb>vOVfC6mgxeq^`#4Dv#QQ){{0De$&qQEcIw{Aaxw29mBQ01e1<7 zjCPC)S{c{`1@OW3q~+5K=|oTai1~n7j6BVa$n$hTTrv;Ug584M7rwTBpdpk{OR0Mq zdl~Vp`mDVFdDN=dN!$@!I*-mf!9B*MbD7+hc+S5;J>ov*HpI4&jy$i+@DMHMEeAcP zj7LFMJO_R3E8cUS3d@#($R?vgAL`;e_ypkkn=n1WQgUl67#ZF%z{9TqIYlnP}Q6UpCecTapRdHJ} z7@qXTvgWdb!F$n(b%axij_F`jFk8ly>E$N*_0Y?qi^7+O_l)csDTO}sE3}fyA;&`& zBezQkPT?qdl6;43m+ULD$c)${&ktT4?3UZ)R}?oCjbsfl!|fXw=^y3aG@xNXKhUVh zW9R=36V(J%8(Axv7v3qv5=+Cdw;nAUEAt0idJQzJ9=S^%Bd;gV3jP;-RGKX1fDg19 zJzfJr|>L19Ac`1Y8VIfW3IeKgGXmKw?01X)8#3#mEr;86*n~4a^m1 ziK9d@IEA!>izORW_%t}bPKrokGCc5|BrE;b_^XjH(cAw&f4kU>ndAUbvfp99_xz9i zSKL?JkKhxev;MFE8ss$NH|F1fk8F&1r1+WWnMf%N7smKS`BD97elj$}bU(76S3vQz zBI8*B&RZlenn&l5ITCgdXy6CggE&c?HJp{4KHw`Z<1B{O@j7Q5XDo-{)=lNis`yX^by<;cyeVO6kpu=lZtVt3G%oye}ij(i+EomY_HdxLX@6UIKe!YgnsU-I%?YQyAkJN>&uB8Qv);xTm`V|NB+<+*dVd#*dot3)E|N$(TSGfxM18}~csXXjPN z1-NoYI)>r=%|L%zisz;c?CUUaDMB4#m@M^jv~V`D0TAN#&XIN%g3T%0{ zWXCbbITXzM_GHU3%XIWk*~pSTWIk-3i}{QWbI!lE4BKjG+s~s1V!|VI+;+;AhYXE$ zXf_>sJEmR<`gVr)hG=7yu^zZHUi5^7Cg}reqH%&zX$UhAtmnmGE&3S+#_#B{W?-MY z#kAS<3x0;P`qTOfeWm`e@vw1$d7in8rMu-c?)-?3H)iQ`^kSW#Zjg4EHcF?|#lTCF zZOk;DLT!NBFj1SJB{Db|S|(B~t|Ogd5;Sl(G|kcDDK!xqwuY&xfZ~$K#F9Z@*;Us= z*A$+LbMTwDN5cCKY&Ngg-K=X@+lE++)K0IPQTHB{xWm|4|5g1~eS-$_B<4$T@RG}* zg0$7xYbL2CsP?H3sOM-yGjF$}{+=htYd8do$yZsq~qd%G}S`#R#QG(e+dnsB-P7&9Tw@MEyv`LWnk< zI)!?V{E)mD-UL2bNZvqQL+#7x&xi&mRmN7Zr$H}FWGuBnb|jW){=JcTEf0}}ghhl$ z1jR^WzQ%rw4UUz@&WfHBJrX+i8Bw#M&PAPyIvsgFG9+9Y-a0fPbUk$5gA{`mg^FTD zt|C)$MxG*{1U3GB_zAipzrqep^B`k`^sA>nkpvB zr^zevj5!u~GO!-b;lWb*{}>|10Am0E8j^9ytGo$4Hyybed=8gWPOqeIK+l?u{@^?{ zg}MXx17cr+2r~LW<{&1KFQw3^^bKpN8I<1?6Uj`P?Vau2>Du9P+T6BTrWvMjn1eD^ELETC zUeyDu2IFcxUez1FPpTe?yq85)@=9gp+=`hMYb!QZEUuVe(F(IeLy5NJPSL%hZiQV7 z0gEiySh%6^X7TOfM&*slH&<<}T3fxLx)GQ}$4X9>)aF;`|H}D^SR6;TC&!iZJnsd1 z$>;eC^5^A0!SB=ae&#L6UzX3zC*|MGd7KlR6__A%ube|-A! z>g(ID$G#rS*kNP(F+lz1SzHLk0l)5Z+RqC&A-@jc#g{CS~$EA)< z9fd#p;`_7jLBE54pUgawIW2!$ep1nhqMV|vA|fD-2zi=PJPE%li?0`5f?F=VXiD+K z;`OKpMURVi7j7%uUvRYG9DIV4^QPr}$o-JpE5B#Hs90E>TK21~e|hh6UooxZ0lY$n zTvP6${Db+y@bxV&T2vH-rJy^oy# z#rcK#8Q>JP$#0&YS5Q z&i}jc?~C+j>HX6Br9Dr5kvjS7#IL7P&!*l_yOXvyV?#zZSV}$9`=oFAv+<8IJtF;n z`knM|>EF}c8LkXlmOX28!KQ*kC5K85mL4w6EKM&ZRWd4Tk=5fdl1#DISZf?MNjj6( zbl!5&l5PJ7=I%B}9?q1VED09k?C2obNW^>o75K3+&M0RV?$-lc16=8pKa?+wuZ$!V z2|M^Vtk-J8|xY`Yr3-Orbg=nc-Wn_j^b6bzAIWp{x*`UaTu^f&Y~e1XnvfPOHvSwV(I$mB7bO;Fx1FrR>~B^8=CCC-{YEmdcL7HvCr zc3o;a)h587DybG#kFFk5Jrni3`bBkC&A*xjwTo+aR&TBTU6x+9wQxt_)x7h0lzdix zRY7UN-ok^0=Rhc9=dg2NkI#Jb@6ErotlwD^@ZKGsH!N>P?yTI#ISq5R=4{W|m$xVH zY(YvvZb5bd4R^pxIp=b2X5Y)EaM}=zTZR4Hho#Ksz5BsX;s^_8Skq58@-(YO8>qC#(iPweK)USzO zdqI1Foo!>chK6f3Wew##&X^BKPe_e@@xJfgUtS4WM6RONPzjG@;>azwUmB2Nmy??2!~< zpO7LxCw_ve5!HzJLcXvIo~s@B?fL$^0NzgacJ@m8YWg+uRkE1mPg)BvLN2YC)(R{p z3)s)WIE;8ffgZpe!0iKFcW+S-kpyan4#GCVFGznn$3Mf@Bbk5^$PA2@MaU|lpAJ$; z6d{UmMSVrAB1|43k4FaHD`d@dM`o=bW=Hoq_rXaE;LhMp;x*#8;P=35lL}VmJWQiM zFg`FIGVd|}vT|6F>`?YkJa3PJ0dw?!mCX9d`oSVHtN(H`IWiE)bZiUG4GUO}SS?xc z06n~Azh`H%GFcOt z`dm~D-`FPU4rv2ftV}AC$?~OnQb;iZQt-;}7akBg1UA7s!Fs_Ei4nmj`w2OlJG^TUXmk(<;*h zc$Wf^lXto9Qk|dHPrDv$zxma3s!vy(tLRhHvt|Y|ZD}@=?GxCM3y{k*%{mjwGRLh1 zH?+CADRTHC4e8o6ZF_A;ZIQM>dqj6cw@0^6XV+S^t#oa5XACC|_brbt2D`={>WFZt ztu>fM1zYM{>RHZ#584-+-%o~*hUSKrhBKHqCE>MjdY#@vQX$Dna*+1p^-F@MZ8PSy zpK#w~g9Ccea>4S>`p!z^`d}Pyi?!FYkHBlja`BLGy3sWp&YWONphXFd=^5R5-E(+5 z9vGh*c>oSQhF4>OWuE1=<&7oI9A_ppd!@KPcBpPwJ+^9m6{*HseW31Oozx&T2(Swn zjkD!4@=7w9>>){ga^GR^A+OG-^<5@kA=g1i{Tx@q8sBo?T;CGk34FC#auKW$I7f439H2o*_1xOPvY27*vStj}Mr8 ztPffh^hWwY+7EiQRbVbhBAww3?k`N_%Io|Lew}%pcvu9mnz5R&iZD~|hdih-P83JS zsAC+Y9f6`fi?)|>kkN%kPmIRulCh2Oh ziKhfjhHl>kH}D|o0_29A3OM<{4EplG;y`w=Ah--LhduIrn9zvjs$fmLXjZ~@mKmE2sjw<8Jxv=$SaxaKf`|;KJG@ywUCf- zWw<)LCY%>83U?`O%GJtsNB}#9{o!|ID`j)#s*v>|_mKg%K)zJI5^Um_u=uc+;Z4Hx zmBq^AAxA@MA}HGy?8t1#-hO0=ETh3sCrn_z%YX zWg~AZ?+|iivcMiVMLUc+e@|K-_;$U#{k`wpFWuq37~f%JZ(ZTs;@tAP>K6kBWErx+ zZu#Hwe=B|?mJ4MoWl*z{$xry44(g=!|=ugloR_+VHkf4%Fk49m+iV6{#UmZV9R%y{ zKJxc6!No1JRoHsiyV+k_UxQT4v97VNvybtN@@yh+A%}p8+!94-Xx`A@(bqvMSBVS; z64S#-rGKXjP>^SWKz57Xn9-PV2)mXA)P>aL&|o`}9X8xO)cxN1$@$Fw#4f>>lF(GN z1v`qEQSSgxx3#;ydpi7EeVqNBAMGFPiTD$(k@0uLamXR^NId;~|M{@=@tyOezy*8V z(*nXhK)RfM6?(ZJq#w|MlYODMPi*n+@OAPgcqd`MIo>hO@x$@mLFg{xP-;-# zTqcicCpKLiGuQ04cq|Couq*_NkjO8*ZoY0_3w|6Q%(#hQA9|7Vo`?y*woX+yP(4(g zq#L3u#XhgExtBQ=J1;rBoh#r!903mB7VS3e6wF~RX|HGrHpL6WOTz-=LJ($RF&~PD zo+Qz<8hqzps5_`fcqWvB5H|$(&wBP4do$!nry2jik?@~!AohQdl<8r9(^yqjRhQZ> zwYzKf)P&S1YWPr3N7aVau7Xx$Kd>h_+c`VCy1V>cLRX=q#F1mq!ZyNXCw9gd61!ARwR62=wPT5G zCAidcEIUlQOg8K~4e$x1pcd#C>Tl|9=%#3={m*x~LAzet3l8K}@RZUG41+-L_dh;# zKjM2p`gJI>NWMG1Ir`x}I|^Bf-Pm2&$(-YyW9VDd*vq}5y`=eQUfMY1#B`^3``>=f z1b^K*yyIU}-%+zDnMlrDOBqEOLwQMlPX3N-y$y6RVYEovC)Rt`KVCM^j5({_&*pa? zdw#-W^cKv8SHZ7>`$HSt8#M>I+T*fgvUtp*ZwK898W%VrkOv>qPw`K2yrhBTvHv6g z&jDY-b^qs|jG6E{{!0E7%wfB;JF};;r?U@(t0!gqvqOO^@E|mDAAJw@_;aasq-yL$ z+WJnx3)>XjlUJCFTw>i|4dndCX@RZ8XYLmygFfb7#C*0Mm=0vhN~)-Tp@#t23SYI|yHN^4}5 z^rdoW4BBqWK}tPR1nD+3pi$6=%D{wPM_)x}P+8PmQZ5PT5b{mv*-o%dvmT>&oR3{i zBvb=yJsUkU(3S6lmqHEI(mZ%;M?1zkDA??72AiDFU`CpvFsJ-!*o9qlXI*FA6U}1{ z+}mnlonPJTy18{l>U=e^``V1W+#6s&{!)KapMgszUHwDdN!?97K{ZZwp!#5STE(vl zdzrKBK-u21$K}t;dEl~auGvy^yY^P?RrsjSR-UQ+TAEt=0Q<9nzzK+Qrll>Ak3I()AR`-A1|Bc8Po)MZJmVP_^R{G|D zoB!R+xskK5U~$2tq6bCKieDCwL2g@mNmfZwNq))2lFP_22`Q~Et19bO*}d{=)zzx0 zsQ#7xE7yQ|Nk-Kxk1g+5*17Cf+10Yil`|_{HI|yasza(f(4BHBS(UHgoo$X;R8B=< zMTg2Zl^4Jw8U-&gw}f9(UR+u%Es>WrEoo44wB$rdTIr9{Bxv^(6^e=(<}o>^1Ol9LqnKFUV)-&&->h_dV}h-tGK*`Q6}Gt|`zKR2P*N z^(z}#mQs1HvQKrN>Yy5V&2H5;)h*ov-G7)U-6T9#W**pJy&;IJ>S zEw*jLzvJKuB6A13UxF|G#QV&vhNtGX>8>fonqn>X6nO;5fci@Pf}{-zHHkKYR*Afj zbY=$g0pkH<7-J~o7P3?OAR8(aM6q?y^2`L)_ZfC~0Rn#k7WICB(hDXbM?=F`^Huyh z{&_(Pd=DgHdr@1_CeaqrRM8aC0r3Ix;(#RqJ%hTVKl>C!j_r>pBqtPi zDC=;h-HCRM+ct08rd`W+`5g*6oa>mA%Z= z8+dQs@cOvm7nqH%?UDSxyn(zS)c&pI;}sJX7+i-eRjgE$%ZvZ#$(R*BJ$y1)DjmYx zho@p*lO3EHoE&^Sczf`c;6BoRQeqzc-tUv&H2x(1R^$jCg@$4mb2n3hoa0r2HIY&U1)ZaP8HPF@8(;l`wt!I{djyn@P@$0Uet{|_}djqx5z0m#E z`PNBfSQGl)ljPH6f3Mhk9~m`U?OW_$z$KrCdTe=ONim%LpH61JVV+^VVS_=5{mDr6 zNcGRUA9cizq#T;%e0b7->c7Ed_gX&~GniOBrOy~o8xxHkjVXrnh9X0WVUuZ{X)5+b z>!7I-nF35Cy;pY@%!EW;2i-B9G+0s-3dR@IE38-eJ^yF^@Z6!fIoWyH+cBw$$dAe=bh&*> zdX>y7om=WHbCg{}0#lJXS4~C^#ZPGTi?JE|)<|>p&J+ zRm4M*l_+;VQXCfkwcCM`!|f6t@@SKSnOR24|2*T>_8nNaQF_#4N`PKSCTl zQIDyQsA{jqd%$+UwhVe@j)jAa^0^=|bg@U8qs?s%t)O{GHyv{BckV=%Yd2~as+Ojs zJ%vAFs$hyhE7pm}fwP__`7IeG87Y}6nJ&o|{}Ts_f<>+Tn)?L_i}{AWc|3O74Rbe;r!-=^CEfQd0%;Vd5?I>yaT*DyobD3yr@>=t9czL{^_}mLnupjaMqQ`&2f6lKGmJ8Q{JM{+MY!QN? z|ANW>Rq|Hy4Ea9m;mhKJLH-duU%)Q$^WpjB3Uh=%;oYi(59)rC)-aMbZUQ zLer{{OXbbrg<1y66dS688NoAy$AydzX%gN%d`QH=h^=8e!&ZkZ3)wGAmf59x>BWGX z0gL=+`Bz~J-Wp8&(Sc(FJNUQrUxC@qQgkNyV4PRe%jrHUnRcFfo;rp$ipHh;(buEj zc*pp{*u&n+UW+r(2>xLHaqbyzEIX2YkNJ@Kj`4<(Nz0~uy~w`MPR#fY*bmwl z&?1p7mb!Uh;lH z-tcP6G7H}%GCejwF!uv1f?}eW_JcK3U0GQvt_rA%L~nhv=2Q)$tjg&{^dcN5OLR5* z8VDPV(bh=o4D`f>rd-oS(^b%^Pnyc03*3tCziVB$x`VX`p*TNQJ3=)=wGUNRTUvXh zHo5jLc;0i+IXA-eWjlP@jp18tVrXWVjRZa}G={yvwOguLrAfm*YBe}8cC*Di+%?*@ z9+_I3!Bpwx>E^iwO-~O;Plpel91^(Y4-9t<5`0ZX#sVV?ddC`^OHxdiOe^vI-M2on zGA%TVOXtxwQZ-azavpN#yYgH|+(+CaaDKezyX>RVn6&lKAIxPhV4L92U+h`tsj!vXLZIEGI+&ns z?r=22iElnU5qgu+6k!Ro7(iYBOEAsh`e_2DO#?W)iCsO#L-m{m6UN2zvbG{i@4o1v zXidPXfKEYOgSKJUFc!5D)0ol7&ddt<7mz8*mIPrg8z-tK>Lct6ZSY!t6!Z^UC|f8A z@PT#T1()`l= zmW!8*_eu6j^b!N;96xa9njx8q(n{14k-tBY zQgl+ZI<_v>6HSVl0e@pfNNtD{9ztU8k^=W%C+vx5z(ILm{!q?P&=f;uNwU{q5?lP8 z{%OJ=LLb}9o(e5kCeq%HBQx+Y@;*MfKf5Q`CxY9u({diHC<@r;=P@hW&D+geC|)T3 z7Wg$VKBz$uFOVBpBq|nR9VTecYsZ_+o5FjFnaFs-c!7~?;@*evX18~bH_8>^y5+v( z{^!l`zI4BFFG7CNROsSPJI^?mV4m=r^?@~(U_xV;caXK0wHUrUFW=2as2OhE0jCFd%jZ4 zrPlH{@||F&<%8Su272-1l;f0#iRjqe{ECts0wRL>q^Z-R`4uI>c4&TIA_@a^&4^xpB3yk3vbL-B4xhw$5#;TnJ* zZ9G_==isNhLAy$8%WThFjU=tsVBvITc4W5Yci?Z7td|T37!Xh^Qi+IMp9DCSz{skPpbK_FbIdYQjs4Yw)%!39X<6HEAv`~^A_*ym_%nC+Nmu*}c#F7TcUo(RYg z_y5NGYp7_rXt8La=#B8La1WAFUI<>Jler`)=hyI`aUOEc(k{``Jl{MfyWKv=Hrk=bI4pb$H5`;g#vYon>N_d>!a^7(WFYhDv6ZU`9KGa^I zHGFb>aHQG(*dAFPSoWC?nD!g@fsXsw*bcQ)w^E03nd(~AwW^C1mnzOyp03;q=hsHO zm)=_5T5=q@4vWL=s0R(fR6KLH)NQQuz#~}$jnrG?dt)biBK!_hoO2!X9I^IjB#ceP zm3bUH53Y5wVUb}ySQUR70A6)%_(>?z@%}7m17oqh_^Zi5`UK8JL-!|V`(q?2buO;)433D5J)KXuyZ-!^G zhv?}Jxc9kNcvg9OqW`-HoAVLzF>}V#yV47sje7TBr;0hJ13xbDs@%47GTbO4?Ma? zu6UQs6XFT*iG62r4||2zX18aL=c@aP`v|`z7t?KkMoZ45-gNKIOCL4 zDk*bmb7&7K4=K+uLovC`t~2;E9Fl60Nf6_UM9kMplJ(;8&+x6zQw z72p!M9@}2pP8*LI%XC$`L}(Rhda9ma-^{C?UA?t(XJuN=&l-o;uI-LlwgHp18d6q)=iJ=c z+}YUH1e?vywvEW)>FMo(@5An$?waWu20jnZEHclx&a(D*4R)bX^#1UDhmyRl?~rf5 zFOJ-R96<@Ad?bA$okd>}83g5>Q`}zB&U@Jcw z_nY4I-tO^=+{!;hS4#Gz{h`s?iO9D2RUBoNlH54{P=cN&fa3+3$ zyvj6g7WW)Zdq?rPX7MNTuXFEkx3V^~>H`?Y@-cl|o!gu&JJSx>k?n^4>i^g|SHRTC z!W}yqZsr4^H*JMCS?5)ITf;Y+fm%qJPl@EjaC-Q41HHrRH&ZYZEV=c3fQ|*Jg0FZ= zJQqX@B7jl$@ZZAEc1?IoI0L8UApt`J!u%ur<$i&FO|Z{bF)A2I?2+sxyrnz~$G~Yq zYegFhe}AE?#C4Q(gw%=IiFq2k{im3vEytf($XkrlXbw)R|9IQEJGt@DNp_|5p*Yah z-#5QBn+$5heB&bHGSf0sm@(8yKK0yHy>K<+lnRiOZEYD?JF- zNmd_IFETDMmSGdoAKI(4?z8URo?f0b&_*}vsbWNuvN56)Kk=1)LhV3Ks+-DEkY{u8%zroK7Q6HZ4S%};P8%P|L!p*BEEZ=<{+e<0_PvPk>jCF)D- zMeENT0!Qq7W)MwI+vMHgr6PB6B8r3e^HBF7_dEMXdr!KrmZ;*cnxzJ1K(p z=eFyiYprJm5+28S4#T&;7x%PMcQMpN$B``m*gXn7ulC3Ym9YF+Nnn>{GyXDYOeT}Z z=0J;)#ae@1U{mbm!f+qIgZXGSGUIQ;8+Q`5*|Nnl%reA+8@=hK;ksdxev1B;;klui zr4<;j|2aB(523kTcME1dJk>aLuPICoDB!H$}R`-95ZLy@!#% z^@H@Cbenvi{0y8jm)qwKw};x}@XYB2ZtPj>DQgitb;rR&im}H5ZKZaJe15*mc%75* zzU4vx&==J24D&zpGUHO?Sp8U}MK;w>F-$h(8SxM7I1#8|>H@SNxw@A-^6 z?K$gdf}eruE#EgI~1>Fl5ZPr7wr`51Zz2a1zX8dvIzaq2j(YcKAa)-*!9>vlmj&h`k7Ke zses_w#DWoiigl8;7=-$vm|uNxzjgbAueQ>$1p05Tomg&t#{6)!dlc?BZ#+Fb-95QD zzY^K2QhSiSy}7M9UsnXymsRsl|6U)0Q!8S9JY6YWDPw43XtU_E=@{=YuFG*?<=Uze6dMCy@y7C7fPm%5j`OUb2Vf~`R8`I?ZL;kvWA zzadNa8}9eSS&Ktq;ly-;(v#7hQ6HX!B3=Qn5xX(_FETt%;J;<|TD`Gw(WTSUX%q3h zE2QPo!s(IpB1R!27QE+qoOzt3>}BjKdKLXO_6=XaYAA<)^^X0veK+QgF`jTwdtXQ2 z9ry?@dM!}(|o-mfN*fr)_hey9>nkw$8i8Ha)8Cjp$k zDg0*Vj44L0fn!KFW*E;y>vO|;)w;*J-?|lFFAoWSXK2T1cfdA1%s#{(#2(1r$llD( zfUfQa`wj?IyPz1WLE6g-WR`7b)F;Q24I)lg?svKcr3%A70q(~a}sl(40zqvAWP5-M&oCs zpA!AeCCg>YPB0)|fJ8RYG|4mtH63*tMQ{i+ZU1byP12jce$75 z^Lanv{8!;Bb6t1dz?ABea|$#L$8m-Yat1hCV19PTe9MeaZ5oaK^M&b&iD3P;(zehp z)GXG_$C{D|s^3}huajQj<8_RrWapmd?Kf5BVZ&>Qb{ zp;vdhUH{Xw89XKr&@}E`l+Q!*68bQLbH1Lm5!pN|NFRNvzCPZrUMHR{iJp!g7Eb(h zFxc~;Wl8kw>X(SD^ez0&{5kA-?77J0?9T4ap2DBXe<6G({4V+-dH{|+9e%8y;=bZ> z(DYh3R?bdjL`?Eb@<58=M7)-}7+$01xL*-HlNvl1B{c-%ehohfv;%ew}JG_n^&h5@c&V|mN_TKiD*jdIS4{)SC$sX@%aPq$VWeoT;@vjWo?m_G1fK0 z^}+SU<#9M1#60LOc*t(dJWN)DwGlF?N8vrZ&au*Q+xNhi$@s(A4?TB<3|7H6L4rZ036prk6cz*=r zHR$B)?yK*K@i1L%S2~`~9=qFKU@fqQfg3YMpQLvisK%T2%l0%<2B{YLiWeB?83a#= z@Ex?k_xpl=X)8f)+!Kqx#dtv>wQaB|J*q*ge?+95JUJw?4NCuMsxj zeRu|rk#mK0ne`6(-}Ug;biwP@AO2N|AOQ5gQb8`Bmls)QSxxcm%>X@fC|HD_Xdh@@ zk-oK?wG*%HYt~}ufK!>jn1is-`-^L!7r7g`7IVN}q#mS8U}A^jJv5v)oOXhKg1(Qj zA1<9l#s|zdE+ebuiR+oG7QII??zzW3$2_A*qe&F}rNevg3GFHEF6$0!h+v3duw@{{oLC9)#G8_yQvksa&9_Ix2H1`VU5@$6ukQZ?7BUp?JnDdy#F7O)f5^pqTJm)s; z294!qdmlI+IEqm%y)C?dagytba}LzNj8W9lR0A}l-@MElglY_wwJwiHiEe2wD|14K6njd@vPeb&3Yaba^P4SCoFv{G6r2?(5Xct)ji9dp9*ILrNAE-MC+{-u;tIM5!rU)LXI3v(2DIQ@ zwuCLCOX!i{rq1w8#Wen(XDECQ3w?`xBcYkyf%-!JLM}n3=NZmPOxnoYYWS-~%m8L< z!L4wrzf$;Xak>CD^5$CxK2Vrd6RPh-qZwU0<#INF-=F(lj6N`-dE_C$K!0B6_7AR@Podk93mx?&JwRF+@MKFGVKrV_Hgu{eMx;thn6k!?&!gYERP60>psB8dJ;WFGUg0y%uN0{{yI9~UVPqh*0Buv zm6PpL>`W)!+0NY&d#oXzQ|KWqPBW$PQ+7Ce&=-0?=FXd@JzO^pG9oGFm0=2;2{~7;}qqBgI;@ZOa%&ceQ3Be!5p+a$O zafjkwv{2lkSaF8}1&SAnI}~YgcXxLugy?2{XLjcOZ+m}z4_dm}ot?S&obP-x)jJ6v zw#tEDLZd?$wR74(RQZkY%YDYG%U&SvoioO1qZr9ub9pwOqthQ``q@-gsjB?Xl-p#p zI;;XWffgE(jk&PZ=CG}z7QBT&{u|pH+hpft=k>^Ikw%2%OagNmX&s3Yc?NgjD-_6o zB!D@Mfm71@N-@9?twoZMe2LxF|T&JJ;LO z)x~u;<4MMM85J|~WqgsbBzNKax43K_E=aR)5$755Ihq6+VZ94GPyD> zqFY7}why+CCGX54+vO#uMW$?Wx!CCo&U(S-(}uyPNSOeq7@h$ z8XLL;&V1c})8B}BYXVG3FS?)=_%ZI$ck8#A6HW9@^!0T0a23xinK>VRTyZo&Zhw~l z*U+TU&zv7s)f(!9(B04y@ScjH3Ze1vDa+$-(ks+Gl%{2Axxoon8ml>FTNxvzQPM#A z;^USR7Li#UgR5LI{Y##v75XzWzTZo)rH$HVP4H4LWS`HT=AP<4;5+QQf-c~?|AxPi zw~%+dd%Sx-PB91khy9=M%p0#w))s|{%)A8MbrM*? z`@ow(vtX;>V!UtzFkw%S`w_zLI5*C@6Uj3cdWGLTzk5=$le4d(wfzq5@YsxBVY<%9 z*yP&m`i9!%PT&?QfUg22eZ_o7yvM!&`1kpRrs8{MN_FXhtFgy@)|2(d`VaaBZJic_ z-`Qqn@kiMQfy->`?d$Cf9Id-dJ5AXpw<(rh`W)*av)0Y}X8m>OZOE=#RSZq_*U~FV zAq%95@q^JsZ>X=wS31Cc(O7AwIKjSc;JDxWtNDe0(KI-YA-tEiNSma5V824+F~B#_ zC;TWcc`kY$ky|?r?$EF5ICUib*(~~{F8*Hr(cU3AAuaU|^7Y0o{YPKe6ZYhWaQ_Te z&?@lMGVp56nyd}r*9ocPYt0{+J-LbBY9|R@E)CHpJZQXU2KiS`#KT7r(*a*wZz}8 zgt@3WQOYG%4V4cS^L>qHbqmy1FJUK*_HFO=6Y>$CEz zc~wmh>m_l|84v3`Is6ff+QiYs@yVKO zodaG;6A&5_91O$6!z04Kgd2h>uQk>g-^rTz{#7*E%?*l$;CJ;ck4U#>&)c@{(M?Ft@O0@v|Z&qEz4dY?zKJ4bJu}Y%?!^8 z_vUl#$47FAJW%dHWuIwIHy0;6sv~>+0{cR{;N91;)wWGBO*iE-5)CJJ^#HYxT1@*= zvuGx5YH(VxGB{m1S9#a%jN2Jk(yx$yJt2Kl#)gdh*$=ZF_;nRz9x%x~!Tbg1(+A_d zQI6WB0A2_kRmxTi9aXWG}&NZdS~cn1uLT@e>m!B&0y*kYY`-Z6aHsUHur* zE2>-6A8{+fdEEbv0qY;NA#zu{g>KNNTc3>gYQFc=G$L6N;zVpBcm_GT#V_0ABEL!w!0LM(p&B+cLE=)?)cvE%=z3YG!ZAw zr_3e!E)+c5XUMbV&zAR=9ge+@O%Z=ZJhVNx9Z?P{2k@lL2K6ap`o^@zw$aw#-pAeo z-==$L&~I^uUPiakz*)=rGWvaVJUL>WVG}f^HmYc@WL_)%C5>R7-!0TFG|NBJ|F?G^ zY46XxMcJY9fcX{F3TZ2YtAgwBOG<~&?jQ&2I(vORG#>>B0?DE#ZlSl*6Qu;HpQW#* zrn8RocJz(tr!miCpxv{QV;;pkCavXgOdL%`6}&{&#IK6~fsDYt@jK(I$JUA6)va{li#Dq;-M+{Qt4-k$2PAKA@kPsmxJ!S$13Q(#Nz% zA9YCHD{rQj6W$>^l%0yl=CNfu(j7Oc6Rl>m`3OFGaY~GGnVJ4o^F{Lz^DvSKP-RXlXS^+8ULi3=7p9;mS)!G)`gb&mJeuqWRroE zM=`fV3vd|^i!<~!2bnonkiX>w8X?c7K3qZncM?rf^QhKQx1z2`9gRB9^k`z#K-M%? z4pu&AKIcQ*Lz`+2;g|=|VTX{ZvX1V9M7kd_ZCeXWI z3Z4r_1Y?3#ah5wl_G%Y66{RyPXU4egZU_JTqw(5UXW497WnTuH;FdiNUC;s+rYW|o z_Dl92_$&5_>KXNIT$#8rWDYh0-N}k_M_t0Fri`VWWel#0rLASHN9>2~vO{)=-a;ty z@_`MO36>7#19#|8=F4exM`HgTLwI8UNyhj%-8+#HWN+nl{Ab5mw0 zos||!yU9A6ekQ#U+7zK@SPZg$1&&7+`oOXHYn}cfp zLyfxM{;&NvYNp0|Q$0U9Ws}2`!wsYcWSQNUN1+4GG%}5^snJ@ZYg!yy5SjyKUpiEZ z+WNfq7xl(u^Eh)6W)mOr);Wk4y$0XwAkGqzZ&n0a>8%qrh zhIf)i&RKqMVQ(XJTpO8f-!QHjBczeiVdIEVki94pYz)gA>oD+uo8(&Mq96W~I&+{j zgxTs}W{gj5IjlLXd8qmG%K1Pji^?VF33BoKLU_$iKwa{hRflYyUpSNf_<0Ai1KA^( z_nmYfcVG5g@!VlGV5Xdhn&}cO>#DfY%!38lB)M*KzT|?*yU=_DKC7Q&(xcL2$^R?C zOz{Bg7M?ii{xm-%YP~+shRAK~CHDrWNH+e)(Yy#Vu1EL~rY8PD_t8;x##b>SJ1ToTTBpbE zXYN%0C%>#JYEgKRn^{8Vz6pJ5NlS6dM%yOaY4k~b?7i*t(EZ&o-8PB*pnj%-rY)Rn zEh1Y-u8&?5eIBj2;#4Cp!?8I?ANvW-xE%U z4=8x8!sqP=CZJ+&`Cj{|RSQ-Qz9x(JHhsaC;PznuK<_|PcXRju=$=}pw@x4A8sy3g zN99`RcIYtkrnlr_Orut*MsFz2$S$EDLo4ZXUMsH@q31KP?96HBqP4qagIz;GxjT&}n!{^I!%<={CJ_utiXzz^v`*qjW|s+`}ykzbXNl$dk}tl?zD$%rG?!`8LnO;zy+B4k!M zEFYGIhjR<5rPMs!EW9DOF}TyelN;X~@G{HNIOh|U97-j@Xb4pf{pjoLTi~7VeMmmH zxIa?i0hA4t3cL!w3uZ7=t*TYkwuZKcJ_g?g+c9$t;aTzpTDE`iG5=EjMt)@6Gpga_0Sj!I5%UT;7D&we4| z7SFVpR~6^^-N(688N8qeEQeQ?H9-&o#ST9K2r2k)bR=EW@3 z6PifgXh_z`SDkBWOP5;HU)w*4*-UXbxFy_0-BSWH0$;<^_##v+^d25s1W(g(JkM9) zV_pVa{gvyD>v7hjtTXPj?k;3Tp3%?iZ{!yotxn~taf$iH9PI{N>`cw2od!QzV_##( z+ARX(Icr0yp(JJtbHUJO*yq~ML>-H&NuslrbGbBg{WTnNR~oaJw%nthFJk_}{5yL{z~nVOho>ocH+kedvfu^m(0Az~JHf`iUD#aA zjB*YS&cQ}!t)o^9|I)PVwCvvK8z-cVPaB>-GW{do9zSLG$}Z_Cj)HT7=K>yI6Ede{ zwn=ZE-U4rrx8CR8mGllhm2OHs_O9=^cgAwBi@b{aQTL;kM=y(B75Qi61!ljiZGYM( za6cZg{%aj=A8-H0S;F~MRB5WLnNh;4@Q28zkprEBoHx<@30?R!d7|79A4I#=Y@Gp~ zyT-cGT2Zbde@&867E|If;0sgLiT~+mDuI0=?3PNwTNZV05?XxWr8Jgz{fBpnccN#! zr(AZ0?3?uR9lh)rH2WVSC<=czUHx_WoinH9lwIyu@pCO9Xdcs$_Dw!7@W;ikRRMIZ2r zYb*aKS233(LeXIvh)3it=DBTbZETku7aak6$S!bjp;0*=JP}+Fm>&>xfc?IGz8G(e z7n^9`_0Ww_ZK6Fj5J31txB$!GWi8Ve>CNR<@^yL?D_F!l zYVn%z<~L_=%(i6OG8?BhPF+fdY{^e0KK1+D|MUEe1sO${8;te+>MM(Gce8hkcO2hO zvhSVmN4OKc%stIB*{7~pu2{re>oGc5F?&9O)*AS|2jNK5Mf(lM#CLd$w zJlr-4Kikjt|3!QeagX%%HO1@E=h3 zs~zF%3t#%B!6m`FzWcru_OHHLUu`bF84Kiv@=j?RX$LQ*b;=s$rSe+AvOymk7#k4y z!v^0bUrp^httqF~MCn)Q6o}$pWsf2}Nsh-H$60B9%m@1B*Wg*J@W#KOUDQf*pZ3=W zQ4tl^Tf^_T#r#5WC`Ks1D2Js3{Hzz!OnU5JtiM=)vh}iEXYa~qD`;!TIrBLpHA47- z3T^t*@KU^UduS{0a}fLFT)z8SQY~q{w&A~AP=W7s<+C*b@2E#jp2wHhcMl%RNwn^L z=-VGlkEG7#u1px_n$M8y@E1I^E3h!K{F(l{a4;?z7Ys2go5Z~x4-PxZGTf4)xRgJb zt$k%HZ<}ZxZC%I==>dCpd+xa&madkiww1P?^eMO4i+18&To$jnugs-j0Dm@Lo^$o3{8T<>oHTxct3oWV+rS4ap64Nz zytUcx8SX||KV&t=+t225y7sttxXbt|_>u!31EMbYkB_LuYX|XA6h21R?3-Ls%3Z7S5 zDg}15qN9LpX_)d3tc6uCmjbwY>-p!oTBeh>(Wi8S2sd->! zMygfxN_tIGEz>{Nf2`G608PpuYTttNn<7i$xaF9z!8cV7R}K^Xfyeb*pWvkTqc0z4 z{9;_Bp1x$9r_bAOst>~FHSu?Jm8hqTwb$Gev2p&NbfLH8lI*W>e; zyt22syOp~z9BrtZo_m42fm7kL;dwk)_vOd(1$6Er-+Y99h<&Vef^{`>i(kM1z6k#> zT$4SIluJ`pOEpU)b0c#%>ZPm9Ogx5<*;7O53#FhU_?V*J6dt*U=r{J6_nDXB&D+h? zm8!Y0sfp55IVK(DT=-Tes1q50$(!hkcdgIboHYkO%3HpNzDBS&6FfORU*SQXjN{_( z@a&^8MmqKB_@_lFf20gb8Jx24(|nvDwNKa4dN*)4a!(FS2@HZuzsJAde=%?&0A<$5 z8<9Wa>)4X9wrFScUh7V)1gGI=xvSj4+STfg$c&ihT))uNy;5EKTm<*lm)jDIGP!Z ztQ);#UG}6ZOxTW@PMXeA(@Zc=HvfaJ}?~a38IYCZ1_S4$G5FlT0&BvrS<|RTi_Sy-=Pg@5ss7ZQgF)VcKol10GYC z^wJ=l<0Zl0$xBT4M-iAX3a`|IzWu%&ft-PB@WKb;OI{nUlJMLR*MFFQu>Y3(u6v-X zKkS^luIYIBCwUS)Q`yH#fH~d+C&~(^g>Tbet)>zEroC;B3~%-_iBnn6YLoIPX`-m;a^ z7&n05_ZqL*Gpv?sOZ9~Rgx}?M2{glP@=D%JpBty8UcS=!;J?d$oqfiA#$C}@$=4Ns zgbSQno6)lg|J%Fz9epCNIT3EuW8WR$_r7|*Bk1n_3akwrQje-F^(OTBH}yX`o4i_3 ztHC`dczzSftz5y}QSc>7^E^6?NTUpOztG5?XAfv;Z}p${q6HYxavZe9eEJdBq#wBv z4V4DUx8OeC@OKMp!qs?P9YKTpnA&;}`7>qI3Thqp$)Va1?KWqTxW6@O=_Y}OfyeHr zxXYDx_o7bf0@r&Tt1D|c+Wi`8E%j$|A+GX#{T7}c9<5K(&#+%qpocC=577i1qzkoS z`Ec3rKxwe_6cl zmcsU;_FmKt4=oQY)4??J;1?!5-KHv26v>bb;Y~MD{#EV=zVs9hX)#+d+dzChMv!H{ zQ`xNu3~sY+yDh-IchY*oTHO4lIoWt;bR`?3m$#SK=C-t)!1fiG2TfZrHl9p`jn8;P^vBE(h6u+1`|sIe+ANm zAA*y?fkuP1uf&&&DDG@Gn#e`qS3rTr0%@VNmpSATJgXM+>)Fg%2avmS7!`6K=U``v zh@ug0cuTp^D7zgQjzf-Pj**VxjuvRz-qVc>U)w^a;-+{^UMt(avn>Rx8-_o-iT+}Z zZMCf!JoFQIslRl-aBd-s@}~0!DJkDOO^nwb!=o;V{vih#Z;(`nICj>P=<+j2);tk9 z7doi!RX4)dZ^?X)&4h`=G`ORyDX-F zf+Djp$W9*nMR39D)E#}zz0Fwlgf|2>&<)HF)M7U5WHy)&X4ZP#-A_4AI2JQAw>hki z6?iT|2XI`qUA0YTdC1e8=bY^{a|X4DX&iG5EmaR{oh6Rn9ntnAy3M8b-i|>Izs+T< zjwby(GM0}8P6lMZ;k)9y=Ck{(zR~V6?yi}gGrv!*nOY~MR!UN8QfkAjMp<_Dx?;iN zL8vOh9D&3@-eBHfFYRZolvGL@hokK<+X!1PbO8=BD=m0z<;(fMoQLB7jqecMIeJ9o z;K(_VzejG5+!)ye?7fb?wmr8wr+Gej$8+_88pGLqBXl{`1zc~xv{#~VFg>?Ex4Q5R z8{`<^*pF9!Kl1={P5FEIq4C_9!7T5Hd`jLg|1I}2`WpGQJX!#L@EQ~>qr#)ZlVIj$ z%h~cqd4s%^J?8**OI~{CMQAs3>3Q_i;Zvx#+Jxug!L&}@sFq?0eT$eQ>Q~qwUpx*XPr3jZ#vW6Z+|Q>qj(*kkMTR%|UZ5 z&bzkca74lskN5g__Cd;ON8%o6>=0W$d~2rQfH|wy=%AdkKyO=cHBX zig_wQhT+5Tr}Am}sbpJfj{>L(U5{MT&{2Izd7tt*Ej2CH73=E7Y-ATxk&*C{SE4De zj90nfaflh#7ojggiCU7D0q-&xR>Lo(=TbA~VTJJ%njSGV!bO&OEk|`nd-7y|cg*2w za5}2ntD>Yx0xg(j?_vK5-QE?ez(GDsnNmJHH7=>w)b63qq4A-~q0{OKb+firJEk5} zOK@Kg^9}Rm^yWmhHq?8EbFEvTd!PvX*p`9Tf!w~FzLIbq?xDHg1;ctKXGL>xv1qR0 z0oXtH^?Uji>X_ryKRM8L6h|MlLjD6J$&kB)8J6PQDha2kZ>Vo*v463D1^IXD$mf+q zMsSukTU#IA5H7-u?pwNlH)nE}>~7iJsdGN4$?7!r?2mBje#3)$n`s9QGSQ|X=E3Gp z^jL}X#gW!{Ye{nvb3ODcCRyi{8V)OFnIv-5qVW!GKtH$#e$p;&o3;k;)?WC9)PzSU zw9H4*>vjSkJBc=LvN~N|gzw`w^yIsOJA%$|RQNaPHz^v0_*d3a)?LiES23%$sWzAc zW7Ie~Q7&lvpRJ3dyJNh4EU9Tr?9=EI8$>sa{wH#8WJ7BstKj^O*T!i*$*Jyb>0_x3 z=V~kYRP*s8zXwh?ooA^AJc-59QqcNg(jIh%vjQ^%X2RnSasLdK21yEe*$I5=nesfj zC%o2^)Z5Eki(DU5Kc&|BT>Eoj@+yrCIn#j$S5sWX3cB;TKWArU_jmp5%9)in>u=Xi zR~zz@%D`{iYuRrZWglrjVmoeoWqAgZ|69v1)}hv0Xi{E8Jd6l3Lmm*>J5qB>ks=$b z5T5Ce;mR4BIozF}-CgOW%(l$n30h&X0}@|C&ovhfPRa0>;d$%_0;jnQ7ucyeG$;A< zJJI(w_qFkD#&ds=I!ryo+}Rv9!zsIE2w&H2c)DFe^XtvZ&Ki(8G;<7^%RkAEdWBbi zXZSRNSMV!dkN5P)R9)BgxA<`47l(_7oy z#N7}Uw#icl_U@+OCic#0`Y61&GA&t__T)fE^2Ais%7MGMLm$=mYTIzTut_)T!hdoZ z>s#u=Q{;k`W;%5c4QwZPBq?MRErz4mj5?_<*|WcqJM@LKpmU02vLkHP%(9NY=5eN^r{Cd~W}FdGeCvQz_?C zI=b4r3ZeO#VoWg#!qpd^@vkt?=!19KF!C*?@*c%CE*?1%0@t}#+2hFd@?>Xb&nE>e#r@G;+*87{0zFg#c+0<% zm3tSJY=30As{3sZ=RcuvknPxY0zrNJqMmU1rZWP>> z9q^p;+Va57oyX4H-QFDi*%Qls%L?-vP|;rI0Q1?Ytd4Sfxu8}=`^7)pKbGD~WyW62 zUxIGsn16?V7rwMJ{HH+O?{TK|P`jx8a6)~FmM*6)x6N!<>@Tgatr?teFUgI2hGu3X z`s)qwX%#|Ld&3WxBMiqq`+fUDD!^f6Lzdvad8Ry9gfHd;`1%!ND#dh;>x2tutGLZE z8)FtkE{I%Z{}Ye@HMl*-TAHx0wT0K54sTB2`1k$y{I`9#eH}v`Lvi$*hv7GuvVLtn zYCdkBPV&`ACZ|84hbkd`DUD?&e#L*;-_F~?I~iWv`M~8sWFR82o4nI$sWVcCCJ#z( zozgC)Wmb!w2WyUGa_ze+?Mz)@umbhVIYp|-DA7NE{$9s zS>0a4z88){5AK&{px15j>-mXvmA1AHwyLmjqpVTZWA@|r!4U%^>YzUm^G?|$nFcaV zFD;j1qW%jN)k!L*4YD}fa)lGazk+kj_DuCWAmwa!2Wg|n&iT;yexquV0SMqG+GgvVJyrHE1quG7!#qvy~F z9B~|Qtahw-2pwV%e2??PhqE%Bu4$>w{LN}E0Z-fo-sq)c5a(bQu48qn+JCwWmy)Hd zhb5fkNOEkW4{i>}`!HH7p)o#VJ#VeRY<(O#3nS&>a$c~~SGJe7ht9`1CUtf$wJx(h zVXsoEg?%Ddp^6UGag_d{wo@vy7v0^4P+y3 zV*Xk`qJD(fr(W>P_Xh1b0Cp>U`16xq9|7OwSoGoO+wiH^nOB>CfM;JB9hmU%wc)WE zVx3XWE8D12MGoSZ^d={)r>#32+Z_FaP8+y|}fQwJ!R#ZLH?z7Us�&CNU-(U{g+uzsU&EJLT{20GEC@L@AdxeEy5%=S1?zw2w97zedT=Rz2AEG`VRRPh313` z8YPTm~T5hrdvc z>qns2_yeW!9O%V=Etq@W9vr+^#P23pgdT{ z!ZqATYoz6&XZTk>AY0(0T!614_`@5)Aeu5On#wFo^vWH{D8IpXQ5;{5K6ommh10`R z@O=`zy%Kmr%y-Rqy-j(WQjB#4H~d8EcMZ+^4X}~}=o{^RD|k|@Px0%%w`lH*ay7NhO_$|sawPNf6~-z<_?jK& zI~_zS*+Jg-5$%Y!hrZ?lT=a>-slmVDSM8&RY0KQN1bxqTu-OkgkLATZySPY*?8r_p#eMr~^QaDyR!^VX9AN zQZ==bTAF_59bDTAbZB!dvn{{c#@W(r8Ma2`B|cQ1Dr?~gJPW-HeM7GITYiqf8Lz_- z=*9U}2R%eZ@J!bPMa@Hd;$O4_70c-D!YRdDojniFsjJ7cavMzWYg<^wyquib&4 zy^1MPVdmn|p;MkSp4!< z%Y!8k1hXp!1pl?$6qw9{9+d#0RQ@T3>z4S^LEM*jvt1j>OUVo(b$# zFTAh+>rtO`Pj%3{=s&@Ie2=c+V)%S`8yt(F@RjR?>V|q~-L>8L8s(GnOLgf3n@Y{4 z#$c&KnZLD*Xb>KUGcG;$xrkRf6Zy~%hBo4t+>X+&|bu-HI`|46W=^My(Ce3fNQ*xGuQ@ zc>{M?W0>XkVOG<{(#diXy!&w7{0=9_+7n2>oJydP}CO7n0ue1 z`%s|3R6|33cQ{w#(KD6 zVwO3Vbv1M~G#3tvoi#N$CHUC?#BU}K*9zI=Zs=Ah1V(d=?jS~r;mW*{?wanH9-^xh z+EJbCz!mo8BmjI1W2L&iJ2SEA@(ei#m{dv}0YsrIs!+Dc6&R*okfVU+wE{`c?staF`nNxuBbDL9mcZG~(Dtpk}Lv_#of z#fkvIdJ=s}2UZhHQ*iLyFgiY1UvjR@Qf4W;nXO5(Ax*YUu?kM`>w`_Nq9o%!&CEw&))a|Gbk*^{Lpr!qXzO;t9uDL7op4rv~ z)(e(XmIvgDMw=pFXnZvFH1|MnMgk%8_{r!y(zMT7u5i9^4Exk-ZLRizXD0$J7e;&j zMetW2;HOa!H`2x0Qti5ag_&P>y(B0_F7B%?oE;~)&WY^t-=TmmN6$S59YQ1*&@d~O zHO%j?S#MhVQd5ZYX$#*)ekDH>z=>$4>Y<|;kG^Y&yg^xyr^qS!TeK68$-J2bZ?81C z*Jzkq!cR?j+Y5i&JK8<1EE6B`zKg0wRWDq%zXQ7hyKo;9GlNOIULKT%f`60BOs#>p zf%i5m*`Mqe9IA1_iLl|C1h-P7ln;GFK4M*V4VnL~1N$>QqM z4Lm3`G~`!(>IHbIllZv@;p{u4NG5o@q}F7Q3!J_N(@>$o($KgIJa;QRMsfa50%P4E zt(7*yU2vGJv{^a8@7IR+h5ij)!i#Gv_2_u&*J*fkUJ6|biN2w5uvl<1xdFpC=MD!C z1toMc6TwoOYb~^^^BX8(h-=?u)u=K?MH1eT!NX#wEwW4+31?NII)u#K)pNy6;G{gJnpn6vKUSFLWKH zi>&SdCp77b{%`nDxFH@@eaMB}g6n5pSAEx?S%0D)$jBP&`qkxfWs>{YI(r%Gb@r?5 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 z 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

oxVE^qx=y)Hxc+tBaNT#^ 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 diff --git a/ISLE/res/3ds/banner.png b/packaging/3ds/banner.png similarity index 100% rename from ISLE/res/3ds/banner.png rename to packaging/3ds/banner.png diff --git a/packaging/3ds/banner.wav b/packaging/3ds/banner.wav new file mode 100644 index 0000000000000000000000000000000000000000..8eeb79ba86a9b30b3b8cfada4a6041a581f2467d GIT binary patch literal 423408 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≫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

XOggjQ50+||8nn&Tx@z4l%`D9*)hHEZ+m} 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 literal 0 HcmV?d00001 diff --git a/ISLE/res/3ds/icon.png b/packaging/3ds/icon.png similarity index 100% rename from ISLE/res/3ds/icon.png rename to packaging/3ds/icon.png diff --git a/ISLE/res/3ds/logo.bcma.lz b/packaging/3ds/logo.bcma.lz similarity index 100% rename from ISLE/res/3ds/logo.bcma.lz rename to packaging/3ds/logo.bcma.lz diff --git a/ISLE/res/3ds/template.rsf b/packaging/3ds/template.rsf similarity index 100% rename from ISLE/res/3ds/template.rsf rename to packaging/3ds/template.rsf From 996900ae9a5e1d35b157c06bdf6d522dc2596ffe Mon Sep 17 00:00:00 2001 From: Christian Semmler Date: Mon, 14 Jul 2025 11:44:42 -0700 Subject: [PATCH 136/188] Pin Emscripten version in Dockerfile --- docker/emscripten/Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docker/emscripten/Dockerfile b/docker/emscripten/Dockerfile index 707b3c7a..40c17cce 100644 --- a/docker/emscripten/Dockerfile +++ b/docker/emscripten/Dockerfile @@ -1,4 +1,4 @@ -FROM emscripten/emsdk:latest AS builder +FROM emscripten/emsdk:4.0.10 AS builder ARG CMAKE_VERSION=3.29.3 From 19edb0340eac58bc9647f178f49f464d3310d434 Mon Sep 17 00:00:00 2001 From: Christian Semmler Date: Mon, 14 Jul 2025 11:51:11 -0700 Subject: [PATCH 137/188] Remove SDL mouse/touch event bug workaround (#606) * Remove SDL mouse/touch event bug workaround * Small refactor * Add include --- CMakeLists.txt | 3 +++ ISLE/isleapp.cpp | 69 ++++++++++++++++++++---------------------------- ISLE/isleapp.h | 1 + 3 files changed, 32 insertions(+), 41 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index b6e1f3aa..7a457968 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -526,6 +526,9 @@ if (ISLE_BUILD_APP) # Allow unconditional include of miniwin/miniwindevice.h target_link_libraries(isle PRIVATE miniwin-headers) + # Vector math + target_link_libraries(isle PRIVATE Vec::Vec) + # Link DSOUND and WINMM if (WIN32) target_link_libraries(isle PRIVATE winmm) diff --git a/ISLE/isleapp.cpp b/ISLE/isleapp.cpp index c3035de6..867f1744 100644 --- a/ISLE/isleapp.cpp +++ b/ISLE/isleapp.cpp @@ -37,8 +37,10 @@ #include "tgl/d3drm/impl.h" #include "viewmanager/viewmanager.h" +#include #include #include +#include #define SDL_MAIN_USE_CALLBACKS #include @@ -421,17 +423,6 @@ SDL_AppResult SDL_AppEvent(void* appstate, SDL_Event* event) return SDL_APP_CONTINUE; } - // [library:window] - // Remaining functionality to be implemented: - // WM_TIMER - use SDL_Timer functionality instead - -#ifdef __EMSCRIPTEN__ - // Workaround for the fact we are getting both mouse & touch events on mobile devices running Emscripten. - // On desktops, we are only getting mouse events, but a touch device (pen_input) may also be present... - // See: https://github.com/libsdl-org/SDL/issues/13161 - static bool detectedTouchEvents = false; -#endif - switch (event->type) { case SDL_EVENT_WINDOW_PIXEL_SIZE_CHANGED: case SDL_EVENT_MOUSE_MOTION: @@ -616,11 +607,7 @@ SDL_AppResult SDL_AppEvent(void* appstate, SDL_Event* event) g_mouseWarped = FALSE; break; } -#ifdef __EMSCRIPTEN__ - if (detectedTouchEvents) { - break; - } -#endif + g_mousemoved = TRUE; if (InputManager()) { @@ -643,9 +630,6 @@ SDL_AppResult SDL_AppEvent(void* appstate, SDL_Event* event) } break; case SDL_EVENT_FINGER_MOTION: { -#ifdef __EMSCRIPTEN__ - detectedTouchEvents = true; -#endif g_mousemoved = TRUE; float x = SDL_clamp(event->tfinger.x, 0, 1) * g_targetWidth; @@ -671,11 +655,6 @@ SDL_AppResult SDL_AppEvent(void* appstate, SDL_Event* event) break; } case SDL_EVENT_MOUSE_BUTTON_DOWN: -#ifdef __EMSCRIPTEN__ - if (detectedTouchEvents) { - break; - } -#endif g_mousedown = TRUE; if (InputManager()) { @@ -689,9 +668,6 @@ SDL_AppResult SDL_AppEvent(void* appstate, SDL_Event* event) } break; case SDL_EVENT_FINGER_DOWN: { -#ifdef __EMSCRIPTEN__ - detectedTouchEvents = true; -#endif g_mousedown = TRUE; float x = SDL_clamp(event->tfinger.x, 0, 1) * g_targetWidth; @@ -713,17 +689,6 @@ SDL_AppResult SDL_AppEvent(void* appstate, SDL_Event* event) break; } case SDL_EVENT_MOUSE_BUTTON_UP: -#ifdef __EMSCRIPTEN__ - if (detectedTouchEvents) { - // Abusing the fact (bug?) that we are always getting mouse events on Emscripten. - // This functionality should be enabled in a more general way with touch events, - // but SDL touch event's don't have a "double tap" indicator right now. - if (event->button.clicks == 2) { - InputManager()->QueueEvent(c_notificationKeyPress, SDLK_SPACE, 0, 0, SDLK_SPACE); - } - break; - } -#endif g_mousedown = FALSE; if (InputManager()) { @@ -737,14 +702,13 @@ SDL_AppResult SDL_AppEvent(void* appstate, SDL_Event* event) } break; case SDL_EVENT_FINGER_UP: { -#ifdef __EMSCRIPTEN__ - detectedTouchEvents = true; -#endif g_mousedown = FALSE; float x = SDL_clamp(event->tfinger.x, 0, 1) * g_targetWidth; float y = SDL_clamp(event->tfinger.y, 0, 1) * g_targetHeight; + g_isle->DetectDoubleTap(event->tfinger); + if (InputManager()) { InputManager()->HandleTouchEvent(event, g_isle->GetTouchScheme()); InputManager()->QueueEvent(c_notificationButtonUp, 0, x, y, 0); @@ -1475,3 +1439,26 @@ void IsleApp::MoveVirtualMouseViaJoystick() } } } + +void IsleApp::DetectDoubleTap(const SDL_TouchFingerEvent& p_event) +{ + typedef std::pair> LastTap; + + const MxU32 doubleTapMs = 500; + const float doubleTapDist = 0.001; + static LastTap lastTap = {0, {0, 0}}; + + LastTap currentTap = {p_event.timestamp, {p_event.x, p_event.y}}; + if (SDL_NS_TO_MS(currentTap.first - lastTap.first) < doubleTapMs && + DISTSQRD2(currentTap.second, lastTap.second) < doubleTapDist) { + + if (InputManager()) { + InputManager()->QueueEvent(c_notificationKeyPress, SDLK_SPACE, 0, 0, SDLK_SPACE); + } + + lastTap = {0, {0, 0}}; + } + else { + lastTap = currentTap; + } +} diff --git a/ISLE/isleapp.h b/ISLE/isleapp.h index 180c2252..4bec7dd6 100644 --- a/ISLE/isleapp.h +++ b/ISLE/isleapp.h @@ -65,6 +65,7 @@ class IsleApp { MxResult VerifyFilesystem(); void DetectGameVersion(); void MoveVirtualMouseViaJoystick(); + void DetectDoubleTap(const SDL_TouchFingerEvent& p_event); private: char* m_hdPath; // 0x00 From 3b79b4c83477ca6d133a945cbf33adb320329f30 Mon Sep 17 00:00:00 2001 From: Fabian Neundorf Date: Mon, 14 Jul 2025 22:18:21 +0200 Subject: [PATCH 138/188] Use enum values for `LegoPathStructNotificationParam.GetTrigger()` (#1625) --- LEGO1/lego/legoomni/src/race/carrace.cpp | 2 +- LEGO1/lego/legoomni/src/race/jetskirace.cpp | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/LEGO1/lego/legoomni/src/race/carrace.cpp b/LEGO1/lego/legoomni/src/race/carrace.cpp index 7a9cfef2..18ea1c89 100644 --- a/LEGO1/lego/legoomni/src/race/carrace.cpp +++ b/LEGO1/lego/legoomni/src/race/carrace.cpp @@ -179,7 +179,7 @@ MxLong CarRace::HandlePathStruct(LegoPathStructNotificationParam& p_param) { MxLong result = 0; - if (p_param.GetTrigger() == 68) { + if (p_param.GetTrigger() == LegoPathStruct::c_d) { MxEntity* sender = (MxEntity*) p_param.GetSender(); MxS32 paramData = p_param.GetData(); diff --git a/LEGO1/lego/legoomni/src/race/jetskirace.cpp b/LEGO1/lego/legoomni/src/race/jetskirace.cpp index 71746cd0..2893868f 100644 --- a/LEGO1/lego/legoomni/src/race/jetskirace.cpp +++ b/LEGO1/lego/legoomni/src/race/jetskirace.cpp @@ -158,7 +158,7 @@ MxLong JetskiRace::HandlePathStruct(LegoPathStructNotificationParam& p_param) MxLong result = 0; MxEntity* sender = (MxEntity*) p_param.GetSender(); - if (p_param.GetTrigger() == 68) { + if (p_param.GetTrigger() == LegoPathStruct::c_d) { MxS32 paramData = p_param.GetData(); switch (sender->GetEntityId()) { From 9c9baf3c3e81ef779cbb341809ff37053e0c6168 Mon Sep 17 00:00:00 2001 From: MS Date: Tue, 15 Jul 2025 10:43:40 -0400 Subject: [PATCH 139/188] Beta matching in `mxdirectxinfo.cpp` (#1626) * Use goto * Fix constructors * Reorder error strings * Compat mode todo * Rename p_context to p_d * Use count variable * Fix typo --- LEGO1/mxdirectx/mxdirectxinfo.cpp | 343 +++++++++++++++--------------- LEGO1/mxdirectx/mxdirectxinfo.h | 12 +- 2 files changed, 182 insertions(+), 173 deletions(-) diff --git a/LEGO1/mxdirectx/mxdirectxinfo.cpp b/LEGO1/mxdirectx/mxdirectxinfo.cpp index 3070eb08..ac3fce38 100644 --- a/LEGO1/mxdirectx/mxdirectxinfo.cpp +++ b/LEGO1/mxdirectx/mxdirectxinfo.cpp @@ -29,13 +29,12 @@ MxAssignedDevice::~MxAssignedDevice() } // FUNCTION: BETA10 0x1011d7f0 -MxDriver::MxDriver(LPGUID p_guid) +MxDriver::MxDriver() { m_guid = NULL; m_driverDesc = NULL; m_driverName = NULL; memset(&m_ddCaps, 0, sizeof(m_ddCaps)); - // TODO: ret vs ret 4 } // FUNCTION: CONFIG 0x00401180 @@ -98,6 +97,12 @@ void MxDriver::Init(LPGUID p_guid, LPCSTR p_driverDesc, LPCSTR p_driverName) } } +// FUNCTION: BETA10 0x1011dba4 +Direct3DDeviceInfo::Direct3DDeviceInfo() +{ + memset(this, 0, sizeof(*this)); +} + // FUNCTION: CONFIG 0x401420 // FUNCTION: LEGO1 0x1009bd20 // FUNCTION: BETA10 0x1011dbd0 @@ -213,36 +218,37 @@ BOOL MxDeviceEnumerate::EnumDirectDrawCallback(LPGUID p_guid, LPSTR p_driverDesc if (result != DD_OK) { BuildErrorString("DirectDraw Create failed: %s\n", EnumerateErrorToString(result)); - } - else { - lpDD->EnumDisplayModes(0, NULL, this, DisplayModesEnumerateCallback); - newDevice.m_ddCaps.dwSize = sizeof(newDevice.m_ddCaps); - result = lpDD->GetCaps(&newDevice.m_ddCaps, NULL); - - if (result != DD_OK) { - BuildErrorString("GetCaps failed: %s\n", EnumerateErrorToString(result)); - } - else { - result = lpDD->QueryInterface(IID_IDirect3D2, (LPVOID*) &lpDirect3d2); - - if (result != DD_OK) { - BuildErrorString("D3D creation failed: %s\n", EnumerateErrorToString(result)); - } - else { - result = lpDirect3d2->EnumDevices(DevicesEnumerateCallback, this); - - if (result != DD_OK) { - BuildErrorString("D3D enum devices failed: %s\n", EnumerateErrorToString(result)); - } - else { - if (!newDevice.m_devices.size()) { - m_ddInfo.pop_back(); - } - } - } - } + goto done; } + lpDD->EnumDisplayModes(0, NULL, this, DisplayModesEnumerateCallback); + newDevice.m_ddCaps.dwSize = sizeof(newDevice.m_ddCaps); + result = lpDD->GetCaps(&newDevice.m_ddCaps, NULL); + + if (result != DD_OK) { + BuildErrorString("GetCaps failed: %s\n", EnumerateErrorToString(result)); + goto done; + } + + result = lpDD->QueryInterface(IID_IDirect3D2, (LPVOID*) &lpDirect3d2); + + if (result != DD_OK) { + BuildErrorString("D3D creation failed: %s\n", EnumerateErrorToString(result)); + goto done; + } + + result = lpDirect3d2->EnumDevices(DevicesEnumerateCallback, this); + + if (result != DD_OK) { + BuildErrorString("D3D enum devices failed: %s\n", EnumerateErrorToString(result)); + goto done; + } + + if (!newDevice.m_devices.size()) { + m_ddInfo.pop_back(); + } + +done: if (lpDirect3d2) { lpDirect3d2->Release(); } @@ -263,7 +269,7 @@ void MxDeviceEnumerate::BuildErrorString(const char* p_format, ...) char buf[512]; va_start(args, p_format); - vsprintf(buf, p_format, args); + int count = vsprintf(buf, p_format, args); va_end(args); OutputDebugString(buf); @@ -272,13 +278,10 @@ void MxDeviceEnumerate::BuildErrorString(const char* p_format, ...) // FUNCTION: CONFIG 0x00401bf0 // FUNCTION: LEGO1 0x1009c4f0 // FUNCTION: BETA10 0x1011e1dd -HRESULT CALLBACK MxDeviceEnumerate::DisplayModesEnumerateCallback(LPDDSURFACEDESC p_ddsd, LPVOID p_context) +HRESULT CALLBACK MxDeviceEnumerate::DisplayModesEnumerateCallback(LPDDSURFACEDESC p_ddsd, LPVOID p_d) { - if (p_context == NULL) { - assert(0); - } - - return ((MxDeviceEnumerate*) p_context)->EnumDisplayModesCallback(p_ddsd); + assert(p_d); + return ((MxDeviceEnumerate*) p_d)->EnumDisplayModesCallback(p_ddsd); } // FUNCTION: CONFIG 0x00401c10 @@ -290,15 +293,14 @@ HRESULT CALLBACK MxDeviceEnumerate::DevicesEnumerateCallback( LPSTR p_deviceName, LPD3DDEVICEDESC p_HWDesc, LPD3DDEVICEDESC p_HELDesc, - LPVOID p_context + LPVOID p_d ) { - if (p_context == NULL) { + if (p_d == NULL) { assert(0); } - return ((MxDeviceEnumerate*) p_context) - ->EnumDevicesCallback(p_guid, p_deviceDesc, p_deviceName, p_HWDesc, p_HELDesc); + return ((MxDeviceEnumerate*) p_d)->EnumDevicesCallback(p_guid, p_deviceDesc, p_deviceName, p_HWDesc, p_HELDesc); } // FUNCTION: CONFIG 0x00401c40 @@ -309,9 +311,15 @@ HRESULT MxDeviceEnumerate::EnumDisplayModesCallback(LPDDSURFACEDESC p_ddsd) assert(m_ddInfo.size() > 0); assert(p_ddsd); - // TODO: compat_mode? +#ifdef COMPAT_MODE MxDisplayMode displayMode(p_ddsd->dwWidth, p_ddsd->dwHeight, p_ddsd->ddpfPixelFormat.dwRGBBitCount); m_ddInfo.back().m_displayModes.push_back(displayMode); +#else + m_ddInfo.back().m_displayModes.push_back( + MxDisplayMode(p_ddsd->dwWidth, p_ddsd->dwHeight, p_ddsd->ddpfPixelFormat.dwRGBBitCount) + ); +#endif + return DDENUMRET_OK; } @@ -355,13 +363,10 @@ int MxDeviceEnumerate::DoEnumerate() // FUNCTION: LEGO1 0x1009c710 // FUNCTION: BETA10 0x1011e476 BOOL CALLBACK -MxDeviceEnumerate::DirectDrawEnumerateCallback(LPGUID p_guid, LPSTR p_driverDesc, LPSTR p_driverName, LPVOID p_context) +MxDeviceEnumerate::DirectDrawEnumerateCallback(LPGUID p_guid, LPSTR p_driverDesc, LPSTR p_driverName, LPVOID p_d) { - if (p_context == NULL) { - assert(0); - } - - return ((MxDeviceEnumerate*) p_context)->EnumDirectDrawCallback(p_guid, p_driverDesc, p_driverName); + assert(p_d); + return ((MxDeviceEnumerate*) p_d)->EnumDirectDrawCallback(p_guid, p_driverDesc, p_driverName); } // FUNCTION: CONFIG 0x00401e30 @@ -372,79 +377,123 @@ const char* MxDeviceEnumerate::EnumerateErrorToString(HRESULT p_error) switch (p_error) { case DD_OK: return "No error."; - case DDERR_GENERIC: - return "Generic failure."; - case DDERR_UNSUPPORTED: - return "Action not supported."; - case DDERR_INVALIDPARAMS: - return "One or more of the parameters passed to the function are incorrect."; - case DDERR_OUTOFMEMORY: - return "DirectDraw does not have enough memory to perform the operation."; - case DDERR_CANNOTATTACHSURFACE: - return "This surface can not be attached to the requested surface."; case DDERR_ALREADYINITIALIZED: return "This object is already initialized."; - case DDERR_CURRENTLYNOTAVAIL: - return "Support is currently not available."; + case DDERR_BLTFASTCANTCLIP: + return "Return if a clipper object is attached to the source surface passed into a BltFast call."; + case DDERR_CANNOTATTACHSURFACE: + return "This surface can not be attached to the requested surface."; case DDERR_CANNOTDETACHSURFACE: return "This surface can not be detached from the requested surface."; - case DDERR_HEIGHTALIGN: - return "Height of rectangle provided is not a multiple of reqd alignment."; + case DDERR_CANTCREATEDC: + return "Windows can not create any more DCs."; + case DDERR_CANTDUPLICATE: + return "Can't duplicate primary & 3D surfaces, or surfaces that are implicitly created."; + case DDERR_CLIPPERISUSINGHWND: + return "An attempt was made to set a cliplist for a clipper object that is already monitoring an hwnd."; + case DDERR_COLORKEYNOTSET: + return "No src color key specified for this operation."; + case DDERR_CURRENTLYNOTAVAIL: + return "Support is currently not available."; + case DDERR_DIRECTDRAWALREADYCREATED: + return "A DirectDraw object representing this driver has already been created for this process."; case DDERR_EXCEPTION: return "An exception was encountered while performing the requested operation."; - case DDERR_INVALIDCAPS: - return "One or more of the caps bits passed to the callback are incorrect."; + case DDERR_EXCLUSIVEMODEALREADYSET: + return "An attempt was made to set the cooperative level when it was already set to exclusive."; + case DDERR_GENERIC: + return "Generic failure."; + case DDERR_HEIGHTALIGN: + return "Height of rectangle provided is not a multiple of reqd alignment."; + case DDERR_HWNDALREADYSET: + return "The CooperativeLevel HWND has already been set. It can not be reset while the process has surfaces or " + "palettes created."; + case DDERR_HWNDSUBCLASSED: + return "HWND used by DirectDraw CooperativeLevel has been subclassed, this prevents DirectDraw from restoring " + "state."; + case DDERR_IMPLICITLYCREATED: + return "This surface can not be restored because it is an implicitly created surface."; case DDERR_INCOMPATIBLEPRIMARY: return "Unable to match primary surface creation request with existing primary surface."; - case DDERR_INVALIDMODE: - return "DirectDraw does not support the requested mode."; + case DDERR_INVALIDCAPS: + return "One or more of the caps bits passed to the callback are incorrect."; case DDERR_INVALIDCLIPLIST: return "DirectDraw does not support the provided cliplist."; - case DDERR_INVALIDPIXELFORMAT: - return "The pixel format was invalid as specified."; + case DDERR_INVALIDDIRECTDRAWGUID: + return "The GUID passed to DirectDrawCreate is not a valid DirectDraw driver identifier."; + case DDERR_INVALIDMODE: + return "DirectDraw does not support the requested mode."; case DDERR_INVALIDOBJECT: return "DirectDraw received a pointer that was an invalid DIRECTDRAW object."; - case DDERR_LOCKEDSURFACES: - return "Operation could not be carried out because one or more surfaces are locked."; + case DDERR_INVALIDPARAMS: + return "One or more of the parameters passed to the function are incorrect."; + case DDERR_INVALIDPIXELFORMAT: + return "The pixel format was invalid as specified."; + case DDERR_INVALIDPOSITION: + return "Returned when the position of the overlay on the destination is no longer legal for that " + "destination."; case DDERR_INVALIDRECT: return "Rectangle provided was invalid."; + case DDERR_LOCKEDSURFACES: + return "Operation could not be carried out because one or more surfaces are locked."; + case DDERR_NO3D: + return "There is no 3D present."; case DDERR_NOALPHAHW: return "Operation could not be carried out because there is no alpha accleration hardware present or " "available."; - case DDERR_NO3D: - return "There is no 3D present."; - case DDERR_NOCOLORCONVHW: - return "Operation could not be carried out because there is no color conversion hardware present or available."; + case DDERR_NOBLTHW: + return "No blitter hardware present."; case DDERR_NOCLIPLIST: return "No cliplist available."; + case DDERR_NOCLIPPERATTACHED: + return "No clipper object attached to surface object."; + case DDERR_NOCOLORCONVHW: + return "Operation could not be carried out because there is no color conversion hardware present or " + "available."; case DDERR_NOCOLORKEY: return "Surface doesn't currently have a color key"; + case DDERR_NOCOLORKEYHW: + return "Operation could not be carried out because there is no hardware support of the destination color " + "key."; case DDERR_NOCOOPERATIVELEVELSET: return "Create function called without DirectDraw object method SetCooperativeLevel being called."; + case DDERR_NODC: + return "No DC was ever created for this surface."; + case DDERR_NODDROPSHW: + return "No DirectDraw ROP hardware."; + case DDERR_NODIRECTDRAWHW: + return "A hardware-only DirectDraw object creation was attempted but the driver did not support any " + "hardware."; + case DDERR_NOEMULATION: + return "Software emulation not available."; case DDERR_NOEXCLUSIVEMODE: return "Operation requires the application to have exclusive mode but the application does not have exclusive " "mode."; - case DDERR_NOCOLORKEYHW: - return "Operation could not be carried out because there is no hardware support of the destination color key."; - case DDERR_NOGDI: - return "There is no GDI present."; case DDERR_NOFLIPHW: return "Flipping visible surfaces is not supported."; - case DDERR_NOTFOUND: - return "Requested item was not found."; + case DDERR_NOGDI: + return "There is no GDI present."; + case DDERR_NOHWND: + return "Clipper notification requires an HWND or no HWND has previously been set as the CooperativeLevel " + "HWND."; case DDERR_NOMIRRORHW: return "Operation could not be carried out because there is no hardware present or available."; + case DDERR_NOOVERLAYDEST: + return "Returned when GetOverlayPosition is called on an overlay that UpdateOverlay has never been called on " + "to establish a destination."; + case DDERR_NOOVERLAYHW: + return "Operation could not be carried out because there is no overlay hardware present or available."; + case DDERR_NOPALETTEATTACHED: + return "No palette object attached to this surface."; + case DDERR_NOPALETTEHW: + return "No hardware support for 16 or 256 color palettes."; case DDERR_NORASTEROPHW: return "Operation could not be carried out because there is no appropriate raster op hardware present or " "available."; - case DDERR_NOOVERLAYHW: - return "Operation could not be carried out because there is no overlay hardware present or available."; - case DDERR_NOSTRETCHHW: - return "Operation could not be carried out because there is no hardware support for stretching."; case DDERR_NOROTATIONHW: return "Operation could not be carried out because there is no rotation hardware present or available."; - case DDERR_NOTEXTUREHW: - return "Operation could not be carried out because there is no texture mapping hardware present or available."; + case DDERR_NOSTRETCHHW: + return "Operation could not be carried out because there is no hardware support for stretching."; case DDERR_NOT4BITCOLOR: return "DirectDrawSurface is not in 4 bit color palette and the requested operation requires 4 bit color " "palette."; @@ -453,118 +502,80 @@ const char* MxDeviceEnumerate::EnumerateErrorToString(HRESULT p_error) "index palette."; case DDERR_NOT8BITCOLOR: return "DirectDrawSurface is not in 8 bit color mode and the requested operation requires 8 bit color."; - case DDERR_NOZBUFFERHW: - return "Operation could not be carried out because there is no hardware support for zbuffer blitting."; + case DDERR_NOTAOVERLAYSURFACE: + return "Returned when an overlay member is called for a non-overlay surface."; + case DDERR_NOTEXTUREHW: + return "Operation could not be carried out because there is no texture mapping hardware present or " + "available."; + case DDERR_NOTFLIPPABLE: + return "An attempt has been made to flip a surface that is not flippable."; + case DDERR_NOTFOUND: + return "Requested item was not found."; + case DDERR_NOTLOCKED: + return "Surface was not locked. An attempt to unlock a surface that was not locked at all, or by this " + "process, has been attempted."; + case DDERR_NOTPALETTIZED: + return "The surface being used is not a palette-based surface."; case DDERR_NOVSYNCHW: return "Operation could not be carried out because there is no hardware support for vertical blank " "synchronized operations."; - case DDERR_OUTOFCAPS: - return "The hardware needed for the requested operation has already been allocated."; + case DDERR_NOZBUFFERHW: + return "Operation could not be carried out because there is no hardware support for zbuffer blitting."; case DDERR_NOZOVERLAYHW: return "Overlay surfaces could not be z layered based on their BltOrder because the hardware does not support " "z layering of overlays."; - case DDERR_COLORKEYNOTSET: - return "No src color key specified for this operation."; + case DDERR_OUTOFCAPS: + return "The hardware needed for the requested operation has already been allocated."; + case DDERR_OUTOFMEMORY: + return "DirectDraw does not have enough memory to perform the operation."; case DDERR_OUTOFVIDEOMEMORY: return "DirectDraw does not have enough memory to perform the operation."; case DDERR_OVERLAYCANTCLIP: return "The hardware does not support clipped overlays."; case DDERR_OVERLAYCOLORKEYONLYONEACTIVE: return "Can only have ony color key active at one time for overlays."; + case DDERR_OVERLAYNOTVISIBLE: + return "Returned when GetOverlayPosition is called on a hidden overlay."; case DDERR_PALETTEBUSY: return "Access to this palette is being refused because the palette is already locked by another thread."; - case DDERR_SURFACEALREADYDEPENDENT: - return "This surface is already a dependency of the surface it is being made a dependency of."; + case DDERR_PRIMARYSURFACEALREADYEXISTS: + return "This process already has created a primary surface."; + case DDERR_REGIONTOOSMALL: + return "Region passed to Clipper::GetClipList is too small."; case DDERR_SURFACEALREADYATTACHED: return "This surface is already attached to the surface it is being attached to."; - case DDERR_SURFACEISOBSCURED: - return "Access to surface refused because the surface is obscured."; + case DDERR_SURFACEALREADYDEPENDENT: + return "This surface is already a dependency of the surface it is being made a dependency of."; case DDERR_SURFACEBUSY: return "Access to this surface is being refused because the surface is already locked by another thread."; - case DDERR_SURFACENOTATTACHED: - return "The requested surface is not attached."; + case DDERR_SURFACEISOBSCURED: + return "Access to surface refused because the surface is obscured."; case DDERR_SURFACELOST: return "Access to this surface is being refused because the surface memory is gone. The DirectDrawSurface " "object representing this surface should have Restore called on it."; - case DDERR_TOOBIGSIZE: - return "Size requested by DirectDraw is too large, but the individual height and width are OK."; + case DDERR_SURFACENOTATTACHED: + return "The requested surface is not attached."; case DDERR_TOOBIGHEIGHT: return "Height requested by DirectDraw is too large."; - case DDERR_UNSUPPORTEDFORMAT: - return "FOURCC format requested is unsupported by DirectDraw."; + case DDERR_TOOBIGSIZE: + return "Size requested by DirectDraw is too large, but the individual height and width are OK."; case DDERR_TOOBIGWIDTH: return "Width requested by DirectDraw is too large."; - case DDERR_VERTICALBLANKINPROGRESS: - return "Vertical blank is in progress."; + case DDERR_UNSUPPORTED: + return "Action not supported."; + case DDERR_UNSUPPORTEDFORMAT: + return "FOURCC format requested is unsupported by DirectDraw."; case DDERR_UNSUPPORTEDMASK: return "Bitmask in the pixel format requested is unsupported by DirectDraw."; - case DDERR_XALIGN: - return "Rectangle provided was not horizontally aligned on required boundary."; + case DDERR_VERTICALBLANKINPROGRESS: + return "Vertical blank is in progress."; case DDERR_WASSTILLDRAWING: return "Informs DirectDraw that the previous Blt which is transfering information to or from this Surface is " "incomplete."; - case DDERR_INVALIDDIRECTDRAWGUID: - return "The GUID passed to DirectDrawCreate is not a valid DirectDraw driver identifier."; - case DDERR_DIRECTDRAWALREADYCREATED: - return "A DirectDraw object representing this driver has already been created for this process."; - case DDERR_NODIRECTDRAWHW: - return "A hardware-only DirectDraw object creation was attempted but the driver did not support any hardware."; - case DDERR_PRIMARYSURFACEALREADYEXISTS: - return "This process already has created a primary surface."; - case DDERR_NOEMULATION: - return "Software emulation not available."; - case DDERR_REGIONTOOSMALL: - return "Region passed to Clipper::GetClipList is too small."; - case DDERR_CLIPPERISUSINGHWND: - return "An attempt was made to set a cliplist for a clipper object that is already monitoring an hwnd."; - case DDERR_NOCLIPPERATTACHED: - return "No clipper object attached to surface object."; - case DDERR_NOHWND: - return "Clipper notification requires an HWND or no HWND has previously been set as the CooperativeLevel HWND."; - case DDERR_HWNDSUBCLASSED: - return "HWND used by DirectDraw CooperativeLevel has been subclassed, this prevents DirectDraw from restoring " - "state."; - case DDERR_HWNDALREADYSET: - return "The CooperativeLevel HWND has already been set. It can not be reset while the process has surfaces or " - "palettes created."; - case DDERR_NOPALETTEATTACHED: - return "No palette object attached to this surface."; - case DDERR_NOPALETTEHW: - return "No hardware support for 16 or 256 color palettes."; - case DDERR_BLTFASTCANTCLIP: - return "Return if a clipper object is attached to the source surface passed into a BltFast call."; - case DDERR_NOBLTHW: - return "No blitter hardware present."; - case DDERR_NODDROPSHW: - return "No DirectDraw ROP hardware."; - case DDERR_OVERLAYNOTVISIBLE: - return "Returned when GetOverlayPosition is called on a hidden overlay."; - case DDERR_NOOVERLAYDEST: - return "Returned when GetOverlayPosition is called on an overlay that UpdateOverlay has never been called on " - "to establish a destination."; - case DDERR_INVALIDPOSITION: - return "Returned when the position of the overlay on the destination is no longer legal for that destination."; - case DDERR_NOTAOVERLAYSURFACE: - return "Returned when an overlay member is called for a non-overlay surface."; - case DDERR_EXCLUSIVEMODEALREADYSET: - return "An attempt was made to set the cooperative level when it was already set to exclusive."; - case DDERR_NOTFLIPPABLE: - return "An attempt has been made to flip a surface that is not flippable."; - case DDERR_CANTDUPLICATE: - return "Can't duplicate primary & 3D surfaces, or surfaces that are implicitly created."; - case DDERR_NOTLOCKED: - return "Surface was not locked. An attempt to unlock a surface that was not locked at all, or by this " - "process, has been attempted."; - case DDERR_CANTCREATEDC: - return "Windows can not create any more DCs."; - case DDERR_NODC: - return "No DC was ever created for this surface."; case DDERR_WRONGMODE: return "This surface can not be restored because it was created in a different mode."; - case DDERR_IMPLICITLYCREATED: - return "This surface can not be restored because it is an implicitly created surface."; - case DDERR_NOTPALETTIZED: - return "The surface being used is not a palette-based surface."; + case DDERR_XALIGN: + return "Rectangle provided was not horizontally aligned on required boundary."; default: return "Unrecognized error value."; } diff --git a/LEGO1/mxdirectx/mxdirectxinfo.h b/LEGO1/mxdirectx/mxdirectxinfo.h index 03370c1e..49fea43d 100644 --- a/LEGO1/mxdirectx/mxdirectxinfo.h +++ b/LEGO1/mxdirectx/mxdirectxinfo.h @@ -63,7 +63,7 @@ class MxAssignedDevice { // SIZE 0x1a4 struct Direct3DDeviceInfo { - Direct3DDeviceInfo() {} + Direct3DDeviceInfo(); ~Direct3DDeviceInfo(); Direct3DDeviceInfo( LPGUID p_guid, @@ -112,9 +112,8 @@ struct MxDisplayMode { // SIZE 0x190 struct MxDriver { - MxDriver() {} + MxDriver(); ~MxDriver(); - MxDriver(LPGUID p_guid); MxDriver(LPGUID p_guid, LPCSTR p_driverDesc, LPCSTR p_driverName); void Init(LPGUID p_guid, LPCSTR p_driverDesc, LPCSTR p_driverName); @@ -205,16 +204,15 @@ class MxDeviceEnumerate { ); const char* EnumerateErrorToString(HRESULT p_error); static void BuildErrorString(const char*, ...); - static BOOL CALLBACK - DirectDrawEnumerateCallback(LPGUID p_guid, LPSTR p_driverDesc, LPSTR p_driverName, LPVOID p_context); - static HRESULT CALLBACK DisplayModesEnumerateCallback(LPDDSURFACEDESC p_ddsd, LPVOID p_context); + static BOOL CALLBACK DirectDrawEnumerateCallback(LPGUID p_guid, LPSTR p_driverDesc, LPSTR p_driverName, LPVOID p_d); + static HRESULT CALLBACK DisplayModesEnumerateCallback(LPDDSURFACEDESC p_ddsd, LPVOID p_d); static HRESULT CALLBACK DevicesEnumerateCallback( LPGUID p_guid, LPSTR p_deviceDesc, LPSTR p_deviceName, LPD3DDEVICEDESC p_HWDesc, LPD3DDEVICEDESC p_HELDesc, - LPVOID p_context + LPVOID p_d ); friend class MxDirect3D; From bf98bfffdfa739d4d1e9a97d47a7cfb23b93fde4 Mon Sep 17 00:00:00 2001 From: MS Date: Tue, 15 Jul 2025 12:58:13 -0400 Subject: [PATCH 140/188] CONFIG (#1627) --- CMakeLists.txt | 1 + CONFIG/AboutDlg.cpp | 3 + CONFIG/AboutDlg.h | 10 ++ CONFIG/ConfigCommandLineInfo.cpp | 8 + CONFIG/ConfigCommandLineInfo.h | 3 + CONFIG/MainDlg.cpp | 205 ++++++++++++++------- CONFIG/MainDlg.h | 13 ++ CONFIG/config.cpp | 64 +++---- CONFIG/config.h | 36 ++-- CONFIG/detectdx5.cpp | 297 +++++++++++++++++++++---------- CONFIG/detectdx5.h | 2 +- CONFIG/res/resource.h | 2 + 12 files changed, 441 insertions(+), 203 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index c9fe6030..c8d030e6 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -593,6 +593,7 @@ if (MSVC_FOR_DECOMP) endif() if (ISLE_BUILD_CONFIG) + target_link_options(config PRIVATE "/OPT:REF") target_link_libraries(config PRIVATE mfc42) endif() diff --git a/CONFIG/AboutDlg.cpp b/CONFIG/AboutDlg.cpp index 9585d1c1..61d0d075 100644 --- a/CONFIG/AboutDlg.cpp +++ b/CONFIG/AboutDlg.cpp @@ -6,13 +6,16 @@ DECOMP_SIZE_ASSERT(CDialog, 0x60) DECOMP_SIZE_ASSERT(CAboutDialog, 0x60) // FUNCTION: CONFIG 0x00403c20 +// FUNCTION: CONFIGD 0x00408630 CAboutDialog::CAboutDialog() : CDialog(IDD) { } // FUNCTION: CONFIG 0x00403d20 +// FUNCTION: CONFIGD 0x004086a3 void CAboutDialog::DoDataExchange(CDataExchange* pDX) { + CWnd::DoDataExchange(pDX); } BEGIN_MESSAGE_MAP(CAboutDialog, CDialog) diff --git a/CONFIG/AboutDlg.h b/CONFIG/AboutDlg.h index dc648d63..65db159f 100644 --- a/CONFIG/AboutDlg.h +++ b/CONFIG/AboutDlg.h @@ -6,6 +6,7 @@ #include "res/resource.h" // VTABLE: CONFIG 0x00406308 +// VTABLE: CONFIGD 0x0040c3f8 // SIZE 0x60 class CAboutDialog : public CDialog { public: @@ -22,18 +23,27 @@ class CAboutDialog : public CDialog { }; // SYNTHETIC: CONFIG 0x00403cb0 +// SYNTHETIC: CONFIGD 0x00409840 // CAboutDialog::`scalar deleting destructor' +// SYNTHETIC: CONFIG 0x00404100 +// SYNTHETIC: CONFIGD 0x00409890 +// CAboutDialog::~CAboutDialog + // FUNCTION: CONFIG 0x00403d30 +// FUNCTION: CONFIGD 0x004086c7 // CAboutDialog::_GetBaseMessageMap // FUNCTION: CONFIG 0x00403d40 +// FUNCTION: CONFIGD 0x004086dc // CAboutDialog::GetMessageMap // GLOBAL: CONFIG 0x00406100 +// GLOBAL: CONFIGD 0x0040c188 // CAboutDialog::messageMap // GLOBAL: CONFIG 0x00406108 +// GLOBAL: CONFIGD 0x0040c190 // CAboutDialog::_messageEntries #endif // !defined(AFX_ABOUTDLG_H) diff --git a/CONFIG/ConfigCommandLineInfo.cpp b/CONFIG/ConfigCommandLineInfo.cpp index cf3180c4..4542746f 100644 --- a/CONFIG/ConfigCommandLineInfo.cpp +++ b/CONFIG/ConfigCommandLineInfo.cpp @@ -6,12 +6,20 @@ DECOMP_SIZE_ASSERT(CCommandLineInfo, 0x24) DECOMP_SIZE_ASSERT(CConfigCommandLineInfo, 0x24) // FUNCTION: CONFIG 0x00403b10 +// FUNCTION: CONFIGD 0x00407caa CConfigCommandLineInfo::CConfigCommandLineInfo() { currentConfigApp->m_run_config_dialog = FALSE; } +// FUNCTION: CONFIG 0x00403ba0 +// FUNCTION: CONFIGD 0x00407d2e +CConfigCommandLineInfo::~CConfigCommandLineInfo() +{ +} + // FUNCTION: CONFIG 0x00403bf0 +// FUNCTION: CONFIGD 0x00407d96 void CConfigCommandLineInfo::ParseParam(LPCSTR pszParam, BOOL bFlag, BOOL bLast) { if (bFlag) { diff --git a/CONFIG/ConfigCommandLineInfo.h b/CONFIG/ConfigCommandLineInfo.h index 82510df1..762ecb16 100644 --- a/CONFIG/ConfigCommandLineInfo.h +++ b/CONFIG/ConfigCommandLineInfo.h @@ -7,15 +7,18 @@ #include "decomp.h" // VTABLE: CONFIG 0x004060e8 +// VTABLE: CONFIGD 0x0040c168 // SIZE 0x24 class CConfigCommandLineInfo : public CCommandLineInfo { public: CConfigCommandLineInfo(); + ~CConfigCommandLineInfo() override; void ParseParam(LPCSTR pszParam, BOOL bFlag, BOOL bLast) override; }; // SYNTHETIC: CONFIG 0x00403b80 +// SYNTHETIC: CONFIGD 0x004085e0 // CConfigCommandLineInfo::`scalar deleting destructor' #endif // !defined(AFX_CONFIGCOMMANDLINEINFO_H) diff --git a/CONFIG/MainDlg.cpp b/CONFIG/MainDlg.cpp index cf88727a..9a99f121 100644 --- a/CONFIG/MainDlg.cpp +++ b/CONFIG/MainDlg.cpp @@ -4,21 +4,24 @@ #include "config.h" #include "res/resource.h" +#include #include DECOMP_SIZE_ASSERT(CDialog, 0x60) DECOMP_SIZE_ASSERT(CMainDialog, 0x70) // FUNCTION: CONFIG 0x00403d50 +// FUNCTION: CONFIGD 0x004086f7 CMainDialog::CMainDialog(CWnd* pParent) : CDialog(IDD, pParent) { - afxCurrentWinApp; - m_icon = LoadIcon(AfxFindResourceHandle(MAKEINTRESOURCE(IDI_CONFIG), RT_GROUP_ICON), MAKEINTRESOURCE(IDI_CONFIG)); + m_icon = currentConfigApp->LoadIcon(IDI_CONFIG); } // FUNCTION: CONFIG 0x00403e50 +// FUNCTION: CONFIGD 0x00408785 void CMainDialog::DoDataExchange(CDataExchange* pDX) { + CWnd::DoDataExchange(pDX); } BEGIN_MESSAGE_MAP(CMainDialog, CDialog) @@ -44,53 +47,64 @@ ON_COMMAND(IDC_CHK_MUSIC, OnCheckboxMusic) END_MESSAGE_MAP() // FUNCTION: CONFIG 0x00403e80 +// FUNCTION: CONFIGD 0x004087d9 BOOL CMainDialog::OnInitDialog() { CDialog::OnInitDialog(); SwitchToAdvanced(FALSE); - CMenu* system_menu = CMenu::FromHandle(::GetSystemMenu(m_hWnd, FALSE)); + CMenu* system_menu = CWnd::GetSystemMenu(FALSE); CString about_text; about_text.LoadString(IDS_ABOUT); - if (system_menu) { - AppendMenu(system_menu->m_hMenu, MF_SEPARATOR, 0, NULL); - AppendMenu(system_menu->m_hMenu, MF_STRING, 16, (LPCTSTR) about_text); + if (!about_text.IsEmpty()) { + system_menu->AppendMenu(MF_SEPARATOR, 0, (LPCTSTR) NULL); + system_menu->AppendMenu(MF_STRING, 16, (LPCTSTR) about_text); } - SendMessage(WM_SETICON, ICON_BIG, (LPARAM) m_icon); - SendMessage(WM_SETICON, ICON_SMALL, (LPARAM) m_icon); - LegoDeviceEnumerate* enumerator = currentConfigApp->m_device_enumerator; - enumerator->FUN_1009d210(); + + CWnd::SetIcon(m_icon, TRUE); + CWnd::SetIcon(m_icon, FALSE); + + LegoDeviceEnumerate* info = currentConfigApp->m_dxInfo; + assert(info); + + info->FUN_1009d210(); m_modified = currentConfigApp->ReadRegisterSettings(); - CWnd* list_3d_devices = GetDlgItem(IDC_LIST_3DDEVICES); + CListBox* list_3d_devices = (CListBox*) GetDlgItem(IDC_LIST_3DDEVICES); int driver_i = 0; int device_i = 0; int selected = 0; - char device_name[256]; - const list& driver_list = enumerator->m_ddInfo; - for (list::const_iterator it_driver = driver_list.begin(); it_driver != driver_list.end(); it_driver++) { + + for (list::iterator it_driver = info->m_ddInfo.begin(); it_driver != info->m_ddInfo.end(); + it_driver++, driver_i++) { const MxDriver& driver = *it_driver; + for (list::const_iterator it_device = driver.m_devices.begin(); it_device != driver.m_devices.end(); it_device++) { - const Direct3DDeviceInfo& device = *it_device; - if (&device == currentConfigApp->m_device) { + + if (&(*it_device) == currentConfigApp->m_d3dInfo) { selected = device_i; } + + char device_name[256]; + if (driver_i == 0) { + sprintf(device_name, "%s ( Primary Device )", (*it_device).m_deviceName); + } + else { + sprintf(device_name, "%s ( Secondary Device )", (*it_device).m_deviceName); + } + + list_3d_devices->AddString(device_name); device_i += 1; - sprintf( - device_name, - driver_i == 0 ? "%s ( Primary Device )" : "%s ( Secondary Device )", - device.m_deviceName - ); - ::SendMessage(list_3d_devices->m_hWnd, LB_ADDSTRING, 0, (LPARAM) device_name); } - driver_i += 1; } - ::SendMessage(list_3d_devices->m_hWnd, LB_SETCURSEL, selected, 0); + + list_3d_devices->SetCurSel(selected); UpdateInterface(); return TRUE; } // FUNCTION: CONFIG 0x00404080 +// FUNCTION: CONFIGD 0x00408ab7 void CMainDialog::OnSysCommand(UINT nID, LPARAM lParam) { if ((nID & 0xfff0) == 0x10) { @@ -98,86 +112,109 @@ void CMainDialog::OnSysCommand(UINT nID, LPARAM lParam) about_dialog.DoModal(); } else { - Default(); + CWnd::OnSysCommand(nID, lParam); } } // FUNCTION: CONFIG 0x00404150 +// FUNCTION: CONFIGD 0x00408b49 void CMainDialog::OnPaint() { if (IsIconic()) { CPaintDC painter(this); - ::SendMessage(m_hWnd, WM_ICONERASEBKGND, (WPARAM) painter.m_hDC, 0); - RECT dim; + CWnd::SendMessage(WM_ICONERASEBKGND, (WPARAM) painter.GetSafeHdc(), 0); + + int iconWidth = GetSystemMetrics(SM_CXICON); + int iconHeight = GetSystemMetrics(SM_CYICON); + + CRect dim; GetClientRect(&dim); - DrawIcon( - painter.m_hDC, - (dim.right - dim.left - GetSystemMetrics(SM_CXICON) + 1) / 2, - (dim.bottom - dim.top - GetSystemMetrics(SM_CYICON) + 1) / 2, - m_icon - ); + + int x = (dim.Width() - iconWidth + 1) / 2; + int y = (dim.Height() - iconHeight + 1) / 2; + + painter.DrawIcon(x, y, m_icon); } else { - Default(); + CWnd::OnPaint(); } } // FUNCTION: CONFIG 0x00404230 +// FUNCTION: CONFIGD 0x00408c61 HCURSOR CMainDialog::OnQueryDragIcon() { return m_icon; } // FUNCTION: CONFIG 0x00404240 +// FUNCTION: CONFIGD 0x00408c7d void CMainDialog::OnList3DevicesSelectionChanged() { - LegoDeviceEnumerate* device_enumerator = currentConfigApp->m_device_enumerator; - int selected = ::SendMessage(GetDlgItem(IDC_LIST_3DDEVICES)->m_hWnd, LB_GETCURSEL, 0, 0); - device_enumerator->GetDevice(selected, currentConfigApp->m_driver, currentConfigApp->m_device); - if (currentConfigApp->GetHardwareDeviceColorModel()) { - GetDlgItem(IDC_CHK_DRAW_CURSOR)->EnableWindow(TRUE); - } - else { + CConfigApp* app = currentConfigApp; + assert(app); + + LegoDeviceEnumerate* device_enumerator = app->m_dxInfo; + assert(device_enumerator); + + CListBox* listbox = (CListBox*) GetDlgItem(IDC_LIST_3DDEVICES); + int selected = listbox->GetCurSel(); + int r = device_enumerator->GetDevice(selected, app->m_ddInfo, app->m_d3dInfo); + assert(r == 0); + + if (!currentConfigApp->GetHardwareDeviceColorModel()) { currentConfigApp->m_3d_video_ram = FALSE; currentConfigApp->m_flip_surfaces = FALSE; CheckDlgButton(IDC_CHK_3D_VIDEO_MEMORY, currentConfigApp->m_3d_video_ram); CheckDlgButton(IDC_CHK_FLIP_VIDEO_MEM_PAGES, currentConfigApp->m_flip_surfaces); } + else { + GetDlgItem(IDC_CHK_DRAW_CURSOR)->EnableWindow(TRUE); + } + m_modified = TRUE; UpdateInterface(); } // FUNCTION: CONFIG 0x00404320 +// FUNCTION: CONFIGD 0x00408d79 void CMainDialog::OnCancel() { CDialog::OnCancel(); } // FUNCTION: CONFIG 0x00404330 +// FUNCTION: CONFIGD 0x00408de5 void CMainDialog::OnDestroy() { - CDialog::Default(); + CWnd::OnDestroy(); } // FUNCTION: CONFIG 0x00404340 +// FUNCTION: CONFIGD 0x00408e03 void CMainDialog::OnButtonCancel() { if (m_modified) { currentConfigApp->WriteRegisterSettings(); } - OnCancel(); + CDialog::OnCancel(); } // FUNCTION: CONFIG 0x00404360 +// FUNCTION: CONFIGD 0x00408e2f void CMainDialog::UpdateInterface() { currentConfigApp->ValidateSettings(); - GetDlgItem(IDC_CHK_3D_VIDEO_MEMORY) - ->EnableWindow(!currentConfigApp->m_flip_surfaces && !currentConfigApp->GetHardwareDeviceColorModel()); - CheckDlgButton(IDC_CHK_FLIP_VIDEO_MEM_PAGES, currentConfigApp->m_flip_surfaces); + BOOL flip_surfaces = currentConfigApp->m_flip_surfaces; + + BOOL enable3d = !flip_surfaces && !currentConfigApp->GetHardwareDeviceColorModel(); + GetDlgItem(IDC_CHK_3D_VIDEO_MEMORY)->EnableWindow(enable3d); + + CheckDlgButton(IDC_CHK_FLIP_VIDEO_MEM_PAGES, flip_surfaces); CheckDlgButton(IDC_CHK_3D_VIDEO_MEMORY, currentConfigApp->m_3d_video_ram); BOOL full_screen = currentConfigApp->m_full_screen; currentConfigApp->AdjustDisplayBitDepthBasedOnRenderStatus(); + if (currentConfigApp->GetHardwareDeviceColorModel()) { CheckDlgButton(IDC_CHK_DRAW_CURSOR, TRUE); } @@ -186,24 +223,32 @@ void CMainDialog::UpdateInterface() currentConfigApp->m_draw_cursor = FALSE; GetDlgItem(IDC_CHK_DRAW_CURSOR)->EnableWindow(FALSE); } + if (full_screen) { - CheckRadioButton( - IDC_RAD_PALETTE_256, - IDC_RAD_PALETTE_16BIT, - currentConfigApp->m_display_bit_depth == 8 ? IDC_RAD_PALETTE_256 : IDC_RAD_PALETTE_16BIT - ); + if (currentConfigApp->m_display_bit_depth == 8) { + CheckRadioButton(IDC_RAD_PALETTE_256, IDC_RAD_PALETTE_16BIT, IDC_RAD_PALETTE_256); + } + else { + CheckRadioButton(IDC_RAD_PALETTE_256, IDC_RAD_PALETTE_16BIT, IDC_RAD_PALETTE_16BIT); + } } else { CheckDlgButton(IDC_RAD_PALETTE_256, 0); CheckDlgButton(IDC_RAD_PALETTE_16BIT, 0); currentConfigApp->m_display_bit_depth = 0; } - GetDlgItem(IDC_RAD_PALETTE_256) - ->EnableWindow(full_screen && currentConfigApp->GetConditionalDeviceRenderBitDepth()); - GetDlgItem(IDC_RAD_PALETTE_16BIT)->EnableWindow(full_screen && currentConfigApp->GetDeviceRenderBitStatus()); + + BOOL enable256 = full_screen && currentConfigApp->GetConditionalDeviceRenderBitDepth() != 0; + GetDlgItem(IDC_RAD_PALETTE_256)->EnableWindow(enable256); + + BOOL enable16 = full_screen && currentConfigApp->GetDeviceRenderBitStatus() != 0; + GetDlgItem(IDC_RAD_PALETTE_16BIT)->EnableWindow(enable16); + CheckDlgButton(IDC_CHK_3DSOUND, currentConfigApp->m_3d_sound); CheckDlgButton(IDC_CHK_DRAW_CURSOR, currentConfigApp->m_draw_cursor); + switch (currentConfigApp->m_model_quality) { + // DECOMP: case 0 removed for retail. case 1: CheckRadioButton(IDC_RAD_MODEL_QUALITY_LOW, IDC_RAD_MODEL_QUALITY_HIGH, IDC_RAD_MODEL_QUALITY_LOW); break; @@ -211,16 +256,29 @@ void CMainDialog::UpdateInterface() CheckRadioButton(IDC_RAD_MODEL_QUALITY_LOW, IDC_RAD_MODEL_QUALITY_HIGH, IDC_RAD_MODEL_QUALITY_HIGH); break; } - CheckRadioButton( - IDC_RAD_TEXTURE_QUALITY_LOW, - IDC_RAD_TEXTURE_QUALITY_HIGH, - currentConfigApp->m_texture_quality == 0 ? IDC_RAD_TEXTURE_QUALITY_LOW : IDC_RAD_TEXTURE_QUALITY_HIGH - ); + + if (currentConfigApp->m_texture_quality == 0) { + CheckRadioButton(IDC_RAD_TEXTURE_QUALITY_LOW, IDC_RAD_TEXTURE_QUALITY_HIGH, IDC_RAD_TEXTURE_QUALITY_LOW); + } + else { + CheckRadioButton(IDC_RAD_TEXTURE_QUALITY_LOW, IDC_RAD_TEXTURE_QUALITY_HIGH, IDC_RAD_TEXTURE_QUALITY_HIGH); + } + CheckDlgButton(IDC_CHK_JOYSTICK, currentConfigApp->m_use_joystick); CheckDlgButton(IDC_CHK_MUSIC, currentConfigApp->m_music); } +// STUB: CONFIGD 0x00409152 +void CMainDialog::OnCheckboxWideAngle() +{ + // DECOMP: m_wide_angle member removed for retail. + // currentConfigApp->m_wide_angle = IsDlgButtonChecked(IDC_CHK_WIDE_ANGLE); + // m_modified = TRUE; + // UpdateInterface(); +} + // FUNCTION: CONFIG 0x004045e0 +// FUNCTION: CONFIGD 0x00409198 void CMainDialog::OnCheckbox3DSound() { currentConfigApp->m_3d_sound = IsDlgButtonChecked(IDC_CHK_3DSOUND); @@ -229,6 +287,7 @@ void CMainDialog::OnCheckbox3DSound() } // FUNCTION: CONFIG 0x00404610 +// FUNCTION: CONFIGD 0x004091de void CMainDialog::OnCheckbox3DVideoMemory() { currentConfigApp->m_3d_video_ram = IsDlgButtonChecked(IDC_CHK_3D_VIDEO_MEMORY); @@ -237,6 +296,7 @@ void CMainDialog::OnCheckbox3DVideoMemory() } // FUNCTION: CONFIG 0x00404640 +// FUNCTION: CONFIGD 0x00409224 void CMainDialog::OnRadiobuttonPalette16bit() { currentConfigApp->m_display_bit_depth = 16; @@ -245,6 +305,7 @@ void CMainDialog::OnRadiobuttonPalette16bit() } // FUNCTION: CONFIG 0x00404670 +// FUNCTION: CONFIGD 0x00409261 void CMainDialog::OnRadiobuttonPalette256() { currentConfigApp->m_display_bit_depth = 8; @@ -253,6 +314,7 @@ void CMainDialog::OnRadiobuttonPalette256() } // FUNCTION: CONFIG 0x004046a0 +// FUNCTION: CONFIGD 0x0040929e void CMainDialog::OnCheckboxFlipVideoMemPages() { currentConfigApp->m_flip_surfaces = IsDlgButtonChecked(IDC_CHK_FLIP_VIDEO_MEM_PAGES); @@ -260,7 +322,24 @@ void CMainDialog::OnCheckboxFlipVideoMemPages() UpdateInterface(); } +// FUNCTION: CONFIGD 0x004092e4 +void CMainDialog::OnCheckboxFullScreen() +{ + currentConfigApp->m_full_screen = IsDlgButtonChecked(IDC_CHK_FULL_SCREEN); + m_modified = TRUE; + UpdateInterface(); +} + +// FUNCTION: CONFIGD 0x0040932a +void CMainDialog::OnRadiobuttonModelLowestQuality() +{ + currentConfigApp->m_model_quality = 0; + m_modified = TRUE; + UpdateInterface(); +} + // FUNCTION: CONFIG 0x004046d0 +// FUNCTION: CONFIGD 0x00409367 void CMainDialog::OnRadiobuttonModelLowQuality() { currentConfigApp->m_model_quality = 1; @@ -269,6 +348,7 @@ void CMainDialog::OnRadiobuttonModelLowQuality() } // FUNCTION: CONFIG 0x00404700 +// FUNCTION: CONFIGD 0x004093a4 void CMainDialog::OnRadiobuttonModelHighQuality() { currentConfigApp->m_model_quality = 2; @@ -277,6 +357,7 @@ void CMainDialog::OnRadiobuttonModelHighQuality() } // FUNCTION: CONFIG 0x00404730 +// FUNCTION: CONFIGD 0x004093e1 void CMainDialog::OnRadiobuttonTextureLowQuality() { currentConfigApp->m_texture_quality = 0; @@ -285,6 +366,7 @@ void CMainDialog::OnRadiobuttonTextureLowQuality() } // FUNCTION: CONFIG 0x00404760 +// FUNCTION: CONFIGD 0x0040941e void CMainDialog::OnRadiobuttonTextureHighQuality() { currentConfigApp->m_texture_quality = 1; @@ -293,6 +375,7 @@ void CMainDialog::OnRadiobuttonTextureHighQuality() } // FUNCTION: CONFIG 0x00404790 +// FUNCTION: CONFIGD 0x0040945b void CMainDialog::OnCheckboxJoystick() { currentConfigApp->m_use_joystick = IsDlgButtonChecked(IDC_CHK_JOYSTICK); @@ -311,8 +394,8 @@ void CMainDialog::SwitchToAdvanced(BOOL p_advanced) { RECT dialog_rect; RECT grp_advanced_rect; - ::GetWindowRect(m_hWnd, &dialog_rect); - ::GetWindowRect(GetDlgItem(IDC_GRP_ADVANCED)->m_hWnd, &grp_advanced_rect); + CWnd::GetWindowRect(&dialog_rect); + GetDlgItem(IDC_GRP_ADVANCED)->GetWindowRect(&grp_advanced_rect); CWnd* button_advanced = GetDlgItem(IDC_BTN_ADVANCED); m_advanced = p_advanced; int height; diff --git a/CONFIG/MainDlg.h b/CONFIG/MainDlg.h index eeebc868..b89299e3 100644 --- a/CONFIG/MainDlg.h +++ b/CONFIG/MainDlg.h @@ -7,6 +7,7 @@ #include "res/resource.h" // VTABLE: CONFIG 0x004063e0 +// VTABLE: CONFIGD 0x0040c500 // SIZE 0x70 class CMainDialog : public CDialog { public: @@ -35,11 +36,14 @@ class CMainDialog : public CDialog { void OnCancel(); void OnDestroy(); void OnButtonCancel(); + void OnCheckboxWideAngle(); void OnCheckbox3DSound(); void OnCheckbox3DVideoMemory(); void OnRadiobuttonPalette16bit(); void OnRadiobuttonPalette256(); void OnCheckboxFlipVideoMemPages(); + void OnCheckboxFullScreen(); + void OnRadiobuttonModelLowestQuality(); void OnRadiobuttonModelLowQuality(); void OnRadiobuttonModelHighQuality(); void OnRadiobuttonTextureLowQuality(); @@ -52,19 +56,28 @@ class CMainDialog : public CDialog { DECLARE_MESSAGE_MAP() }; +// SYNTHETIC: CONFIG 0x00403160 +// SYNTHETIC: CONFIGD 0x00408490 +// CMainDialog::~CMainDialog + // SYNTHETIC: CONFIG 0x00403de0 +// SYNTHETIC: CONFIGD 0x00409910 // CMainDialog::`scalar deleting destructor' // FUNCTION: CONFIG 0x00403e60 +// FUNCTION: CONFIGD 0x004087a9 // CMainDialog::_GetBaseMessageMap // FUNCTION: CONFIG 0x00403e70 +// FUNCTION: CONFIGD 0x004087be // CMainDialog::GetMessageMap // GLOBAL: CONFIG 0x00406120 +// GLOBAL: CONFIGD 0x0040c1a8 // CMainDialog::messageMap // GLOBAL: CONFIG 0x00406128 +// GLOBAL: CONFIGD 0x0040c1b0 // CMainDialog::_messageEntries #endif // !defined(AFX_MAINDLG_H) diff --git a/CONFIG/config.cpp b/CONFIG/config.cpp index c42469ee..9eac2dcb 100644 --- a/CONFIG/config.cpp +++ b/CONFIG/config.cpp @@ -55,14 +55,14 @@ BOOL CConfigApp::InitInstance() m_run_config_dialog = TRUE; } - m_device_enumerator = new LegoDeviceEnumerate; - if (m_device_enumerator->DoEnumerate()) { + m_dxInfo = new LegoDeviceEnumerate; + if (m_dxInfo->DoEnumerate()) { assert("Could not build device list." == NULL); return FALSE; } - m_driver = NULL; - m_device = NULL; + m_ddInfo = NULL; + m_d3dInfo = NULL; m_full_screen = TRUE; m_wide_view_angle = TRUE; m_use_joystick = FALSE; @@ -99,10 +99,10 @@ BOOL CConfigApp::InitInstance() ReadRegisterSettings(); ValidateSettings(); WriteRegisterSettings(); - delete m_device_enumerator; - m_device_enumerator = NULL; - m_driver = NULL; - m_device = NULL; + delete m_dxInfo; + m_dxInfo = NULL; + m_ddInfo = NULL; + m_d3dInfo = NULL; char password[256]; BOOL read = ReadReg("password", password, sizeof(password)); const char* exe = _stricmp("ogel", password) == 0 ? "isled.exe" : "isle.exe"; @@ -226,26 +226,26 @@ BOOL CConfigApp::IsDeviceInBasicRGBMode() const { /* * BUG: should be: - * return !GetHardwareDeviceColorModel() && (m_device->m_HELDesc.dcmColorModel & D3DCOLOR_RGB); + * return !GetHardwareDeviceColorModel() && (m_d3dInfo->m_HELDesc.dcmColorModel & D3DCOLOR_RGB); */ - assert(m_device); - return !GetHardwareDeviceColorModel() && m_device->m_HELDesc.dcmColorModel == D3DCOLOR_RGB; + assert(m_d3dInfo); + return !GetHardwareDeviceColorModel() && m_d3dInfo->m_HELDesc.dcmColorModel == D3DCOLOR_RGB; } // FUNCTION: CONFIG 0x00403400 // FUNCTION: CONFIGD 0x004070fa D3DCOLORMODEL CConfigApp::GetHardwareDeviceColorModel() const { - assert(m_device); - return m_device->m_HWDesc.dcmColorModel; + assert(m_d3dInfo); + return m_d3dInfo->m_HWDesc.dcmColorModel; } // FUNCTION: CONFIG 0x00403410 // FUNCTION: CONFIGD 0x0040714e BOOL CConfigApp::IsPrimaryDriver() const { - assert(m_driver && m_device_enumerator); - return m_driver == &m_device_enumerator->m_ddInfo.front(); + assert(m_ddInfo && m_dxInfo); + return m_ddInfo == &m_dxInfo->m_ddInfo.front(); } // FUNCTION: CONFIG 0x00403430 @@ -259,9 +259,9 @@ BOOL CConfigApp::ReadRegisterSettings() int r = -1; if (read) { - r = m_device_enumerator->ParseDeviceName(buffer); + r = m_dxInfo->ParseDeviceName(buffer); if (r >= 0) { - r = m_device_enumerator->GetDevice(r, m_driver, m_device); + r = m_dxInfo->GetDevice(r, m_ddInfo, m_d3dInfo); if (r) { r = -1; } @@ -269,14 +269,14 @@ BOOL CConfigApp::ReadRegisterSettings() } if (r < 0) { - m_device_enumerator->FUN_1009d210(); - r = m_device_enumerator->GetBestDevice(); + m_dxInfo->FUN_1009d210(); + r = m_dxInfo->GetBestDevice(); is_modified = TRUE; assert(r >= 0); - r = m_device_enumerator->GetDevice(r, m_driver, m_device); + r = m_dxInfo->GetDevice(r, m_ddInfo, m_d3dInfo); } - assert(r == 0 && m_driver && m_device); + assert(r == 0 && m_ddInfo && m_d3dInfo); if (!ReadRegInt("Display Bit Depth", &m_display_bit_depth)) { is_modified = TRUE; @@ -384,26 +384,26 @@ BOOL CConfigApp::ValidateSettings() // FUNCTION: CONFIGD 0x00407793 DWORD CConfigApp::GetConditionalDeviceRenderBitDepth() const { - assert(m_device); + assert(m_d3dInfo); if (IsDeviceInBasicRGBMode()) { return 0; } if (GetHardwareDeviceColorModel()) { return 0; } - return m_device->m_HELDesc.dwDeviceRenderBitDepth & DDBD_8; + return m_d3dInfo->m_HELDesc.dwDeviceRenderBitDepth & DDBD_8; } // FUNCTION: CONFIG 0x004037e0 // FUNCTION: CONFIGD 0x00407822 DWORD CConfigApp::GetDeviceRenderBitStatus() const { - assert(m_device); + assert(m_d3dInfo); if (GetHardwareDeviceColorModel()) { - return m_device->m_HWDesc.dwDeviceRenderBitDepth & DDBD_16; + return m_d3dInfo->m_HWDesc.dwDeviceRenderBitDepth & DDBD_16; } else { - return m_device->m_HELDesc.dwDeviceRenderBitDepth & DDBD_16; + return m_d3dInfo->m_HELDesc.dwDeviceRenderBitDepth & DDBD_16; } } @@ -447,10 +447,10 @@ void CConfigApp::WriteRegisterSettings() const WriteReg(NAME, buffer); \ } while (0) - assert(m_device_enumerator && m_driver && m_device); - m_device_enumerator->FormatDeviceName(buffer, m_driver, m_device); + assert(m_dxInfo && m_ddInfo && m_d3dInfo); + m_dxInfo->FormatDeviceName(buffer, m_ddInfo, m_d3dInfo); WriteReg("3D Device ID", buffer); - WriteReg("3D Device Name", m_device->m_deviceName); + WriteReg("3D Device Name", m_d3dInfo->m_deviceName); WriteRegInt("Display Bit Depth", m_display_bit_depth); WriteRegBool("Flip Surfaces", m_flip_surfaces); WriteRegBool("Full Screen", m_full_screen); @@ -472,9 +472,9 @@ void CConfigApp::WriteRegisterSettings() const // FUNCTION: CONFIGD 0x00407c44 int CConfigApp::ExitInstance() { - if (m_device_enumerator) { - delete m_device_enumerator; - m_device_enumerator = NULL; + if (m_dxInfo) { + delete m_dxInfo; + m_dxInfo = NULL; } return CWinApp::ExitInstance(); } diff --git a/CONFIG/config.h b/CONFIG/config.h index 02366100..1c8a94a4 100644 --- a/CONFIG/config.h +++ b/CONFIG/config.h @@ -11,7 +11,7 @@ class LegoDeviceEnumerate; struct Direct3DDeviceInfo; struct MxDriver; -#define currentConfigApp ((CConfigApp*) afxCurrentWinApp) +#define currentConfigApp ((CConfigApp*) AfxGetApp()) // VTABLE: CONFIG 0x00406040 // VTABLE: CONFIGD 0x0040c0a0 @@ -55,23 +55,23 @@ class CConfigApp : public CWinApp { BOOL IsLegoNotRunning(); public: - LegoDeviceEnumerate* m_device_enumerator; // 0x0c4 - MxDriver* m_driver; // 0x0c8 - Direct3DDeviceInfo* m_device; // 0x0cc - int m_display_bit_depth; // 0x0d0 - BOOL m_flip_surfaces; // 0x0d4 - BOOL m_full_screen; // 0x0d8 - BOOL m_3d_video_ram; // 0x0dc - BOOL m_wide_view_angle; // 0x0e0 - BOOL m_3d_sound; // 0x0e4 - BOOL m_draw_cursor; // 0x0e8 - BOOL m_use_joystick; // 0x0ec - int m_joystick_index; // 0x0f0 - BOOL m_run_config_dialog; // 0x0f4 - int m_model_quality; // 0x0f8 - int m_texture_quality; // 0x0fc - undefined m_unk0x100[4]; // 0x100 - BOOL m_music; // 0x104 + LegoDeviceEnumerate* m_dxInfo; // 0x0c4 + MxDriver* m_ddInfo; // 0x0c8 + Direct3DDeviceInfo* m_d3dInfo; // 0x0cc + int m_display_bit_depth; // 0x0d0 + BOOL m_flip_surfaces; // 0x0d4 + BOOL m_full_screen; // 0x0d8 + BOOL m_3d_video_ram; // 0x0dc + BOOL m_wide_view_angle; // 0x0e0 + BOOL m_3d_sound; // 0x0e4 + BOOL m_draw_cursor; // 0x0e8 + BOOL m_use_joystick; // 0x0ec + int m_joystick_index; // 0x0f0 + BOOL m_run_config_dialog; // 0x0f4 + int m_model_quality; // 0x0f8 + int m_texture_quality; // 0x0fc + undefined m_unk0x100[4]; // 0x100 + BOOL m_music; // 0x104 }; // SYNTHETIC: CONFIG 0x00402cd0 diff --git a/CONFIG/detectdx5.cpp b/CONFIG/detectdx5.cpp index 8a308a8d..4e27fbee 100644 --- a/CONFIG/detectdx5.cpp +++ b/CONFIG/detectdx5.cpp @@ -3,140 +3,255 @@ #include #include -typedef HRESULT WINAPI DirectDrawCreate_fn(GUID FAR* lpGUID, LPDIRECTDRAW FAR* lplpDD, IUnknown FAR* pUnkOuter); -typedef HRESULT WINAPI -DirectInputCreateA_fn(HINSTANCE hinst, DWORD dwVersion, LPDIRECTINPUTA* ppDI, LPUNKNOWN punkOuter); +typedef HRESULT(WINAPI* DIRECTDRAWCREATE)(GUID*, LPDIRECTDRAW*, IUnknown*); +typedef HRESULT(WINAPI* DIRECTINPUTCREATE)(HINSTANCE, DWORD, LPDIRECTINPUT*, IUnknown*); // FUNCTION: CONFIG 0x004048f0 BOOL DetectDirectX5() { - unsigned int version; - BOOL found; - DetectDirectX(&version, &found); + DWORD version; + DWORD platform; + GetDXVersion(&version, &platform); return version >= 0x500; } // FUNCTION: CONFIG 0x00404920 -void DetectDirectX(unsigned int* p_version, BOOL* p_found) +void GetDXVersion(LPDWORD pdwDXVersion, LPDWORD pdwDXPlatform) { - OSVERSIONINFOA os_version; + // From GetDXVer.cpp + HRESULT hr; + HINSTANCE DDHinst = 0; + HINSTANCE DIHinst = 0; + LPDIRECTDRAW pDDraw = 0; + LPDIRECTDRAW2 pDDraw2 = 0; + DIRECTDRAWCREATE DirectDrawCreate = 0; + DIRECTINPUTCREATE DirectInputCreate = 0; + OSVERSIONINFO osVer; + LPDIRECTDRAWSURFACE pSurf = 0; + LPDIRECTDRAWSURFACE3 pSurf3 = 0; - os_version.dwOSVersionInfoSize = sizeof(os_version); - if (!GetVersionEx(&os_version)) { - *p_version = 0; - *p_found = 0; + /* + * First get the windows platform + */ + osVer.dwOSVersionInfoSize = sizeof(osVer); + if (!GetVersionEx(&osVer)) { + *pdwDXVersion = 0; + *pdwDXPlatform = 0; return; } - if (os_version.dwPlatformId == 2) { - *p_found = 2; - if (os_version.dwMajorVersion < 4) { - *p_found = 0; + + if (osVer.dwPlatformId == VER_PLATFORM_WIN32_NT) { + *pdwDXPlatform = VER_PLATFORM_WIN32_NT; + /* + * NT is easy... NT 4.0 is DX2, 4.0 SP3 is DX3, 5.0 is DX5 + * and no DX on earlier versions. + */ + if (osVer.dwMajorVersion < 4) { + *pdwDXPlatform = 0; // No DX on NT3.51 or earlier return; } - if (os_version.dwMajorVersion != 4) { - *p_version = 0x501; + if (osVer.dwMajorVersion == 4) { + /* + * NT4 up to SP2 is DX2, and SP3 onwards is DX3, so we are at least DX2 + */ + *pdwDXVersion = 0x200; + + /* + * We're not supposed to be able to tell which SP we're on, so check for dinput + */ + DIHinst = LoadLibrary("DINPUT.DLL"); + if (DIHinst == 0) { + /* + * No DInput... must be DX2 on NT 4 pre-SP3 + */ + OutputDebugString("Couldn't LoadLibrary DInput\r\n"); + return; + } + + DirectInputCreate = (DIRECTINPUTCREATE) GetProcAddress(DIHinst, "DirectInputCreateA"); + FreeLibrary(DIHinst); + + if (DirectInputCreate == 0) { + /* + * No DInput... must be pre-SP3 DX2 + */ + OutputDebugString("Couldn't GetProcAddress DInputCreate\r\n"); + return; + } + + /* + * It must be NT4, DX2 + */ + *pdwDXVersion = 0x300; // DX3 on NT4 SP3 or higher return; } - *p_version = 0x200; - HMODULE dinput_module = LoadLibrary("DINPUT.DLL"); - if (!dinput_module) { - OutputDebugString("Couldn't LoadLibrary DInput\r\n"); - return; - } - DirectInputCreateA_fn* func_DirectInputCreateA = - (DirectInputCreateA_fn*) GetProcAddress(dinput_module, "DirectInputCreateA"); - FreeLibrary(dinput_module); - if (!func_DirectInputCreateA) { - OutputDebugString("Couldn't GetProcAddress DInputCreate\r\n"); - return; - } - *p_version = 0x300; + /* + * Else it's NT5 or higher, and it's DX5a or higher: + */ + *pdwDXVersion = 0x501; // DX5a on NT5 return; } - *p_found = 1; - if (LOWORD(os_version.dwBuildNumber) >= 0x550) { - *p_version = 0x501; + + /* + * Not NT... must be Win9x + */ + *pdwDXPlatform = VER_PLATFORM_WIN32_WINDOWS; + + /* + * If we are on Memphis or higher, then we are at least DX5a + */ + if ((osVer.dwBuildNumber & 0xffff) > 1353) // Check for higher than developer release + { + *pdwDXVersion = 0x501; // DX5a on Memphis or higher return; } - HMODULE ddraw_module = LoadLibrary("DDRAW.DLL"); - if (!ddraw_module) { - *p_version = 0; - *p_found = 0; - FreeLibrary(ddraw_module); + + /* + * Now we know we are in Windows 9x (or maybe 3.1), so anything's possible. + * First see if DDRAW.DLL even exists. + */ + DDHinst = LoadLibrary("DDRAW.DLL"); + if (DDHinst == 0) { + *pdwDXVersion = 0; + *pdwDXPlatform = 0; + FreeLibrary(DDHinst); return; } - DirectDrawCreate_fn* func_DirectDrawCreate = - (DirectDrawCreate_fn*) GetProcAddress(ddraw_module, "DirectDrawCreate"); - if (!func_DirectDrawCreate) { - *p_version = 0; - *p_found = 0; - FreeLibrary(ddraw_module); + + /* + * See if we can create the DirectDraw object. + */ + DirectDrawCreate = (DIRECTDRAWCREATE) GetProcAddress(DDHinst, "DirectDrawCreate"); + if (DirectDrawCreate == 0) { + *pdwDXVersion = 0; + *pdwDXPlatform = 0; + FreeLibrary(DDHinst); OutputDebugString("Couldn't LoadLibrary DDraw\r\n"); return; } - LPDIRECTDRAW ddraw; - if (FAILED(func_DirectDrawCreate(NULL, &ddraw, NULL))) { - *p_version = 0; - *p_found = 0; - FreeLibrary(ddraw_module); + + hr = DirectDrawCreate(NULL, &pDDraw, NULL); + if (FAILED(hr)) { + *pdwDXVersion = 0; + *pdwDXPlatform = 0; + FreeLibrary(DDHinst); OutputDebugString("Couldn't create DDraw\r\n"); return; } - *p_version = 0x100; - LPDIRECTDRAW2 ddraw2; - if (FAILED(ddraw->QueryInterface(IID_IDirectDraw2, (LPVOID*) &ddraw2))) { - ddraw->Release(); - FreeLibrary(ddraw_module); + + /* + * So DirectDraw exists. We are at least DX1. + */ + *pdwDXVersion = 0x100; + + /* + * Let's see if IID_IDirectDraw2 exists. + */ + hr = pDDraw->QueryInterface(IID_IDirectDraw2, (LPVOID*) &pDDraw2); + if (FAILED(hr)) { + /* + * No IDirectDraw2 exists... must be DX1 + */ + pDDraw->Release(); + FreeLibrary(DDHinst); OutputDebugString("Couldn't QI DDraw2\r\n"); return; } - ddraw->Release(); - *p_version = 0x200; - HMODULE dinput_module = LoadLibrary("DINPUT.DLL"); - if (!dinput_module) { + /* + * IDirectDraw2 exists. We must be at least DX2 + */ + pDDraw2->Release(); + *pdwDXVersion = 0x200; + + /* + * See if we can create the DirectInput object. + */ + DIHinst = LoadLibrary("DINPUT.DLL"); + if (DIHinst == 0) { + /* + * No DInput... must be DX2 + */ OutputDebugString("Couldn't LoadLibrary DInput\r\n"); - ddraw2->Release(); - FreeLibrary(ddraw_module); + pDDraw->Release(); + FreeLibrary(DDHinst); return; } - DirectInputCreateA_fn* func_DirectInputCreateA = - (DirectInputCreateA_fn*) GetProcAddress(dinput_module, "DirectInputCreateA"); - FreeLibrary(dinput_module); - if (!func_DirectInputCreateA) { - FreeLibrary(ddraw_module); - ddraw2->Release(); + + DirectInputCreate = (DIRECTINPUTCREATE) GetProcAddress(DIHinst, "DirectInputCreateA"); + FreeLibrary(DIHinst); + + if (DirectInputCreate == 0) { + /* + * No DInput... must be DX2 + */ + FreeLibrary(DDHinst); + pDDraw->Release(); OutputDebugString("Couldn't GetProcAddress DInputCreate\r\n"); return; } - *p_version = 0x300; - DDSURFACEDESC surface_desc; - memset(&surface_desc, 0, sizeof(surface_desc)); - surface_desc.dwSize = sizeof(surface_desc); - surface_desc.dwFlags = DDSD_CAPS; - surface_desc.ddsCaps.dwCaps = DDSCAPS_PRIMARYSURFACE; - if (FAILED(ddraw2->SetCooperativeLevel(NULL, DDSCL_NORMAL))) { - ddraw2->Release(); - FreeLibrary(ddraw_module); - *p_version = 0; + + /* + * DirectInputCreate exists. That's enough to tell us that we are at least DX3 + */ + *pdwDXVersion = 0x300; + + /* + * Checks for 3a vs 3b? + */ + + /* + * We can tell if DX5 is present by checking for the existence of IDirectDrawSurface3. + * First we need a surface to QI off of. + */ + DDSURFACEDESC desc; + + ZeroMemory(&desc, sizeof(desc)); + desc.dwSize = sizeof(desc); + desc.dwFlags = DDSD_CAPS; + desc.ddsCaps.dwCaps = DDSCAPS_PRIMARYSURFACE; + + hr = pDDraw->SetCooperativeLevel(NULL, DDSCL_NORMAL); + if (FAILED(hr)) { + /* + * Failure. This means DDraw isn't properly installed. + */ + pDDraw->Release(); + FreeLibrary(DDHinst); + *pdwDXVersion = 0; OutputDebugString("Couldn't Set coop level\r\n"); return; } - LPDIRECTDRAWSURFACE surface; - if (FAILED(ddraw2->CreateSurface(&surface_desc, &surface, NULL))) { - ddraw2->Release(); - FreeLibrary(ddraw_module); - *p_version = 0; + + hr = pDDraw->CreateSurface(&desc, &pSurf, NULL); + if (FAILED(hr)) { + /* + * Failure. This means DDraw isn't properly installed. + */ + pDDraw->Release(); + FreeLibrary(DDHinst); + *pdwDXVersion = 0; OutputDebugString("Couldn't CreateSurface\r\n"); return; } - LPDIRECTDRAWSURFACE3 surface3; - if (FAILED(surface->QueryInterface(IID_IDirectDrawSurface3, (LPVOID*) &surface3))) { - ddraw2->Release(); - FreeLibrary(ddraw_module); + + /* + * Try for the IDirectDrawSurface3 interface. If it works, we're on DX5 at least + */ + if (FAILED(pSurf->QueryInterface(IID_IDirectDrawSurface3, (LPVOID*) &pSurf3))) { + pDDraw->Release(); + FreeLibrary(DDHinst); + return; } - *p_version = 0x500; - surface3->Release(); - ddraw2->Release(); - FreeLibrary(ddraw_module); + + /* + * QI for IDirectDrawSurface3 succeeded. We must be at least DX5 + */ + *pdwDXVersion = 0x500; + + pSurf->Release(); + pDDraw->Release(); + FreeLibrary(DDHinst); + + return; } diff --git a/CONFIG/detectdx5.h b/CONFIG/detectdx5.h index 6f45837c..ddb56f60 100644 --- a/CONFIG/detectdx5.h +++ b/CONFIG/detectdx5.h @@ -5,6 +5,6 @@ extern BOOL DetectDirectX5(); -extern void DetectDirectX(unsigned int* p_version, BOOL* p_found); +extern void GetDXVersion(LPDWORD pdwDXVersion, LPDWORD pdwDXPlatform); #endif // !defined(AFX_DETECTDX5_H) diff --git a/CONFIG/res/resource.h b/CONFIG/res/resource.h index 4c72eaa1..bf3dfe74 100644 --- a/CONFIG/res/resource.h +++ b/CONFIG/res/resource.h @@ -7,10 +7,12 @@ #define IDC_LIST_3DDEVICES 1000 #define IDC_CHK_FLIP_VIDEO_MEM_PAGES 1001 +#define IDC_CHK_FULL_SCREEN 1002 // beta only #define IDC_CHK_3D_VIDEO_MEMORY 1003 #define IDC_RAD_PALETTE_256 1004 #define IDC_RAD_PALETTE_16BIT 1005 #define IDC_CHK_3DSOUND 1006 +#define IDC_CHK_WIDE_ANGLE 1007 // beta only #define IDC_CHK_DRAW_CURSOR 1008 #define IDC_RAD_MODEL_QUALITY_LOW 1010 #define IDC_RAD_MODEL_QUALITY_HIGH 1011 From 8a82ebeb7e1642bfcfb0d05bba0d266d0508453b Mon Sep 17 00:00:00 2001 From: Joshua Peisach Date: Tue, 15 Jul 2025 13:16:25 -0400 Subject: [PATCH 141/188] Fix typo in SetSkyColor (#1628) --- LEGO1/lego/legoomni/src/video/legovideomanager.cpp | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/LEGO1/lego/legoomni/src/video/legovideomanager.cpp b/LEGO1/lego/legoomni/src/video/legovideomanager.cpp index 2039d381..c9c79097 100644 --- a/LEGO1/lego/legoomni/src/video/legovideomanager.cpp +++ b/LEGO1/lego/legoomni/src/video/legovideomanager.cpp @@ -634,13 +634,13 @@ void LegoVideoManager::EnableFullScreenMovie(MxBool p_enable, MxBool p_scale) // FUNCTION: LEGO1 0x1007c440 void LegoVideoManager::SetSkyColor(float p_red, float p_green, float p_blue) { - PALETTEENTRY colorStrucure; + PALETTEENTRY colorStructure; - colorStrucure.peRed = (p_red * 255.0f); - colorStrucure.peGreen = (p_green * 255.0f); - colorStrucure.peBlue = (p_blue * 255.0f); - colorStrucure.peFlags = D3DPAL_RESERVED | PC_NOCOLLAPSE; - m_videoParam.GetPalette()->SetSkyColor(&colorStrucure); + colorStructure.peRed = (p_red * 255.0f); + colorStructure.peGreen = (p_green * 255.0f); + colorStructure.peBlue = (p_blue * 255.0f); + colorStructure.peFlags = D3DPAL_RESERVED | PC_NOCOLLAPSE; + m_videoParam.GetPalette()->SetSkyColor(&colorStructure); m_videoParam.GetPalette()->SetOverrideSkyColor(TRUE); m_3dManager->GetLego3DView()->GetView()->SetBackgroundColor(p_red, p_green, p_blue); } From 05360b4468c1d840a83dae198544842c25f59b75 Mon Sep 17 00:00:00 2001 From: Fabian Neundorf Date: Tue, 15 Jul 2025 20:39:22 +0200 Subject: [PATCH 142/188] Clear unknowns in `TowTrack` and `TowTrackMissionState` (#1629) --- LEGO1/lego/legoomni/include/towtrack.h | 41 +++++++------ LEGO1/lego/legoomni/src/actors/towtrack.cpp | 65 +++++++++++---------- LEGO1/lego/legoomni/src/worlds/isle.cpp | 6 +- 3 files changed, 60 insertions(+), 52 deletions(-) diff --git a/LEGO1/lego/legoomni/include/towtrack.h b/LEGO1/lego/legoomni/include/towtrack.h index 812751bf..e9e41418 100644 --- a/LEGO1/lego/legoomni/include/towtrack.h +++ b/LEGO1/lego/legoomni/include/towtrack.h @@ -12,6 +12,13 @@ class MxEndActionNotificationParam; // SIZE 0x28 class TowTrackMissionState : public LegoState { public: + enum { + e_none = 0, + e_started = 1, + e_hookedUp = 2, + e_hookingUp = 3, + }; + TowTrackMissionState(); // FUNCTION: LEGO1 0x1004dde0 @@ -126,19 +133,19 @@ class TowTrackMissionState : public LegoState { // SYNTHETIC: LEGO1 0x1004e060 // TowTrackMissionState::`scalar deleting destructor' - undefined4 m_unk0x08; // 0x08 - MxLong m_startTime; // 0x0c - MxBool m_unk0x10; // 0x10 - MxS16 m_peScore; // 0x12 - MxS16 m_maScore; // 0x14 - MxS16 m_paScore; // 0x16 - MxS16 m_niScore; // 0x18 - MxS16 m_laScore; // 0x1a - MxS16 m_peHighScore; // 0x1c - MxS16 m_maHighScore; // 0x1e - MxS16 m_paHighScore; // 0x20 - MxS16 m_niHighScore; // 0x22 - MxS16 m_laHighScore; // 0x24 + MxU32 m_state; // 0x08 + MxLong m_startTime; // 0x0c + MxBool m_takingTooLong; // 0x10 + MxS16 m_peScore; // 0x12 + MxS16 m_maScore; // 0x14 + MxS16 m_paScore; // 0x16 + MxS16 m_niScore; // 0x18 + MxS16 m_laScore; // 0x1a + MxS16 m_peHighScore; // 0x1c + MxS16 m_maHighScore; // 0x1e + MxS16 m_paHighScore; // 0x20 + MxS16 m_niHighScore; // 0x22 + MxS16 m_laHighScore; // 0x24 }; // VTABLE: LEGO1 0x100d7ee0 @@ -174,10 +181,10 @@ class TowTrack : public IslePathActor { virtual MxLong HandleEndAction(MxEndActionNotificationParam& p_param); // vtable+0xf0 void CreateState(); - void FUN_1004dab0(); + void Init(); void ActivateSceneActions(); void StopActions(); - void FUN_1004dbe0(); + void Reset(); // SYNTHETIC: LEGO1 0x1004c950 // TowTrack::`scalar deleting destructor' @@ -192,8 +199,8 @@ class TowTrack : public IslePathActor { TowTrackMissionState* m_state; // 0x164 MxS16 m_unk0x168; // 0x168 MxS16 m_actorId; // 0x16a - MxS16 m_unk0x16c; // 0x16c - MxS16 m_unk0x16e; // 0x16e + MxS16 m_treeBlockageTriggered; // 0x16c + MxS16 m_speedComplaintTriggered; // 0x16e IsleScript::Script m_lastAction; // 0x170 IsleScript::Script m_lastAnimation; // 0x174 MxFloat m_fuel; // 0x178 diff --git a/LEGO1/lego/legoomni/src/actors/towtrack.cpp b/LEGO1/lego/legoomni/src/actors/towtrack.cpp index 5d62fedc..6c3f5785 100644 --- a/LEGO1/lego/legoomni/src/actors/towtrack.cpp +++ b/LEGO1/lego/legoomni/src/actors/towtrack.cpp @@ -32,9 +32,9 @@ TowTrack::TowTrack() m_unk0x168 = 0; m_actorId = -1; m_state = NULL; - m_unk0x16c = 0; + m_treeBlockageTriggered = 0; m_lastAction = IsleScript::c_noneIsle; - m_unk0x16e = 0; + m_speedComplaintTriggered = 0; m_lastAnimation = IsleScript::c_noneIsle; m_maxLinearVel = 40.0; m_fuel = 1.0; @@ -62,7 +62,7 @@ MxResult TowTrack::Create(MxDSAction& p_dsAction) m_state = (TowTrackMissionState*) GameState()->GetState("TowTrackMissionState"); if (!m_state) { m_state = new TowTrackMissionState(); - m_state->m_unk0x08 = 0; + m_state->m_state = TowTrackMissionState::e_none; GameState()->RegisterState(m_state); } } @@ -96,9 +96,10 @@ void TowTrack::Animate(float p_time) sprintf(buf, "%g", m_fuel); VariableTable()->SetVariable(g_varTOWFUEL, buf); - if (p_time - m_state->m_startTime > 100000.0f && m_state->m_unk0x08 == 1 && !m_state->m_unk0x10) { + if (p_time - m_state->m_startTime > 100000.0f && m_state->m_state == TowTrackMissionState::e_started && + !m_state->m_takingTooLong) { PlayAction(IsleScript::c_Avo909In_PlayWav); - m_state->m_unk0x10 = TRUE; + m_state->m_takingTooLong = TRUE; } } } @@ -188,7 +189,7 @@ MxLong TowTrack::HandleEndAction(MxEndActionNotificationParam& p_param) } } else if (objectId == IsleScript::c_wrt074sl_RunAnim || objectId == IsleScript::c_wrt075rh_RunAnim || objectId == IsleScript::c_wrt076df_RunAnim || objectId == IsleScript::c_wrt078ni_RunAnim) { - m_state->m_unk0x08 = 2; + m_state->m_state = TowTrackMissionState::e_hookedUp; CurrentWorld()->PlaceActor(UserActor()); HandleClick(); } @@ -297,10 +298,10 @@ MxLong TowTrack::HandlePathStruct(LegoPathStructNotificationParam& p_param) return 0; } - if (m_state->m_unk0x08 == 2 && + if (m_state->m_state == TowTrackMissionState::e_hookedUp && ((p_param.GetTrigger() == LegoPathStruct::c_camAnim && (p_param.GetData() == 9 || p_param.GetData() == 8)) || (p_param.GetTrigger() == LegoPathStruct::c_w && p_param.GetData() == 0x169))) { - m_state->m_unk0x08 = 0; + m_state->m_state = TowTrackMissionState::e_none; MxLong time = Timer()->GetTime() - m_state->m_startTime; Leave(); @@ -315,8 +316,8 @@ MxLong TowTrack::HandlePathStruct(LegoPathStructNotificationParam& p_param) PlayFinalAnimation(IsleScript::c_wgs097nu_RunAnim); } } - else if (m_state->m_unk0x08 == 1 && p_param.GetTrigger() == LegoPathStruct::c_camAnim && p_param.GetData() == 0x37) { - m_state->m_unk0x08 = 3; + else if (m_state->m_state == TowTrackMissionState::e_started && p_param.GetTrigger() == LegoPathStruct::c_camAnim && p_param.GetData() == 0x37) { + m_state->m_state = TowTrackMissionState::e_hookingUp; StopActions(); if (m_lastAction != IsleScript::c_noneIsle) { @@ -326,20 +327,20 @@ MxLong TowTrack::HandlePathStruct(LegoPathStructNotificationParam& p_param) Leave(); PlayFinalAnimation(IsleScript::c_wrt060bm_RunAnim); } - else if (p_param.GetTrigger() == LegoPathStruct::c_w && m_state->m_unk0x08 == 1) { + else if (p_param.GetTrigger() == LegoPathStruct::c_w && m_state->m_state == TowTrackMissionState::e_started) { if (p_param.GetData() == 0x15f) { - if (m_unk0x16c == 0) { - m_unk0x16c = 1; + if (m_treeBlockageTriggered == 0) { + m_treeBlockageTriggered = 1; InvokeAction(Extra::e_start, *g_isleScript, IsleScript::c_wns050p1_RunAnim, NULL); } } else if (p_param.GetData() == 0x160) { - if (m_unk0x16e == 0) { - m_unk0x16e = 1; + if (m_speedComplaintTriggered == 0) { + m_speedComplaintTriggered = 1; InvokeAction(Extra::e_start, *g_isleScript, IsleScript::c_wns046mg_RunAnim, NULL); } - if (!m_state->m_unk0x10 && m_lastAction == IsleScript::c_noneIsle) { + if (!m_state->m_takingTooLong && m_lastAction == IsleScript::c_noneIsle) { if (m_actorId < LegoActor::c_pepper || m_actorId > LegoActor::c_laura) { m_actorId = LegoActor::c_laura; } @@ -407,7 +408,7 @@ MxLong TowTrack::HandleClick() return 1; } - if (m_state->m_unk0x08 == 3) { + if (m_state->m_state == TowTrackMissionState::e_hookingUp) { return 1; } @@ -426,11 +427,11 @@ MxLong TowTrack::HandleClick() InvokeAction(Extra::e_start, *g_isleScript, IsleScript::c_TowTrackDashboard, NULL); ControlManager()->Register(this); - if (m_state->m_unk0x08 == 0) { + if (m_state->m_state == TowTrackMissionState::e_none) { return 1; } - if (m_state->m_unk0x08 == 2) { + if (m_state->m_state == TowTrackMissionState::e_hookedUp) { SpawnPlayer(LegoGameState::e_unk52, TRUE, 0); FindROI("rcred")->SetVisibility(FALSE); } @@ -439,7 +440,7 @@ MxLong TowTrack::HandleClick() m_lastAction = IsleScript::c_noneIsle; m_lastAnimation = IsleScript::c_noneIsle; m_state->m_startTime = Timer()->GetTime(); - m_state->m_unk0x10 = FALSE; + m_state->m_takingTooLong = 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); @@ -457,7 +458,7 @@ void TowTrack::Exit() { GameState()->m_currentArea = LegoGameState::e_garageExterior; StopActions(); - FUN_1004dbe0(); + Reset(); Leave(); } @@ -507,9 +508,9 @@ MxLong TowTrack::HandleControl(LegoControlManagerNotificationParam& p_param) } // FUNCTION: LEGO1 0x1004dab0 -void TowTrack::FUN_1004dab0() +void TowTrack::Init() { - m_state->m_unk0x08 = 1; + m_state->m_state = TowTrackMissionState::e_started; HandleClick(); } @@ -518,8 +519,8 @@ void TowTrack::ActivateSceneActions() { PlayMusic(JukeboxScript::c_JBMusic2); - if (m_state->m_unk0x08 != 0) { - if (m_state->m_unk0x08 == 2) { + if (m_state->m_state != TowTrackMissionState::e_none) { + if (m_state->m_state == TowTrackMissionState::e_hookedUp) { PlayAction(IsleScript::c_wrt082na_PlayWav); } else { @@ -543,22 +544,22 @@ void TowTrack::StopActions() } // FUNCTION: LEGO1 0x1004dbe0 -void TowTrack::FUN_1004dbe0() +void TowTrack::Reset() { if (m_lastAction != -1) { InvokeAction(Extra::e_stop, *g_isleScript, m_lastAction, NULL); } ((Act1State*) GameState()->GetState("Act1State"))->m_state = Act1State::e_none; - m_state->m_unk0x08 = 0; + m_state->m_state = TowTrackMissionState::e_none; g_isleFlags |= Isle::c_playMusic; AnimationManager()->EnableCamAnims(TRUE); AnimationManager()->FUN_1005f6d0(TRUE); m_state->m_startTime = INT_MIN; - m_state->m_unk0x10 = FALSE; + m_state->m_takingTooLong = FALSE; m_state = NULL; - m_unk0x16c = 0; - m_unk0x16e = 0; + m_treeBlockageTriggered = 0; + m_speedComplaintTriggered = 0; } // FUNCTION: LEGO1 0x1004dc80 @@ -593,9 +594,9 @@ void TowTrack::PlayAction(IsleScript::Script p_objectId) // FUNCTION: LEGO1 0x1004dd30 TowTrackMissionState::TowTrackMissionState() { - m_unk0x08 = 0; + m_state = TowTrackMissionState::e_none; m_startTime = 0; - m_unk0x10 = FALSE; + m_takingTooLong = FALSE; m_peScore = 0; m_maScore = 0; m_paScore = 0; diff --git a/LEGO1/lego/legoomni/src/worlds/isle.cpp b/LEGO1/lego/legoomni/src/worlds/isle.cpp index aeedd601..3ec9af8c 100644 --- a/LEGO1/lego/legoomni/src/worlds/isle.cpp +++ b/LEGO1/lego/legoomni/src/worlds/isle.cpp @@ -801,7 +801,7 @@ void Isle::Enable(MxBool p_enable) AnimationManager()->EnableCamAnims(FALSE); g_isleFlags &= ~c_playMusic; - m_towtrack->FUN_1004dab0(); + m_towtrack->Init(); break; case Act1State::e_transitionToAmbulance: m_act1state->m_state = Act1State::e_ambulance; @@ -1203,7 +1203,7 @@ MxBool Isle::Escape() case Act1State::e_towtrack: if (UserActor() != NULL && !UserActor()->IsA("TowTrack")) { m_towtrack->StopActions(); - m_towtrack->FUN_1004dbe0(); + m_towtrack->Reset(); } break; case Act1State::e_ambulance: @@ -1257,7 +1257,7 @@ void Isle::FUN_10033350() if (m_act1state->m_state == Act1State::e_towtrack) { if (UserActor() != NULL && !UserActor()->IsA("TowTrack")) { m_towtrack->StopActions(); - m_towtrack->FUN_1004dbe0(); + m_towtrack->Reset(); } } From 3543f5f4944eabb654b75560c96a6fb67928a5ad Mon Sep 17 00:00:00 2001 From: Christian Semmler Date: Tue, 15 Jul 2025 12:16:38 -0700 Subject: [PATCH 143/188] Fix CONFIG app after isle/master merge --- CONFIG/MainDlg.cpp | 2 +- CONFIG/config.cpp | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/CONFIG/MainDlg.cpp b/CONFIG/MainDlg.cpp index dfba47ea..1b93f8d9 100644 --- a/CONFIG/MainDlg.cpp +++ b/CONFIG/MainDlg.cpp @@ -98,7 +98,7 @@ bool CMainDialog::OnInitDialog() int device_i = 0; int selected = 0; char device_name[256]; - const list& driver_list = enumerator->GetDriverList(); + const list& driver_list = enumerator->m_ddInfo; for (list::const_iterator it_driver = driver_list.begin(); it_driver != driver_list.end(); it_driver++) { const MxDriver& driver = *it_driver; for (list::const_iterator it_device = driver.m_devices.begin(); diff --git a/CONFIG/config.cpp b/CONFIG/config.cpp index c88f779b..aaca44e7 100644 --- a/CONFIG/config.cpp +++ b/CONFIG/config.cpp @@ -126,7 +126,7 @@ D3DCOLORMODEL CConfigApp::GetHardwareDeviceColorModel() const // FUNCTION: CONFIG 0x00403410 bool CConfigApp::IsPrimaryDriver() const { - return m_driver == &m_device_enumerator->GetDriverList().front(); + return m_driver == &m_device_enumerator->m_ddInfo.front(); } // FUNCTION: CONFIG 0x00403430 From 87c89885baf9de0d1cdc7e60b42e4b6fde55de98 Mon Sep 17 00:00:00 2001 From: Christian Semmler Date: Tue, 15 Jul 2025 13:21:41 -0700 Subject: [PATCH 144/188] For MaxLOD values > 5, disable LOD threshold and view limits (#609) --- CONFIG/res/maindialog.ui | 2 +- LEGO1/viewmanager/viewmanager.cpp | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/CONFIG/res/maindialog.ui b/CONFIG/res/maindialog.ui index 526c31d8..f51f70a5 100644 --- a/CONFIG/res/maindialog.ui +++ b/CONFIG/res/maindialog.ui @@ -291,7 +291,7 @@ A higher setting will cause higher quality textures to be drawn regardless of di - 50 + 60 5 diff --git a/LEGO1/viewmanager/viewmanager.cpp b/LEGO1/viewmanager/viewmanager.cpp index fd0d4a84..a6f4aaba 100644 --- a/LEGO1/viewmanager/viewmanager.cpp +++ b/LEGO1/viewmanager/viewmanager.cpp @@ -235,7 +235,7 @@ inline void ViewManager::ManageVisibilityAndDetailRecursively(ViewROI* p_from, i if (p_from->GetWorldBoundingSphere().Radius() > 0.001F) { float projectedSize = ProjectedSize(p_from->GetWorldBoundingSphere()); - if (projectedSize < seconds_allowed * g_viewDistance) { + if (RealtimeView::GetUserMaxLOD() <= 5.0f && projectedSize < seconds_allowed * g_viewDistance) { if (p_from->GetLodLevel() != ViewROI::c_lodLevelInvisible) { ManageVisibilityAndDetailRecursively(p_from, ViewROI::c_lodLevelInvisible); } @@ -361,7 +361,7 @@ inline int ViewManager::CalculateLODLevel(float p_maximumScale, float p_initialS assert(from); if (GetFirstLODIndex(from) != 0) { - if (p_maximumScale < g_minLODThreshold) { + if (RealtimeView::GetUserMaxLOD() <= 5.0f && p_maximumScale < g_minLODThreshold) { return 0; } else { From f20fc475c7abf80dcfcf15ddaff2b3de4859a034 Mon Sep 17 00:00:00 2001 From: Christian Semmler Date: Tue, 15 Jul 2025 14:47:37 -0700 Subject: [PATCH 145/188] Integrate SDL Haptic API (#607) * Integrate SDL Haptic API * Close other devices * Fixes --- ISLE/isleapp.cpp | 54 +++- ISLE/isleapp.h | 2 - .../lego/legoomni/include/legoinputmanager.h | 29 +- .../legoomni/src/input/legoinputmanager.cpp | 284 +++++++++++++----- LEGO1/lego/sources/misc/legoutil.h | 8 + tools/ncc/skip.yml | 3 + 6 files changed, 284 insertions(+), 96 deletions(-) diff --git a/ISLE/isleapp.cpp b/ISLE/isleapp.cpp index 867f1744..5a071664 100644 --- a/ISLE/isleapp.cpp +++ b/ISLE/isleapp.cpp @@ -137,8 +137,6 @@ IsleApp::IsleApp() m_drawCursor = FALSE; m_use3dSound = TRUE; m_useMusic = TRUE; - m_useJoystick = TRUE; - m_joystickIndex = 0; m_wideViewAngle = TRUE; m_islandQuality = 2; m_islandTexture = 1; @@ -297,7 +295,7 @@ SDL_AppResult SDL_AppInit(void** appstate, int argc, char** argv) SDL_SetHint(SDL_HINT_MOUSE_TOUCH_EVENTS, "0"); SDL_SetHint(SDL_HINT_TOUCH_MOUSE_EVENTS, "0"); - if (!SDL_Init(SDL_INIT_VIDEO | SDL_INIT_AUDIO | SDL_INIT_GAMEPAD)) { + if (!SDL_Init(SDL_INIT_VIDEO | SDL_INIT_AUDIO | SDL_INIT_GAMEPAD | SDL_INIT_HAPTIC)) { char buffer[256]; SDL_snprintf( buffer, @@ -340,6 +338,23 @@ SDL_AppResult SDL_AppInit(void** appstate, int argc, char** argv) // Get reference to window *appstate = g_isle->GetWindowHandle(); + // Currently, SDL doesn't send SDL_EVENT_MOUSE_ADDED at startup (unlike for gamepads) + // This will probably be fixed in the future: https://github.com/libsdl-org/SDL/issues/12815 + { + int count; + SDL_MouseID* mice = SDL_GetMice(&count); + + if (mice) { + for (int i = 0; i < count; i++) { + if (InputManager()) { + InputManager()->AddMouse(mice[i]); + } + } + + SDL_free(mice); + } + } + #ifdef __EMSCRIPTEN__ SDL_AddEventWatch( [](void* userdata, SDL_Event* event) -> bool { @@ -423,6 +438,10 @@ SDL_AppResult SDL_AppEvent(void* appstate, SDL_Event* event) return SDL_APP_CONTINUE; } + if (InputManager()) { + InputManager()->UpdateLastInputMethod(event); + } + switch (event->type) { case SDL_EVENT_WINDOW_PIXEL_SIZE_CHANGED: case SDL_EVENT_MOUSE_MOTION: @@ -482,13 +501,26 @@ SDL_AppResult SDL_AppEvent(void* appstate, SDL_Event* event) } break; } - case SDL_EVENT_GAMEPAD_ADDED: - case SDL_EVENT_GAMEPAD_REMOVED: { + case SDL_EVENT_MOUSE_ADDED: if (InputManager()) { - InputManager()->GetJoystick(); + InputManager()->AddMouse(event->mdevice.which); + } + break; + case SDL_EVENT_MOUSE_REMOVED: + if (InputManager()) { + InputManager()->RemoveMouse(event->mdevice.which); + } + break; + case SDL_EVENT_GAMEPAD_ADDED: + if (InputManager()) { + InputManager()->AddJoystick(event->jdevice.which); + } + break; + case SDL_EVENT_GAMEPAD_REMOVED: + if (InputManager()) { + InputManager()->RemoveJoystick(event->jdevice.which); } break; - } case SDL_EVENT_GAMEPAD_BUTTON_DOWN: { switch (event->gbutton.button) { case SDL_GAMEPAD_BUTTON_DPAD_UP: @@ -903,10 +935,6 @@ MxResult IsleApp::SetupWindow() MxTransitionManager::configureMxTransitionManager(m_transitionType); RealtimeView::SetUserMaxLOD(m_maxLod); if (LegoOmni::GetInstance()) { - if (LegoOmni::GetInstance()->GetInputManager()) { - LegoOmni::GetInstance()->GetInputManager()->SetUseJoystick(m_useJoystick); - LegoOmni::GetInstance()->GetInputManager()->SetJoystickIndex(m_joystickIndex); - } if (LegoOmni::GetInstance()->GetVideoManager()) { LegoOmni::GetInstance()->GetVideoManager()->SetCursorBitmap(m_cursorCurrentBitmap); } @@ -998,8 +1026,6 @@ bool IsleApp::LoadConfig() iniparser_set(dict, "isle:3DSound", m_use3dSound ? "true" : "false"); iniparser_set(dict, "isle:Music", m_useMusic ? "true" : "false"); - iniparser_set(dict, "isle:UseJoystick", m_useJoystick ? "true" : "false"); - iniparser_set(dict, "isle:JoystickIndex", SDL_itoa(m_joystickIndex, buf, 10)); SDL_snprintf(buf, sizeof(buf), "%f", m_cursorSensitivity); iniparser_set(dict, "isle:Cursor Sensitivity", buf); @@ -1058,8 +1084,6 @@ bool IsleApp::LoadConfig() m_wideViewAngle = iniparser_getboolean(dict, "isle:Wide View Angle", m_wideViewAngle); m_use3dSound = iniparser_getboolean(dict, "isle:3DSound", m_use3dSound); m_useMusic = iniparser_getboolean(dict, "isle:Music", m_useMusic); - m_useJoystick = iniparser_getboolean(dict, "isle:UseJoystick", m_useJoystick); - m_joystickIndex = iniparser_getint(dict, "isle:JoystickIndex", m_joystickIndex); m_cursorSensitivity = iniparser_getdouble(dict, "isle:Cursor Sensitivity", m_cursorSensitivity); MxS32 backBuffersInVRAM = iniparser_getboolean(dict, "isle:Back Buffers in Video RAM", -1); diff --git a/ISLE/isleapp.h b/ISLE/isleapp.h index 4bec7dd6..cfe11bf0 100644 --- a/ISLE/isleapp.h +++ b/ISLE/isleapp.h @@ -80,8 +80,6 @@ class IsleApp { MxS32 m_hasLightSupport; // 0x24 MxS32 m_use3dSound; // 0x28 MxS32 m_useMusic; // 0x2c - MxS32 m_useJoystick; // 0x30 - MxS32 m_joystickIndex; // 0x34 MxS32 m_wideViewAngle; // 0x38 MxS32 m_islandQuality; // 0x3c MxS32 m_islandTexture; // 0x40 diff --git a/LEGO1/lego/legoomni/include/legoinputmanager.h b/LEGO1/lego/legoomni/include/legoinputmanager.h index ebb3b352..b86cd137 100644 --- a/LEGO1/lego/legoomni/include/legoinputmanager.h +++ b/LEGO1/lego/legoomni/include/legoinputmanager.h @@ -8,6 +8,7 @@ #include "mxpresenter.h" #include "mxqueue.h" +#include #include #include #include @@ -19,6 +20,7 @@ #endif #include +#include class LegoCameraController; class LegoControlManager; @@ -129,8 +131,6 @@ class LegoInputManager : public MxPresenter { void SetUnknown88(MxBool p_unk0x88) { m_unk0x88 = p_unk0x88; } void SetUnknown335(MxBool p_unk0x335) { m_unk0x335 = p_unk0x335; } void SetUnknown336(MxBool p_unk0x336) { m_unk0x336 = p_unk0x336; } - void SetUseJoystick(MxBool p_useJoystick) { m_useJoystick = p_useJoystick; } - void SetJoystickIndex(MxS32 p_joystickIndex) { m_joystickIndex = p_joystickIndex; } // FUNCTION: BETA10 0x1002e290 void DisableInputProcessing() @@ -153,13 +153,26 @@ class LegoInputManager : public MxPresenter { void GetKeyboardState(); MxResult GetNavigationKeyStates(MxU32& p_keyFlags); MxResult GetNavigationTouchStates(MxU32& p_keyFlags); + LEGO1_EXPORT void AddMouse(SDL_MouseID p_mouseID); + LEGO1_EXPORT void RemoveMouse(SDL_MouseID p_mouseID); + LEGO1_EXPORT void AddJoystick(SDL_JoystickID p_joystickID); + LEGO1_EXPORT void RemoveJoystick(SDL_JoystickID p_joystickID); LEGO1_EXPORT MxBool HandleTouchEvent(SDL_Event* p_event, TouchScheme p_touchScheme); LEGO1_EXPORT MxBool HandleRumbleEvent(); + LEGO1_EXPORT void UpdateLastInputMethod(SDL_Event* p_event); // SYNTHETIC: LEGO1 0x1005b8d0 // LegoInputManager::`scalar deleting destructor' private: + // clang-format off + enum class SDL_MouseID_v : SDL_MouseID {}; + enum class SDL_JoystickID_v : SDL_JoystickID {}; + enum class SDL_TouchID_v : SDL_TouchID {}; + // clang-format on + + void InitializeHaptics(); + MxCriticalSection m_criticalSection; // 0x58 LegoNotifyList* m_keyboardNotifyList; // 0x5c LegoCameraController* m_camera; // 0x60 @@ -176,16 +189,16 @@ class LegoInputManager : public MxPresenter { MxBool m_unk0x88; // 0x88 const bool* m_keyboardState; MxBool m_unk0x195; // 0x195 - SDL_JoystickID* m_joyids; - SDL_Gamepad* m_joystick; - MxS32 m_joystickIndex; // 0x19c - MxBool m_useJoystick; // 0x334 - MxBool m_unk0x335; // 0x335 - MxBool m_unk0x336; // 0x336 + MxBool m_unk0x335; // 0x335 + MxBool m_unk0x336; // 0x336 std::map m_touchOrigins; std::map m_touchFlags; std::map> m_touchLastMotion; + std::map> m_mice; + std::map> m_joysticks; + std::map m_otherHaptics; + std::variant m_lastInputMethod; }; // TEMPLATE: LEGO1 0x10028850 diff --git a/LEGO1/lego/legoomni/src/input/legoinputmanager.cpp b/LEGO1/lego/legoomni/src/input/legoinputmanager.cpp index 7c80e78e..432b4fe7 100644 --- a/LEGO1/lego/legoomni/src/input/legoinputmanager.cpp +++ b/LEGO1/lego/legoomni/src/input/legoinputmanager.cpp @@ -11,6 +11,8 @@ #include "mxdebug.h" #include "roi/legoroi.h" +#include + DECOMP_SIZE_ASSERT(LegoInputManager, 0x338) DECOMP_SIZE_ASSERT(LegoNotifyList, 0x18) DECOMP_SIZE_ASSERT(LegoNotifyListCursor, 0x10) @@ -40,10 +42,6 @@ LegoInputManager::LegoInputManager() m_unk0x81 = FALSE; m_unk0x88 = FALSE; m_unk0x195 = 0; - m_joyids = NULL; - m_joystickIndex = -1; - m_joystick = NULL; - m_useJoystick = FALSE; m_unk0x335 = FALSE; m_unk0x336 = FALSE; m_unk0x74 = 0x19; @@ -71,8 +69,6 @@ MxResult LegoInputManager::Create(HWND p_hwnd) m_eventQueue = new LegoEventQueue; } - GetJoystick(); - if (!m_keyboardNotifyList || !m_eventQueue) { Destroy(); result = FAILURE; @@ -98,7 +94,23 @@ void LegoInputManager::Destroy() delete m_controlManager; } - SDL_free(m_joyids); + for (const auto& [id, joystick] : m_joysticks) { + if (joystick.second) { + SDL_CloseHaptic(joystick.second); + } + + SDL_CloseGamepad(joystick.first); + } + + for (const auto& [id, mouse] : m_mice) { + if (mouse.second) { + SDL_CloseHaptic(mouse.second); + } + } + + for (const auto& [id, haptic] : m_otherHaptics) { + SDL_CloseHaptic(haptic); + } } // FUNCTION: LEGO1 0x1005c0f0 @@ -145,74 +157,33 @@ MxResult LegoInputManager::GetNavigationKeyStates(MxU32& p_keyFlags) return SUCCESS; } -// FUNCTION: LEGO1 0x1005c240 -MxResult LegoInputManager::GetJoystick() -{ - if (m_joystick != NULL && SDL_GamepadConnected(m_joystick) == TRUE) { - return SUCCESS; - } - - MxS32 numJoysticks = 0; - if (m_joyids != NULL) { - SDL_free(m_joyids); - m_joyids = NULL; - } - m_joyids = SDL_GetGamepads(&numJoysticks); - - if (m_useJoystick != FALSE && numJoysticks != 0) { - MxS32 joyid = m_joystickIndex; - if (joyid >= 0) { - m_joystick = SDL_OpenGamepad(m_joyids[joyid]); - if (m_joystick != NULL) { - return SUCCESS; - } - } - - for (joyid = 0; joyid < numJoysticks; joyid++) { - m_joystick = SDL_OpenGamepad(m_joyids[joyid]); - if (m_joystick != NULL) { - return SUCCESS; - } - } - } - - return FAILURE; -} - // FUNCTION: LEGO1 0x1005c320 MxResult LegoInputManager::GetJoystickState(MxU32* p_joystickX, MxU32* p_joystickY, MxU32* p_povPosition) { - if (m_useJoystick != FALSE) { - if (GetJoystick() == -1) { - if (m_joystick != NULL) { - // GetJoystick() failed but handle to joystick is still open, close it - SDL_CloseGamepad(m_joystick); - m_joystick = NULL; - } + if (m_joysticks.empty()) { + return FAILURE; + } - return FAILURE; - } - - MxS16 xPos = SDL_GetGamepadAxis(m_joystick, SDL_GAMEPAD_AXIS_LEFTX); - MxS16 yPos = SDL_GetGamepadAxis(m_joystick, SDL_GAMEPAD_AXIS_LEFTY); + MxS16 xPos, yPos = 0; + for (const auto& [id, joystick] : m_joysticks) { + xPos = SDL_GetGamepadAxis(joystick.first, SDL_GAMEPAD_AXIS_LEFTX); + yPos = SDL_GetGamepadAxis(joystick.first, SDL_GAMEPAD_AXIS_LEFTY); if (xPos > -8000 && xPos < 8000) { - // Ignore small axis values xPos = 0; } if (yPos > -8000 && yPos < 8000) { - // Ignore small axis values yPos = 0; } - // normalize values acquired from joystick axes - *p_joystickX = ((xPos + 32768) * 100) / 65535; - *p_joystickY = ((yPos + 32768) * 100) / 65535; - *p_povPosition = -1; - - return SUCCESS; + if (xPos || yPos) { + break; + } } - return FAILURE; + *p_joystickX = ((xPos + 32768) * 100) / 65535; + *p_joystickY = ((yPos + 32768) * 100) / 65535; + *p_povPosition = -1; + return SUCCESS; } // FUNCTION: LEGO1 0x1005c470 @@ -546,6 +517,74 @@ MxResult LegoInputManager::GetNavigationTouchStates(MxU32& p_keyStates) return SUCCESS; } +void LegoInputManager::AddMouse(SDL_MouseID p_mouseID) +{ + if (m_mice.count(p_mouseID)) { + return; + } + + // Currently no way to get an individual haptic device for a mouse. + SDL_Haptic* haptic = SDL_OpenHapticFromMouse(); + if (haptic) { + if (!SDL_InitHapticRumble(haptic)) { + SDL_CloseHaptic(haptic); + haptic = nullptr; + } + } + + m_mice[p_mouseID] = {nullptr, haptic}; +} + +void LegoInputManager::RemoveMouse(SDL_MouseID p_mouseID) +{ + if (!m_mice.count(p_mouseID)) { + return; + } + + if (m_mice[p_mouseID].second) { + SDL_CloseHaptic(m_mice[p_mouseID].second); + } + + m_mice.erase(p_mouseID); +} + +void LegoInputManager::AddJoystick(SDL_JoystickID p_joystickID) +{ + if (m_joysticks.count(p_joystickID)) { + return; + } + + SDL_Gamepad* joystick = SDL_OpenGamepad(p_joystickID); + if (joystick) { + SDL_Haptic* haptic = SDL_OpenHapticFromJoystick(SDL_GetGamepadJoystick(joystick)); + if (haptic) { + if (!SDL_InitHapticRumble(haptic)) { + SDL_CloseHaptic(haptic); + haptic = nullptr; + } + } + + m_joysticks[p_joystickID] = {joystick, haptic}; + } + else { + SDL_Log("Failed to open gamepad: %s", SDL_GetError()); + } +} + +void LegoInputManager::RemoveJoystick(SDL_JoystickID p_joystickID) +{ + if (!m_joysticks.count(p_joystickID)) { + return; + } + + if (m_joysticks[p_joystickID].second) { + SDL_CloseHaptic(m_joysticks[p_joystickID].second); + } + + SDL_CloseGamepad(m_joysticks[p_joystickID].first); + m_joysticks.erase(p_joystickID); +} + MxBool LegoInputManager::HandleTouchEvent(SDL_Event* p_event, TouchScheme p_touchScheme) { const SDL_TouchFingerEvent& event = p_event->tfinger; @@ -629,15 +668,118 @@ MxBool LegoInputManager::HandleTouchEvent(SDL_Event* p_event, TouchScheme p_touc MxBool LegoInputManager::HandleRumbleEvent() { - if (m_joystick != NULL && SDL_GamepadConnected(m_joystick) == TRUE) { - const Uint16 frequency = 65535 / 2; - const Uint32 durationMs = 700; - SDL_RumbleGamepad(m_joystick, frequency, frequency, durationMs); - } - else { - return FALSE; + static bool g_hapticsInitialized = false; + + if (!g_hapticsInitialized) { + InitializeHaptics(); + g_hapticsInitialized = true; } - // Add support for SDL Haptic API - return TRUE; + SDL_Haptic* haptic = nullptr; + std::visit( + overloaded{ + [&haptic, this](SDL_MouseID_v p_id) { + if (m_mice.count((SDL_MouseID) p_id)) { + haptic = m_mice[(SDL_MouseID) p_id].second; + } + }, + [&haptic, this](SDL_JoystickID_v p_id) { + if (m_joysticks.count((SDL_JoystickID) p_id)) { + haptic = m_joysticks[(SDL_JoystickID) p_id].second; + } + }, + [&haptic, this](SDL_TouchID_v p_id) { + // We can't currently correlate Touch devices with Haptic devices + if (!m_otherHaptics.empty()) { + haptic = m_otherHaptics.begin()->second; + } + } + }, + m_lastInputMethod + ); + + const float strength = 0.5f; + const Uint32 durationMs = 700; + + if (haptic) { + return SDL_PlayHapticRumble(haptic, strength, durationMs); + } + + // A joystick isn't necessarily a haptic device; try basic rumble instead + if (const SDL_JoystickID_v* joystick = std::get_if(&m_lastInputMethod)) { + if (m_joysticks.count((SDL_JoystickID) *joystick)) { + return SDL_RumbleGamepad( + m_joysticks[(SDL_JoystickID) *joystick].first, + strength * 65535, + strength * 65535, + durationMs + ); + } + } + + return FALSE; +} + +void LegoInputManager::InitializeHaptics() +{ + // We don't get added/removed events for haptic devices that are not joysticks or mice, + // so we initialize "otherHaptics" once at this point. + std::vector existingHaptics; + + for (const auto& [id, mouse] : m_mice) { + if (mouse.second) { + existingHaptics.push_back(SDL_GetHapticID(mouse.second)); + } + } + + for (const auto& [id, joystick] : m_joysticks) { + if (joystick.second) { + existingHaptics.push_back(SDL_GetHapticID(joystick.second)); + } + } + + int count; + SDL_HapticID* haptics = SDL_GetHaptics(&count); + if (haptics) { + for (int i = 0; i < count; i++) { + if (std::find(existingHaptics.begin(), existingHaptics.end(), haptics[i]) == existingHaptics.end()) { + SDL_Haptic* haptic = SDL_OpenHaptic(haptics[i]); + if (haptic) { + if (SDL_InitHapticRumble(haptic)) { + m_otherHaptics[haptics[i]] = haptic; + } + else { + SDL_CloseHaptic(haptic); + } + } + } + } + + SDL_free(haptics); + } +} + +void LegoInputManager::UpdateLastInputMethod(SDL_Event* p_event) +{ + switch (p_event->type) { + case SDL_EVENT_MOUSE_BUTTON_DOWN: + case SDL_EVENT_MOUSE_BUTTON_UP: + m_lastInputMethod = SDL_MouseID_v{p_event->button.which}; + break; + case SDL_EVENT_MOUSE_MOTION: + m_lastInputMethod = SDL_MouseID_v{p_event->motion.which}; + break; + case SDL_EVENT_GAMEPAD_BUTTON_DOWN: + case SDL_EVENT_GAMEPAD_BUTTON_UP: + m_lastInputMethod = SDL_JoystickID_v{p_event->gbutton.which}; + break; + case SDL_EVENT_GAMEPAD_AXIS_MOTION: + m_lastInputMethod = SDL_JoystickID_v{p_event->gaxis.which}; + break; + case SDL_EVENT_FINGER_MOTION: + case SDL_EVENT_FINGER_DOWN: + case SDL_EVENT_FINGER_UP: + m_lastInputMethod = SDL_TouchID_v{p_event->tfinger.touchID}; + break; + } } diff --git a/LEGO1/lego/sources/misc/legoutil.h b/LEGO1/lego/sources/misc/legoutil.h index 871a9b72..78f52d0f 100644 --- a/LEGO1/lego/sources/misc/legoutil.h +++ b/LEGO1/lego/sources/misc/legoutil.h @@ -59,4 +59,12 @@ inline T RToD(T p_r) return p_r * 180.0F / 3.1416F; } +template +struct overloaded : Ts... { + using Ts::operator()...; +}; + +template +overloaded(Ts...) -> overloaded; + #endif // __LEGOUTIL_H diff --git a/tools/ncc/skip.yml b/tools/ncc/skip.yml index c5ceef72..2bae0ab4 100644 --- a/tools/ncc/skip.yml +++ b/tools/ncc/skip.yml @@ -74,3 +74,6 @@ cksize: "Re-defined Windows name" fccType: "Re-defined Windows name" dwDataOffset: "Re-defined Windows name" fccType: "Re-defined Windows name" +SDL_MouseID_v: "SDL-based name" +SDL_JoystickID_v: "SDL-based name" +SDL_TouchID_v: "SDL-based name" \ No newline at end of file From d0dc595fc52cbc954cf96a3fb8a73a6120e059a1 Mon Sep 17 00:00:00 2001 From: Christian Semmler Date: Tue, 15 Jul 2025 15:18:55 -0700 Subject: [PATCH 146/188] (Touch controls) Use full virtual joystick instead of hybrid (#612) * Test touch joystick * Change radius --- .../lego/legoomni/include/legoinputmanager.h | 6 +- .../legoomni/src/input/legoinputmanager.cpp | 78 ++++++++----------- 2 files changed, 38 insertions(+), 46 deletions(-) diff --git a/LEGO1/lego/legoomni/include/legoinputmanager.h b/LEGO1/lego/legoomni/include/legoinputmanager.h index b86cd137..bbe0a2b5 100644 --- a/LEGO1/lego/legoomni/include/legoinputmanager.h +++ b/LEGO1/lego/legoomni/include/legoinputmanager.h @@ -94,6 +94,7 @@ class LegoInputManager : public MxPresenter { }; enum TouchScheme { + e_none = -1, e_mouse = 0, e_arrowKeys, e_gamepad, @@ -192,9 +193,10 @@ class LegoInputManager : public MxPresenter { MxBool m_unk0x335; // 0x335 MxBool m_unk0x336; // 0x336 - std::map m_touchOrigins; + TouchScheme m_touchScheme = e_none; + SDL_Point m_touchVirtualThumb = {0, 0}; + SDL_FPoint m_touchVirtualThumbOrigin; std::map m_touchFlags; - std::map> m_touchLastMotion; std::map> m_mice; std::map> m_joysticks; std::map m_otherHaptics; diff --git a/LEGO1/lego/legoomni/src/input/legoinputmanager.cpp b/LEGO1/lego/legoomni/src/input/legoinputmanager.cpp index 432b4fe7..1bb0295d 100644 --- a/LEGO1/lego/legoomni/src/input/legoinputmanager.cpp +++ b/LEGO1/lego/legoomni/src/input/legoinputmanager.cpp @@ -160,7 +160,7 @@ MxResult LegoInputManager::GetNavigationKeyStates(MxU32& p_keyFlags) // FUNCTION: LEGO1 0x1005c320 MxResult LegoInputManager::GetJoystickState(MxU32* p_joystickX, MxU32* p_joystickY, MxU32* p_povPosition) { - if (m_joysticks.empty()) { + if (m_joysticks.empty() && m_touchScheme != e_gamepad) { return FAILURE; } @@ -180,6 +180,11 @@ MxResult LegoInputManager::GetJoystickState(MxU32* p_joystickX, MxU32* p_joystic } } + if (!xPos && !yPos) { + xPos = m_touchVirtualThumb.x; + yPos = m_touchVirtualThumb.y; + } + *p_joystickX = ((xPos + 32768) * 100) / 65535; *p_joystickY = ((yPos + 32768) * 100) / 65535; *p_povPosition = -1; @@ -499,18 +504,9 @@ void LegoInputManager::EnableInputProcessing() MxResult LegoInputManager::GetNavigationTouchStates(MxU32& p_keyStates) { - for (auto& [fingerID, touchFlags] : m_touchFlags) { - p_keyStates |= touchFlags; - - // We need to clear these as they are not meant to be persistent in e_gamepad mode. - if (m_touchOrigins.count(fingerID) && m_touchLastMotion.count(fingerID)) { - const MxU32 inactivityThreshold = 3; - - if (m_touchLastMotion[fingerID].first++ > inactivityThreshold) { - touchFlags &= ~c_left; - touchFlags &= ~c_right; - m_touchOrigins[fingerID].x = m_touchLastMotion[fingerID].second.x; - } + if (m_touchScheme == e_arrowKeys) { + for (auto& [fingerID, touchFlags] : m_touchFlags) { + p_keyStates |= touchFlags; } } @@ -588,6 +584,7 @@ void LegoInputManager::RemoveJoystick(SDL_JoystickID p_joystickID) MxBool LegoInputManager::HandleTouchEvent(SDL_Event* p_event, TouchScheme p_touchScheme) { const SDL_TouchFingerEvent& event = p_event->tfinger; + m_touchScheme = p_touchScheme; switch (p_touchScheme) { case e_mouse: @@ -619,49 +616,42 @@ MxBool LegoInputManager::HandleTouchEvent(SDL_Event* p_event, TouchScheme p_touc break; } break; - case e_gamepad: + case e_gamepad: { + static SDL_FingerID g_finger = (SDL_FingerID) 0; + switch (p_event->type) { case SDL_EVENT_FINGER_DOWN: - m_touchOrigins[event.fingerID] = {event.x, event.y}; + if (!g_finger) { + g_finger = event.fingerID; + m_touchVirtualThumb = {0, 0}; + m_touchVirtualThumbOrigin = {event.x, event.y}; + } break; case SDL_EVENT_FINGER_UP: - m_touchOrigins.erase(event.fingerID); - m_touchFlags.erase(event.fingerID); + if (event.fingerID == g_finger) { + g_finger = 0; + m_touchVirtualThumb = {0, 0}; + m_touchVirtualThumbOrigin = {0, 0}; + } break; case SDL_EVENT_FINGER_MOTION: - if (m_touchOrigins.count(event.fingerID)) { - const float activationThreshold = 0.03f; - m_touchFlags[event.fingerID] &= ~c_down; - m_touchFlags[event.fingerID] &= ~c_up; + if (event.fingerID == g_finger) { + const float thumbstickRadius = 0.25f; + const float deltaX = + SDL_clamp(event.x - m_touchVirtualThumbOrigin.x, -thumbstickRadius, thumbstickRadius); + const float deltaY = + SDL_clamp(event.y - m_touchVirtualThumbOrigin.y, -thumbstickRadius, thumbstickRadius); - const float deltaY = event.y - m_touchOrigins[event.fingerID].y; - if (SDL_fabsf(deltaY) > activationThreshold) { - if (deltaY > 0) { - m_touchFlags[event.fingerID] |= c_down; - } - else if (deltaY < 0) { - m_touchFlags[event.fingerID] |= c_up; - } - } - - const float deltaX = event.x - m_touchOrigins[event.fingerID].x; - if (SDL_fabsf(deltaX) > activationThreshold && event.dx) { - if (event.dx > 0) { - m_touchFlags[event.fingerID] |= c_right; - m_touchFlags[event.fingerID] &= ~c_left; - } - else if (event.dx < 0) { - m_touchFlags[event.fingerID] |= c_left; - m_touchFlags[event.fingerID] &= ~c_right; - } - - m_touchLastMotion[event.fingerID] = {0, {event.x, event.y}}; - } + m_touchVirtualThumb = { + (int) (deltaX / thumbstickRadius * 32767.0f), + (int) (deltaY / thumbstickRadius * 32767.0f), + }; } break; } break; } + } return TRUE; } From deca5e5a2efc302b571a54acfd2276edef11d149 Mon Sep 17 00:00:00 2001 From: Christian Semmler Date: Tue, 15 Jul 2025 16:50:14 -0700 Subject: [PATCH 147/188] Add device and gamepad haptics to web port (#613) * Add device and gamepad haptics to web port * Update skip.yml --- CMakeLists.txt | 1 + ISLE/emscripten/haptic.cpp | 52 +++++++++++++++++++ ISLE/emscripten/haptic.h | 8 +++ ISLE/isleapp.cpp | 18 ++++++- .../lego/legoomni/include/legoinputmanager.h | 18 ++++--- .../legoomni/src/input/legoinputmanager.cpp | 44 ++++++++++++---- tools/ncc/skip.yml | 1 + 7 files changed, 125 insertions(+), 17 deletions(-) create mode 100644 ISLE/emscripten/haptic.cpp create mode 100644 ISLE/emscripten/haptic.h diff --git a/CMakeLists.txt b/CMakeLists.txt index 7a457968..a3bdb8e4 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -555,6 +555,7 @@ if (ISLE_BUILD_APP) ISLE/emscripten/config.cpp ISLE/emscripten/events.cpp ISLE/emscripten/filesystem.cpp + ISLE/emscripten/haptic.cpp ISLE/emscripten/messagebox.cpp ISLE/emscripten/window.cpp ) diff --git a/ISLE/emscripten/haptic.cpp b/ISLE/emscripten/haptic.cpp new file mode 100644 index 00000000..364701d2 --- /dev/null +++ b/ISLE/emscripten/haptic.cpp @@ -0,0 +1,52 @@ +#include "haptic.h" + +#include "compat.h" +#include "lego/sources/misc/legoutil.h" +#include "legoinputmanager.h" +#include "misc.h" + +#include + +void Emscripten_HandleRumbleEvent(float p_lowFrequencyRumble, float p_highFrequencyRumble, MxU32 p_milliseconds) +{ + std::visit( + overloaded{ + [](LegoInputManager::SDL_KeyboardID_v p_id) {}, + [](LegoInputManager::SDL_MouseID_v p_id) {}, + [p_lowFrequencyRumble, p_highFrequencyRumble, p_milliseconds](LegoInputManager::SDL_JoystickID_v p_id) { + const char* name = SDL_GetJoystickNameForID((SDL_JoystickID) p_id); + if (name) { + MAIN_THREAD_EM_ASM( + { + const name = UTF8ToString($0); + const gamepads = navigator.getGamepads(); + for (const gamepad of gamepads) { + if (gamepad && gamepad.connected && gamepad.id == name && gamepad.vibrationActuator) { + gamepad.vibrationActuator.playEffect("dual-rumble", { + startDelay : 0, + weakMagnitude : $1, + strongMagnitude : $2, + duration : $3, + }); + break; + } + } + }, + name, + SDL_clamp(p_lowFrequencyRumble, 0, 1), + SDL_clamp(p_highFrequencyRumble, 0, 1), + p_milliseconds + ); + } + }, + [](LegoInputManager::SDL_TouchID_v p_id) { + MAIN_THREAD_EM_ASM({ + if (navigator.vibrate) { + navigator.vibrate(700); + } + }); + } + }, + InputManager()->GetLastInputMethod() + ); +} diff --git a/ISLE/emscripten/haptic.h b/ISLE/emscripten/haptic.h new file mode 100644 index 00000000..51605128 --- /dev/null +++ b/ISLE/emscripten/haptic.h @@ -0,0 +1,8 @@ +#ifndef EMSCRIPTEN_HAPTIC_H +#define EMSCRIPTEN_HAPTIC_H + +#include "mxtypes.h" + +void Emscripten_HandleRumbleEvent(float p_lowFrequencyRumble, float p_highFrequencyRumble, MxU32 p_milliseconds); + +#endif // EMSCRIPTEN_HAPTIC_H diff --git a/ISLE/isleapp.cpp b/ISLE/isleapp.cpp index 5a071664..a3ddb0d2 100644 --- a/ISLE/isleapp.cpp +++ b/ISLE/isleapp.cpp @@ -54,6 +54,7 @@ #include "emscripten/config.h" #include "emscripten/events.h" #include "emscripten/filesystem.h" +#include "emscripten/haptic.h" #include "emscripten/messagebox.h" #include "emscripten/window.h" #endif @@ -501,6 +502,16 @@ SDL_AppResult SDL_AppEvent(void* appstate, SDL_Event* event) } break; } + case SDL_EVENT_KEYBOARD_ADDED: + if (InputManager()) { + InputManager()->AddKeyboard(event->kdevice.which); + } + break; + case SDL_EVENT_KEYBOARD_REMOVED: + if (InputManager()) { + InputManager()->RemoveKeyboard(event->kdevice.which); + } + break; case SDL_EVENT_MOUSE_ADDED: if (InputManager()) { InputManager()->AddMouse(event->mdevice.which); @@ -789,8 +800,11 @@ SDL_AppResult SDL_AppEvent(void* appstate, SDL_Event* event) } } else if (event->user.type == g_legoSdlEvents.m_hitActor && g_isle->GetHaptic()) { - if (InputManager()) { - InputManager()->HandleRumbleEvent(); + if (!InputManager()->HandleRumbleEvent(0.5f, 0.5f, 0.5f, 700)) { +// Platform-specific handling +#ifdef __EMSCRIPTEN__ + Emscripten_HandleRumbleEvent(0.5f, 0.5f, 700); +#endif } } diff --git a/LEGO1/lego/legoomni/include/legoinputmanager.h b/LEGO1/lego/legoomni/include/legoinputmanager.h index bbe0a2b5..04ac7294 100644 --- a/LEGO1/lego/legoomni/include/legoinputmanager.h +++ b/LEGO1/lego/legoomni/include/legoinputmanager.h @@ -154,24 +154,29 @@ class LegoInputManager : public MxPresenter { void GetKeyboardState(); MxResult GetNavigationKeyStates(MxU32& p_keyFlags); MxResult GetNavigationTouchStates(MxU32& p_keyFlags); + LEGO1_EXPORT void AddKeyboard(SDL_KeyboardID p_keyboardID); + LEGO1_EXPORT void RemoveKeyboard(SDL_KeyboardID p_keyboardID); LEGO1_EXPORT void AddMouse(SDL_MouseID p_mouseID); LEGO1_EXPORT void RemoveMouse(SDL_MouseID p_mouseID); LEGO1_EXPORT void AddJoystick(SDL_JoystickID p_joystickID); LEGO1_EXPORT void RemoveJoystick(SDL_JoystickID p_joystickID); LEGO1_EXPORT MxBool HandleTouchEvent(SDL_Event* p_event, TouchScheme p_touchScheme); - LEGO1_EXPORT MxBool HandleRumbleEvent(); + LEGO1_EXPORT MxBool + HandleRumbleEvent(float p_strength, float p_lowFrequencyRumble, float p_highFrequencyRumble, MxU32 p_milliseconds); LEGO1_EXPORT void UpdateLastInputMethod(SDL_Event* p_event); + const auto& GetLastInputMethod() { return m_lastInputMethod; } - // SYNTHETIC: LEGO1 0x1005b8d0 - // LegoInputManager::`scalar deleting destructor' - -private: // clang-format off + enum class SDL_KeyboardID_v : SDL_KeyboardID {}; enum class SDL_MouseID_v : SDL_MouseID {}; enum class SDL_JoystickID_v : SDL_JoystickID {}; enum class SDL_TouchID_v : SDL_TouchID {}; // clang-format on + // SYNTHETIC: LEGO1 0x1005b8d0 + // LegoInputManager::`scalar deleting destructor' + +private: void InitializeHaptics(); MxCriticalSection m_criticalSection; // 0x58 @@ -197,10 +202,11 @@ class LegoInputManager : public MxPresenter { SDL_Point m_touchVirtualThumb = {0, 0}; SDL_FPoint m_touchVirtualThumbOrigin; std::map m_touchFlags; + std::map> m_keyboards; std::map> m_mice; std::map> m_joysticks; std::map m_otherHaptics; - std::variant m_lastInputMethod; + std::variant m_lastInputMethod; }; // TEMPLATE: LEGO1 0x10028850 diff --git a/LEGO1/lego/legoomni/src/input/legoinputmanager.cpp b/LEGO1/lego/legoomni/src/input/legoinputmanager.cpp index 1bb0295d..f7fef27e 100644 --- a/LEGO1/lego/legoomni/src/input/legoinputmanager.cpp +++ b/LEGO1/lego/legoomni/src/input/legoinputmanager.cpp @@ -160,7 +160,8 @@ MxResult LegoInputManager::GetNavigationKeyStates(MxU32& p_keyFlags) // FUNCTION: LEGO1 0x1005c320 MxResult LegoInputManager::GetJoystickState(MxU32* p_joystickX, MxU32* p_joystickY, MxU32* p_povPosition) { - if (m_joysticks.empty() && m_touchScheme != e_gamepad) { + if (!std::holds_alternative(m_lastInputMethod) && + !(std::holds_alternative(m_lastInputMethod) && m_touchScheme == e_gamepad)) { return FAILURE; } @@ -513,6 +514,24 @@ MxResult LegoInputManager::GetNavigationTouchStates(MxU32& p_keyStates) return SUCCESS; } +void LegoInputManager::AddKeyboard(SDL_KeyboardID p_keyboardID) +{ + if (m_keyboards.count(p_keyboardID)) { + return; + } + + m_keyboards[p_keyboardID] = {nullptr, nullptr}; +} + +void LegoInputManager::RemoveKeyboard(SDL_KeyboardID p_keyboardID) +{ + if (!m_keyboards.count(p_keyboardID)) { + return; + } + + m_keyboards.erase(p_keyboardID); +} + void LegoInputManager::AddMouse(SDL_MouseID p_mouseID) { if (m_mice.count(p_mouseID)) { @@ -656,7 +675,12 @@ MxBool LegoInputManager::HandleTouchEvent(SDL_Event* p_event, TouchScheme p_touc return TRUE; } -MxBool LegoInputManager::HandleRumbleEvent() +MxBool LegoInputManager::HandleRumbleEvent( + float p_strength, + float p_lowFrequencyRumble, + float p_highFrequencyRumble, + MxU32 p_milliseconds +) { static bool g_hapticsInitialized = false; @@ -668,6 +692,7 @@ MxBool LegoInputManager::HandleRumbleEvent() SDL_Haptic* haptic = nullptr; std::visit( overloaded{ + [](SDL_KeyboardID_v p_id) {}, [&haptic, this](SDL_MouseID_v p_id) { if (m_mice.count((SDL_MouseID) p_id)) { haptic = m_mice[(SDL_MouseID) p_id].second; @@ -688,11 +713,8 @@ MxBool LegoInputManager::HandleRumbleEvent() m_lastInputMethod ); - const float strength = 0.5f; - const Uint32 durationMs = 700; - if (haptic) { - return SDL_PlayHapticRumble(haptic, strength, durationMs); + return SDL_PlayHapticRumble(haptic, p_strength, p_milliseconds); } // A joystick isn't necessarily a haptic device; try basic rumble instead @@ -700,9 +722,9 @@ MxBool LegoInputManager::HandleRumbleEvent() if (m_joysticks.count((SDL_JoystickID) *joystick)) { return SDL_RumbleGamepad( m_joysticks[(SDL_JoystickID) *joystick].first, - strength * 65535, - strength * 65535, - durationMs + SDL_clamp(p_lowFrequencyRumble, 0, 1) * USHRT_MAX, + SDL_clamp(p_highFrequencyRumble, 0, 1) * USHRT_MAX, + p_milliseconds ); } } @@ -752,6 +774,10 @@ void LegoInputManager::InitializeHaptics() void LegoInputManager::UpdateLastInputMethod(SDL_Event* p_event) { switch (p_event->type) { + case SDL_EVENT_KEY_DOWN: + case SDL_EVENT_KEY_UP: + m_lastInputMethod = SDL_KeyboardID_v{p_event->key.which}; + break; case SDL_EVENT_MOUSE_BUTTON_DOWN: case SDL_EVENT_MOUSE_BUTTON_UP: m_lastInputMethod = SDL_MouseID_v{p_event->button.which}; diff --git a/tools/ncc/skip.yml b/tools/ncc/skip.yml index 2bae0ab4..322cded0 100644 --- a/tools/ncc/skip.yml +++ b/tools/ncc/skip.yml @@ -74,6 +74,7 @@ cksize: "Re-defined Windows name" fccType: "Re-defined Windows name" dwDataOffset: "Re-defined Windows name" fccType: "Re-defined Windows name" +SDL_KeyboardID_v: "SDL-based name" SDL_MouseID_v: "SDL-based name" SDL_JoystickID_v: "SDL-based name" SDL_TouchID_v: "SDL-based name" \ No newline at end of file From ad2832b096dd9111aaec6d3055270ac51b187259 Mon Sep 17 00:00:00 2001 From: Christian Semmler Date: Tue, 15 Jul 2025 17:18:54 -0700 Subject: [PATCH 148/188] (Web port) Pass vibration milliseconds into device haptics --- ISLE/emscripten/haptic.cpp | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/ISLE/emscripten/haptic.cpp b/ISLE/emscripten/haptic.cpp index 364701d2..4a7244e5 100644 --- a/ISLE/emscripten/haptic.cpp +++ b/ISLE/emscripten/haptic.cpp @@ -39,12 +39,15 @@ void Emscripten_HandleRumbleEvent(float p_lowFrequencyRumble, float p_highFreque ); } }, - [](LegoInputManager::SDL_TouchID_v p_id) { - MAIN_THREAD_EM_ASM({ - if (navigator.vibrate) { - navigator.vibrate(700); - } - }); + [p_milliseconds](LegoInputManager::SDL_TouchID_v p_id) { + MAIN_THREAD_EM_ASM( + { + if (navigator.vibrate) { + navigator.vibrate($0); + } + }, + p_milliseconds + ); } }, InputManager()->GetLastInputMethod() From b6a4f65db432145c83a92869b8baccdec8f131fa Mon Sep 17 00:00:00 2001 From: VoxelTek <53562267+VoxelTek@users.noreply.github.com> Date: Wed, 16 Jul 2025 10:29:42 +1000 Subject: [PATCH 149/188] Add CLI help message, accessible with --help (#614) * Add CLI --help argument * Make clang-format happy * Move to switch instead of creating temp var * Remove unnecesary break Co-authored-by: Christian Semmler * Remove unnecesary break 2 Co-authored-by: Christian Semmler * Make clang-format happy again --------- Co-authored-by: Christian Semmler --- ISLE/isleapp.cpp | 29 +++++++++++++++++++++++++---- ISLE/isleapp.h | 3 ++- 2 files changed, 27 insertions(+), 5 deletions(-) diff --git a/ISLE/isleapp.cpp b/ISLE/isleapp.cpp index a3ddb0d2..46e1c2e2 100644 --- a/ISLE/isleapp.cpp +++ b/ISLE/isleapp.cpp @@ -315,7 +315,8 @@ SDL_AppResult SDL_AppInit(void** appstate, int argc, char** argv) // Create global app instance g_isle = new IsleApp(); - if (g_isle->ParseArguments(argc, argv) != SUCCESS) { + switch (g_isle->ParseArguments(argc, argv)) { + case SDL_APP_FAILURE: Any_ShowSimpleMessageBox( SDL_MESSAGEBOX_ERROR, "LEGO® Island Error", @@ -323,6 +324,10 @@ SDL_AppResult SDL_AppInit(void** appstate, int argc, char** argv) window ); return SDL_APP_FAILURE; + case SDL_APP_SUCCESS: + return SDL_APP_SUCCESS; + case SDL_APP_CONTINUE: + break; } // Create window @@ -1301,7 +1306,7 @@ void IsleApp::SetupCursor(Cursor p_cursor) } } -MxResult IsleApp::ParseArguments(int argc, char** argv) +SDL_AppResult IsleApp::ParseArguments(int argc, char** argv) { for (int i = 1, consumed; i < argc; i += consumed) { consumed = -1; @@ -1318,13 +1323,29 @@ MxResult IsleApp::ParseArguments(int argc, char** argv) #endif consumed = 1; } + else if (strcmp(argv[i], "--help") == 0) { + DisplayArgumentHelp(); + return SDL_APP_SUCCESS; + } if (consumed <= 0) { SDL_Log("Invalid argument(s): %s", argv[i]); - return FAILURE; + DisplayArgumentHelp(); + return SDL_APP_FAILURE; } } - return SUCCESS; + return SDL_APP_CONTINUE; +} + +void IsleApp::DisplayArgumentHelp() +{ + SDL_Log("Usage: isle [options]"); + SDL_Log("Options:"); + SDL_Log(" --ini Set custom path to .ini config"); +#ifdef ISLE_DEBUG + SDL_Log(" --debug Launch in debug mode"); +#endif + SDL_Log(" --help Show this help message"); } MxResult IsleApp::VerifyFilesystem() diff --git a/ISLE/isleapp.h b/ISLE/isleapp.h index cfe11bf0..f175cecf 100644 --- a/ISLE/isleapp.h +++ b/ISLE/isleapp.h @@ -61,7 +61,7 @@ class IsleApp { void SetGameStarted(MxS32 p_gameStarted) { m_gameStarted = p_gameStarted; } void SetDrawCursor(MxS32 p_drawCursor) { m_drawCursor = p_drawCursor; } - MxResult ParseArguments(int argc, char** argv); + SDL_AppResult ParseArguments(int argc, char** argv); MxResult VerifyFilesystem(); void DetectGameVersion(); void MoveVirtualMouseViaJoystick(); @@ -99,6 +99,7 @@ class IsleApp { const CursorBitmap* m_cursorCurrentBitmap; char* m_mediaPath; MxFloat m_cursorSensitivity; + void DisplayArgumentHelp(); char* m_iniPath; MxFloat m_maxLod; From 84bd0a1a8731d6ff8db534b48e21be5e46a0f075 Mon Sep 17 00:00:00 2001 From: VoxelTek <53562267+VoxelTek@users.noreply.github.com> Date: Wed, 16 Jul 2025 12:29:45 +1000 Subject: [PATCH 150/188] Rework of isle-config, add resolution adjustment and max framerate settings (#608) * Add resolution adjustment, framerate limit, etc - isle-config has been reworked to be a smaller, more organised window - resolution adjustment has now been added to isle-config, so that the resolution on windowed game start can be set - max framerate setting added to isle-config - higher-quality options disabled in isle-config if computer has too little RAM (unlikely) * Make clang-format happy * Switch to MxS32, move variable declaration to end * Adjust sizing of resolution spinboxes * Add full screen video mode When full screen is enabled, the game goes full-screen, and the screen resolution changes. * Rework to add Exclusive Fullscreen option. * Remove comment * Raise max LoD value to 60 * Fix tab order * Simplify code --- CONFIG/MainDlg.cpp | 110 +++ CONFIG/MainDlg.h | 6 + CONFIG/config.cpp | 21 +- CONFIG/config.h | 6 + CONFIG/res/maindialog.ui | 1470 ++++++++++++++++++++++---------------- ISLE/isleapp.cpp | 25 + ISLE/isleapp.h | 6 +- 7 files changed, 1017 insertions(+), 627 deletions(-) diff --git a/CONFIG/MainDlg.cpp b/CONFIG/MainDlg.cpp index 1b93f8d9..3f69d096 100644 --- a/CONFIG/MainDlg.cpp +++ b/CONFIG/MainDlg.cpp @@ -58,6 +58,7 @@ CMainDialog::CMainDialog(QWidget* pParent) : QDialog(pParent) connect(m_ui->musicCheckBox, &QCheckBox::toggled, this, &CMainDialog::OnCheckboxMusic); connect(m_ui->sound3DCheckBox, &QCheckBox::toggled, this, &CMainDialog::OnCheckbox3DSound); connect(m_ui->fullscreenCheckBox, &QCheckBox::toggled, this, &CMainDialog::OnCheckboxFullscreen); + connect(m_ui->exclusiveFullscreenCheckbox, &QCheckBox::toggled, this, &CMainDialog::OnCheckboxExclusiveFullscreen); connect(m_ui->rumbleCheckBox, &QCheckBox::toggled, this, &CMainDialog::OnCheckboxRumble); connect(m_ui->textureCheckBox, &QCheckBox::toggled, this, &CMainDialog::OnCheckboxTexture); connect(m_ui->touchComboBox, &QComboBox::currentIndexChanged, this, &CMainDialog::TouchControlsChanged); @@ -80,7 +81,44 @@ CMainDialog::CMainDialog(QWidget* pParent) : QDialog(pParent) connect(m_ui->maxActorsSlider, &QSlider::valueChanged, this, &CMainDialog::MaxActorsChanged); connect(m_ui->maxActorsSlider, &QSlider::sliderMoved, this, &CMainDialog::MaxActorsChanged); + connect(m_ui->aspectRatioComboBox, &QComboBox::currentIndexChanged, this, &CMainDialog::AspectRatioChanged); + connect(m_ui->xResSpinBox, &QSpinBox::valueChanged, this, &CMainDialog::XResChanged); + connect(m_ui->yResSpinBox, &QSpinBox::valueChanged, this, &CMainDialog::YResChanged); + connect(m_ui->framerateSpinBox, &QSpinBox::valueChanged, this, &CMainDialog::FramerateChanged); + layout()->setSizeConstraint(QLayout::SetFixedSize); + + if (currentConfigApp->m_ram_quality_limit != 0) { + m_modified = true; + const QString ramError = QString("Insufficient RAM!"); + m_ui->sound3DCheckBox->setChecked(false); + m_ui->sound3DCheckBox->setEnabled(false); + m_ui->sound3DCheckBox->setToolTip(ramError); + m_ui->modelQualityHighRadioButton->setEnabled(false); + m_ui->modelQualityHighRadioButton->setToolTip(ramError); + m_ui->modelQualityLowRadioButton->setEnabled(true); + if (currentConfigApp->m_ram_quality_limit == 2) { + m_ui->modelQualityLowRadioButton->setChecked(true); + m_ui->modelQualityMediumRadioButton->setEnabled(false); + m_ui->modelQualityMediumRadioButton->setToolTip(ramError); + m_ui->maxLoDSlider->setMaximum(30); + m_ui->maxActorsSlider->setMaximum(15); + } + else { + m_ui->modelQualityMediumRadioButton->setChecked(true); + m_ui->modelQualityMediumRadioButton->setEnabled(true); + m_ui->maxLoDSlider->setMaximum(40); + m_ui->maxActorsSlider->setMaximum(30); + } + } + else { + m_ui->sound3DCheckBox->setEnabled(true); + m_ui->modelQualityLowRadioButton->setEnabled(true); + m_ui->modelQualityMediumRadioButton->setEnabled(true); + m_ui->modelQualityHighRadioButton->setEnabled(true); + m_ui->maxLoDSlider->setMaximum(60); + m_ui->maxActorsSlider->setMaximum(40); + } } CMainDialog::~CMainDialog() @@ -125,6 +163,7 @@ bool CMainDialog::OnInitDialog() m_ui->LoDNum->setNum((int) currentConfigApp->m_max_lod * 10); m_ui->maxActorsSlider->setValue(currentConfigApp->m_max_actors); m_ui->maxActorsNum->setNum(currentConfigApp->m_max_actors); + UpdateInterface(); return true; } @@ -221,6 +260,8 @@ void CMainDialog::UpdateInterface() } m_ui->musicCheckBox->setChecked(currentConfigApp->m_music); m_ui->fullscreenCheckBox->setChecked(currentConfigApp->m_full_screen); + m_ui->exclusiveFullscreenCheckbox->setEnabled(currentConfigApp->m_full_screen); + m_ui->exclusiveFullscreenCheckbox->setChecked(currentConfigApp->m_exclusive_full_screen); m_ui->rumbleCheckBox->setChecked(currentConfigApp->m_haptic); m_ui->touchComboBox->setCurrentIndex(currentConfigApp->m_touch_scheme); m_ui->transitionTypeComboBox->setCurrentIndex(currentConfigApp->m_transition_type); @@ -231,6 +272,11 @@ void CMainDialog::UpdateInterface() m_ui->texturePath->setEnabled(currentConfigApp->m_texture_load); m_ui->texturePathOpen->setEnabled(currentConfigApp->m_texture_load); + + m_ui->aspectRatioComboBox->setCurrentIndex(currentConfigApp->m_aspect_ratio); + m_ui->xResSpinBox->setValue(currentConfigApp->m_x_res); + m_ui->yResSpinBox->setValue(currentConfigApp->m_y_res); + m_ui->framerateSpinBox->setValue(static_cast(std::round(1000.0f / currentConfigApp->m_frame_delta))); } // FUNCTION: CONFIG 0x004045e0 @@ -301,6 +347,14 @@ void CMainDialog::OnCheckboxMusic(bool checked) void CMainDialog::OnCheckboxFullscreen(bool checked) { currentConfigApp->m_full_screen = checked; + m_ui->exclusiveFullscreenCheckbox->setEnabled(checked); + m_modified = true; + UpdateInterface(); +} + +void CMainDialog::OnCheckboxExclusiveFullscreen(bool checked) +{ + currentConfigApp->m_exclusive_full_screen = checked; m_modified = true; UpdateInterface(); } @@ -444,3 +498,59 @@ void CMainDialog::TexturePathEdited() } UpdateInterface(); } + +void CMainDialog::AspectRatioChanged(int index) +{ + currentConfigApp->m_aspect_ratio = index; + EnsureAspectRatio(); + m_modified = true; + UpdateInterface(); +} + +void CMainDialog::XResChanged(int i) +{ + currentConfigApp->m_x_res = i; + m_modified = true; + UpdateInterface(); +} + +void CMainDialog::YResChanged(int i) +{ + currentConfigApp->m_y_res = i; + EnsureAspectRatio(); + m_modified = true; + UpdateInterface(); +} + +void CMainDialog::EnsureAspectRatio() +{ + if (currentConfigApp->m_aspect_ratio != 3) { + m_ui->xResSpinBox->setReadOnly(true); + switch (currentConfigApp->m_aspect_ratio) { + case 0: { + float standardAspect = 4.0f / 3.0f; + currentConfigApp->m_x_res = static_cast(std::round((currentConfigApp->m_y_res) * standardAspect)); + break; + } + case 1: { + float wideAspect = 16.0f / 9.0f; + currentConfigApp->m_x_res = static_cast(std::round((currentConfigApp->m_y_res) * wideAspect)); + break; + } + case 2: { + currentConfigApp->m_x_res = currentConfigApp->m_y_res; + break; + } + } + } + else { + m_ui->xResSpinBox->setReadOnly(false); + } +} + +void CMainDialog::FramerateChanged(int i) +{ + currentConfigApp->m_frame_delta = (1000.0f / static_cast(i)); + m_modified = true; + UpdateInterface(); +} diff --git a/CONFIG/MainDlg.h b/CONFIG/MainDlg.h index 40156790..ece4dbfd 100644 --- a/CONFIG/MainDlg.h +++ b/CONFIG/MainDlg.h @@ -42,6 +42,7 @@ private slots: void OnRadiobuttonTextureHighQuality(bool checked); void OnCheckboxMusic(bool checked); void OnCheckboxFullscreen(bool checked); + void OnCheckboxExclusiveFullscreen(bool checked); void OnCheckboxRumble(bool checked); void OnCheckboxTexture(bool checked); void TouchControlsChanged(int index); @@ -57,6 +58,11 @@ private slots: void MaxActorsChanged(int value); void SelectTexturePathDialog(); void TexturePathEdited(); + void XResChanged(int i); + void YResChanged(int i); + void AspectRatioChanged(int index); + void EnsureAspectRatio(); + void FramerateChanged(int i); }; // SYNTHETIC: CONFIG 0x00403de0 diff --git a/CONFIG/config.cpp b/CONFIG/config.cpp index aaca44e7..d0f1dda6 100644 --- a/CONFIG/config.cpp +++ b/CONFIG/config.cpp @@ -67,9 +67,14 @@ bool CConfigApp::InitInstance() return FALSE; } SDL_DestroyWindow(window); + m_aspect_ratio = 0; + m_x_res = 640; + m_y_res = 480; + m_frame_delta = 10.0f; m_driver = NULL; m_device = NULL; m_full_screen = TRUE; + m_exclusive_full_screen = FALSE; m_transition_type = 3; // 3: Mosaic m_wide_view_angle = TRUE; m_use_joystick = TRUE; @@ -84,6 +89,7 @@ bool CConfigApp::InitInstance() m_texture_path = "/textures/"; int totalRamMiB = SDL_GetSystemRAM(); if (totalRamMiB < 12) { + m_ram_quality_limit = 2; m_3d_sound = FALSE; m_model_quality = 0; m_texture_quality = 1; @@ -91,6 +97,7 @@ bool CConfigApp::InitInstance() m_max_actors = 5; } else if (totalRamMiB < 20) { + m_ram_quality_limit = 1; m_3d_sound = FALSE; m_model_quality = 1; m_texture_quality = 1; @@ -98,6 +105,7 @@ bool CConfigApp::InitInstance() m_max_actors = 10; } else { + m_ram_quality_limit = 0; m_model_quality = 2; m_3d_sound = TRUE; m_texture_quality = 1; @@ -158,6 +166,7 @@ bool CConfigApp::ReadRegisterSettings() m_display_bit_depth = iniparser_getint(dict, "isle:Display Bit Depth", -1); m_flip_surfaces = iniparser_getboolean(dict, "isle:Flip Surfaces", m_flip_surfaces); m_full_screen = iniparser_getboolean(dict, "isle:Full Screen", m_full_screen); + m_exclusive_full_screen = iniparser_getboolean(dict, "isle:Exclusive Full Screen", m_exclusive_full_screen); m_transition_type = iniparser_getint(dict, "isle:Transition Type", m_transition_type); m_touch_scheme = iniparser_getint(dict, "isle:Touch Scheme", m_touch_scheme); m_3d_video_ram = iniparser_getboolean(dict, "isle:Back Buffers in Video RAM", m_3d_video_ram); @@ -174,6 +183,10 @@ bool CConfigApp::ReadRegisterSettings() m_max_actors = iniparser_getint(dict, "isle:Max Allowed Extras", m_max_actors); m_texture_load = iniparser_getboolean(dict, "extensions:texture loader", m_texture_load); m_texture_path = iniparser_getstring(dict, "texture loader:texture path", m_texture_path.c_str()); + m_aspect_ratio = iniparser_getint(dict, "isle:Aspect Ratio", m_aspect_ratio); + m_x_res = iniparser_getint(dict, "isle:Horizontal Resolution", m_x_res); + m_y_res = iniparser_getint(dict, "isle:Vertical Resolution", m_y_res); + m_frame_delta = iniparser_getdouble(dict, "isle:Frame Delta", m_frame_delta); iniparser_freedict(dict); return true; } @@ -230,7 +243,7 @@ bool CConfigApp::ValidateSettings() is_modified = TRUE; } - if (m_max_lod < 0.0f || m_max_lod > 5.0f) { + if (m_max_lod < 0.0f || m_max_lod > 6.0f) { m_max_lod = 3.5f; is_modified = TRUE; } @@ -326,6 +339,7 @@ void CConfigApp::WriteRegisterSettings() const SetIniInt(dict, "isle:Display Bit Depth", m_display_bit_depth); SetIniBool(dict, "isle:Flip Surfaces", m_flip_surfaces); SetIniBool(dict, "isle:Full Screen", m_full_screen); + SetIniBool(dict, "isle:Exclusive Full Screen", m_exclusive_full_screen); SetIniBool(dict, "isle:Wide View Angle", m_wide_view_angle); SetIniInt(dict, "isle:Transition Type", m_transition_type); @@ -350,6 +364,11 @@ void CConfigApp::WriteRegisterSettings() const iniparser_set(dict, "isle:Max LOD", std::to_string(m_max_lod).c_str()); SetIniInt(dict, "isle:Max Allowed Extras", m_max_actors); + SetIniInt(dict, "isle:Aspect Ratio", m_aspect_ratio); + SetIniInt(dict, "isle:Horizontal Resolution", m_x_res); + SetIniInt(dict, "isle:Vertical Resolution", m_y_res); + iniparser_set(dict, "isle:Frame Delta", std::to_string(m_frame_delta).c_str()); + #undef SetIniBool #undef SetIniInt diff --git a/CONFIG/config.h b/CONFIG/config.h index 796a5659..ad3321ac 100644 --- a/CONFIG/config.h +++ b/CONFIG/config.h @@ -59,12 +59,17 @@ class CConfigApp { // DECLARE_MESSAGE_MAP() public: + int m_aspect_ratio; + int m_x_res; + int m_y_res; + float m_frame_delta; LegoDeviceEnumerate* m_device_enumerator; MxDriver* m_driver; Direct3DDeviceInfo* m_device; int m_display_bit_depth; bool m_flip_surfaces; bool m_full_screen; + bool m_exclusive_full_screen; int m_transition_type; bool m_3d_video_ram; bool m_wide_view_angle; @@ -85,6 +90,7 @@ class CConfigApp { float m_max_lod; int m_max_actors; int m_touch_scheme; + int m_ram_quality_limit; }; extern CConfigApp g_theApp; diff --git a/CONFIG/res/maindialog.ui b/CONFIG/res/maindialog.ui index f51f70a5..5a0d3b31 100644 --- a/CONFIG/res/maindialog.ui +++ b/CONFIG/res/maindialog.ui @@ -6,8 +6,8 @@ 0 0 - 550 - 700 + 600 + 480 @@ -68,654 +68,861 @@ - - - - 0 - 0 - + + + 0 - - - 0 - - - 0 - - - - - - 0 - 0 - - - - - 50 - 16777215 - - - - Open - - - - - - - - 0 - 0 - - - - - 50 - 16777215 - - - - Open - - - - - - - - 0 - 0 - - - - Save Path: - - - Qt::PlainText - - - - - - - Path to the game data files. -Set this to the CD image root. - - - - - - - Folder where save files are kept. - - - - - - - - 0 - 0 - - - - Data Path: - - - Qt::PlainText - - - - - - - - - - - 0 - 80 - - - - - 0 - - - 0 - - - - - - 0 - 0 - - - - Maximum number of LEGO actors to exist in the world at a time. -The game will gradually increase the number of actors until this maximum is reached and while performance is acceptable. - - - Maximum Actors (5..40) - - + + + Game + + + + - - - - 0 - 0 - - - - 5 - - - 40 - - - 5 - - - 20 - - - 20 - - - false - - - Qt::Horizontal - - - QSlider::NoTicks - - - 5 - - - - - - - - 0 - 0 - - - - - 15 - 0 - - - - 20 - - - - - - - - - - - 0 - 0 - - - - - 0 - 70 - - - - Maximum Level of Detail (LOD). -A higher setting will cause higher quality textures to be drawn regardless of distance. - - - Maximum LOD - - - - - - - 0 - 0 - - - - 60 - - - 5 - - - 10 - - - 35 - - - false - - - Qt::Horizontal - - - QSlider::NoTicks - - - 10 - - - - - - - - 0 - 0 - - - - - 15 - 0 - - - - 35 - - - - - - - - - - - 0 - 0 - - - - Set 3D model detail level. - - - Island Model Quality - - - - + - Broken, not recommended. - - - color: rgb(255, 0, 0); + Enable 3D positional audio effects. - Low - BROKEN! + 3D Sound - - - Medium + + + Enable in-game background music. - - - - - High + Music - - - - - - true - - - - 0 - 0 - - - - Set texture detail level. - - - Island Texture Quality - - - false - - - - 0 + + + + + Qt::Orientation::Vertical - - 0 + + + 20 + 40 + - - - - Fast - - - - - - - High - - - - - - - - - - - - - - 0 - 0 - - - - - 0 - - - 0 - - - - - Enable 3D positional audio effects. - - - 3D Sound - - - - - - - Enable in-game background music. - - - Music - - - - - - - Toggle fullscreen display mode. - - - Fullscreen - - - - - - - true - - - Enable controller rumble. - - - Rumble - - - true - - - - - - - - - - - 0 - - - 0 - - - 0 - - - - - - 0 - 0 - - - - Sets the transition effect to be used in game. - - - Transition Type - - - - - - Mosaic - - - 3 - - - - Idle - Broken - - - - - No Animation - - - - - Dissolve - - - - + + + + + + + 0 + 0 + + + + Sets the transition effect to be used in game. + + + Transition Type + + + + + Mosaic - - - - Wipe Down + + 3 - - - - Windows + + + Idle - Broken + + + + + No Animation + + + + + Dissolve + + + + + Mosaic + + + + + Wipe Down + + + + + Windows + + + + + Unknown - Broken + + + + + + + + + + + + 0 + 0 + + + + + 0 + + + 0 + + + + + Folder where save files are kept. - - - - Unknown - Broken + + + + + + Path to the game data files. +Set this to the CD image root. - - - - - - - - - - <html><head/><body><p><span style=" font-weight:600;">Virtual Gamepad (Recommended):</span> Slide your finger to move and turn.</p><p><span style=" font-weight:600;">Virtual Arrow Keys:</span> Tap screen areas to move. The top moves forward, the bottom turns or moves back.</p><p><span style=" font-weight:600;">Virtual Mouse:</span> Emulates classic mouse controls with touch.</p></body></html> - - - Touch Control Scheme - - - - - - 2 - - - - Virtual Mouse + + + + + + + 0 + 0 + - - - Virtual Arrow Keys + Data Path: + + + Qt::TextFormat::PlainText + + + + + + + + 0 + 0 + + + + + 50 + 16777215 + - - - Virtual Gamepad + Open - - - - - - - - - - - - - Texture Loader Extension - - - - - - false - - - Path to texture replacements. - - - textures/ - - - - - - - false - - - - 0 - 0 - - - - - 50 - 16777215 - - - - Open - - - - - - - Enabled - - - - - - - - - - - 0 - 0 - - - - 3D graphics device used to render the game. - - - Graphics Devices - - - false - - - - 6 - - - 6 - - - 6 - - - 6 - - - - - true - - - - 0 - 0 - - - - - 16777215 - 100 - - - - QAbstractItemView::NoEditTriggers - - - true - - - QAbstractItemView::SelectItems - - - - + + + + + + + 0 + 0 + + + + Save Path: + + + Qt::TextFormat::PlainText + + + + + + + + 0 + 0 + + + + + 50 + 16777215 + + + + Open + + + + + + + + + + + Graphics + + + + + + + 0 + 0 + + + + + 0 + 70 + + + + Maximum Level of Detail (LOD). +A higher setting will cause higher quality textures to be drawn regardless of distance. + + + Maximum LOD + + + + + + + 0 + 0 + + + + 60 + + + 5 + + + 10 + + + 35 + + + false + + + Qt::Orientation::Horizontal + + + QSlider::TickPosition::NoTicks + + + 10 + + + + + + + + 0 + 0 + + + + + 15 + 0 + + + + 35 + + + + + + + + + + + 0 + 0 + + + + 3D graphics device used to render the game. + + + Graphics Devices + + + false + + + + 6 + + + 6 + + + 6 + + + 6 + + + + + true + + + + 0 + 0 + + + + + 16777215 + 100 + + + + QAbstractItemView::EditTrigger::NoEditTriggers + + + true + + + QAbstractItemView::SelectionBehavior::SelectItems + + + + + + + + + + true + + + + 0 + 0 + + + + Set texture detail level. + + + Island Texture Quality + + + false + + + + 0 + + + 0 + + + + + Fast + + + + + + + High + + + + + + + + + + + 0 + 0 + + + + Maximum number of LEGO actors to exist in the world at a time. +The game will gradually increase the number of actors until this maximum is reached and while performance is acceptable. + + + Maximum Actors + + + + + + + 0 + 0 + + + + 5 + + + 40 + + + 5 + + + 20 + + + 20 + + + false + + + Qt::Orientation::Horizontal + + + QSlider::TickPosition::NoTicks + + + 5 + + + + + + + + 0 + 0 + + + + + 15 + 0 + + + + 20 + + + + + + + + + + + 0 + 0 + + + + Set 3D model detail level. + + + Island Model Quality + + + + + + Broken, not recommended. + + + color: rgb(255, 0, 0); + + + Low - BROKEN! + + + + + + + Medium + + + + + + + High + + + + + + + + + + + Display + + + + + + General + + + + + + <html><head/><body><p>Maximum framerate. Values above 100fps are untested.</p></body></html> + + + fps + + + Max Framerate: + + + 200 + + + 100 + + + + + + + + + Toggle fullscreen display mode. + + + Fullscreen + + + + + + + false + + + Grants the app full control of the display for better performance and lower input lag. May cause slower alt-tabbing. + + + Exclusive Fullscreen + + + + + + + + + + + + Windowed Resolution + + + + + + <html><head/><body><p>The aspect ratio you intend to use.<br/>Select <span style=" font-weight:700;">Custom</span> to define your own width and height.<br/>Has no effect on the game itself.</p></body></html> + + + 0 + + + + Standard (4:3) + + + + + Widescreen (16:9) + + + + + Square (1:1) + + + + + Custom + + + + + + + + + + + 0 + 0 + + + + <html><head/><body><p>Horizontal resolution. </p><p>Locked to aspect ratio, unless <span style=" font-weight:700;">Custom</span> is select as the aspect ratio.</p></body></html> + + + Qt::AlignmentFlag::AlignCenter + + + true + + + 10000 + + + 640 + + + 10 + + + + + + + + 0 + 0 + + + + x + + + Qt::AlignmentFlag::AlignCenter + + + + + + + true + + + + 0 + 0 + + + + <html><head/><body><p>Vertical resolution. </p><p>If an aspect ratio other than <span style=" font-weight:700;">Custom</span> is selected, this directly affects the horizontal resolution.</p></body></html> + + + Qt::AlignmentFlag::AlignCenter + + + 10000 + + + 480 + + + + + + + + + + + + Qt::Orientation::Vertical + + + + 20 + 40 + + + + + + + + + Controls + + + + + + <html><head/><body><p><span style=" font-weight:600;">Virtual Gamepad (Recommended):</span> Slide your finger to move and turn.</p><p><span style=" font-weight:600;">Virtual Arrow Keys:</span> Tap screen areas to move. The top moves forward, the bottom turns or moves back.</p><p><span style=" font-weight:600;">Virtual Mouse:</span> Emulates classic mouse controls with touch.</p></body></html> + + + Touch Control Scheme + + + + + + 2 + + + + Virtual Mouse + + + + + Virtual Arrow Keys + + + + + Virtual Gamepad + + + + + + + + + + + true + + + Enable controller rumble. + + + Rumble + + + true + + + + + + + Qt::Orientation::Vertical + + + + 20 + 40 + + + + + + + + + Extensions + + + + + + Settings for Texture Loader extension. + + + Texture Loader Extension + + + + + + Enabled + + + + + + + false + + + Path to texture replacements. + + + textures/ + + + + + + + false + + + + 0 + 0 + + + + + 50 + 16777215 + + + + Open + + + + + + + + + + Qt::Orientation::Vertical + + + + 20 + 40 + + + + + + @@ -784,10 +991,14 @@ A higher setting will cause higher quality textures to be drawn regardless of di + tabWidget dataPath dataPathOpen savePath savePathOpen + transitionTypeComboBox + sound3DCheckBox + musicCheckBox textureQualityFastRadioButton textureQualityHighRadioButton modelQualityLowRadioButton @@ -795,9 +1006,18 @@ A higher setting will cause higher quality textures to be drawn regardless of di modelQualityHighRadioButton maxLoDSlider maxActorsSlider - sound3DCheckBox - musicCheckBox devicesList + fullscreenCheckBox + exclusiveFullscreenCheckbox + framerateSpinBox + aspectRatioComboBox + xResSpinBox + yResSpinBox + touchComboBox + rumbleCheckBox + textureCheckBox + texturePath + texturePathOpen okButton launchButton cancelButton diff --git a/ISLE/isleapp.cpp b/ISLE/isleapp.cpp index 46e1c2e2..10894633 100644 --- a/ISLE/isleapp.cpp +++ b/ISLE/isleapp.cpp @@ -175,6 +175,10 @@ IsleApp::IsleApp() m_cursorSensitivity = 4; m_touchScheme = LegoInputManager::e_gamepad; m_haptic = TRUE; + m_xRes = 640; + m_yRes = 480; + m_frameRate = 100.0f; + m_exclusiveFullScreen = FALSE; } // FUNCTION: ISLE 0x4011a0 @@ -888,6 +892,15 @@ MxResult IsleApp::SetupWindow() #endif window = SDL_CreateWindowWithProperties(props); + + if (m_exclusiveFullScreen && m_fullScreen) { + SDL_DisplayMode closestMode; + SDL_DisplayID displayID = SDL_GetDisplayForWindow(window); + if (SDL_GetClosestFullscreenDisplayMode(displayID, m_xRes, m_yRes, m_frameRate, true, &closestMode)) { + SDL_SetWindowFullscreenMode(window, &closestMode); + } + } + #ifdef MINIWIN m_windowHandle = reinterpret_cast(window); #else @@ -1040,6 +1053,7 @@ bool IsleApp::LoadConfig() iniparser_set(dict, "isle:Flip Surfaces", m_flipSurfaces ? "true" : "false"); iniparser_set(dict, "isle:Full Screen", m_fullScreen ? "true" : "false"); + iniparser_set(dict, "isle:Exclusive Full Screen", m_exclusiveFullScreen ? "true" : "false"); iniparser_set(dict, "isle:Wide View Angle", m_wideViewAngle ? "true" : "false"); iniparser_set(dict, "isle:3DSound", m_use3dSound ? "true" : "false"); @@ -1058,6 +1072,9 @@ bool IsleApp::LoadConfig() iniparser_set(dict, "isle:Transition Type", SDL_itoa(m_transitionType, buf, 10)); iniparser_set(dict, "isle:Touch Scheme", SDL_itoa(m_touchScheme, buf, 10)); iniparser_set(dict, "isle:Haptic", m_haptic ? "true" : "false"); + iniparser_set(dict, "isle:Horizontal Resolution", SDL_itoa(m_xRes, buf, 10)); + iniparser_set(dict, "isle:Vertical Resolution", SDL_itoa(m_yRes, buf, 10)); + iniparser_set(dict, "isle:Frame Delta", SDL_itoa(m_frameDelta, buf, 10)); #ifdef EXTENSIONS iniparser_set(dict, "extensions", NULL); @@ -1100,6 +1117,7 @@ bool IsleApp::LoadConfig() m_flipSurfaces = iniparser_getboolean(dict, "isle:Flip Surfaces", m_flipSurfaces); m_fullScreen = iniparser_getboolean(dict, "isle:Full Screen", m_fullScreen); + m_exclusiveFullScreen = iniparser_getboolean(dict, "isle:Exclusive Full Screen", m_exclusiveFullScreen); m_wideViewAngle = iniparser_getboolean(dict, "isle:Wide View Angle", m_wideViewAngle); m_use3dSound = iniparser_getboolean(dict, "isle:3DSound", m_use3dSound); m_useMusic = iniparser_getboolean(dict, "isle:Music", m_useMusic); @@ -1128,6 +1146,13 @@ bool IsleApp::LoadConfig() (MxTransitionManager::TransitionType) iniparser_getint(dict, "isle:Transition Type", m_transitionType); m_touchScheme = (LegoInputManager::TouchScheme) iniparser_getint(dict, "isle:Touch Scheme", m_touchScheme); m_haptic = iniparser_getboolean(dict, "isle:Haptic", m_haptic); + m_xRes = iniparser_getint(dict, "isle:Horizontal Resolution", m_xRes); + m_yRes = iniparser_getint(dict, "isle:Vertical Resolution", m_yRes); + if (!m_fullScreen) { + m_videoParam.GetRect() = MxRect32(0, 0, (m_xRes - 1), (m_yRes - 1)); + } + m_frameRate = (1000.0f / iniparser_getdouble(dict, "isle:Frame Delta", m_frameDelta)); + m_frameDelta = static_cast(std::round(iniparser_getdouble(dict, "isle:Frame Delta", m_frameDelta))); const char* deviceId = iniparser_getstring(dict, "isle:3D Device ID", NULL); if (deviceId != NULL) { diff --git a/ISLE/isleapp.h b/ISLE/isleapp.h index f175cecf..4a23ea23 100644 --- a/ISLE/isleapp.h +++ b/ISLE/isleapp.h @@ -72,7 +72,7 @@ class IsleApp { char* m_cdPath; // 0x04 char* m_deviceId; // 0x08 char* m_savePath; // 0x0c - MxS32 m_fullScreen; // 0x10 + MxBool m_fullScreen; // 0x10 MxS32 m_flipSurfaces; // 0x14 MxS32 m_backBuffersInVram; // 0x18 MxS32 m_using8bit; // 0x1c @@ -107,6 +107,10 @@ class IsleApp { MxTransitionManager::TransitionType m_transitionType; LegoInputManager::TouchScheme m_touchScheme; MxBool m_haptic; + MxS32 m_xRes; + MxS32 m_yRes; + MxFloat m_frameRate; + MxBool m_exclusiveFullScreen; }; extern IsleApp* g_isle; From bf3820c054fb1a069941af379506746360694991 Mon Sep 17 00:00:00 2001 From: VoxelTek <53562267+VoxelTek@users.noreply.github.com> Date: Wed, 16 Jul 2025 18:22:36 +1000 Subject: [PATCH 151/188] Fix debug mode when OpenGL is set as graphics device (#615) --- miniwin/src/d3drm/backends/opengl1/renderer.cpp | 6 ++++++ miniwin/src/d3drm/backends/opengles2/renderer.cpp | 6 ++++++ 2 files changed, 12 insertions(+) diff --git a/miniwin/src/d3drm/backends/opengl1/renderer.cpp b/miniwin/src/d3drm/backends/opengl1/renderer.cpp index e537a396..88b3b81b 100644 --- a/miniwin/src/d3drm/backends/opengl1/renderer.cpp +++ b/miniwin/src/d3drm/backends/opengl1/renderer.cpp @@ -175,6 +175,7 @@ static Uint32 UploadTextureData(SDL_Surface* src, bool useNPOT, bool isUI, float Uint32 OpenGL1Renderer::GetTextureId(IDirect3DRMTexture* iTexture, bool isUI, float scaleX, float scaleY) { + SDL_GL_MakeCurrent(DDWindow, m_context); auto texture = static_cast(iTexture); auto surface = static_cast(texture->m_surface); @@ -314,6 +315,7 @@ Uint32 OpenGL1Renderer::GetMeshId(IDirect3DRMMesh* mesh, const MeshGroup* meshGr HRESULT OpenGL1Renderer::BeginFrame() { + SDL_GL_MakeCurrent(DDWindow, m_context); GL11_BeginFrame((Matrix4x4*) &m_projection[0][0]); int lightIdx = 0; @@ -361,6 +363,7 @@ HRESULT OpenGL1Renderer::FinalizeFrame() void OpenGL1Renderer::Resize(int width, int height, const ViewportTransform& viewportTransform) { + SDL_GL_MakeCurrent(DDWindow, m_context); m_width = width; m_height = height; m_viewportTransform = viewportTransform; @@ -371,12 +374,14 @@ void OpenGL1Renderer::Resize(int width, int height, const ViewportTransform& vie void OpenGL1Renderer::Clear(float r, float g, float b) { + SDL_GL_MakeCurrent(DDWindow, m_context); m_dirty = true; GL11_Clear(r, g, b); } void OpenGL1Renderer::Flip() { + SDL_GL_MakeCurrent(DDWindow, m_context); if (m_dirty) { SDL_GL_SwapWindow(DDWindow); m_dirty = false; @@ -385,6 +390,7 @@ void OpenGL1Renderer::Flip() void OpenGL1Renderer::Draw2DImage(Uint32 textureId, const SDL_Rect& srcRect, const SDL_Rect& dstRect, FColor color) { + SDL_GL_MakeCurrent(DDWindow, m_context); m_dirty = true; float left = -m_viewportTransform.offsetX / m_viewportTransform.scale; diff --git a/miniwin/src/d3drm/backends/opengles2/renderer.cpp b/miniwin/src/d3drm/backends/opengles2/renderer.cpp index 50e43d02..e1c86a62 100644 --- a/miniwin/src/d3drm/backends/opengles2/renderer.cpp +++ b/miniwin/src/d3drm/backends/opengles2/renderer.cpp @@ -390,6 +390,7 @@ void OpenGLES2Renderer::AddTextureDestroyCallback(Uint32 id, IDirect3DRMTexture* Uint32 OpenGLES2Renderer::GetTextureId(IDirect3DRMTexture* iTexture, bool isUI, float scaleX, float scaleY) { + SDL_GL_MakeCurrent(DDWindow, m_context); auto texture = static_cast(iTexture); auto surface = static_cast(texture->m_surface); @@ -484,6 +485,7 @@ Uint32 OpenGLES2Renderer::GetMeshId(IDirect3DRMMesh* mesh, const MeshGroup* mesh HRESULT OpenGLES2Renderer::BeginFrame() { + SDL_GL_MakeCurrent(DDWindow, m_context); m_dirty = true; glBindFramebuffer(GL_FRAMEBUFFER, m_fbo); @@ -599,6 +601,7 @@ HRESULT OpenGLES2Renderer::FinalizeFrame() void OpenGLES2Renderer::Resize(int width, int height, const ViewportTransform& viewportTransform) { + SDL_GL_MakeCurrent(DDWindow, m_context); m_width = width; m_height = height; m_viewportTransform = viewportTransform; @@ -639,6 +642,7 @@ void OpenGLES2Renderer::Resize(int width, int height, const ViewportTransform& v void OpenGLES2Renderer::Clear(float r, float g, float b) { + SDL_GL_MakeCurrent(DDWindow, m_context); m_dirty = true; glBindFramebuffer(GL_FRAMEBUFFER, m_fbo); @@ -651,6 +655,7 @@ void OpenGLES2Renderer::Clear(float r, float g, float b) void OpenGLES2Renderer::Flip() { + SDL_GL_MakeCurrent(DDWindow, m_context); if (!m_dirty) { return; } @@ -714,6 +719,7 @@ void OpenGLES2Renderer::Flip() void OpenGLES2Renderer::Draw2DImage(Uint32 textureId, const SDL_Rect& srcRect, const SDL_Rect& dstRect, FColor color) { + SDL_GL_MakeCurrent(DDWindow, m_context); m_dirty = true; glBindFramebuffer(GL_FRAMEBUFFER, m_fbo); From df84f4d7a597e2dca935b7e50d7f53b98636d65e Mon Sep 17 00:00:00 2001 From: Helloyunho Date: Thu, 17 Jul 2025 00:32:37 +0900 Subject: [PATCH 152/188] =?UTF-8?q?=F0=9F=A9=B9=20fix:=20use=20actual=20tr?= =?UTF-8?q?ansparent=20pixel=20if=20surface=20supports=20alpha=20channel?= =?UTF-8?q?=20(#616)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- LEGO1/omni/src/video/mxdisplaysurface.cpp | 19 ++++++++++++++----- 1 file changed, 14 insertions(+), 5 deletions(-) diff --git a/LEGO1/omni/src/video/mxdisplaysurface.cpp b/LEGO1/omni/src/video/mxdisplaysurface.cpp index a6c2d086..2248a0ad 100644 --- a/LEGO1/omni/src/video/mxdisplaysurface.cpp +++ b/LEGO1/omni/src/video/mxdisplaysurface.cpp @@ -1198,6 +1198,8 @@ LPDIRECTDRAWSURFACE MxDisplaySurface::CreateCursorSurface(const CursorBitmap* p_ } MxS32 bytesPerPixel = ddsd.ddpfPixelFormat.dwRGBBitCount / 8; + MxBool isAlphaAvailable = ((ddsd.ddpfPixelFormat.dwFlags & DDPF_ALPHAPIXELS) == DDPF_ALPHAPIXELS) && + (ddsd.ddpfPixelFormat.dwRGBAlphaBitMask != 0); ddsd.dwWidth = p_cursorBitmap->width; ddsd.dwHeight = p_cursorBitmap->height; @@ -1260,7 +1262,12 @@ LPDIRECTDRAWSURFACE MxDisplaySurface::CreateCursorSurface(const CursorBitmap* p_ MxS32 pixel; if (!isOpaque) { - pixel = RGB8888_CREATE(0xff, 0, 0xff, 0); // Transparent pixel + if (isAlphaAvailable) { + pixel = RGB8888_CREATE(0, 0, 0, 0); + } + else { + pixel = RGB8888_CREATE(0xff, 0, 0xff, 0); + } // Transparent pixel } else { pixel = isBlack ? RGB8888_CREATE(0, 0, 0, 0xff) : RGB8888_CREATE(0xff, 0xff, 0xff, 0xff); @@ -1290,10 +1297,12 @@ LPDIRECTDRAWSURFACE MxDisplaySurface::CreateCursorSurface(const CursorBitmap* p_ break; } default: { - DDCOLORKEY colorkey; - colorkey.dwColorSpaceHighValue = RGB8888_CREATE(0xff, 0, 0xff, 0); - colorkey.dwColorSpaceLowValue = RGB8888_CREATE(0xff, 0, 0xff, 0); - newSurface->SetColorKey(DDCKEY_SRCBLT, &colorkey); + if (!isAlphaAvailable) { + DDCOLORKEY colorkey; + colorkey.dwColorSpaceHighValue = RGB8888_CREATE(0xff, 0, 0xff, 0); + colorkey.dwColorSpaceLowValue = RGB8888_CREATE(0xff, 0, 0xff, 0); + newSurface->SetColorKey(DDCKEY_SRCBLT, &colorkey); + } break; } } From 6d6971734bf7daa48b61c3ef7e2f8df7080fd818 Mon Sep 17 00:00:00 2001 From: Fabian Neundorf Date: Thu, 17 Jul 2025 01:19:17 +0200 Subject: [PATCH 153/188] Clear unknowns in `LegoROI` (#1630) --- LEGO1/lego/legoomni/src/actors/act2actor.cpp | 2 +- LEGO1/lego/legoomni/src/actors/act3actors.cpp | 6 ++-- .../lego/legoomni/src/paths/legoanimactor.cpp | 2 +- .../legoomni/src/paths/legoextraactor.cpp | 2 +- .../legoomni/src/video/legoanimpresenter.cpp | 4 +-- LEGO1/lego/sources/roi/legoroi.cpp | 34 +++++++++---------- LEGO1/lego/sources/roi/legoroi.h | 21 +++++++----- 7 files changed, 38 insertions(+), 33 deletions(-) diff --git a/LEGO1/lego/legoomni/src/actors/act2actor.cpp b/LEGO1/lego/legoomni/src/actors/act2actor.cpp index bf531641..743934c1 100644 --- a/LEGO1/lego/legoomni/src/actors/act2actor.cpp +++ b/LEGO1/lego/legoomni/src/actors/act2actor.cpp @@ -631,7 +631,7 @@ MxU32 Act2Actor::FUN_10019700(MxFloat p_param) MxFloat time = p_param - (m_unk0x2c - m_shootAnim->GetDuration()); for (MxS32 i = 0; i < root->GetNumChildren(); i++) { - LegoROI::FUN_100a8e80(root->GetChild(i), matrix, time, m_shootAnim->GetROIMap()); + LegoROI::ApplyAnimationTransformation(root->GetChild(i), matrix, time, m_shootAnim->GetROIMap()); } return FALSE; diff --git a/LEGO1/lego/legoomni/src/actors/act3actors.cpp b/LEGO1/lego/legoomni/src/actors/act3actors.cpp index c9750667..9a2acd16 100644 --- a/LEGO1/lego/legoomni/src/actors/act3actors.cpp +++ b/LEGO1/lego/legoomni/src/actors/act3actors.cpp @@ -638,7 +638,7 @@ void Act3Brickster::Animate(float p_time) float time = p_time - (m_unk0x50 - m_shootAnim->GetDuration()); for (MxS32 i = 0; i < root->GetNumChildren(); i++) { - LegoROI::FUN_100a8e80(root->GetChild(i), local70, time, m_shootAnim->GetROIMap()); + LegoROI::ApplyAnimationTransformation(root->GetChild(i), local70, time, m_shootAnim->GetROIMap()); } } @@ -685,7 +685,7 @@ void Act3Brickster::Animate(float p_time) float time = p_time - (m_unk0x50 - m_shootAnim->GetDuration()); for (MxS32 i = 0; i < root->GetNumChildren(); i++) { - LegoROI::FUN_100a8e80(root->GetChild(i), locale4, time, m_shootAnim->GetROIMap()); + LegoROI::ApplyAnimationTransformation(root->GetChild(i), locale4, time, m_shootAnim->GetROIMap()); } } @@ -1186,7 +1186,7 @@ void Act3Shark::Animate(float p_time) vec = m_unk0x3c; LegoTreeNode* node = m_unk0x34->GetAnimTreePtr()->GetRoot(); - LegoROI::FUN_100a8e80(node, mat, duration, m_unk0x34->GetROIMap()); + LegoROI::ApplyAnimationTransformation(node, mat, duration, m_unk0x34->GetROIMap()); } else { roiMap[1] = m_unk0x38; diff --git a/LEGO1/lego/legoomni/src/paths/legoanimactor.cpp b/LEGO1/lego/legoomni/src/paths/legoanimactor.cpp index 182945d5..f6ca062c 100644 --- a/LEGO1/lego/legoomni/src/paths/legoanimactor.cpp +++ b/LEGO1/lego/legoomni/src/paths/legoanimactor.cpp @@ -133,7 +133,7 @@ MxResult LegoAnimActor::FUN_1001c360(float p_und, Matrix4& p_transform) } for (MxS32 j = 0; j < n->GetNumChildren(); j++) { - LegoROI::FUN_100a8e80(n->GetChild(j), p_transform, p_und, roiMap); + LegoROI::ApplyAnimationTransformation(n->GetChild(j), p_transform, p_und, roiMap); } if (m_cameraFlag) { diff --git a/LEGO1/lego/legoomni/src/paths/legoextraactor.cpp b/LEGO1/lego/legoomni/src/paths/legoextraactor.cpp index 45633818..b9cd25bb 100644 --- a/LEGO1/lego/legoomni/src/paths/legoextraactor.cpp +++ b/LEGO1/lego/legoomni/src/paths/legoextraactor.cpp @@ -373,7 +373,7 @@ void LegoExtraActor::Animate(float p_time) MxS32 count = root->GetNumChildren(); for (MxS32 i = 0; i < count; i++) { - LegoROI::FUN_100a8e80(root->GetChild(i), matrix, duration2, laas->m_roiMap); + LegoROI::ApplyAnimationTransformation(root->GetChild(i), matrix, duration2, laas->m_roiMap); } } } diff --git a/LEGO1/lego/legoomni/src/video/legoanimpresenter.cpp b/LEGO1/lego/legoomni/src/video/legoanimpresenter.cpp index f966cf38..d3f38b22 100644 --- a/LEGO1/lego/legoomni/src/video/legoanimpresenter.cpp +++ b/LEGO1/lego/legoomni/src/video/legoanimpresenter.cpp @@ -906,7 +906,7 @@ void LegoAnimPresenter::FUN_1006b900(LegoAnim* p_anim, MxLong p_time, Matrix4* p } } - LegoROI::FUN_100a8fd0(root, mat, p_time, m_roiMap); + LegoROI::ApplyTransform(root, mat, p_time, m_roiMap); } // FUNCTION: LEGO1 0x1006b9a0 @@ -940,7 +940,7 @@ void LegoAnimPresenter::FUN_1006b9a0(LegoAnim* p_anim, MxLong p_time, Matrix4* p } } - LegoROI::FUN_100a8e80(root, mat, p_time, m_roiMap); + LegoROI::ApplyAnimationTransformation(root, mat, p_time, m_roiMap); } // FUNCTION: LEGO1 0x1006bac0 diff --git a/LEGO1/lego/sources/roi/legoroi.cpp b/LEGO1/lego/sources/roi/legoroi.cpp index 25d4362e..02ed727a 100644 --- a/LEGO1/lego/sources/roi/legoroi.cpp +++ b/LEGO1/lego/sources/roi/legoroi.cpp @@ -114,7 +114,7 @@ LegoROI::~LegoROI() // FUNCTION: LEGO1 0x100a84a0 // FUNCTION: BETA10 0x10189b99 LegoResult LegoROI::Read( - OrientableROI* p_unk0xd4, + OrientableROI* p_parentROI, Tgl::Renderer* p_renderer, ViewLODListManager* p_viewLODListManager, LegoTextureContainer* p_textureContainer, @@ -134,7 +134,7 @@ LegoResult LegoROI::Read( LegoSphere sphere; LegoBox box; - m_parentROI = p_unk0xd4; + m_parentROI = p_parentROI; if (p_storage->Read(&length, sizeof(LegoU32)) != SUCCESS) { goto done; @@ -177,11 +177,11 @@ LegoResult LegoROI::Read( textureName = NULL; } - if (p_storage->Read(&m_unk0x100, sizeof(undefined)) != SUCCESS) { + if (p_storage->Read(&m_sharedLodList, sizeof(LegoBool)) != SUCCESS) { goto done; } - if (m_unk0x100) { + if (m_sharedLodList) { for (roiLength = strlen(m_name); roiLength; roiLength--) { if (m_name[roiLength - 1] < '0' || m_name[roiLength - 1] > '9') { break; @@ -389,7 +389,7 @@ LegoROI* LegoROI::FindChildROI(const LegoChar* p_name, LegoROI* p_roi) // FUNCTION: LEGO1 0x100a8da0 // FUNCTION: BETA10 0x1018a9fb -LegoResult LegoROI::ApplyAnimationTransformation( +LegoResult LegoROI::ApplyChildAnimationTransformation( LegoTreeNode* p_node, const Matrix4& p_matrix, LegoTime p_time, @@ -410,11 +410,11 @@ LegoResult LegoROI::ApplyAnimationTransformation( roi->m_local2world.Product(mat, p_matrix); roi->UpdateWorldData(); - LegoBool und = data->GetVisibility(p_time); - roi->SetVisibility(und); + LegoBool visibility = data->GetVisibility(p_time); + roi->SetVisibility(visibility); for (LegoU32 i = 0; i < p_node->GetNumChildren(); i++) { - ApplyAnimationTransformation(p_node->GetChild(i), roi->m_local2world, p_time, roi); + ApplyChildAnimationTransformation(p_node->GetChild(i), roi->m_local2world, p_time, roi); } } else { @@ -426,7 +426,7 @@ LegoResult LegoROI::ApplyAnimationTransformation( // FUNCTION: LEGO1 0x100a8e80 // FUNCTION: BETA10 0x1018ab3a -void LegoROI::FUN_100a8e80(LegoTreeNode* p_node, Matrix4& p_matrix, LegoTime p_time, LegoROI** p_roiMap) +void LegoROI::ApplyAnimationTransformation(LegoTreeNode* p_node, Matrix4& p_matrix, LegoTime p_time, LegoROI** p_roiMap) { MxMatrix mat; @@ -438,11 +438,11 @@ void LegoROI::FUN_100a8e80(LegoTreeNode* p_node, Matrix4& p_matrix, LegoTime p_t roi->m_local2world.Product(mat, p_matrix); roi->UpdateWorldData(); - LegoBool und = data->GetVisibility(p_time); - roi->SetVisibility(und); + LegoBool visiblity = data->GetVisibility(p_time); + roi->SetVisibility(visiblity); for (LegoU32 i = 0; i < p_node->GetNumChildren(); i++) { - FUN_100a8e80(p_node->GetChild(i), roi->m_local2world, p_time, p_roiMap); + ApplyAnimationTransformation(p_node->GetChild(i), roi->m_local2world, p_time, p_roiMap); } } else { @@ -450,14 +450,14 @@ void LegoROI::FUN_100a8e80(LegoTreeNode* p_node, Matrix4& p_matrix, LegoTime p_t local2world.Product(mat, p_matrix); for (LegoU32 i = 0; i < p_node->GetNumChildren(); i++) { - FUN_100a8e80(p_node->GetChild(i), local2world, p_time, p_roiMap); + ApplyAnimationTransformation(p_node->GetChild(i), local2world, p_time, p_roiMap); } } } // FUNCTION: LEGO1 0x100a8fd0 // FUNCTION: BETA10 0x1018ac81 -void LegoROI::FUN_100a8fd0(LegoTreeNode* p_node, Matrix4& p_matrix, LegoTime p_time, LegoROI** p_roiMap) +void LegoROI::ApplyTransform(LegoTreeNode* p_node, Matrix4& p_matrix, LegoTime p_time, LegoROI** p_roiMap) { MxMatrix mat; @@ -469,7 +469,7 @@ void LegoROI::FUN_100a8fd0(LegoTreeNode* p_node, Matrix4& p_matrix, LegoTime p_t roi->m_local2world.Product(mat, p_matrix); for (LegoU32 i = 0; i < p_node->GetNumChildren(); i++) { - FUN_100a8fd0(p_node->GetChild(i), roi->m_local2world, p_time, p_roiMap); + ApplyTransform(p_node->GetChild(i), roi->m_local2world, p_time, p_roiMap); } } else { @@ -477,7 +477,7 @@ void LegoROI::FUN_100a8fd0(LegoTreeNode* p_node, Matrix4& p_matrix, LegoTime p_t local2world.Product(mat, p_matrix); for (LegoU32 i = 0; i < p_node->GetNumChildren(); i++) { - FUN_100a8fd0(p_node->GetChild(i), local2world, p_time, p_roiMap); + ApplyTransform(p_node->GetChild(i), local2world, p_time, p_roiMap); } } } @@ -492,7 +492,7 @@ LegoResult LegoROI::SetFrame(LegoAnim* p_anim, LegoTime p_time) mat = m_local2world; mat.SetIdentity(); // this clears the matrix, assignment above is redundant - return ApplyAnimationTransformation(root, mat, p_time, this); + return ApplyChildAnimationTransformation(root, mat, p_time, this); } // FUNCTION: LEGO1 0x100a9170 diff --git a/LEGO1/lego/sources/roi/legoroi.h b/LEGO1/lego/sources/roi/legoroi.h index 216bb68d..47e0a74d 100644 --- a/LEGO1/lego/sources/roi/legoroi.h +++ b/LEGO1/lego/sources/roi/legoroi.h @@ -26,21 +26,26 @@ class LegoROI : public ViewROI { ~LegoROI() override; LegoResult Read( - OrientableROI* p_unk0xd4, + OrientableROI* p_parentROI, Tgl::Renderer* p_renderer, ViewLODListManager* p_viewLODListManager, LegoTextureContainer* p_textureContainer, LegoStorage* p_storage ); LegoROI* FindChildROI(const LegoChar* p_name, LegoROI* p_roi); - LegoResult ApplyAnimationTransformation( + LegoResult ApplyChildAnimationTransformation( LegoTreeNode* p_node, const Matrix4& p_matrix, LegoTime p_time, LegoROI* p_roi ); - static void FUN_100a8e80(LegoTreeNode* p_node, Matrix4& p_matrix, LegoTime p_time, LegoROI** p_roiMap); - static void FUN_100a8fd0(LegoTreeNode* p_node, Matrix4& p_matrix, LegoTime p_time, LegoROI** p_roiMap); + static void ApplyAnimationTransformation( + LegoTreeNode* p_node, + Matrix4& p_matrix, + LegoTime p_time, + LegoROI** p_roiMap + ); + static void ApplyTransform(LegoTreeNode* p_node, Matrix4& p_matrix, LegoTime p_time, LegoROI** p_roiMap); LegoResult SetFrame(LegoAnim* p_anim, LegoTime p_time); LegoResult SetLodColor(LegoFloat p_red, LegoFloat p_green, LegoFloat p_blue, LegoFloat p_alpha); LegoResult SetTextureInfo(LegoTextureInfo* p_textureInfo); @@ -91,10 +96,10 @@ class LegoROI : public ViewROI { // LegoROI::`scalar deleting destructor' private: - LegoChar* m_name; // 0xe4 - BoundingSphere m_sphere; // 0xe8 - undefined m_unk0x100; // 0x100 - LegoEntity* m_entity; // 0x104 + LegoChar* m_name; // 0xe4 + BoundingSphere m_sphere; // 0xe8 + LegoBool m_sharedLodList; // 0x100 + LegoEntity* m_entity; // 0x104 }; // VTABLE: LEGO1 0x100dbea8 From 66844aa9458bab54e7ccc7188f242a4c26141c2c Mon Sep 17 00:00:00 2001 From: Fabian Neundorf Date: Fri, 18 Jul 2025 01:53:35 +0200 Subject: [PATCH 154/188] Clear unknown in `TimeROI` (#1631) --- LEGO1/lego/legoomni/src/entity/legocameracontroller.cpp | 2 +- LEGO1/lego/legoomni/src/entity/legopovcontroller.cpp | 2 +- LEGO1/lego/sources/roi/legoroi.cpp | 2 +- LEGO1/lego/sources/roi/legoroi.h | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/LEGO1/lego/legoomni/src/entity/legocameracontroller.cpp b/LEGO1/lego/legoomni/src/entity/legocameracontroller.cpp index 563ba615..2f192058 100644 --- a/LEGO1/lego/legoomni/src/entity/legocameracontroller.cpp +++ b/LEGO1/lego/legoomni/src/entity/legocameracontroller.cpp @@ -171,7 +171,7 @@ void LegoCameraController::FUN_100123e0(const Matrix4& p_transform, MxU32 p_und) mat = p_transform; } - ((TimeROI*) pov)->FUN_100a9b40(mat, Timer()->GetTime()); + ((TimeROI*) pov)->CalculateWorldVelocity(mat, Timer()->GetTime()); pov->WrappedSetLocal2WorldWithWorldDataUpdate(mat); m_lego3DView->Moved(*pov); diff --git a/LEGO1/lego/legoomni/src/entity/legopovcontroller.cpp b/LEGO1/lego/legoomni/src/entity/legopovcontroller.cpp index 7541e98b..fc823c60 100644 --- a/LEGO1/lego/legoomni/src/entity/legopovcontroller.cpp +++ b/LEGO1/lego/legoomni/src/entity/legopovcontroller.cpp @@ -153,7 +153,7 @@ MxResult LegoPointOfViewController::Tickle() MxMatrix mat; CalcLocalTransform(newPos, newDir, pov->GetWorldUp(), mat); - ((TimeROI*) pov)->FUN_100a9b40(mat, Timer()->GetTime()); + ((TimeROI*) pov)->CalculateWorldVelocity(mat, Timer()->GetTime()); pov->WrappedSetLocal2WorldWithWorldDataUpdate(mat); m_lego3DView->Moved(*pov); diff --git a/LEGO1/lego/sources/roi/legoroi.cpp b/LEGO1/lego/sources/roi/legoroi.cpp index 02ed727a..a8f16636 100644 --- a/LEGO1/lego/sources/roi/legoroi.cpp +++ b/LEGO1/lego/sources/roi/legoroi.cpp @@ -755,7 +755,7 @@ TimeROI::TimeROI(Tgl::Renderer* p_renderer, ViewLODList* p_lodList, LegoTime p_t // FUNCTION: LEGO1 0x100a9b40 // FUNCTION: BETA10 0x1018bbf0 -void TimeROI::FUN_100a9b40(Matrix4& p_matrix, LegoTime p_time) +void TimeROI::CalculateWorldVelocity(Matrix4& p_matrix, LegoTime p_time) { LegoTime time = p_time - m_time; diff --git a/LEGO1/lego/sources/roi/legoroi.h b/LEGO1/lego/sources/roi/legoroi.h index 47e0a74d..7f475680 100644 --- a/LEGO1/lego/sources/roi/legoroi.h +++ b/LEGO1/lego/sources/roi/legoroi.h @@ -109,7 +109,7 @@ class TimeROI : public LegoROI { public: TimeROI(Tgl::Renderer* p_renderer, ViewLODList* p_lodList, LegoTime p_time); - void FUN_100a9b40(Matrix4& p_matrix, LegoTime p_time); + void CalculateWorldVelocity(Matrix4& p_matrix, LegoTime p_time); // SYNTHETIC: LEGO1 0x100a9ad0 // SYNTHETIC: BETA10 0x1018c540 From cfd36ec88ff47b1eb3c58a33c3fec26b3ca06854 Mon Sep 17 00:00:00 2001 From: Fabian Neundorf Date: Fri, 18 Jul 2025 23:26:34 +0200 Subject: [PATCH 155/188] Clear unknowns in `LegoPhonemePresenter` (#1632) --- LEGO1/lego/legoomni/include/legophonemepresenter.h | 4 ++-- .../legoomni/src/video/legophonemepresenter.cpp | 14 +++++++------- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/LEGO1/lego/legoomni/include/legophonemepresenter.h b/LEGO1/lego/legoomni/include/legophonemepresenter.h index e470f6b7..4a6a3c05 100644 --- a/LEGO1/lego/legoomni/include/legophonemepresenter.h +++ b/LEGO1/lego/legoomni/include/legophonemepresenter.h @@ -42,9 +42,9 @@ class LegoPhonemePresenter : public MxFlcPresenter { MxS32 m_rectCount; // 0x68 LegoTextureInfo* m_textureInfo; // 0x6c - MxBool m_unk0x70; // 0x70 + MxBool m_reusedPhoneme; // 0x70 MxString m_roiName; // 0x74 - MxBool m_unk0x84; // 0x84 + MxBool m_isPartOfAnimMM; // 0x84 }; // TEMPLATE: LEGO1 0x1004eb20 diff --git a/LEGO1/lego/legoomni/src/video/legophonemepresenter.cpp b/LEGO1/lego/legoomni/src/video/legophonemepresenter.cpp index 3e931ce6..ff019526 100644 --- a/LEGO1/lego/legoomni/src/video/legophonemepresenter.cpp +++ b/LEGO1/lego/legoomni/src/video/legophonemepresenter.cpp @@ -25,8 +25,8 @@ void LegoPhonemePresenter::Init() { m_rectCount = 0; m_textureInfo = NULL; - m_unk0x70 = FALSE; - m_unk0x84 = FALSE; + m_reusedPhoneme = FALSE; + m_isPartOfAnimMM = FALSE; } // FUNCTION: LEGO1 0x1004e3d0 @@ -49,7 +49,7 @@ void LegoPhonemePresenter::StartingTickle() if (m_compositePresenter != NULL && m_compositePresenter->IsA("LegoAnimMMPresenter")) { entityROI = FindROI(m_roiName.GetData()); - m_unk0x84 = TRUE; + m_isPartOfAnimMM = TRUE; } else { entityROI = CharacterManager()->GetActorROI(m_roiName.GetData(), TRUE); @@ -81,7 +81,7 @@ void LegoPhonemePresenter::StartingTickle() phoneme->SetCount(phoneme->GetCount() + 1); cursor.SetValue(phoneme); - m_unk0x70 = TRUE; + m_reusedPhoneme = TRUE; } } } @@ -139,7 +139,7 @@ void LegoPhonemePresenter::EndAction() if (phoneme->GetCount() == 1) { LegoROI* roi; - if (m_unk0x84) { + if (m_isPartOfAnimMM) { roi = FindROI(m_roiName.GetData()); } else { @@ -150,7 +150,7 @@ void LegoPhonemePresenter::EndAction() CharacterManager()->SetHeadTexture(roi, NULL); } - if (!m_unk0x84) { + if (!m_isPartOfAnimMM) { CharacterManager()->ReleaseActor(m_roiName.GetData()); } @@ -163,7 +163,7 @@ void LegoPhonemePresenter::EndAction() cursor.SetValue(phoneme); } - if (!m_unk0x84) { + if (!m_isPartOfAnimMM) { CharacterManager()->ReleaseActor(m_roiName.GetData()); } } From 556b2deef29a5f05fbbbe555ac1e732461d7c74f Mon Sep 17 00:00:00 2001 From: Christian Semmler Date: Fri, 18 Jul 2025 14:34:23 -0700 Subject: [PATCH 156/188] Fix `ViewLODList` leaks (#623) * Fix ViewLODList leaks * Add vector clear * Fix naming --- LEGO1/lego/legoomni/include/legopartpresenter.h | 12 ++++++++++++ .../lego/legoomni/src/entity/legoworldpresenter.cpp | 8 ++++++-- LEGO1/lego/legoomni/src/main/legomain.cpp | 2 ++ LEGO1/lego/legoomni/src/video/legopartpresenter.cpp | 4 ++++ 4 files changed, 24 insertions(+), 2 deletions(-) diff --git a/LEGO1/lego/legoomni/include/legopartpresenter.h b/LEGO1/lego/legoomni/include/legopartpresenter.h index 7ff7692d..3c3fc2f1 100644 --- a/LEGO1/lego/legoomni/include/legopartpresenter.h +++ b/LEGO1/lego/legoomni/include/legopartpresenter.h @@ -4,6 +4,7 @@ #include "lego1_export.h" #include "legonamedpartlist.h" #include "mxmediapresenter.h" +#include "viewmanager/viewlodlist.h" // VTABLE: LEGO1 0x100d4df0 // SIZE 0x54 @@ -50,10 +51,21 @@ class LegoPartPresenter : public MxMediaPresenter { MxResult Read(MxDSChunk& p_chunk); void Store(); + static void Release() + { + for (auto* lodList : g_lodLists) { + lodList->Release(); + } + + g_lodLists.clear(); + } + private: void Destroy(MxBool p_fromDestructor); LegoNamedPartList* m_parts; // 0x50 + + static vector g_lodLists; }; #endif // LEGOPARTPRESENTER_H diff --git a/LEGO1/lego/legoomni/src/entity/legoworldpresenter.cpp b/LEGO1/lego/legoomni/src/entity/legoworldpresenter.cpp index b3cfc365..abf6c32a 100644 --- a/LEGO1/lego/legoomni/src/entity/legoworldpresenter.cpp +++ b/LEGO1/lego/legoomni/src/entity/legoworldpresenter.cpp @@ -260,8 +260,12 @@ MxResult LegoWorldPresenter::LoadWorld(char* p_worldName, LegoWorld* p_world) ModelDbPart* part; while (cursor.Next(part)) { - if (GetViewLODListManager()->Lookup(part->m_roiName.GetData()) == NULL && - LoadWorldPart(*part, wdbFile) != SUCCESS) { + ViewLODList* lodList = GetViewLODListManager()->Lookup(part->m_roiName.GetData()); + if (lodList) { + lodList->Release(); + } + + if (lodList == NULL && LoadWorldPart(*part, wdbFile) != SUCCESS) { return FAILURE; } } diff --git a/LEGO1/lego/legoomni/src/main/legomain.cpp b/LEGO1/lego/legoomni/src/main/legomain.cpp index 0c515a1d..78ddf033 100644 --- a/LEGO1/lego/legoomni/src/main/legomain.cpp +++ b/LEGO1/lego/legoomni/src/main/legomain.cpp @@ -122,6 +122,8 @@ void LegoOmni::Destroy() m_textureContainer = NULL; } + LegoPartPresenter::Release(); + if (m_viewLODListManager) { delete m_viewLODListManager; m_viewLODListManager = NULL; diff --git a/LEGO1/lego/legoomni/src/video/legopartpresenter.cpp b/LEGO1/lego/legoomni/src/video/legopartpresenter.cpp index 4849e747..8686cb17 100644 --- a/LEGO1/lego/legoomni/src/video/legopartpresenter.cpp +++ b/LEGO1/lego/legoomni/src/video/legopartpresenter.cpp @@ -22,6 +22,8 @@ MxS32 g_partPresenterConfig1 = 1; // GLOBAL: LEGO1 0x100f7aa4 MxS32 g_partPresenterConfig2 = 100; +vector LegoPartPresenter::g_lodLists; + // FUNCTION: LEGO1 0x1007c990 void LegoPartPresenter::configureLegoPartPresenter(MxS32 p_partPresenterConfig1, MxS32 p_partPresenterConfig2) { @@ -261,6 +263,8 @@ void LegoPartPresenter::Store() lodCursor.Detach(); lodList->PushBack(lod); } + + g_lodLists.push_back(lodList); } else { lodList->Release(); From 38213275cf11d01bfe50a7829b257c15d89828cd Mon Sep 17 00:00:00 2001 From: Christian Semmler Date: Fri, 18 Jul 2025 14:40:25 -0700 Subject: [PATCH 157/188] Proper cleanup on user requested exit (#624) --- ISLE/isleapp.cpp | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/ISLE/isleapp.cpp b/ISLE/isleapp.cpp index 10894633..2d865ad9 100644 --- a/ISLE/isleapp.cpp +++ b/ISLE/isleapp.cpp @@ -488,6 +488,7 @@ SDL_AppResult SDL_AppEvent(void* appstate, SDL_Event* event) } break; case SDL_EVENT_WINDOW_CLOSE_REQUESTED: + case SDL_EVENT_QUIT: if (!g_closed) { delete g_isle; g_isle = NULL; @@ -767,9 +768,6 @@ SDL_AppResult SDL_AppEvent(void* appstate, SDL_Event* event) } break; } - case SDL_EVENT_QUIT: - return SDL_APP_SUCCESS; - break; } if (event->user.type == g_legoSdlEvents.m_windowsMessage) { From 55a3ad71c6ce72599cb3a5d331c660564850057e Mon Sep 17 00:00:00 2001 From: Christian Semmler Date: Fri, 18 Jul 2025 14:51:11 -0700 Subject: [PATCH 158/188] Bump Python to 3.12 (#625) --- .pylintrc | 2 +- CMakeLists.txt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.pylintrc b/.pylintrc index ab83fceb..68704fad 100644 --- a/.pylintrc +++ b/.pylintrc @@ -88,7 +88,7 @@ persistent=yes # Minimum Python version to use for version dependent checks. Will default to # the version used to run pylint. -py-version=3.11 +py-version=3.12 # Discover python modules and packages in the file system subtree. recursive=no diff --git a/CMakeLists.txt b/CMakeLists.txt index a3bdb8e4..9b2be772 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -39,7 +39,7 @@ else() endif() find_program(SDL_SHADERCROSS_BIN NAMES "shadercross") -find_package(Python3 3.11 COMPONENTS Interpreter) +find_package(Python3 3.12 COMPONENTS Interpreter) option(ISLE_BUILD_APP "Build isle application" ON) option(ISLE_ASAN "Enable Address Sanitizer" OFF) From 6b551b14c04025fe5fb7ebc0cc96b24f267cb9ba Mon Sep 17 00:00:00 2001 From: Christian Semmler Date: Fri, 18 Jul 2025 15:06:47 -0700 Subject: [PATCH 159/188] Add assert for dynamic_cast (#626) --- LEGO1/lego/legoomni/src/common/mxcontrolpresenter.cpp | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/LEGO1/lego/legoomni/src/common/mxcontrolpresenter.cpp b/LEGO1/lego/legoomni/src/common/mxcontrolpresenter.cpp index 16e72fbb..bccfd6c7 100644 --- a/LEGO1/lego/legoomni/src/common/mxcontrolpresenter.cpp +++ b/LEGO1/lego/legoomni/src/common/mxcontrolpresenter.cpp @@ -80,10 +80,7 @@ MxBool MxControlPresenter::CheckButtonDown(MxS32 p_x, MxS32 p_y, MxPresenter* p_ { assert(p_presenter); MxVideoPresenter* presenter = dynamic_cast(p_presenter); - if (!presenter) { - SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Invalid presenter"); - return FALSE; - } + assert(presenter); if (m_style == e_map) { MxStillPresenter* map = (MxStillPresenter*) m_list.front(); From 10195dcbcb0e7a89f86346404a3d15e7c854aca5 Mon Sep 17 00:00:00 2001 From: Christian Semmler Date: Fri, 18 Jul 2025 15:52:42 -0700 Subject: [PATCH 160/188] Add more rumble events (#627) * Add more rumble events * Add check for haptic --- ISLE/isleapp.cpp | 25 ++++++++++++++++--- LEGO1/lego/legoomni/include/legoutils.h | 10 +++++++- LEGO1/lego/legoomni/src/common/legoutils.cpp | 5 ++-- .../legoomni/src/paths/legoextraactor.cpp | 8 ++++-- LEGO1/lego/legoomni/src/race/carrace.cpp | 2 ++ LEGO1/lego/legoomni/src/race/jetskirace.cpp | 1 + LEGO1/lego/legoomni/src/race/legoracers.cpp | 7 ++++++ LEGO1/lego/legoomni/src/worlds/act3.cpp | 4 +++ LEGO1/lego/legoomni/src/worlds/legoact2.cpp | 1 + LEGO1/omni/include/mxutilities.h | 2 +- LEGO1/omni/src/main/mxomni.cpp | 2 +- 11 files changed, 57 insertions(+), 10 deletions(-) diff --git a/ISLE/isleapp.cpp b/ISLE/isleapp.cpp index 2d865ad9..16508553 100644 --- a/ISLE/isleapp.cpp +++ b/ISLE/isleapp.cpp @@ -806,12 +806,31 @@ SDL_AppResult SDL_AppEvent(void* appstate, SDL_Event* event) SDL_Log("Game started"); } } - else if (event->user.type == g_legoSdlEvents.m_hitActor && g_isle->GetHaptic()) { - if (!InputManager()->HandleRumbleEvent(0.5f, 0.5f, 0.5f, 700)) { + else if (event->user.type == g_legoSdlEvents.m_gameEvent) { + auto rumble = [](float p_strength, float p_lowFrequencyRumble, float p_highFrequencyRumble, MxU32 p_milliseconds + ) { + if (g_isle->GetHaptic() && + !InputManager() + ->HandleRumbleEvent(p_strength, p_lowFrequencyRumble, p_highFrequencyRumble, p_milliseconds)) { // Platform-specific handling #ifdef __EMSCRIPTEN__ - Emscripten_HandleRumbleEvent(0.5f, 0.5f, 700); + Emscripten_HandleRumbleEvent(p_lowFrequencyRumble, p_highFrequencyRumble, p_milliseconds); #endif + } + }; + + switch (event->user.code) { + case e_hitActor: + rumble(0.5f, 0.5f, 0.5f, 700); + break; + case e_skeletonKick: + rumble(0.8f, 0.8f, 0.8f, 2500); + break; + case e_raceFinished: + case e_goodEnding: + case e_badEnding: + rumble(1.0f, 1.0f, 1.0f, 3000); + break; } } diff --git a/LEGO1/lego/legoomni/include/legoutils.h b/LEGO1/lego/legoomni/include/legoutils.h index b29a336c..849662df 100644 --- a/LEGO1/lego/legoomni/include/legoutils.h +++ b/LEGO1/lego/legoomni/include/legoutils.h @@ -32,6 +32,14 @@ enum Cursor { e_cursorNone }; +enum GameEvent { + e_hitActor, + e_skeletonKick, + e_raceFinished, + e_badEnding, + e_goodEnding +}; + class BoundingSphere; class MxAtomId; class LegoEntity; @@ -71,7 +79,7 @@ LegoNamedTexture* ReadNamedTexture(LegoStorage* p_storage); void WriteDefaultTexture(LegoStorage* p_storage, const char* p_name); void WriteNamedTexture(LegoStorage* p_storage, LegoNamedTexture* p_namedTexture); void LoadFromNamedTexture(LegoNamedTexture* p_namedTexture); -void HitActorEvent(); +void EmitGameEvent(GameEvent p_event); // FUNCTION: BETA10 0x100260a0 inline void StartIsleAction(IsleScript::Script p_objectId) diff --git a/LEGO1/lego/legoomni/src/common/legoutils.cpp b/LEGO1/lego/legoomni/src/common/legoutils.cpp index 46d9b4d0..fdc6755a 100644 --- a/LEGO1/lego/legoomni/src/common/legoutils.cpp +++ b/LEGO1/lego/legoomni/src/common/legoutils.cpp @@ -784,9 +784,10 @@ void LoadFromNamedTexture(LegoNamedTexture* p_namedTexture) } } -void HitActorEvent() +void EmitGameEvent(GameEvent p_event) { SDL_Event event; - event.user.type = g_legoSdlEvents.m_hitActor; + event.user.type = g_legoSdlEvents.m_gameEvent; + event.user.code = p_event; SDL_PushEvent(&event); } diff --git a/LEGO1/lego/legoomni/src/paths/legoextraactor.cpp b/LEGO1/lego/legoomni/src/paths/legoextraactor.cpp index 8a02235c..316d010b 100644 --- a/LEGO1/lego/legoomni/src/paths/legoextraactor.cpp +++ b/LEGO1/lego/legoomni/src/paths/legoextraactor.cpp @@ -235,7 +235,9 @@ MxResult LegoExtraActor::HitActor(LegoPathActor* p_actor, MxBool p_bool) assert(m_roi); assert(SoundManager()->GetCacheSoundManager()); SoundManager()->GetCacheSoundManager()->Play("crash5", m_roi->GetName(), FALSE); - HitActorEvent(); + if (p_actor->GetUserNavFlag()) { + EmitGameEvent(e_hitActor); + } m_scheduledTime = Timer()->GetTime() + m_disAnim->GetDuration(); m_prevWorldSpeed = GetWorldSpeed(); VTable0xc4(); @@ -249,7 +251,9 @@ MxResult LegoExtraActor::HitActor(LegoPathActor* p_actor, MxBool p_bool) LegoROI* roi = GetROI(); assert(roi); SoundManager()->GetCacheSoundManager()->Play("crash5", m_roi->GetName(), FALSE); - HitActorEvent(); + if (p_actor->GetUserNavFlag()) { + EmitGameEvent(e_hitActor); + } VTable0xc4(); SetActorState(c_two | c_noCollide); Mx3DPointFloat dir = p_actor->GetWorldDirection(); diff --git a/LEGO1/lego/legoomni/src/race/carrace.cpp b/LEGO1/lego/legoomni/src/race/carrace.cpp index 33ac928e..52315e79 100644 --- a/LEGO1/lego/legoomni/src/race/carrace.cpp +++ b/LEGO1/lego/legoomni/src/race/carrace.cpp @@ -267,6 +267,8 @@ MxLong CarRace::HandlePathStruct(LegoPathStructNotificationParam& p_param) FALSE, TRUE ); + + EmitGameEvent(e_raceFinished); } result = 1; diff --git a/LEGO1/lego/legoomni/src/race/jetskirace.cpp b/LEGO1/lego/legoomni/src/race/jetskirace.cpp index 2eaa986d..edeb6aa2 100644 --- a/LEGO1/lego/legoomni/src/race/jetskirace.cpp +++ b/LEGO1/lego/legoomni/src/race/jetskirace.cpp @@ -206,6 +206,7 @@ MxLong JetskiRace::HandlePathStruct(LegoPathStructNotificationParam& p_param) m_destLocation = LegoGameState::e_jetrace2; TransitionManager()->StartTransition(MxTransitionManager::e_mosaic, 50, FALSE, FALSE); + EmitGameEvent(e_raceFinished); } result = 1; diff --git a/LEGO1/lego/legoomni/src/race/legoracers.cpp b/LEGO1/lego/legoomni/src/race/legoracers.cpp index 74fd9f9a..d8e4dff5 100644 --- a/LEGO1/lego/legoomni/src/race/legoracers.cpp +++ b/LEGO1/lego/legoomni/src/race/legoracers.cpp @@ -392,6 +392,7 @@ MxU32 LegoRaceCar::HandleSkeletonKicks(float p_param1) m_kickStart = p_param1; SoundManager()->GetCacheSoundManager()->Play(g_soundSkel3, NULL, FALSE); + EmitGameEvent(e_skeletonKick); return TRUE; } @@ -528,6 +529,9 @@ MxResult LegoRaceCar::HitActor(LegoPathActor* p_actor, MxBool p_bool) } } } + else { + EmitGameEvent(e_hitActor); + } return SUCCESS; } @@ -726,6 +730,9 @@ MxResult LegoJetski::HitActor(LegoPathActor* p_actor, MxBool p_bool) } } } + else { + EmitGameEvent(e_hitActor); + } return SUCCESS; } diff --git a/LEGO1/lego/legoomni/src/worlds/act3.cpp b/LEGO1/lego/legoomni/src/worlds/act3.cpp index af9cd09b..8e839e3c 100644 --- a/LEGO1/lego/legoomni/src/worlds/act3.cpp +++ b/LEGO1/lego/legoomni/src/worlds/act3.cpp @@ -793,6 +793,8 @@ void Act3::GoodEnding(const Matrix4& p_destination) m_copter->m_unk0x1a8, m_copter->m_unk0x1f4 ); + + EmitGameEvent(e_goodEnding); } // FUNCTION: LEGO1 0x10073500 @@ -872,6 +874,8 @@ void Act3::BadEnding(const Matrix4& p_destination) m_copter->m_unk0x1a8, m_copter->m_unk0x1f4 ); + + EmitGameEvent(e_badEnding); } // FUNCTION: LEGO1 0x10073a60 diff --git a/LEGO1/lego/legoomni/src/worlds/legoact2.cpp b/LEGO1/lego/legoomni/src/worlds/legoact2.cpp index 5fe73440..b3f92433 100644 --- a/LEGO1/lego/legoomni/src/worlds/legoact2.cpp +++ b/LEGO1/lego/legoomni/src/worlds/legoact2.cpp @@ -939,6 +939,7 @@ MxResult LegoAct2::BadEnding() MxTrace("Bad End of Act2\n"); m_unk0x10c4 = 14; + EmitGameEvent(e_badEnding); return SUCCESS; } diff --git a/LEGO1/omni/include/mxutilities.h b/LEGO1/omni/include/mxutilities.h index 33754f3c..7ea9fed5 100644 --- a/LEGO1/omni/include/mxutilities.h +++ b/LEGO1/omni/include/mxutilities.h @@ -10,7 +10,7 @@ struct LegoSdlEvents { Uint32 m_windowsMessage; Uint32 m_presenterProgress; - Uint32 m_hitActor; + Uint32 m_gameEvent; }; LEGO1_EXPORT extern LegoSdlEvents g_legoSdlEvents; diff --git a/LEGO1/omni/src/main/mxomni.cpp b/LEGO1/omni/src/main/mxomni.cpp index b6081b16..c314c7a8 100644 --- a/LEGO1/omni/src/main/mxomni.cpp +++ b/LEGO1/omni/src/main/mxomni.cpp @@ -166,7 +166,7 @@ MxResult MxOmni::Create(MxOmniCreateParam& p_param) Uint32 event = SDL_RegisterEvents(3); g_legoSdlEvents.m_windowsMessage = event + 0; g_legoSdlEvents.m_presenterProgress = event + 1; - g_legoSdlEvents.m_hitActor = event + 2; + g_legoSdlEvents.m_gameEvent = event + 2; } result = SUCCESS; From bd713706028dfcd54e81c0bafa4c5330fa479fdc Mon Sep 17 00:00:00 2001 From: Fabian Neundorf Date: Sat, 19 Jul 2025 01:09:37 +0200 Subject: [PATCH 161/188] Clear unknown in `LegoControlManagerNotificationParam` (#1633) --- LEGO1/lego/legoomni/include/legocontrolmanager.h | 4 ++-- LEGO1/lego/legoomni/src/actors/ambulance.cpp | 4 ++-- LEGO1/lego/legoomni/src/actors/bike.cpp | 4 ++-- LEGO1/lego/legoomni/src/actors/dunebuggy.cpp | 4 ++-- LEGO1/lego/legoomni/src/actors/helicopter.cpp | 2 +- LEGO1/lego/legoomni/src/actors/jetski.cpp | 2 +- LEGO1/lego/legoomni/src/actors/motorcycle.cpp | 2 +- LEGO1/lego/legoomni/src/actors/skateboard.cpp | 2 +- LEGO1/lego/legoomni/src/actors/towtrack.cpp | 4 ++-- LEGO1/lego/legoomni/src/build/legocarbuild.cpp | 4 ++-- LEGO1/lego/legoomni/src/common/mxcontrolpresenter.cpp | 4 ++-- LEGO1/lego/legoomni/src/race/carrace.cpp | 2 +- LEGO1/lego/legoomni/src/race/jetskirace.cpp | 2 +- LEGO1/lego/legoomni/src/race/legoracemap.cpp | 2 +- LEGO1/lego/legoomni/src/worlds/elevatorbottom.cpp | 2 +- LEGO1/lego/legoomni/src/worlds/gasstation.cpp | 2 +- LEGO1/lego/legoomni/src/worlds/hospital.cpp | 2 +- LEGO1/lego/legoomni/src/worlds/infocenter.cpp | 2 +- LEGO1/lego/legoomni/src/worlds/infocenterdoor.cpp | 2 +- LEGO1/lego/legoomni/src/worlds/isle.cpp | 2 +- LEGO1/lego/legoomni/src/worlds/jukebox.cpp | 2 +- LEGO1/lego/legoomni/src/worlds/police.cpp | 2 +- LEGO1/lego/legoomni/src/worlds/registrationbook.cpp | 2 +- LEGO1/lego/legoomni/src/worlds/score.cpp | 2 +- 24 files changed, 31 insertions(+), 31 deletions(-) diff --git a/LEGO1/lego/legoomni/include/legocontrolmanager.h b/LEGO1/lego/legoomni/include/legocontrolmanager.h index f962ee0c..680bef59 100644 --- a/LEGO1/lego/legoomni/include/legocontrolmanager.h +++ b/LEGO1/lego/legoomni/include/legocontrolmanager.h @@ -24,11 +24,11 @@ class LegoControlManagerNotificationParam : public LegoEventNotificationParam { void SetClickedObjectId(MxS32 p_clickedObjectId) { m_clickedObjectId = p_clickedObjectId; } void SetClickedAtom(const char* p_clickedAtom) { m_clickedAtom = p_clickedAtom; } - void SetUnknown0x28(MxS16 p_unk0x28) { m_unk0x28 = p_unk0x28; } + void SetEnabledChild(MxS16 p_enabledChild) { m_enabledChild = p_enabledChild; } MxS32 m_clickedObjectId; // 0x20 const char* m_clickedAtom; // 0x24 - MxS16 m_unk0x28; // 0x28 + MxS16 m_enabledChild; // 0x28 }; // SYNTHETIC: LEGO1 0x10028bf0 diff --git a/LEGO1/lego/legoomni/src/actors/ambulance.cpp b/LEGO1/lego/legoomni/src/actors/ambulance.cpp index fa98fc65..0bc2a9e6 100644 --- a/LEGO1/lego/legoomni/src/actors/ambulance.cpp +++ b/LEGO1/lego/legoomni/src/actors/ambulance.cpp @@ -437,7 +437,7 @@ MxLong Ambulance::HandleControl(LegoControlManagerNotificationParam& p_param) { MxLong result = 0; - if (p_param.m_unk0x28 == 1) { + if (p_param.m_enabledChild == 1) { switch (p_param.m_clickedObjectId) { case IsleScript::c_AmbulanceArms_Ctl: Exit(); @@ -454,7 +454,7 @@ MxLong Ambulance::HandleControl(LegoControlManagerNotificationParam& p_param) case IsleScript::c_AmbulanceHorn_Ctl: MxSoundPresenter* presenter = (MxSoundPresenter*) CurrentWorld()->Find("MxSoundPresenter", "AmbulanceHorn_Sound"); - presenter->Enable(p_param.m_unk0x28); + presenter->Enable(p_param.m_enabledChild); break; } } diff --git a/LEGO1/lego/legoomni/src/actors/bike.cpp b/LEGO1/lego/legoomni/src/actors/bike.cpp index fa9bedcb..fbdce39e 100644 --- a/LEGO1/lego/legoomni/src/actors/bike.cpp +++ b/LEGO1/lego/legoomni/src/actors/bike.cpp @@ -81,7 +81,7 @@ MxLong Bike::HandleControl(LegoControlManagerNotificationParam& p_param) { MxLong result = 0; - if (p_param.m_unk0x28 == 1) { + if (p_param.m_enabledChild == 1) { switch (p_param.m_clickedObjectId) { case IsleScript::c_BikeArms_Ctl: Exit(); @@ -97,7 +97,7 @@ MxLong Bike::HandleControl(LegoControlManagerNotificationParam& p_param) case IsleScript::c_BikeHorn_Ctl: MxSoundPresenter* presenter = (MxSoundPresenter*) CurrentWorld()->Find("MxSoundPresenter", "BikeHorn_Sound"); - presenter->Enable(p_param.m_unk0x28); + presenter->Enable(p_param.m_enabledChild); break; } } diff --git a/LEGO1/lego/legoomni/src/actors/dunebuggy.cpp b/LEGO1/lego/legoomni/src/actors/dunebuggy.cpp index 562edfd9..d8244be4 100644 --- a/LEGO1/lego/legoomni/src/actors/dunebuggy.cpp +++ b/LEGO1/lego/legoomni/src/actors/dunebuggy.cpp @@ -121,7 +121,7 @@ MxLong DuneBuggy::HandleControl(LegoControlManagerNotificationParam& p_param) { MxLong result = 0; - if (p_param.m_unk0x28 == 1) { + if (p_param.m_enabledChild == 1) { switch (p_param.m_clickedObjectId) { case IsleScript::c_DuneCarArms_Ctl: Exit(); @@ -137,7 +137,7 @@ MxLong DuneBuggy::HandleControl(LegoControlManagerNotificationParam& p_param) case IsleScript::c_DuneCarHorn_Ctl: MxSoundPresenter* presenter = (MxSoundPresenter*) CurrentWorld()->Find("MxSoundPresenter", "DuneCarHorn_Sound"); - presenter->Enable(p_param.m_unk0x28); + presenter->Enable(p_param.m_enabledChild); break; } } diff --git a/LEGO1/lego/legoomni/src/actors/helicopter.cpp b/LEGO1/lego/legoomni/src/actors/helicopter.cpp index 46353585..6cbb9675 100644 --- a/LEGO1/lego/legoomni/src/actors/helicopter.cpp +++ b/LEGO1/lego/legoomni/src/actors/helicopter.cpp @@ -189,7 +189,7 @@ MxLong Helicopter::HandleControl(LegoControlManagerNotificationParam& p_param) break; } - if (p_param.m_unk0x28 == 1) { + if (p_param.m_enabledChild == 1) { MxU32 isPizza = FALSE; switch (p_param.m_clickedObjectId) { diff --git a/LEGO1/lego/legoomni/src/actors/jetski.cpp b/LEGO1/lego/legoomni/src/actors/jetski.cpp index 2abc3862..03c3ca91 100644 --- a/LEGO1/lego/legoomni/src/actors/jetski.cpp +++ b/LEGO1/lego/legoomni/src/actors/jetski.cpp @@ -137,7 +137,7 @@ void Jetski::RemoveFromWorld() // FUNCTION: LEGO1 0x1007e8e0 MxLong Jetski::HandleControl(LegoControlManagerNotificationParam& p_param) { - if (p_param.m_unk0x28 == 1 && CurrentWorld()->IsA("Isle")) { + if (p_param.m_enabledChild == 1 && CurrentWorld()->IsA("Isle")) { switch (p_param.m_clickedObjectId) { case IsleScript::c_JetskiArms_Ctl: Exit(); diff --git a/LEGO1/lego/legoomni/src/actors/motorcycle.cpp b/LEGO1/lego/legoomni/src/actors/motorcycle.cpp index 99810420..5dae1547 100644 --- a/LEGO1/lego/legoomni/src/actors/motorcycle.cpp +++ b/LEGO1/lego/legoomni/src/actors/motorcycle.cpp @@ -115,7 +115,7 @@ MxLong Motocycle::HandleControl(LegoControlManagerNotificationParam& p_param) { MxLong result = 0; - if (p_param.m_unk0x28 == 1) { + if (p_param.m_enabledChild == 1) { switch (p_param.m_clickedObjectId) { case IsleScript::c_MotoBikeArms_Ctl: Exit(); diff --git a/LEGO1/lego/legoomni/src/actors/skateboard.cpp b/LEGO1/lego/legoomni/src/actors/skateboard.cpp index d47b2178..17730148 100644 --- a/LEGO1/lego/legoomni/src/actors/skateboard.cpp +++ b/LEGO1/lego/legoomni/src/actors/skateboard.cpp @@ -110,7 +110,7 @@ MxLong SkateBoard::HandleControl(LegoControlManagerNotificationParam& p_param) { MxU32 result = 0; - if (p_param.m_unk0x28 == 1 && p_param.m_clickedObjectId == IsleScript::c_SkateArms_Ctl) { + if (p_param.m_enabledChild == 1 && p_param.m_clickedObjectId == IsleScript::c_SkateArms_Ctl) { Exit(); GameState()->m_currentArea = LegoGameState::Area::e_vehicleExited; result = 1; diff --git a/LEGO1/lego/legoomni/src/actors/towtrack.cpp b/LEGO1/lego/legoomni/src/actors/towtrack.cpp index 6c3f5785..7a54d593 100644 --- a/LEGO1/lego/legoomni/src/actors/towtrack.cpp +++ b/LEGO1/lego/legoomni/src/actors/towtrack.cpp @@ -483,7 +483,7 @@ MxLong TowTrack::HandleControl(LegoControlManagerNotificationParam& p_param) { MxLong result = 0; - if (p_param.m_unk0x28 == 1) { + if (p_param.m_enabledChild == 1) { switch (p_param.m_clickedObjectId) { case IsleScript::c_TowTrackArms_Ctl: Exit(); @@ -499,7 +499,7 @@ MxLong TowTrack::HandleControl(LegoControlManagerNotificationParam& p_param) break; case IsleScript::c_TowHorn_Ctl: MxSoundPresenter* presenter = (MxSoundPresenter*) CurrentWorld()->Find("MxSoundPresenter", "TowHorn_Sound"); - presenter->Enable(p_param.m_unk0x28); + presenter->Enable(p_param.m_enabledChild); break; } } diff --git a/LEGO1/lego/legoomni/src/build/legocarbuild.cpp b/LEGO1/lego/legoomni/src/build/legocarbuild.cpp index 8809cd5c..e1044c79 100644 --- a/LEGO1/lego/legoomni/src/build/legocarbuild.cpp +++ b/LEGO1/lego/legoomni/src/build/legocarbuild.cpp @@ -953,7 +953,7 @@ undefined4 LegoCarBuild::FUN_10024890(MxParam* p_param) LegoControlManagerNotificationParam* param = (LegoControlManagerNotificationParam*) p_param; assert(m_buildState); - if (param->m_unk0x28) { + if (param->m_enabledChild) { switch (param->m_clickedObjectId) { // The enum values are all identical between CopterScript, DunecarScript, JetskiScript, and RacecarScript case CopterScript::c_Info_Ctl: @@ -1009,7 +1009,7 @@ undefined4 LegoCarBuild::FUN_10024890(MxParam* p_param) case CopterScript::c_Platform_Ctl: FUN_10024f50(); m_unk0xf8 = c_unknown8; - m_unk0xfc = param->m_unk0x28; + m_unk0xfc = param->m_enabledChild; result = 1; break; default: diff --git a/LEGO1/lego/legoomni/src/common/mxcontrolpresenter.cpp b/LEGO1/lego/legoomni/src/common/mxcontrolpresenter.cpp index 9374cb9f..f39ad192 100644 --- a/LEGO1/lego/legoomni/src/common/mxcontrolpresenter.cpp +++ b/LEGO1/lego/legoomni/src/common/mxcontrolpresenter.cpp @@ -154,7 +154,7 @@ MxBool MxControlPresenter::Notify(LegoControlManagerNotificationParam* p_param, p_param->SetClickedAtom(m_action->GetAtomId().GetInternal()); UpdateEnabledChild(0); p_param->SetNotification(c_notificationControl); - p_param->SetUnknown0x28(m_enabledChild); + p_param->SetEnabledChild(m_enabledChild); return TRUE; } break; @@ -164,7 +164,7 @@ MxBool MxControlPresenter::Notify(LegoControlManagerNotificationParam* p_param, p_param->SetClickedAtom(m_action->GetAtomId().GetInternal()); UpdateEnabledChild(m_stateOrCellIndex); p_param->SetNotification(c_notificationControl); - p_param->SetUnknown0x28(m_enabledChild); + p_param->SetEnabledChild(m_enabledChild); return TRUE; } break; diff --git a/LEGO1/lego/legoomni/src/race/carrace.cpp b/LEGO1/lego/legoomni/src/race/carrace.cpp index 18ea1c89..6c36223a 100644 --- a/LEGO1/lego/legoomni/src/race/carrace.cpp +++ b/LEGO1/lego/legoomni/src/race/carrace.cpp @@ -335,7 +335,7 @@ MxLong CarRace::HandlePathStruct(LegoPathStructNotificationParam& p_param) // FUNCTION: LEGO1 0x10017650 MxLong CarRace::HandleControl(LegoControlManagerNotificationParam& p_param) { - if (p_param.m_unk0x28 == 1) { + if (p_param.m_enabledChild == 1) { switch (p_param.m_clickedObjectId) { case 3: InvokeAction(Extra::e_stop, *g_carraceScript, CarraceScript::c_irtx08ra_PlayWav, NULL); diff --git a/LEGO1/lego/legoomni/src/race/jetskirace.cpp b/LEGO1/lego/legoomni/src/race/jetskirace.cpp index 2893868f..79391c6d 100644 --- a/LEGO1/lego/legoomni/src/race/jetskirace.cpp +++ b/LEGO1/lego/legoomni/src/race/jetskirace.cpp @@ -124,7 +124,7 @@ MxLong JetskiRace::HandleControl(LegoControlManagerNotificationParam& p_param) { MxLong result = 0; - if (p_param.m_unk0x28 == 1) { + if (p_param.m_enabledChild == 1) { switch (p_param.m_clickedObjectId) { case JetraceScript::c_JetskiArms_Ctl: m_act1State->m_state = Act1State::e_none; diff --git a/LEGO1/lego/legoomni/src/race/legoracemap.cpp b/LEGO1/lego/legoomni/src/race/legoracemap.cpp index bd0a04bd..9637114d 100644 --- a/LEGO1/lego/legoomni/src/race/legoracemap.cpp +++ b/LEGO1/lego/legoomni/src/race/legoracemap.cpp @@ -129,7 +129,7 @@ MxLong LegoRaceMap::Notify(MxParam& p_param) if (param.GetNotification() == c_notificationControl && m_Map_Ctl->GetAction()->GetObjectId() == ((LegoControlManagerNotificationParam&) p_param).m_clickedObjectId) { - if (((LegoControlManagerNotificationParam&) p_param).m_unk0x28 == 1) { + if (((LegoControlManagerNotificationParam&) p_param).m_enabledChild == 1) { m_unk0x08 = TRUE; FUN_1005d4b0(); m_stillPresenter->Enable(TRUE); diff --git a/LEGO1/lego/legoomni/src/worlds/elevatorbottom.cpp b/LEGO1/lego/legoomni/src/worlds/elevatorbottom.cpp index 2a9c0772..6e1d484d 100644 --- a/LEGO1/lego/legoomni/src/worlds/elevatorbottom.cpp +++ b/LEGO1/lego/legoomni/src/worlds/elevatorbottom.cpp @@ -87,7 +87,7 @@ MxLong ElevatorBottom::HandleControl(LegoControlManagerNotificationParam& p_para { MxLong result = 0; - if (p_param.m_unk0x28 == 1) { + if (p_param.m_enabledChild == 1) { switch (p_param.m_clickedObjectId) { case ElevbottScript::c_LeftArrow_Ctl: m_destLocation = LegoGameState::e_infodoor; diff --git a/LEGO1/lego/legoomni/src/worlds/gasstation.cpp b/LEGO1/lego/legoomni/src/worlds/gasstation.cpp index 4a449ba2..271c94de 100644 --- a/LEGO1/lego/legoomni/src/worlds/gasstation.cpp +++ b/LEGO1/lego/legoomni/src/worlds/gasstation.cpp @@ -387,7 +387,7 @@ MxLong GasStation::HandleButtonDown(LegoControlManagerNotificationParam& p_param // FUNCTION: BETA10 0x10029445 MxLong GasStation::HandleControl(LegoControlManagerNotificationParam& p_param) { - if (p_param.m_unk0x28 == 1) { + if (p_param.m_enabledChild == 1) { MxDSAction action; switch (p_param.m_clickedObjectId) { diff --git a/LEGO1/lego/legoomni/src/worlds/hospital.cpp b/LEGO1/lego/legoomni/src/worlds/hospital.cpp index 6ab9c218..2d44bc9c 100644 --- a/LEGO1/lego/legoomni/src/worlds/hospital.cpp +++ b/LEGO1/lego/legoomni/src/worlds/hospital.cpp @@ -553,7 +553,7 @@ MxLong Hospital::HandleButtonDown(LegoControlManagerNotificationParam& p_param) // FUNCTION: LEGO1 0x10075f90 MxBool Hospital::HandleControl(LegoControlManagerNotificationParam& p_param) { - if (p_param.m_unk0x28 == 1) { + if (p_param.m_enabledChild == 1) { switch (p_param.m_clickedObjectId) { case HospitalScript::c_Info_Ctl: BackgroundAudioManager()->RaiseVolume(); diff --git a/LEGO1/lego/legoomni/src/worlds/infocenter.cpp b/LEGO1/lego/legoomni/src/worlds/infocenter.cpp index 19957ac8..67a21d69 100644 --- a/LEGO1/lego/legoomni/src/worlds/infocenter.cpp +++ b/LEGO1/lego/legoomni/src/worlds/infocenter.cpp @@ -941,7 +941,7 @@ MxU8 Infocenter::HandleButtonUp(MxS32 p_x, MxS32 p_y) // FUNCTION: BETA10 0x1002ffd4 MxU8 Infocenter::HandleControl(LegoControlManagerNotificationParam& p_param) { - if (p_param.m_unk0x28 == 1) { + if (p_param.m_enabledChild == 1) { m_infoManDialogueTimer = 0; InfomainScript::Script actionToPlay = InfomainScript::c_noneInfomain; diff --git a/LEGO1/lego/legoomni/src/worlds/infocenterdoor.cpp b/LEGO1/lego/legoomni/src/worlds/infocenterdoor.cpp index 8f92a323..8dc21750 100644 --- a/LEGO1/lego/legoomni/src/worlds/infocenterdoor.cpp +++ b/LEGO1/lego/legoomni/src/worlds/infocenterdoor.cpp @@ -96,7 +96,7 @@ MxLong InfocenterDoor::HandleControl(LegoControlManagerNotificationParam& p_para { MxLong result = 0; - if (p_param.m_unk0x28 == 1) { + if (p_param.m_enabledChild == 1) { DeleteObjects(&m_atomId, InfodoorScript::c_iic037in_PlayWav, 510); switch (p_param.m_clickedObjectId) { diff --git a/LEGO1/lego/legoomni/src/worlds/isle.cpp b/LEGO1/lego/legoomni/src/worlds/isle.cpp index 3ec9af8c..ba73754d 100644 --- a/LEGO1/lego/legoomni/src/worlds/isle.cpp +++ b/LEGO1/lego/legoomni/src/worlds/isle.cpp @@ -284,7 +284,7 @@ void Isle::ReadyWorld() // FUNCTION: LEGO1 0x10031030 MxLong Isle::HandleControl(LegoControlManagerNotificationParam& p_param) { - if (p_param.m_unk0x28 == 1) { + if (p_param.m_enabledChild == 1) { MxDSAction action; switch (p_param.m_clickedObjectId) { diff --git a/LEGO1/lego/legoomni/src/worlds/jukebox.cpp b/LEGO1/lego/legoomni/src/worlds/jukebox.cpp index db244e00..aa8858cf 100644 --- a/LEGO1/lego/legoomni/src/worlds/jukebox.cpp +++ b/LEGO1/lego/legoomni/src/worlds/jukebox.cpp @@ -123,7 +123,7 @@ MxBool JukeBox::HandleControl(LegoControlManagerNotificationParam& p_param) { MxStillPresenter* presenter; - if (p_param.m_unk0x28 == 1) { + if (p_param.m_enabledChild == 1) { switch (p_param.m_clickedObjectId) { case JukeboxwScript::c_Dback_Ctl: switch (m_state->m_music) { diff --git a/LEGO1/lego/legoomni/src/worlds/police.cpp b/LEGO1/lego/legoomni/src/worlds/police.cpp index 1c3bccd7..1cbcea48 100644 --- a/LEGO1/lego/legoomni/src/worlds/police.cpp +++ b/LEGO1/lego/legoomni/src/worlds/police.cpp @@ -101,7 +101,7 @@ void Police::ReadyWorld() // FUNCTION: LEGO1 0x1005e550 MxLong Police::HandleControl(LegoControlManagerNotificationParam& p_param) { - if (p_param.m_unk0x28 == 1) { + if (p_param.m_enabledChild == 1) { switch (p_param.m_clickedObjectId) { case PoliceScript::c_LeftArrow_Ctl: case PoliceScript::c_RightArrow_Ctl: diff --git a/LEGO1/lego/legoomni/src/worlds/registrationbook.cpp b/LEGO1/lego/legoomni/src/worlds/registrationbook.cpp index 6b177308..64f981ba 100644 --- a/LEGO1/lego/legoomni/src/worlds/registrationbook.cpp +++ b/LEGO1/lego/legoomni/src/worlds/registrationbook.cpp @@ -226,7 +226,7 @@ MxLong RegistrationBook::HandleKeyPress(MxU8 p_key) // FUNCTION: LEGO1 0x100774a0 MxLong RegistrationBook::HandleControl(LegoControlManagerNotificationParam& p_param) { - MxS16 buttonId = p_param.m_unk0x28; + MxS16 buttonId = p_param.m_enabledChild; if (buttonId >= 1 && buttonId <= 28) { if (p_param.m_clickedObjectId == RegbookScript::c_Alphabet_Ctl) { diff --git a/LEGO1/lego/legoomni/src/worlds/score.cpp b/LEGO1/lego/legoomni/src/worlds/score.cpp index edc2d991..56dacaf1 100644 --- a/LEGO1/lego/legoomni/src/worlds/score.cpp +++ b/LEGO1/lego/legoomni/src/worlds/score.cpp @@ -165,7 +165,7 @@ void Score::ReadyWorld() // FUNCTION: LEGO1 0x100016d0 MxLong Score::FUN_100016d0(LegoControlManagerNotificationParam& p_param) { - MxS16 unk0x28 = p_param.m_unk0x28; + MxS16 unk0x28 = p_param.m_enabledChild; if (unk0x28 == 1 || p_param.m_clickedObjectId == InfoscorScript::c_LegoBox_Ctl) { switch (p_param.m_clickedObjectId) { From 29d7a5df8955812b7d146ac8127bc5546e2d640d Mon Sep 17 00:00:00 2001 From: jonschz <17198703+jonschz@users.noreply.github.com> Date: Sat, 19 Jul 2025 12:24:49 +0200 Subject: [PATCH 162/188] Get BETA10 to compile again (#1635) Co-authored-by: jonschz --- LEGO1/lego/legoomni/src/build/legocarbuild.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/LEGO1/lego/legoomni/src/build/legocarbuild.cpp b/LEGO1/lego/legoomni/src/build/legocarbuild.cpp index e1044c79..f50dcdfe 100644 --- a/LEGO1/lego/legoomni/src/build/legocarbuild.cpp +++ b/LEGO1/lego/legoomni/src/build/legocarbuild.cpp @@ -1051,7 +1051,7 @@ undefined4 LegoCarBuild::FUN_10024890(MxParam* p_param) LegoControlManagerNotificationParam* param = (LegoControlManagerNotificationParam*) p_param; assert(m_buildState); - if (param->m_unk0x28) { + if (param->m_enabledChild) { switch (param->m_clickedObjectId) { case CopterScript::c_Info_Ctl: m_animPresenter->SetShelfState(LegoCarBuildAnimPresenter::e_selected); @@ -1113,7 +1113,7 @@ undefined4 LegoCarBuild::FUN_10024890(MxParam* p_param) case CopterScript::c_Platform_Ctl: FUN_10024f50(); m_unk0xf8 = c_unknown8; - m_unk0xfc = param->m_unk0x28; + m_unk0xfc = param->m_enabledChild; result = 1; break; default: From 73d9ef1d8074665fdd43107a2befb5cd444c9e37 Mon Sep 17 00:00:00 2001 From: jonschz <17198703+jonschz@users.noreply.github.com> Date: Sat, 19 Jul 2025 16:28:35 +0200 Subject: [PATCH 163/188] Improve `FUN_10061010`, other fixes (#1634) Co-authored-by: jonschz --- .../legoomni/include/legoanimationmanager.h | 3 + .../lego/legoomni/include/legotraninfolist.h | 8 +++ .../src/common/legoanimationmanager.cpp | 69 ++++++++++++++----- .../src/common/legoanimmmpresenter.cpp | 21 ++++++ LEGO1/lego/legoomni/src/entity/legoworld.cpp | 2 + LEGO1/omni/include/mxlist.h | 6 +- LEGO1/realtime/orientableroi.cpp | 1 + 7 files changed, 90 insertions(+), 20 deletions(-) diff --git a/LEGO1/lego/legoomni/include/legoanimationmanager.h b/LEGO1/lego/legoomni/include/legoanimationmanager.h index 60f63d4a..efe23bc2 100644 --- a/LEGO1/lego/legoomni/include/legoanimationmanager.h +++ b/LEGO1/lego/legoomni/include/legoanimationmanager.h @@ -303,4 +303,7 @@ class LegoAnimationManager : public MxCore { // TEMPLATE: LEGO1 0x10061750 // MxListCursor::MxListCursor +// TEMPLATE: BETA10 0x1004b5d0 +// MxListCursor::Next + #endif // LEGOANIMATIONMANAGER_H diff --git a/LEGO1/lego/legoomni/include/legotraninfolist.h b/LEGO1/lego/legoomni/include/legotraninfolist.h index 06cd2558..0bfe4bd8 100644 --- a/LEGO1/lego/legoomni/include/legotraninfolist.h +++ b/LEGO1/lego/legoomni/include/legotraninfolist.h @@ -28,9 +28,11 @@ class LegoTranInfoList : public MxPtrList { // class MxPtrListCursor // VTABLE: LEGO1 0x100d8d20 +// VTABLE: BETA10 0x101bad70 // SIZE 0x10 class LegoTranInfoListCursor : public MxPtrListCursor { public: + // FUNCTION: BETA10 0x100496d0 LegoTranInfoListCursor(LegoTranInfoList* p_list) : MxPtrListCursor(p_list) {} }; @@ -62,9 +64,14 @@ class LegoTranInfoListCursor : public MxPtrListCursor { // MxPtrList::`scalar deleting destructor' // SYNTHETIC: LEGO1 0x100612f0 +// SYNTHETIC: BETA10 0x100498c0 // LegoTranInfoListCursor::`scalar deleting destructor' +// SYNTHETIC: BETA10 0x10049770 +// MxPtrListCursor::MxPtrListCursor + // FUNCTION: LEGO1 0x10061360 +// FUNCTION: BETA10 0x10049910 // MxPtrListCursor::~MxPtrListCursor // SYNTHETIC: LEGO1 0x100613b0 @@ -77,6 +84,7 @@ class LegoTranInfoListCursor : public MxPtrListCursor { // MxListCursor::~MxListCursor // FUNCTION: LEGO1 0x100614e0 +// FUNCTION: BETA10 0x10049ab0 // LegoTranInfoListCursor::~LegoTranInfoListCursor #endif // LEGOTRANINFOLIST_H diff --git a/LEGO1/lego/legoomni/src/common/legoanimationmanager.cpp b/LEGO1/lego/legoomni/src/common/legoanimationmanager.cpp index 616be97a..e6dd3513 100644 --- a/LEGO1/lego/legoomni/src/common/legoanimationmanager.cpp +++ b/LEGO1/lego/legoomni/src/common/legoanimationmanager.cpp @@ -20,6 +20,7 @@ #include "legoworld.h" #include "misc.h" #include "mxbackgroundaudiomanager.h" +#include "mxdebug.h" #include "mxmisc.h" #include "mxnotificationmanager.h" #include "mxticklemanager.h" @@ -1225,12 +1226,10 @@ void LegoAnimationManager::CameraTriggerFire(LegoPathActor* p_actor, MxBool, MxU } } -// FUNCTION: LEGO1 0x10061010 +#ifdef BETA10 // FUNCTION: BETA10 0x100422cc void LegoAnimationManager::FUN_10061010(MxBool p_und) { - MxBool unk0x39 = FALSE; - FUN_10064b50(-1); if (m_tranInfoList != NULL) { @@ -1238,17 +1237,47 @@ void LegoAnimationManager::FUN_10061010(MxBool p_und) LegoTranInfo* tranInfo; while (cursor.Next(tranInfo)) { - if (tranInfo->m_presenter != NULL) { - // TODO: Match - MxU32 flags = tranInfo->m_flags; + if (tranInfo->m_unk0x14 && tranInfo->m_location != -1) { + MxTrace("Releasing user from %d\n", tranInfo->m_objectId); + if (tranInfo->m_presenter != NULL) { + tranInfo->m_presenter->FUN_1004b8c0(); + } + + tranInfo->m_unk0x14 = FALSE; + } + else { + MxTrace("Stopping %d\n", tranInfo->m_objectId); + + if (tranInfo->m_presenter != NULL) { + tranInfo->m_presenter->FUN_1004b840(); + } + } + } + } + + m_animRunning = FALSE; + m_unk0x404 = Timer()->GetTime(); +} +#else +// FUNCTION: LEGO1 0x10061010 +void LegoAnimationManager::FUN_10061010(MxBool p_und) +{ + MxBool animRunning = FALSE; + FUN_10064b50(-1); + + if (m_tranInfoList != NULL) { + LegoTranInfoListCursor cursor(m_tranInfoList); + LegoTranInfo* tranInfo; + + while (cursor.Next(tranInfo)) { + if (tranInfo->m_presenter) { + // LINE: LEGO1 0x100610e6 if (tranInfo->m_unk0x14 && tranInfo->m_location != -1 && p_und) { - LegoAnim* anim; - - if (tranInfo->m_presenter->GetPresenter() != NULL && - (anim = tranInfo->m_presenter->GetPresenter()->GetAnimation()) != NULL && - anim->GetCamAnim() != NULL) { - if (flags & LegoTranInfo::c_bit2) { + if (tranInfo->m_presenter->GetPresenter() && + tranInfo->m_presenter->GetPresenter()->GetAnimation() && + tranInfo->m_presenter->GetPresenter()->GetAnimation()->GetCamAnim()) { + if (tranInfo->m_flags & LegoTranInfo::c_bit2) { BackgroundAudioManager()->RaiseVolume(); tranInfo->m_flags &= ~LegoTranInfo::c_bit2; } @@ -1257,37 +1286,43 @@ void LegoAnimationManager::FUN_10061010(MxBool p_und) tranInfo->m_unk0x14 = FALSE; } else { + MxTrace("Releasing user from %d\n", tranInfo->m_objectId); + // LINE: LEGO1 0x10061137 tranInfo->m_presenter->FUN_1004b8c0(); + animRunning = TRUE; tranInfo->m_unk0x14 = FALSE; - unk0x39 = TRUE; } } else { - if (flags & LegoTranInfo::c_bit2) { + if (tranInfo->m_flags & LegoTranInfo::c_bit2) { + // LINE: LEGO1 0x10061150 BackgroundAudioManager()->RaiseVolume(); tranInfo->m_flags &= ~LegoTranInfo::c_bit2; } + MxTrace("Stopping %d\n", tranInfo->m_objectId); tranInfo->m_presenter->FUN_1004b840(); } } else { if (m_tranInfoList2 != NULL) { LegoTranInfoListCursor cursor(m_tranInfoList2); - if (!cursor.Find(tranInfo)) { + // TODO: For some reason, the embedded `MxListEntry` constructor is not inlined. + // This may be the key for getting this function to match correctly. m_tranInfoList2->Append(tranInfo); } } - unk0x39 = TRUE; + animRunning = TRUE; } } } - m_animRunning = unk0x39; + m_animRunning = animRunning; m_unk0x404 = Timer()->GetTime(); } +#endif // FUNCTION: LEGO1 0x10061530 void LegoAnimationManager::FUN_10061530() diff --git a/LEGO1/lego/legoomni/src/common/legoanimmmpresenter.cpp b/LEGO1/lego/legoomni/src/common/legoanimmmpresenter.cpp index ca6c2412..af32a238 100644 --- a/LEGO1/lego/legoomni/src/common/legoanimmmpresenter.cpp +++ b/LEGO1/lego/legoomni/src/common/legoanimmmpresenter.cpp @@ -425,6 +425,21 @@ MxBool LegoAnimMMPresenter::FUN_1004b6b0(MxLong p_time) // FUNCTION: BETA10 0x1004ce18 MxBool LegoAnimMMPresenter::FUN_1004b6d0(MxLong p_time) { +#ifdef BETA10 + switch (m_unk0x58) { + case 0: + break; + case 1: + break; + case 2: + break; + case 3: + break; + case 4: + break; + } +#endif + LegoROI* viewROI = VideoManager()->GetViewROI(); LegoPathActor* actor = UserActor(); @@ -455,9 +470,13 @@ MxBool LegoAnimMMPresenter::FUN_1004b6d0(MxLong p_time) m_world->PlaceActor(actor); } +#ifdef BETA10 + actor->VTable0xa8(); +#else if (m_tranInfo->m_unk0x29) { actor->VTable0xa8(); } +#endif } actor->SetActorState(LegoPathActor::c_initial); @@ -491,9 +510,11 @@ void LegoAnimMMPresenter::FUN_1004b840() FUN_1004b6d0(0); EndAction(); +#ifndef BETA10 if (action != NULL) { Streamer()->FUN_100b98f0(action); } +#endif } // FUNCTION: LEGO1 0x1004b8b0 diff --git a/LEGO1/lego/legoomni/src/entity/legoworld.cpp b/LEGO1/lego/legoomni/src/entity/legoworld.cpp index cc82fe92..f038f3b0 100644 --- a/LEGO1/lego/legoomni/src/entity/legoworld.cpp +++ b/LEGO1/lego/legoomni/src/entity/legoworld.cpp @@ -286,6 +286,7 @@ MxResult LegoWorld::PlaceActor( } // FUNCTION: LEGO1 0x1001fa70 +// FUNCTION: BETA10 0x100da328 MxResult LegoWorld::PlaceActor(LegoPathActor* p_actor) { LegoPathControllerListCursor cursor(&m_pathControllerList); @@ -301,6 +302,7 @@ MxResult LegoWorld::PlaceActor(LegoPathActor* p_actor) } // FUNCTION: LEGO1 0x1001fb70 +// FUNCTION: BETA10 0x100da3f1 MxResult LegoWorld::PlaceActor( LegoPathActor* p_actor, LegoAnimPresenter* p_presenter, diff --git a/LEGO1/omni/include/mxlist.h b/LEGO1/omni/include/mxlist.h index f6954168..980da86e 100644 --- a/LEGO1/omni/include/mxlist.h +++ b/LEGO1/omni/include/mxlist.h @@ -244,11 +244,11 @@ inline MxBool MxListCursor::Next() template inline MxBool MxListCursor::Next(T& p_obj) { - if (!m_match) { - m_match = m_list->m_first; + if (m_match) { + m_match = m_match->GetNext(); } else { - m_match = m_match->GetNext(); + m_match = m_list->m_first; } if (m_match) { diff --git a/LEGO1/realtime/orientableroi.cpp b/LEGO1/realtime/orientableroi.cpp index 92c9ac9e..3c45e90f 100644 --- a/LEGO1/realtime/orientableroi.cpp +++ b/LEGO1/realtime/orientableroi.cpp @@ -153,6 +153,7 @@ void OrientableROI::UpdateWorldDataWithTransformAndChildren(const Matrix4& p_tra } // FUNCTION: LEGO1 0x100a5a30 +// FUNCTION: BETA10 0x10167d31 void OrientableROI::SetWorldVelocity(const Vector3& p_world_velocity) { m_world_velocity = p_world_velocity; From 9e860d910cffcb85ef4ad284c8b3b4e31e0e31e7 Mon Sep 17 00:00:00 2001 From: Fabian Neundorf Date: Sat, 19 Jul 2025 16:29:34 +0200 Subject: [PATCH 164/188] Name spawn areas (#1636) --- LEGO1/lego/legoomni/include/legogamestate.h | 10 +++++----- LEGO1/lego/legoomni/src/actors/helicopter.cpp | 2 +- LEGO1/lego/legoomni/src/actors/islepathactor.cpp | 10 +++++----- LEGO1/lego/legoomni/src/actors/jetski.cpp | 2 +- LEGO1/lego/legoomni/src/actors/towtrack.cpp | 2 +- LEGO1/lego/legoomni/src/worlds/isle.cpp | 10 +++++----- 6 files changed, 18 insertions(+), 18 deletions(-) diff --git a/LEGO1/lego/legoomni/include/legogamestate.h b/LEGO1/lego/legoomni/include/legogamestate.h index e5dd9a26..99155ce0 100644 --- a/LEGO1/lego/legoomni/include/legogamestate.h +++ b/LEGO1/lego/legoomni/include/legogamestate.h @@ -103,19 +103,19 @@ class LegoGameState { e_dunecarbuild, e_jetskibuild, e_racecarbuild, - e_unk40, + e_helicopterSpawn, e_unk41, e_unk42, - e_unk43, - e_unk44, - e_unk45, + e_dunebuggySpawn, + e_racecarSpawn, + e_jetskiSpawn, e_act2main, e_act3script, e_unk48, e_unk49, e_unk50, e_unk51, - e_unk52, + e_towTrackHookedUp, e_jukeboxw, e_jukeboxExterior, e_unk55, diff --git a/LEGO1/lego/legoomni/src/actors/helicopter.cpp b/LEGO1/lego/legoomni/src/actors/helicopter.cpp index 6cbb9675..358657b1 100644 --- a/LEGO1/lego/legoomni/src/actors/helicopter.cpp +++ b/LEGO1/lego/legoomni/src/actors/helicopter.cpp @@ -82,7 +82,7 @@ void Helicopter::Exit() if (GameState()->GetCurrentAct() == LegoGameState::e_act1) { SpawnPlayer( - LegoGameState::e_unk40, + LegoGameState::e_helicopterSpawn, TRUE, IslePathActor::c_spawnBit1 | IslePathActor::c_playMusic | IslePathActor::c_spawnBit3 ); diff --git a/LEGO1/lego/legoomni/src/actors/islepathactor.cpp b/LEGO1/lego/legoomni/src/actors/islepathactor.cpp index 9d5a3a22..d366761e 100644 --- a/LEGO1/lego/legoomni/src/actors/islepathactor.cpp +++ b/LEGO1/lego/legoomni/src/actors/islepathactor.cpp @@ -355,7 +355,7 @@ void IslePathActor::RegisterSpawnLocations() JukeboxScript::c_PoliceStation_Music ); g_spawnLocations[16] = SpawnLocation( - LegoGameState::e_unk40, + LegoGameState::e_helicopterSpawn, g_isleScript, 0, "edg02_51", @@ -379,7 +379,7 @@ void IslePathActor::RegisterSpawnLocations() JukeboxScript::c_noneJukebox ); g_spawnLocations[18] = SpawnLocation( - LegoGameState::e_unk43, + LegoGameState::e_dunebuggySpawn, g_isleScript, 0, "edg02_35", @@ -391,7 +391,7 @@ void IslePathActor::RegisterSpawnLocations() JukeboxScript::c_noneJukebox ); g_spawnLocations[19] = SpawnLocation( - LegoGameState::e_unk44, + LegoGameState::e_racecarSpawn, g_isleScript, 0, "EDG03_01", @@ -403,7 +403,7 @@ void IslePathActor::RegisterSpawnLocations() JukeboxScript::c_noneJukebox ); g_spawnLocations[20] = SpawnLocation( - LegoGameState::e_unk45, + LegoGameState::e_jetskiSpawn, g_isleScript, 0, "edg10_70", @@ -475,7 +475,7 @@ void IslePathActor::RegisterSpawnLocations() JukeboxScript::c_noneJukebox ); g_spawnLocations[26] = SpawnLocation( - LegoGameState::e_unk52, + LegoGameState::e_towTrackHookedUp, g_isleScript, 0, "edg02_19", diff --git a/LEGO1/lego/legoomni/src/actors/jetski.cpp b/LEGO1/lego/legoomni/src/actors/jetski.cpp index 03c3ca91..8b18b889 100644 --- a/LEGO1/lego/legoomni/src/actors/jetski.cpp +++ b/LEGO1/lego/legoomni/src/actors/jetski.cpp @@ -67,7 +67,7 @@ void Jetski::Animate(float p_time) // FUNCTION: LEGO1 0x1007e6f0 void Jetski::Exit() { - SpawnPlayer(LegoGameState::e_unk45, FALSE, c_spawnBit1 | c_playMusic | c_spawnBit3); + SpawnPlayer(LegoGameState::e_jetskiSpawn, FALSE, c_spawnBit1 | c_playMusic | c_spawnBit3); IslePathActor::Exit(); GameState()->m_currentArea = LegoGameState::e_jetski; RemoveFromWorld(); diff --git a/LEGO1/lego/legoomni/src/actors/towtrack.cpp b/LEGO1/lego/legoomni/src/actors/towtrack.cpp index 7a54d593..c0531bb8 100644 --- a/LEGO1/lego/legoomni/src/actors/towtrack.cpp +++ b/LEGO1/lego/legoomni/src/actors/towtrack.cpp @@ -432,7 +432,7 @@ MxLong TowTrack::HandleClick() } if (m_state->m_state == TowTrackMissionState::e_hookedUp) { - SpawnPlayer(LegoGameState::e_unk52, TRUE, 0); + SpawnPlayer(LegoGameState::e_towTrackHookedUp, TRUE, 0); FindROI("rcred")->SetVisibility(FALSE); } else { diff --git a/LEGO1/lego/legoomni/src/worlds/isle.cpp b/LEGO1/lego/legoomni/src/worlds/isle.cpp index ba73754d..556b09df 100644 --- a/LEGO1/lego/legoomni/src/worlds/isle.cpp +++ b/LEGO1/lego/legoomni/src/worlds/isle.cpp @@ -577,7 +577,7 @@ void Isle::Enable(MxBool p_enable) if (UserActor() != NULL && UserActor()->IsA("Jetski")) { IslePathActor* actor = (IslePathActor*) UserActor(); actor->SpawnPlayer( - LegoGameState::e_unk45, + LegoGameState::e_jetskiSpawn, FALSE, IslePathActor::c_spawnBit1 | IslePathActor::c_playMusic | IslePathActor::c_spawnBit3 ); @@ -1633,7 +1633,7 @@ void Act1State::PlaceActors() if (m_helicopter != NULL) { if (!m_helicopterPlane.IsPresent()) { - m_helicopter->SpawnPlayer(LegoGameState::e_unk40, FALSE, 0); + m_helicopter->SpawnPlayer(LegoGameState::e_helicopterSpawn, FALSE, 0); } else { isle->PlaceActor(m_helicopter, m_helicopterPlane.GetName(), 0, 0.5f, 1, 0.5f); @@ -1673,7 +1673,7 @@ void Act1State::PlaceActors() if (m_jetski != NULL) { if (!m_jetskiPlane.IsPresent()) { - m_jetski->SpawnPlayer(LegoGameState::e_unk45, FALSE, 0); + m_jetski->SpawnPlayer(LegoGameState::e_jetskiSpawn, FALSE, 0); } else { isle->PlaceActor(m_jetski, m_jetskiPlane.GetName(), 0, 0.5f, 1, 0.5f); @@ -1703,7 +1703,7 @@ void Act1State::PlaceActors() if (m_dunebuggy != NULL) { if (!m_dunebuggyPlane.IsPresent()) { - m_dunebuggy->SpawnPlayer(LegoGameState::e_unk43, FALSE, 0); + m_dunebuggy->SpawnPlayer(LegoGameState::e_dunebuggySpawn, FALSE, 0); } else { isle->PlaceActor(m_dunebuggy, m_dunebuggyPlane.GetName(), 0, 0.5f, 1, 0.5f); @@ -1731,7 +1731,7 @@ void Act1State::PlaceActors() if (m_racecar != NULL) { if (!m_racecarPlane.IsPresent()) { - m_racecar->SpawnPlayer(LegoGameState::e_unk44, FALSE, 0); + m_racecar->SpawnPlayer(LegoGameState::e_racecarSpawn, FALSE, 0); } else { isle->PlaceActor(m_racecar, m_racecarPlane.GetName(), 0, 0.5f, 1, 0.5f); From 71c767d92b31f37fa117979ab7d02f31ffadcb7a Mon Sep 17 00:00:00 2001 From: Anders Jenbo Date: Sat, 19 Jul 2025 18:00:01 +0200 Subject: [PATCH 165/188] Fix bad merge (#628) --- ISLE/isledebug.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/ISLE/isledebug.cpp b/ISLE/isledebug.cpp index ec27004b..b62f4c09 100644 --- a/ISLE/isledebug.cpp +++ b/ISLE/isledebug.cpp @@ -46,7 +46,7 @@ class DebugViewer { if (plantManager->m_numEntries) { if (ImGui::BeginTable("Animated Entries", 4, ImGuiTableFlags_Borders)) { ImGui::TableSetupColumn("ROI Name"); - ImGui::TableSetupColumn("ROI m_unk0x100"); + ImGui::TableSetupColumn("ROI m_sharedLodList"); ImGui::TableSetupColumn("Entity Name"); ImGui::TableSetupColumn("Time"); ImGui::TableHeadersRow(); @@ -55,7 +55,7 @@ class DebugViewer { ImGui::TableNextRow(); ImGui::Text("%s", entry->m_roi->m_name); ImGui::TableNextColumn(); - ImGui::Text("%d", entry->m_roi->m_unk0x100); + ImGui::Text("%d", entry->m_roi->m_sharedLodList); ImGui::TableNextColumn(); ImGui::Text("%s", entry->m_roi->m_entity->ClassName()); ImGui::TableNextColumn(); @@ -75,7 +75,7 @@ class DebugViewer { if (buildingManager->m_numEntries) { if (ImGui::BeginTable("Animated Entries", 6, ImGuiTableFlags_Borders)) { ImGui::TableSetupColumn("ROI Name"); - ImGui::TableSetupColumn("ROI m_unk0x100"); + ImGui::TableSetupColumn("ROI m_sharedLodList"); ImGui::TableSetupColumn("Entity Name"); ImGui::TableSetupColumn("Time"); ImGui::TableSetupColumn("Y"); @@ -86,7 +86,7 @@ class DebugViewer { ImGui::TableNextRow(); ImGui::Text("%s", entry->m_roi->m_name); ImGui::TableNextColumn(); - ImGui::Text("%d", entry->m_roi->m_unk0x100); + ImGui::Text("%d", entry->m_roi->m_sharedLodList); ImGui::TableNextColumn(); ImGui::Text("%s", entry->m_roi->m_entity->ClassName()); ImGui::TableNextColumn(); From 3c3c36c0b9ffb105f7a8cf18baa363dcddc81b0b Mon Sep 17 00:00:00 2001 From: Steven <139715581+StevenSYS@users.noreply.github.com> Date: Sat, 19 Jul 2025 20:05:09 +0000 Subject: [PATCH 166/188] Added Windows (MSVC) and Linux debug builds to CI (#631) Also fixed two warnings with the debug program --- .github/workflows/ci.yml | 6 ++++-- ISLE/isledebug.cpp | 4 ++-- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 1d3b5e5f..0aefef65 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -35,8 +35,10 @@ jobs: matrix: include: - { name: 'Linux', os: 'ubuntu-latest', generator: 'Ninja', dx5: false, config: true, linux: true, werror: true, clang-tidy: true } + - { name: 'Linux (Debug)', os: 'ubuntu-latest', generator: 'Ninja', dx5: false, config: true, linux: true, werror: true, clang-tidy: true, debug: true } - { name: 'MSVC (x86)', os: 'windows-latest', generator: 'Ninja', dx5: true, config: false, msvc: true, werror: false, clang-tidy: false, vc-arch: 'amd64_x86' } - - { name: 'MSVC (x64)', os: 'windows-latest', generator: 'Ninja', dx5: false, config: true, msvc: true, werror: false, clang-tidy: false, vc-arch: 'amd64' } + - { name: 'MSVC (x64)', os: 'windows-latest', generator: 'Ninja', dx5: false, config: true, msvc: true, werror: false, clang-tidy: false, vc-arch: 'amd64' } + - { name: 'MSVC (x64 Debug)', os: 'windows-latest', generator: 'Ninja', dx5: false, config: true, msvc: true, werror: false, clang-tidy: false, vc-arch: 'amd64', debug: true } - { name: 'MSVC (arm64)', os: 'windows-latest', generator: 'Ninja', dx5: false, config: false, msvc: true, werror: false, clang-tidy: false, vc-arch: 'amd64_arm64' } - { name: 'msys2 mingw32', os: 'windows-latest', generator: 'Ninja', dx5: false, config: false, mingw: true, werror: true, clang-tidy: true, msystem: 'mingw32', msys-env: 'mingw-w64-i686', shell: 'msys2 {0}' } - { name: 'msys2 mingw64', os: 'windows-latest', generator: 'Ninja', dx5: false, config: true, mingw: true, werror: true, clang-tidy: true, msystem: 'mingw64', msys-env: 'mingw-w64-x86_64', shell: 'msys2 {0}' } @@ -113,7 +115,7 @@ jobs: -DISLE_BUILD_CONFIG=${{ !!matrix.config }} \ -DENABLE_CLANG_TIDY=${{ !!matrix.clang-tidy }} \ -DISLE_WERROR=${{ !!matrix.werror }} \ - -DISLE_DEBUG=OFF \ + -DISLE_DEBUG=${{ matrix.debug || 'OFF' }} \ -Werror=dev - name: Build (CMake) diff --git a/ISLE/isledebug.cpp b/ISLE/isledebug.cpp index b62f4c09..5b9bd590 100644 --- a/ISLE/isledebug.cpp +++ b/ISLE/isledebug.cpp @@ -92,7 +92,7 @@ class DebugViewer { ImGui::TableNextColumn(); ImGui::Text("%d", entry->m_time); ImGui::TableNextColumn(); - ImGui::Text("%d", entry->m_y); + ImGui::Text("%f", entry->m_y); ImGui::TableNextColumn(); ImGui::Text("%d", entry->m_muted); } @@ -135,7 +135,7 @@ class DebugViewer { ImGui::Text("unk0x70: %u", videoManager->m_unk0x70); ImGui::Text("Dither: %d", videoManager->m_dither); ImGui::Text("BufferCount: %u", videoManager->m_bufferCount); - ImGui::Text("Paused: %f", videoManager->m_paused); + ImGui::Text("Paused: %d", videoManager->m_paused); ImGui::Text("back: %g", videoManager->m_back); ImGui::Text("front: %g", videoManager->m_front); ImGui::Text("cameraWidth: %g", videoManager->m_cameraWidth); From 6b5f3724c092b4bc185ed29c63d1d3f8974f4531 Mon Sep 17 00:00:00 2001 From: jonschz <17198703+jonschz@users.noreply.github.com> Date: Sun, 20 Jul 2025 07:57:26 +0200 Subject: [PATCH 167/188] Beta match `MxAutoLock` and `MxCriticalSection` (#1638) Co-authored-by: jonschz --- .../src/audio/legoloadcachesoundpresenter.cpp | 2 +- .../legoomni/src/audio/legosoundmanager.cpp | 2 +- .../src/video/legohideanimpresenter.cpp | 2 +- .../src/video/legolocomotionanimpresenter.cpp | 2 +- .../legoomni/src/video/legomodelpresenter.cpp | 2 +- .../src/video/legopalettepresenter.cpp | 2 +- .../legoomni/src/video/legopartpresenter.cpp | 2 +- LEGO1/omni/include/mxautolock.h | 12 +++++++ LEGO1/omni/include/mxcriticalsection.h | 11 +++++++ LEGO1/omni/src/audio/mxaudiomanager.cpp | 6 ++-- .../omni/src/audio/mxloopingmidipresenter.cpp | 2 +- LEGO1/omni/src/audio/mxmidipresenter.cpp | 4 +-- LEGO1/omni/src/audio/mxmusicmanager.cpp | 12 +++---- LEGO1/omni/src/audio/mxmusicpresenter.cpp | 2 +- LEGO1/omni/src/audio/mxsoundmanager.cpp | 6 ++-- LEGO1/omni/src/audio/mxsoundpresenter.cpp | 2 +- LEGO1/omni/src/audio/mxwavepresenter.cpp | 2 +- LEGO1/omni/src/event/mxeventmanager.cpp | 2 +- LEGO1/omni/src/event/mxeventpresenter.cpp | 2 +- LEGO1/omni/src/system/mxautolock.cpp | 14 ++++++++- LEGO1/omni/src/system/mxcriticalsection.cpp | 31 ++++++++++++++++++- .../omni/src/video/mxloopingflcpresenter.cpp | 4 +-- .../omni/src/video/mxloopingsmkpresenter.cpp | 2 +- LEGO1/omni/src/video/mxsmkpresenter.cpp | 2 +- LEGO1/omni/src/video/mxstillpresenter.cpp | 2 +- LEGO1/omni/src/video/mxvideomanager.cpp | 10 +++--- 26 files changed, 103 insertions(+), 39 deletions(-) diff --git a/LEGO1/lego/legoomni/src/audio/legoloadcachesoundpresenter.cpp b/LEGO1/lego/legoomni/src/audio/legoloadcachesoundpresenter.cpp index a0a66e89..0e5761b4 100644 --- a/LEGO1/lego/legoomni/src/audio/legoloadcachesoundpresenter.cpp +++ b/LEGO1/lego/legoomni/src/audio/legoloadcachesoundpresenter.cpp @@ -98,7 +98,7 @@ void LegoLoadCacheSoundPresenter::DoneTickle() // FUNCTION: LEGO1 0x10018700 MxResult LegoLoadCacheSoundPresenter::PutData() { - m_criticalSection.Enter(); + ENTER(m_criticalSection); if (m_currentTickleState == e_done) { m_cacheSound = SoundManager()->GetCacheSoundManager()->ManageSoundEntry(m_cacheSound); diff --git a/LEGO1/lego/legoomni/src/audio/legosoundmanager.cpp b/LEGO1/lego/legoomni/src/audio/legosoundmanager.cpp index c92f2bea..a1657e46 100644 --- a/LEGO1/lego/legoomni/src/audio/legosoundmanager.cpp +++ b/LEGO1/lego/legoomni/src/audio/legosoundmanager.cpp @@ -46,7 +46,7 @@ MxResult LegoSoundManager::Create(MxU32 p_frequencyMS, MxBool p_createThread) MxResult result = FAILURE; if (MxSoundManager::Create(10, FALSE) == SUCCESS) { - m_criticalSection.Enter(); + ENTER(m_criticalSection); locked = TRUE; if (MxOmni::IsSound3D()) { diff --git a/LEGO1/lego/legoomni/src/video/legohideanimpresenter.cpp b/LEGO1/lego/legoomni/src/video/legohideanimpresenter.cpp index 60f8dce5..03cc147e 100644 --- a/LEGO1/lego/legoomni/src/video/legohideanimpresenter.cpp +++ b/LEGO1/lego/legoomni/src/video/legohideanimpresenter.cpp @@ -29,7 +29,7 @@ void LegoHideAnimPresenter::Init() // FUNCTION: LEGO1 0x1006da60 void LegoHideAnimPresenter::Destroy(MxBool p_fromDestructor) { - m_criticalSection.Enter(); + ENTER(m_criticalSection); if (m_boundaryMap) { delete[] m_boundaryMap; diff --git a/LEGO1/lego/legoomni/src/video/legolocomotionanimpresenter.cpp b/LEGO1/lego/legoomni/src/video/legolocomotionanimpresenter.cpp index 90b98e91..32e37669 100644 --- a/LEGO1/lego/legoomni/src/video/legolocomotionanimpresenter.cpp +++ b/LEGO1/lego/legoomni/src/video/legolocomotionanimpresenter.cpp @@ -38,7 +38,7 @@ void LegoLocomotionAnimPresenter::Init() // FUNCTION: LEGO1 0x1006d0e0 void LegoLocomotionAnimPresenter::Destroy(MxBool p_fromDestructor) { - m_criticalSection.Enter(); + ENTER(m_criticalSection); if (m_unk0xc4) { delete[] m_unk0xc4; diff --git a/LEGO1/lego/legoomni/src/video/legomodelpresenter.cpp b/LEGO1/lego/legoomni/src/video/legomodelpresenter.cpp index dcaab81d..b09dbd60 100644 --- a/LEGO1/lego/legoomni/src/video/legomodelpresenter.cpp +++ b/LEGO1/lego/legoomni/src/video/legomodelpresenter.cpp @@ -33,7 +33,7 @@ void LegoModelPresenter::configureLegoModelPresenter(MxS32 p_modelPresenterConfi // FUNCTION: LEGO1 0x1007f670 void LegoModelPresenter::Destroy(MxBool p_fromDestructor) { - m_criticalSection.Enter(); + ENTER(m_criticalSection); m_roi = NULL; m_addedToView = FALSE; m_criticalSection.Leave(); diff --git a/LEGO1/lego/legoomni/src/video/legopalettepresenter.cpp b/LEGO1/lego/legoomni/src/video/legopalettepresenter.cpp index 6ad7f6ec..f958385d 100644 --- a/LEGO1/lego/legoomni/src/video/legopalettepresenter.cpp +++ b/LEGO1/lego/legoomni/src/video/legopalettepresenter.cpp @@ -31,7 +31,7 @@ void LegoPalettePresenter::Init() // FUNCTION: LEGO1 0x1007a0e0 void LegoPalettePresenter::Destroy(MxBool p_fromDestructor) { - m_criticalSection.Enter(); + ENTER(m_criticalSection); if (m_palette) { delete m_palette; } diff --git a/LEGO1/lego/legoomni/src/video/legopartpresenter.cpp b/LEGO1/lego/legoomni/src/video/legopartpresenter.cpp index 88e31dd6..c7edf8f3 100644 --- a/LEGO1/lego/legoomni/src/video/legopartpresenter.cpp +++ b/LEGO1/lego/legoomni/src/video/legopartpresenter.cpp @@ -37,7 +37,7 @@ MxResult LegoPartPresenter::AddToManager() // FUNCTION: LEGO1 0x1007c9d0 void LegoPartPresenter::Destroy(MxBool p_fromDestructor) { - m_criticalSection.Enter(); + ENTER(m_criticalSection); VideoManager()->UnregisterPresenter(*this); if (m_parts) { diff --git a/LEGO1/omni/include/mxautolock.h b/LEGO1/omni/include/mxautolock.h index 8a2b3c35..6a26ba47 100644 --- a/LEGO1/omni/include/mxautolock.h +++ b/LEGO1/omni/include/mxautolock.h @@ -3,15 +3,27 @@ class MxCriticalSection; +#ifdef BETA10 +#define AUTOLOCK(CS) MxAutoLock lock(&CS, __FILE__, __LINE__) +#else #define AUTOLOCK(CS) MxAutoLock lock(&CS) +#endif class MxAutoLock { public: +#ifdef BETA10 + MxAutoLock(MxCriticalSection* p_criticalSection, const char* filename, int line); +#else MxAutoLock(MxCriticalSection* p_criticalSection); +#endif ~MxAutoLock(); private: MxCriticalSection* m_criticalSection; // 0x00 + +#ifdef BETA10 + unsigned long m_currentThreadId; // 0x04 +#endif }; #endif // MXAUTOLOCK_H diff --git a/LEGO1/omni/include/mxcriticalsection.h b/LEGO1/omni/include/mxcriticalsection.h index 23c3fb76..7e24a947 100644 --- a/LEGO1/omni/include/mxcriticalsection.h +++ b/LEGO1/omni/include/mxcriticalsection.h @@ -11,7 +11,11 @@ class MxCriticalSection { static void SetDoMutex(); +#ifdef BETA10 + void Enter(unsigned long p_threadId, const char* filename, int line); +#else void Enter(); +#endif void Leave(); private: @@ -19,4 +23,11 @@ class MxCriticalSection { HANDLE m_mutex; // 0x18 }; +#ifdef BETA10 +// TODO: Not quite correct yet, the second argument becomes a relocated value +#define ENTER(criticalSection) criticalSection.Enter(-1, NULL, 0) +#else +#define ENTER(criticalSection) criticalSection.Enter() +#endif + #endif // MXCRITICALSECTION_H diff --git a/LEGO1/omni/src/audio/mxaudiomanager.cpp b/LEGO1/omni/src/audio/mxaudiomanager.cpp index 81b8922e..34b52eda 100644 --- a/LEGO1/omni/src/audio/mxaudiomanager.cpp +++ b/LEGO1/omni/src/audio/mxaudiomanager.cpp @@ -26,7 +26,7 @@ void MxAudioManager::Init() // FUNCTION: LEGO1 0x100b8e00 void MxAudioManager::Destroy(MxBool p_fromDestructor) { - m_criticalSection.Enter(); + ENTER(m_criticalSection); g_count--; Init(); m_criticalSection.Leave(); @@ -43,7 +43,7 @@ MxResult MxAudioManager::Create() MxBool success = FALSE; if (MxMediaManager::Create() == SUCCESS) { - m_criticalSection.Enter(); + ENTER(m_criticalSection); success = TRUE; result = SUCCESS; g_count++; @@ -69,7 +69,7 @@ void MxAudioManager::Destroy() // FUNCTION: LEGO1 0x100b8ea0 void MxAudioManager::SetVolume(MxS32 p_volume) { - m_criticalSection.Enter(); + ENTER(m_criticalSection); m_volume = p_volume; m_criticalSection.Leave(); } diff --git a/LEGO1/omni/src/audio/mxloopingmidipresenter.cpp b/LEGO1/omni/src/audio/mxloopingmidipresenter.cpp index 51c305fe..9c9bfd33 100644 --- a/LEGO1/omni/src/audio/mxloopingmidipresenter.cpp +++ b/LEGO1/omni/src/audio/mxloopingmidipresenter.cpp @@ -39,7 +39,7 @@ void MxLoopingMIDIPresenter::DoneTickle() // FUNCTION: LEGO1 0x100c2b00 MxResult MxLoopingMIDIPresenter::PutData() { - m_criticalSection.Enter(); + ENTER(m_criticalSection); if (m_currentTickleState == e_streaming && m_chunk && !MusicManager()->GetMIDIInitialized()) { SetVolume(((MxDSSound*) m_action)->GetVolume()); diff --git a/LEGO1/omni/src/audio/mxmidipresenter.cpp b/LEGO1/omni/src/audio/mxmidipresenter.cpp index d3c990c5..decf848a 100644 --- a/LEGO1/omni/src/audio/mxmidipresenter.cpp +++ b/LEGO1/omni/src/audio/mxmidipresenter.cpp @@ -34,7 +34,7 @@ void MxMIDIPresenter::Destroy(MxBool p_fromDestructor) MusicManager()->DeinitializeMIDI(); } - m_criticalSection.Enter(); + ENTER(m_criticalSection); if (m_subscriber && m_chunk) { m_subscriber->FreeDataChunk(m_chunk); @@ -98,7 +98,7 @@ void MxMIDIPresenter::Destroy() // FUNCTION: LEGO1 0x100c2970 MxResult MxMIDIPresenter::PutData() { - m_criticalSection.Enter(); + ENTER(m_criticalSection); if (m_currentTickleState == e_streaming && m_chunk && !MusicManager()->GetMIDIInitialized()) { SetVolume(((MxDSSound*) m_action)->GetVolume()); diff --git a/LEGO1/omni/src/audio/mxmusicmanager.cpp b/LEGO1/omni/src/audio/mxmusicmanager.cpp index 1a25cfc5..beef67bb 100644 --- a/LEGO1/omni/src/audio/mxmusicmanager.cpp +++ b/LEGO1/omni/src/audio/mxmusicmanager.cpp @@ -53,7 +53,7 @@ void MxMusicManager::Destroy(MxBool p_fromDestructor) TickleManager()->UnregisterClient(this); } - m_criticalSection.Enter(); + ENTER(m_criticalSection); DeinitializeMIDI(); Init(); m_criticalSection.Leave(); @@ -146,7 +146,7 @@ MxResult MxMusicManager::Create(MxU32 p_frequencyMS, MxBool p_createThread) if (MxAudioManager::Create() == SUCCESS) { if (p_createThread) { - m_criticalSection.Enter(); + ENTER(m_criticalSection); locked = TRUE; m_thread = new MxTickleThread(this, p_frequencyMS); @@ -183,7 +183,7 @@ void MxMusicManager::Destroy() void MxMusicManager::SetVolume(MxS32 p_volume) { MxAudioManager::SetVolume(p_volume); - m_criticalSection.Enter(); + ENTER(m_criticalSection); SetMIDIVolume(); m_criticalSection.Leave(); } @@ -191,7 +191,7 @@ void MxMusicManager::SetVolume(MxS32 p_volume) // FUNCTION: LEGO1 0x100c0970 void MxMusicManager::SetMultiplier(MxS32 p_multiplier) { - m_criticalSection.Enter(); + ENTER(m_criticalSection); m_multiplier = p_multiplier; SetMIDIVolume(); m_criticalSection.Leave(); @@ -209,7 +209,7 @@ MxResult MxMusicManager::InitializeMIDI(MxU8* p_data, MxS32 p_loopCount) { MxResult result = FAILURE; - m_criticalSection.Enter(); + ENTER(m_criticalSection); if (!m_midiInitialized) { MxU32 total = midiOutGetNumDevs(); @@ -278,7 +278,7 @@ MxResult MxMusicManager::InitializeMIDI(MxU8* p_data, MxS32 p_loopCount) // FUNCTION: LEGO1 0x100c0b20 void MxMusicManager::DeinitializeMIDI() { - m_criticalSection.Enter(); + ENTER(m_criticalSection); if (m_midiInitialized) { m_midiInitialized = FALSE; diff --git a/LEGO1/omni/src/audio/mxmusicpresenter.cpp b/LEGO1/omni/src/audio/mxmusicpresenter.cpp index 9f327ae4..6da836e7 100644 --- a/LEGO1/omni/src/audio/mxmusicpresenter.cpp +++ b/LEGO1/omni/src/audio/mxmusicpresenter.cpp @@ -30,7 +30,7 @@ void MxMusicPresenter::Destroy(MxBool p_fromDestructor) MusicManager()->UnregisterPresenter(*this); } - m_criticalSection.Enter(); + ENTER(m_criticalSection); Init(); m_criticalSection.Leave(); diff --git a/LEGO1/omni/src/audio/mxsoundmanager.cpp b/LEGO1/omni/src/audio/mxsoundmanager.cpp index ccfe2dc2..e77e9f93 100644 --- a/LEGO1/omni/src/audio/mxsoundmanager.cpp +++ b/LEGO1/omni/src/audio/mxsoundmanager.cpp @@ -52,7 +52,7 @@ void MxSoundManager::Destroy(MxBool p_fromDestructor) TickleManager()->UnregisterClient(this); } - m_criticalSection.Enter(); + ENTER(m_criticalSection); if (m_dsBuffer) { m_dsBuffer->Release(); @@ -77,7 +77,7 @@ MxResult MxSoundManager::Create(MxU32 p_frequencyMS, MxBool p_createThread) goto done; } - m_criticalSection.Enter(); + ENTER(m_criticalSection); locked = TRUE; if (DirectSoundCreate(NULL, &m_directSound, NULL) != DS_OK) { @@ -166,7 +166,7 @@ void MxSoundManager::SetVolume(MxS32 p_volume) { MxAudioManager::SetVolume(p_volume); - m_criticalSection.Enter(); + ENTER(m_criticalSection); MxPresenter* presenter; MxPresenterListCursor cursor(m_presenters); diff --git a/LEGO1/omni/src/audio/mxsoundpresenter.cpp b/LEGO1/omni/src/audio/mxsoundpresenter.cpp index 9d784532..3fa2cfaa 100644 --- a/LEGO1/omni/src/audio/mxsoundpresenter.cpp +++ b/LEGO1/omni/src/audio/mxsoundpresenter.cpp @@ -13,7 +13,7 @@ void MxSoundPresenter::Destroy(MxBool p_fromDestructor) MSoundManager()->UnregisterPresenter(*this); } - m_criticalSection.Enter(); + ENTER(m_criticalSection); MxMediaPresenter::Init(); m_criticalSection.Leave(); diff --git a/LEGO1/omni/src/audio/mxwavepresenter.cpp b/LEGO1/omni/src/audio/mxwavepresenter.cpp index 37a75e5e..ca55f73b 100644 --- a/LEGO1/omni/src/audio/mxwavepresenter.cpp +++ b/LEGO1/omni/src/audio/mxwavepresenter.cpp @@ -294,7 +294,7 @@ void MxWavePresenter::EndAction() // FUNCTION: LEGO1 0x100b2300 void MxWavePresenter::SetVolume(MxS32 p_volume) { - m_criticalSection.Enter(); + ENTER(m_criticalSection); m_volume = p_volume; if (m_dsBuffer != NULL) { diff --git a/LEGO1/omni/src/event/mxeventmanager.cpp b/LEGO1/omni/src/event/mxeventmanager.cpp index 29b8aba0..2c38a123 100644 --- a/LEGO1/omni/src/event/mxeventmanager.cpp +++ b/LEGO1/omni/src/event/mxeventmanager.cpp @@ -48,7 +48,7 @@ MxResult MxEventManager::Create(MxU32 p_frequencyMS, MxBool p_createThread) MxResult result = MxMediaManager::Create(); if (result == SUCCESS) { if (p_createThread) { - this->m_criticalSection.Enter(); + ENTER(this->m_criticalSection); locked = TRUE; this->m_thread = new MxTickleThread(this, p_frequencyMS); diff --git a/LEGO1/omni/src/event/mxeventpresenter.cpp b/LEGO1/omni/src/event/mxeventpresenter.cpp index f819b68c..8134465e 100644 --- a/LEGO1/omni/src/event/mxeventpresenter.cpp +++ b/LEGO1/omni/src/event/mxeventpresenter.cpp @@ -50,7 +50,7 @@ void MxEventPresenter::Destroy() EventManager()->UnregisterPresenter(*this); } - m_criticalSection.Enter(); + ENTER(m_criticalSection); if (m_data) { delete[] m_data; diff --git a/LEGO1/omni/src/system/mxautolock.cpp b/LEGO1/omni/src/system/mxautolock.cpp index 4508663e..227085dd 100644 --- a/LEGO1/omni/src/system/mxautolock.cpp +++ b/LEGO1/omni/src/system/mxautolock.cpp @@ -2,8 +2,19 @@ #include "mxcriticalsection.h" -// FUNCTION: LEGO1 0x100b8ed0 +#ifdef BETA10 // FUNCTION: BETA10 0x101386f0 +MxAutoLock::MxAutoLock(MxCriticalSection* p_criticalSection, const char* filename, int line) +{ + m_criticalSection = p_criticalSection; + m_currentThreadId = GetCurrentThreadId(); + + if (m_criticalSection != NULL) { + m_criticalSection->Enter(m_currentThreadId, filename, line); + } +} +#else +// FUNCTION: LEGO1 0x100b8ed0 MxAutoLock::MxAutoLock(MxCriticalSection* p_criticalSection) { m_criticalSection = p_criticalSection; @@ -12,6 +23,7 @@ MxAutoLock::MxAutoLock(MxCriticalSection* p_criticalSection) m_criticalSection->Enter(); } } +#endif // FUNCTION: LEGO1 0x100b8ef0 // FUNCTION: BETA10 0x10138744 diff --git a/LEGO1/omni/src/system/mxcriticalsection.cpp b/LEGO1/omni/src/system/mxcriticalsection.cpp index 01c54c15..dd49e7ae 100644 --- a/LEGO1/omni/src/system/mxcriticalsection.cpp +++ b/LEGO1/omni/src/system/mxcriticalsection.cpp @@ -35,8 +35,35 @@ MxCriticalSection::~MxCriticalSection() } } -// FUNCTION: LEGO1 0x100b6d80 +#ifdef BETA10 // FUNCTION: BETA10 0x1013c725 +void MxCriticalSection::Enter(unsigned long p_threadId, const char* filename, int line) +{ + DWORD result; + FILE* file; + + if (m_mutex != NULL) { + result = WaitForSingleObject(m_mutex, 5000); + if (result == WAIT_FAILED) { + file = fopen("C:\\DEADLOCK.TXT", "a"); + if (file != NULL) { + fprintf(file, "mutex timeout occurred!\n"); + fprintf(file, "file: %s, line: %d\n", filename, line); + fclose(file); + } + + abort(); + } + } + else { + EnterCriticalSection(&m_criticalSection); + } + + // There is way more structure in here, and the MxCriticalSection class is much larger in BETA10. + // The LEGO1 compilation is very unlikely to profit from a further decompilation here. +} +#else +// FUNCTION: LEGO1 0x100b6d80 void MxCriticalSection::Enter() { DWORD result; @@ -58,8 +85,10 @@ void MxCriticalSection::Enter() EnterCriticalSection(&m_criticalSection); } } +#endif // FUNCTION: LEGO1 0x100b6de0 +// FUNCTION: BETA10 0x1013c7ef void MxCriticalSection::Leave() { if (m_mutex != NULL) { diff --git a/LEGO1/omni/src/video/mxloopingflcpresenter.cpp b/LEGO1/omni/src/video/mxloopingflcpresenter.cpp index a828296c..fb0e3134 100644 --- a/LEGO1/omni/src/video/mxloopingflcpresenter.cpp +++ b/LEGO1/omni/src/video/mxloopingflcpresenter.cpp @@ -29,7 +29,7 @@ void MxLoopingFlcPresenter::Init() // FUNCTION: LEGO1 0x100b4430 void MxLoopingFlcPresenter::Destroy(MxBool p_fromDestructor) { - m_criticalSection.Enter(); + ENTER(m_criticalSection); Init(); m_criticalSection.Leave(); @@ -117,7 +117,7 @@ MxResult MxLoopingFlcPresenter::AddToManager() MxBool locked = FALSE; if (MxFlcPresenter::AddToManager() == SUCCESS) { - m_criticalSection.Enter(); + ENTER(m_criticalSection); locked = TRUE; result = SUCCESS; } diff --git a/LEGO1/omni/src/video/mxloopingsmkpresenter.cpp b/LEGO1/omni/src/video/mxloopingsmkpresenter.cpp index 72f663e0..7933b3f3 100644 --- a/LEGO1/omni/src/video/mxloopingsmkpresenter.cpp +++ b/LEGO1/omni/src/video/mxloopingsmkpresenter.cpp @@ -29,7 +29,7 @@ void MxLoopingSmkPresenter::Init() // FUNCTION: LEGO1 0x100b49d0 void MxLoopingSmkPresenter::Destroy(MxBool p_fromDestructor) { - m_criticalSection.Enter(); + ENTER(m_criticalSection); Init(); m_criticalSection.Leave(); diff --git a/LEGO1/omni/src/video/mxsmkpresenter.cpp b/LEGO1/omni/src/video/mxsmkpresenter.cpp index aa21f6ee..e6bbc1d9 100644 --- a/LEGO1/omni/src/video/mxsmkpresenter.cpp +++ b/LEGO1/omni/src/video/mxsmkpresenter.cpp @@ -32,7 +32,7 @@ void MxSmkPresenter::Init() // FUNCTION: LEGO1 0x100b3900 void MxSmkPresenter::Destroy(MxBool p_fromDestructor) { - m_criticalSection.Enter(); + ENTER(m_criticalSection); MxSmk::Destroy(&m_mxSmk); Init(); diff --git a/LEGO1/omni/src/video/mxstillpresenter.cpp b/LEGO1/omni/src/video/mxstillpresenter.cpp index a9731233..db2f2ebb 100644 --- a/LEGO1/omni/src/video/mxstillpresenter.cpp +++ b/LEGO1/omni/src/video/mxstillpresenter.cpp @@ -17,7 +17,7 @@ DECOMP_SIZE_ASSERT(MxStillPresenter, 0x6c); // FUNCTION: LEGO1 0x100b9c70 void MxStillPresenter::Destroy(MxBool p_fromDestructor) { - m_criticalSection.Enter(); + ENTER(m_criticalSection); if (m_bitmapInfo) { delete[] ((MxU8*) m_bitmapInfo); diff --git a/LEGO1/omni/src/video/mxvideomanager.cpp b/LEGO1/omni/src/video/mxvideomanager.cpp index cde2e1ca..9ad9005c 100644 --- a/LEGO1/omni/src/video/mxvideomanager.cpp +++ b/LEGO1/omni/src/video/mxvideomanager.cpp @@ -52,7 +52,7 @@ void MxVideoManager::Destroy(MxBool p_fromDestructor) TickleManager()->UnregisterClient(this); } - m_criticalSection.Enter(); + ENTER(m_criticalSection); if (m_displaySurface) { delete m_displaySurface; @@ -152,7 +152,7 @@ MxResult MxVideoManager::VTable0x28( goto done; } - m_criticalSection.Enter(); + ENTER(m_criticalSection); locked = TRUE; m_videoParam = p_videoParam; @@ -225,7 +225,7 @@ MxResult MxVideoManager::Create(MxVideoParam& p_videoParam, MxU32 p_frequencyMS, goto done; } - m_criticalSection.Enter(); + ENTER(m_criticalSection); locked = TRUE; m_videoParam = p_videoParam; @@ -300,7 +300,7 @@ void MxVideoManager::Destroy() // FUNCTION: LEGO1 0x100bea60 void MxVideoManager::InvalidateRect(MxRect32& p_rect) { - m_criticalSection.Enter(); + ENTER(m_criticalSection); if (m_region) { m_region->AddRect(p_rect); @@ -340,7 +340,7 @@ MxResult MxVideoManager::RealizePalette(MxPalette* p_palette) { PALETTEENTRY paletteEntries[256]; - m_criticalSection.Enter(); + ENTER(m_criticalSection); if (p_palette && m_videoParam.GetPalette()) { p_palette->GetEntries(paletteEntries); From 56c15699dae597f7efa97c5f1ef8cc41b2415662 Mon Sep 17 00:00:00 2001 From: Fabian Neundorf Date: Sun, 20 Jul 2025 16:05:28 +0200 Subject: [PATCH 168/188] Clear unknown in `MxSoundManager` (#1641) --- LEGO1/lego/legoomni/src/build/legocarbuild.cpp | 2 +- LEGO1/omni/include/mxsoundmanager.h | 2 +- LEGO1/omni/src/audio/mxsoundmanager.cpp | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/LEGO1/lego/legoomni/src/build/legocarbuild.cpp b/LEGO1/lego/legoomni/src/build/legocarbuild.cpp index f50dcdfe..899ebeac 100644 --- a/LEGO1/lego/legoomni/src/build/legocarbuild.cpp +++ b/LEGO1/lego/legoomni/src/build/legocarbuild.cpp @@ -1230,7 +1230,7 @@ undefined4 LegoCarBuild::FUN_10024c20(MxNotificationParam* p_param) jukeboxScript = JukeboxScript::c_RaceCarBuild_Music; } - m_unk0x338 = SoundManager()->FUN_100aebd0(*g_jukeboxScript, jukeboxScript); + m_unk0x338 = SoundManager()->FindPresenter(*g_jukeboxScript, jukeboxScript); if (m_unk0x338) { BackgroundAudioManager()->SetPendingPresenter(m_unk0x338, 5, MxPresenter::e_repeating); diff --git a/LEGO1/omni/include/mxsoundmanager.h b/LEGO1/omni/include/mxsoundmanager.h index 36568ef3..3d25856b 100644 --- a/LEGO1/omni/include/mxsoundmanager.h +++ b/LEGO1/omni/include/mxsoundmanager.h @@ -25,7 +25,7 @@ class MxSoundManager : public MxAudioManager { MxS32 GetAttenuation(MxU32 p_volume); - MxPresenter* FUN_100aebd0(const MxAtomId& p_atomId, MxU32 p_objectId); + MxPresenter* FindPresenter(const MxAtomId& p_atomId, MxU32 p_objectId); protected: void Init(); diff --git a/LEGO1/omni/src/audio/mxsoundmanager.cpp b/LEGO1/omni/src/audio/mxsoundmanager.cpp index e77e9f93..1e2b28a1 100644 --- a/LEGO1/omni/src/audio/mxsoundmanager.cpp +++ b/LEGO1/omni/src/audio/mxsoundmanager.cpp @@ -179,7 +179,7 @@ void MxSoundManager::SetVolume(MxS32 p_volume) } // FUNCTION: LEGO1 0x100aebd0 -MxPresenter* MxSoundManager::FUN_100aebd0(const MxAtomId& p_atomId, MxU32 p_objectId) +MxPresenter* MxSoundManager::FindPresenter(const MxAtomId& p_atomId, MxU32 p_objectId) { AUTOLOCK(m_criticalSection); From 0a58d112e8eaf4d36cde8e445c5d90ae908596f9 Mon Sep 17 00:00:00 2001 From: Fabian Neundorf Date: Sun, 20 Jul 2025 17:18:59 +0200 Subject: [PATCH 169/188] Clear unknowns in `LegoOmni` and `misc.h` (#1642) --- LEGO1/lego/legoomni/include/legomain.h | 6 +++--- LEGO1/lego/legoomni/include/misc.h | 2 +- .../lego/legoomni/src/common/legoanimationmanager.cpp | 10 +++++----- LEGO1/lego/legoomni/src/common/misc.cpp | 4 ++-- LEGO1/lego/legoomni/src/entity/legonavcontroller.cpp | 2 +- LEGO1/lego/legoomni/src/main/legomain.cpp | 2 +- LEGO1/lego/legoomni/src/worlds/legoact2.cpp | 2 +- 7 files changed, 14 insertions(+), 14 deletions(-) diff --git a/LEGO1/lego/legoomni/include/legomain.h b/LEGO1/lego/legoomni/include/legomain.h index 423d715c..9246a642 100644 --- a/LEGO1/lego/legoomni/include/legomain.h +++ b/LEGO1/lego/legoomni/include/legomain.h @@ -180,8 +180,8 @@ class LegoOmni : public MxOmni { // FUNCTION: BETA10 0x100d55c0 void SetExit(MxBool p_exit) { m_exit = p_exit; } - MxResult StartActionIfUnknown0x13c(MxDSAction& p_dsAction) { return m_unk0x13c ? Start(&p_dsAction) : SUCCESS; } - void SetUnknown13c(MxBool p_unk0x13c) { m_unk0x13c = p_unk0x13c; } + MxResult StartActionIfInitialized(MxDSAction& p_dsAction) { return m_initialized ? Start(&p_dsAction) : SUCCESS; } + void SetInitialized(MxBool p_unk0x13c) { m_initialized = p_unk0x13c; } void CloseMainWindow() { PostMessage(m_windowHandle, WM_CLOSE, 0, 0); } @@ -208,7 +208,7 @@ class LegoOmni : public MxOmni { MxTransitionManager* m_transitionManager; // 0x138 public: - MxBool m_unk0x13c; // 0x13c + MxBool m_initialized; // 0x13c }; #endif // LEGOMAIN_H diff --git a/LEGO1/lego/legoomni/include/misc.h b/LEGO1/lego/legoomni/include/misc.h index f9a26e8b..52fe63d9 100644 --- a/LEGO1/lego/legoomni/include/misc.h +++ b/LEGO1/lego/legoomni/include/misc.h @@ -52,7 +52,7 @@ void Disable(MxBool p_disable, MxU16 p_flags); LegoROI* FindROI(const char* p_name); void SetROIVisible(const char* p_name, MxBool p_visible); void SetUserActor(LegoPathActor* p_userActor); -MxResult StartActionIfUnknown0x13c(MxDSAction& p_dsAction); +MxResult StartActionIfInitialized(MxDSAction& p_dsAction); void DeleteAction(); LegoWorld* FindWorld(const MxAtomId& p_atom, MxS32 p_entityid); MxDSAction& GetCurrentAction(); diff --git a/LEGO1/lego/legoomni/src/common/legoanimationmanager.cpp b/LEGO1/lego/legoomni/src/common/legoanimationmanager.cpp index e6dd3513..aab443e2 100644 --- a/LEGO1/lego/legoomni/src/common/legoanimationmanager.cpp +++ b/LEGO1/lego/legoomni/src/common/legoanimationmanager.cpp @@ -1016,7 +1016,7 @@ MxResult LegoAnimationManager::FUN_100605e0( action.SetUnknown24(-1); action.AppendExtra(strlen(buf) + 1, buf); - if (StartActionIfUnknown0x13c(action) == SUCCESS) { + if (StartActionIfInitialized(action) == SUCCESS) { BackgroundAudioManager()->LowerVolume(); tranInfo->m_flags |= LegoTranInfo::c_bit2; animInfo.m_unk0x22++; @@ -1083,7 +1083,7 @@ MxResult LegoAnimationManager::FUN_100609f0(MxU32 p_objectId, MxMatrix* p_matrix action.SetUnknown24(-1); action.AppendExtra(strlen(buf) + 1, buf); - if (StartActionIfUnknown0x13c(action) == SUCCESS) { + if (StartActionIfInitialized(action) == SUCCESS) { BackgroundAudioManager()->LowerVolume(); info->m_flags |= LegoTranInfo::c_bit2; m_animRunning = TRUE; @@ -1126,7 +1126,7 @@ MxResult LegoAnimationManager::StartEntityAction(MxDSAction& p_dsAction, LegoEnt } } - if (LegoOmni::GetInstance()->StartActionIfUnknown0x13c(p_dsAction) == SUCCESS) { + if (LegoOmni::GetInstance()->StartActionIfInitialized(p_dsAction) == SUCCESS) { result = SUCCESS; } @@ -1150,7 +1150,7 @@ MxResult LegoAnimationManager::FUN_10060dc0( MxResult result = FAILURE; MxBool found = FALSE; - if (!Lego()->m_unk0x13c) { + if (!Lego()->m_initialized) { return SUCCESS; } @@ -1187,7 +1187,7 @@ MxResult LegoAnimationManager::FUN_10060dc0( // FUNCTION: BETA10 0x1004206c void LegoAnimationManager::CameraTriggerFire(LegoPathActor* p_actor, MxBool, MxU32 p_location, MxBool p_bool) { - if (Lego()->m_unk0x13c && m_enableCamAnims && !m_animRunning) { + if (Lego()->m_initialized && m_enableCamAnims && !m_animRunning) { LegoLocation* location = LegoNavController::GetLocation(p_location); if (location != NULL) { diff --git a/LEGO1/lego/legoomni/src/common/misc.cpp b/LEGO1/lego/legoomni/src/common/misc.cpp index 6b231b56..19ff0a21 100644 --- a/LEGO1/lego/legoomni/src/common/misc.cpp +++ b/LEGO1/lego/legoomni/src/common/misc.cpp @@ -172,9 +172,9 @@ void SetUserActor(LegoPathActor* p_userActor) // FUNCTION: LEGO1 0x10015890 // FUNCTION: BETA10 0x100e4d80 -MxResult StartActionIfUnknown0x13c(MxDSAction& p_dsAction) +MxResult StartActionIfInitialized(MxDSAction& p_dsAction) { - return LegoOmni::GetInstance()->StartActionIfUnknown0x13c(p_dsAction); + return LegoOmni::GetInstance()->StartActionIfInitialized(p_dsAction); } // FUNCTION: LEGO1 0x100158b0 diff --git a/LEGO1/lego/legoomni/src/entity/legonavcontroller.cpp b/LEGO1/lego/legoomni/src/entity/legonavcontroller.cpp index fa8158da..78d2e6d1 100644 --- a/LEGO1/lego/legoomni/src/entity/legonavcontroller.cpp +++ b/LEGO1/lego/legoomni/src/entity/legonavcontroller.cpp @@ -896,7 +896,7 @@ MxLong LegoNavController::Notify(MxParam& p_param) break; case 'A': if (g_animationCalcStep == 1) { - Lego()->m_unk0x13c = TRUE; + Lego()->m_initialized = TRUE; AnimationManager()->FUN_10060570(TRUE); g_animationCalcStep = 0; } diff --git a/LEGO1/lego/legoomni/src/main/legomain.cpp b/LEGO1/lego/legoomni/src/main/legomain.cpp index 0da66855..932ae247 100644 --- a/LEGO1/lego/legoomni/src/main/legomain.cpp +++ b/LEGO1/lego/legoomni/src/main/legomain.cpp @@ -71,7 +71,7 @@ void LegoOmni::Init() m_animationManager = NULL; m_buildingManager = NULL; m_bkgAudioManager = NULL; - m_unk0x13c = TRUE; + m_initialized = TRUE; m_transitionManager = NULL; } diff --git a/LEGO1/lego/legoomni/src/worlds/legoact2.cpp b/LEGO1/lego/legoomni/src/worlds/legoact2.cpp index 49936b28..a3c4f7c0 100644 --- a/LEGO1/lego/legoomni/src/worlds/legoact2.cpp +++ b/LEGO1/lego/legoomni/src/worlds/legoact2.cpp @@ -1116,7 +1116,7 @@ MxResult LegoAct2::FUN_10052560( action.SetDirection(*p_direction); } - StartActionIfUnknown0x13c(action); + StartActionIfInitialized(action); } else { MxMatrix matrix; From 132d9c817fde4e06c42aedff098d2ac8b8d3bc20 Mon Sep 17 00:00:00 2001 From: MS Date: Sun, 20 Jul 2025 11:47:08 -0400 Subject: [PATCH 170/188] Build BETA10 in CI (#1639) --- .github/workflows/build.yml | 37 +++++++++++++++++++++++++++++++++++++ 1 file changed, 37 insertions(+) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 982d9468..4327d20a 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -100,6 +100,43 @@ jobs: build/LEGO1.DLL build/LEGO1.PDB + build-beta: + name: 'MSVC 4.20 (BETA10)' + runs-on: windows-latest + + steps: + - uses: actions/checkout@v4 + + - uses: actions/checkout@v4 + with: + repository: itsmattkc/msvc420 + path: msvc420 + + - name: Setup cmake + uses: jwlawson/actions-setup-cmake@v2 + with: + # Use minimum supported version + cmake-version: '3.15.x' + + - name: Patch MSVC 4.2 + run: | + tools/patch_c2.py msvc420/bin/C2.EXE + + - name: Build + shell: cmd + run: | + call .\msvc420\bin\VCVARS32.BAT x86 + cmake -B build -DCMAKE_BUILD_TYPE=Debug -DISLE_INCLUDE_ENTROPY=OFF -DISLE_BUILD_BETA10=ON -DISLE_BUILD_LEGO1=OFF -DISLE_BUILD_APP=OFF -DISLE_BUILD_CONFIG=OFF -G "NMake Makefiles" + cmake --build build + + - name: Upload Artifact + uses: actions/upload-artifact@main + with: + name: Win32-beta + path: | + build/BETA10.DLL + build/BETA10.PDB + verify: name: Verify decomp needs: [build, fetch-deps] From 2980f88bb0b73e468569d52524f60e91f7f9cdcb Mon Sep 17 00:00:00 2001 From: jonschz <17198703+jonschz@users.noreply.github.com> Date: Sun, 20 Jul 2025 18:20:40 +0200 Subject: [PATCH 171/188] Fix filenames based on BETA10 `MxAutoLock` (#1640) * Inline LegoAnimPresenters --------- Co-authored-by: jonschz --- CMakeLists.txt | 3 - .../lego/legoomni/include/legoanimpresenter.h | 209 ++++++++- .../legoomni/include/legohideanimpresenter.h | 111 ----- .../include/legolocomotionanimpresenter.h | 71 --- .../include/legoloopinganimpresenter.h | 43 -- LEGO1/lego/legoomni/src/actors/act3actors.cpp | 2 +- .../src/common/legoanimationmanager.cpp | 1 - .../legoomni/src/common/legoobjectfactory.cpp | 5 +- .../src/common/mxcompositemediapresenter.cpp | 1 + LEGO1/lego/legoomni/src/entity/legoworld.cpp | 1 - .../lego/legoomni/src/paths/legoanimactor.cpp | 2 +- .../legoomni/src/paths/legoextraactor.cpp | 2 +- .../legoomni/src/paths/legopathboundary.cpp | 2 +- .../legoomni/src/paths/legopathstruct.cpp | 2 +- LEGO1/lego/legoomni/src/race/carrace.cpp | 2 +- LEGO1/lego/legoomni/src/race/jetskirace.cpp | 2 +- .../legoomni/src/video/legoanimpresenter.cpp | 441 ++++++++++++++++++ .../src/video/legohideanimpresenter.cpp | 216 --------- .../src/video/legolocomotionanimpresenter.cpp | 164 ------- .../src/video/legoloopinganimpresenter.cpp | 83 ---- LEGO1/lego/legoomni/src/worlds/legoact2.cpp | 2 +- .../omni/src/common/mxcompositepresenter.cpp | 2 +- 22 files changed, 659 insertions(+), 708 deletions(-) delete mode 100644 LEGO1/lego/legoomni/include/legohideanimpresenter.h delete mode 100644 LEGO1/lego/legoomni/include/legolocomotionanimpresenter.h delete mode 100644 LEGO1/lego/legoomni/include/legoloopinganimpresenter.h delete mode 100644 LEGO1/lego/legoomni/src/video/legohideanimpresenter.cpp delete mode 100644 LEGO1/lego/legoomni/src/video/legolocomotionanimpresenter.cpp delete mode 100644 LEGO1/lego/legoomni/src/video/legoloopinganimpresenter.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index c8d030e6..0f6d1746 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -376,9 +376,6 @@ function(add_lego_libraries NAME) LEGO1/lego/legoomni/src/entity/legoworldpresenter.cpp LEGO1/lego/legoomni/src/actors/dunebuggy.cpp LEGO1/lego/legoomni/src/video/legoanimpresenter.cpp - LEGO1/lego/legoomni/src/video/legoloopinganimpresenter.cpp - LEGO1/lego/legoomni/src/video/legolocomotionanimpresenter.cpp - LEGO1/lego/legoomni/src/video/legohideanimpresenter.cpp LEGO1/lego/legoomni/src/worlds/infocenter.cpp LEGO1/lego/legoomni/src/race/raceskel.cpp LEGO1/lego/legoomni/src/worlds/act3.cpp diff --git a/LEGO1/lego/legoomni/include/legoanimpresenter.h b/LEGO1/lego/legoomni/include/legoanimpresenter.h index 802e8917..6e983144 100644 --- a/LEGO1/lego/legoomni/include/legoanimpresenter.h +++ b/LEGO1/lego/legoomni/include/legoanimpresenter.h @@ -2,6 +2,7 @@ #define LEGOANIMPRESENTER_H #include "legoroilist.h" +#include "legoroimaplist.h" #include "mxatom.h" #include "mxvideopresenter.h" @@ -147,14 +148,191 @@ class LegoAnimPresenter : public MxVideoPresenter { MxS16 m_unk0x9c; // 0x9c Matrix4* m_unk0xa0; // 0xa0 + // SYNTHETIC: LEGO1 0x10068650 + // LegoAnimPresenter::`scalar deleting destructor' + public: float m_unk0xa4; // 0xa4 Mx3DPointFloat m_unk0xa8; // 0xa8 }; +// VTABLE: LEGO1 0x100d4900 +// SIZE 0xc0 +class LegoLoopingAnimPresenter : public LegoAnimPresenter { +public: + // FUNCTION: BETA10 0x1005c6f0 + static const char* HandlerClassName() + { + // STRING: LEGO1 0x100f0700 + return "LegoLoopingAnimPresenter"; + } + + // FUNCTION: LEGO1 0x1000c9a0 + // FUNCTION: BETA10 0x1005c6c0 + const char* ClassName() const override // vtable+0x0c + { + return HandlerClassName(); + } + + // FUNCTION: LEGO1 0x1000c9b0 + MxBool IsA(const char* p_name) const override // vtable+0x10 + { + return !strcmp(p_name, ClassName()) || LegoAnimPresenter::IsA(p_name); + } + + void StreamingTickle() override; // vtable+0x20 + void PutFrame() override; // vtable+0x6c + + // SYNTHETIC: LEGO1 0x1006d000 + // LegoLoopingAnimPresenter::~LegoLoopingAnimPresenter + + // SYNTHETIC: LEGO1 0x1000f440 + // LegoLoopingAnimPresenter::`scalar deleting destructor' + +private: + undefined4 m_unk0xbc; // 0xbc +}; + +class LegoAnimActor; + +// VTABLE: LEGO1 0x100d9170 +// SIZE 0xd8 +class LegoLocomotionAnimPresenter : public LegoLoopingAnimPresenter { +public: + LegoLocomotionAnimPresenter(); + ~LegoLocomotionAnimPresenter() override; + + // FUNCTION: BETA10 0x1005c4e0 + static const char* HandlerClassName() + { + // STRING: LEGO1 0x100f06e4 + return "LegoLocomotionAnimPresenter"; + } + + // FUNCTION: LEGO1 0x1006ce50 + // FUNCTION: BETA10 0x1005c4b0 + const char* ClassName() const override // vtable+0x0c + { + return HandlerClassName(); + } + + // FUNCTION: LEGO1 0x1006ce60 + MxBool IsA(const char* p_name) const override // vtable+0x10 + { + return !strcmp(p_name, ClassName()) || LegoLoopingAnimPresenter::IsA(p_name); + } + + void ReadyTickle() override; // vtable+0x18 + void StartingTickle() override; // vtable+0x1c + void StreamingTickle() override; // vtable+0x20 + MxResult AddToManager() override; // vtable+0x34 + void Destroy() override; // vtable+0x38 + void EndAction() override; // vtable+0x40 + void PutFrame() override; // vtable+0x6c + MxResult CreateAnim(MxStreamChunk* p_chunk) override; // vtable+0x88 + + void FUN_1006d680(LegoAnimActor* p_actor, MxFloat p_value); + + void DecrementUnknown0xd4() + { + if (m_unk0xd4) { + --m_unk0xd4; + } + } + + undefined2 GetUnknown0xd4() { return m_unk0xd4; } + + // SYNTHETIC: LEGO1 0x1006cfe0 + // LegoLocomotionAnimPresenter::`scalar deleting destructor' + +private: + void Init(); + void Destroy(MxBool p_fromDestructor); + + undefined4 m_unk0xc0; // 0xc0 + undefined4* m_unk0xc4; // 0xc4 + LegoROIMapList* m_roiMapList; // 0xc8 + MxS32 m_unk0xcc; // 0xcc + MxS32 m_unk0xd0; // 0xd0 + undefined2 m_unk0xd4; // 0xd4 +}; + +class LegoPathBoundary; + +struct LegoHideAnimStructComparator { + MxBool operator()(const char* const& p_a, const char* const& p_b) const { return strcmp(p_a, p_b) < 0; } +}; + +// SIZE 0x08 +struct LegoHideAnimStruct { + LegoPathBoundary* m_boundary; // 0x00 + MxU32 m_index; // 0x04 +}; + +typedef map LegoHideAnimStructMap; + +// VTABLE: LEGO1 0x100d9278 +// SIZE 0xc4 +class LegoHideAnimPresenter : public LegoLoopingAnimPresenter { +public: + LegoHideAnimPresenter(); + ~LegoHideAnimPresenter() override; + + // FUNCTION: LEGO1 0x1006d860 + void VTable0x8c() override {} // vtable+0x8c + + // FUNCTION: LEGO1 0x1006d870 + void VTable0x90() override {} // vtable+0x90 + + // FUNCTION: BETA10 0x1005d4a0 + static const char* HandlerClassName() + { + // STRING: LEGO1 0x100f06cc + return "LegoHideAnimPresenter"; + } + + // FUNCTION: LEGO1 0x1006d880 + // FUNCTION: BETA10 0x1005d470 + const char* ClassName() const override // vtable+0x0c + { + return HandlerClassName(); + } + + // FUNCTION: LEGO1 0x1006d890 + MxBool IsA(const char* p_name) const override // vtable+0x10 + { + return !strcmp(p_name, ClassName()) || LegoAnimPresenter::IsA(p_name); + } + + void ReadyTickle() override; // vtable+0x18 + void StartingTickle() override; // vtable+0x18 + MxResult AddToManager() override; // vtable+0x34 + void Destroy() override; // vtable+0x38 + void EndAction() override; // vtable+0x40 + void PutFrame() override; // vtable+0x6c + + void FUN_1006db40(LegoTime p_time); + + // SYNTHETIC: LEGO1 0x1006d9d0 + // LegoHideAnimPresenter::`scalar deleting destructor' + +private: + void Init(); + void Destroy(MxBool p_fromDestructor); + void FUN_1006db60(LegoTreeNode* p_node, LegoTime p_time); + void FUN_1006dc10(); + void FUN_1006e3f0(LegoHideAnimStructMap& p_map, LegoTreeNode* p_node); + void FUN_1006e470( + LegoHideAnimStructMap& p_map, + LegoAnimNodeData* p_data, + const char* p_name, + LegoPathBoundary* p_boundary + ); + + LegoPathBoundary** m_boundaryMap; // 0xc0 +}; + // clang-format off -// SYNTHETIC: LEGO1 0x10068650 -// LegoAnimPresenter::`scalar deleting destructor' // TEMPLATE: LEGO1 0x100689c0 // map >::~map > @@ -212,6 +390,33 @@ class LegoAnimPresenter : public MxVideoPresenter { // GLOBAL: LEGO1 0x100f7688 // _Tree,map >::_Kfn,LegoAnimStructComparator,allocator >::_Nil + +// TEMPLATE: LEGO1 0x1006ddb0 +// _Tree,map >::_Kfn,LegoHideAnimStructComparator,allocator >::~_Tree,map >::_Kfn,LegoHideAnimStructComparator,allocator >::iterator::_Inc + +// TEMPLATE: LEGO1 0x1006dec0 +// _Tree,map >::_Kfn,LegoHideAnimStructComparator,allocator >::erase + +// TEMPLATE: LEGO1 0x1006e310 +// _Tree,map >::_Kfn,LegoHideAnimStructComparator,allocator >::_Erase + +// TEMPLATE: LEGO1 0x1006e350 +// Map::~Map + +// TEMPLATE: LEGO1 0x1006e3a0 +// map >::~map > + +// TEMPLATE: LEGO1 0x1006e6d0 +// _Tree,map >::_Kfn,LegoHideAnimStructComparator,allocator >::iterator::_Dec + +// TEMPLATE: LEGO1 0x1006e720 +// _Tree,map >::_Kfn,LegoHideAnimStructComparator,allocator >::_Insert + +// GLOBAL: LEGO1 0x100f768c +// _Tree,map >::_Kfn,LegoHideAnimStructComparator,allocator >::_Nil // clang-format on #endif // LEGOANIMPRESENTER_H diff --git a/LEGO1/lego/legoomni/include/legohideanimpresenter.h b/LEGO1/lego/legoomni/include/legohideanimpresenter.h deleted file mode 100644 index b2bbb7b8..00000000 --- a/LEGO1/lego/legoomni/include/legohideanimpresenter.h +++ /dev/null @@ -1,111 +0,0 @@ -#ifndef LEGOHIDEANIMPRESENTER_H -#define LEGOHIDEANIMPRESENTER_H - -#include "decomp.h" -#include "legoloopinganimpresenter.h" - -class LegoPathBoundary; - -struct LegoHideAnimStructComparator { - MxBool operator()(const char* const& p_a, const char* const& p_b) const { return strcmp(p_a, p_b) < 0; } -}; - -// SIZE 0x08 -struct LegoHideAnimStruct { - LegoPathBoundary* m_boundary; // 0x00 - MxU32 m_index; // 0x04 -}; - -typedef map LegoHideAnimStructMap; - -// VTABLE: LEGO1 0x100d9278 -// SIZE 0xc4 -class LegoHideAnimPresenter : public LegoLoopingAnimPresenter { -public: - LegoHideAnimPresenter(); - ~LegoHideAnimPresenter() override; - - // FUNCTION: LEGO1 0x1006d860 - void VTable0x8c() override {} // vtable+0x8c - - // FUNCTION: LEGO1 0x1006d870 - void VTable0x90() override {} // vtable+0x90 - - // FUNCTION: BETA10 0x1005d4a0 - static const char* HandlerClassName() - { - // STRING: LEGO1 0x100f06cc - return "LegoHideAnimPresenter"; - } - - // FUNCTION: LEGO1 0x1006d880 - // FUNCTION: BETA10 0x1005d470 - const char* ClassName() const override // vtable+0x0c - { - return HandlerClassName(); - } - - // FUNCTION: LEGO1 0x1006d890 - MxBool IsA(const char* p_name) const override // vtable+0x10 - { - return !strcmp(p_name, ClassName()) || LegoAnimPresenter::IsA(p_name); - } - - void ReadyTickle() override; // vtable+0x18 - void StartingTickle() override; // vtable+0x18 - MxResult AddToManager() override; // vtable+0x34 - void Destroy() override; // vtable+0x38 - void EndAction() override; // vtable+0x40 - void PutFrame() override; // vtable+0x6c - - void FUN_1006db40(LegoTime p_time); - -private: - void Init(); - void Destroy(MxBool p_fromDestructor); - void FUN_1006db60(LegoTreeNode* p_node, LegoTime p_time); - void FUN_1006dc10(); - void FUN_1006e3f0(LegoHideAnimStructMap& p_map, LegoTreeNode* p_node); - void FUN_1006e470( - LegoHideAnimStructMap& p_map, - LegoAnimNodeData* p_data, - const char* p_name, - LegoPathBoundary* p_boundary - ); - - LegoPathBoundary** m_boundaryMap; // 0xc0 -}; - -// clang-format off -// SYNTHETIC: LEGO1 0x1006d9d0 -// LegoHideAnimPresenter::`scalar deleting destructor' - -// TEMPLATE: LEGO1 0x1006ddb0 -// _Tree,map >::_Kfn,LegoHideAnimStructComparator,allocator >::~_Tree,map >::_Kfn,LegoHideAnimStructComparator,allocator >::iterator::_Inc - -// TEMPLATE: LEGO1 0x1006dec0 -// _Tree,map >::_Kfn,LegoHideAnimStructComparator,allocator >::erase - -// TEMPLATE: LEGO1 0x1006e310 -// _Tree,map >::_Kfn,LegoHideAnimStructComparator,allocator >::_Erase - -// TEMPLATE: LEGO1 0x1006e350 -// Map::~Map - -// TEMPLATE: LEGO1 0x1006e3a0 -// map >::~map > - -// TEMPLATE: LEGO1 0x1006e6d0 -// _Tree,map >::_Kfn,LegoHideAnimStructComparator,allocator >::iterator::_Dec - -// TEMPLATE: LEGO1 0x1006e720 -// _Tree,map >::_Kfn,LegoHideAnimStructComparator,allocator >::_Insert - -// GLOBAL: LEGO1 0x100f768c -// _Tree,map >::_Kfn,LegoHideAnimStructComparator,allocator >::_Nil -// clang-format on - -#endif // LEGOHIDEANIMPRESENTER_H diff --git a/LEGO1/lego/legoomni/include/legolocomotionanimpresenter.h b/LEGO1/lego/legoomni/include/legolocomotionanimpresenter.h deleted file mode 100644 index df2f872e..00000000 --- a/LEGO1/lego/legoomni/include/legolocomotionanimpresenter.h +++ /dev/null @@ -1,71 +0,0 @@ -#ifndef LEGOLOCOMOTIONANIMPRESENTER_H -#define LEGOLOCOMOTIONANIMPRESENTER_H - -#include "legoloopinganimpresenter.h" -#include "legoroimaplist.h" - -class LegoAnimActor; - -// VTABLE: LEGO1 0x100d9170 -// SIZE 0xd8 -class LegoLocomotionAnimPresenter : public LegoLoopingAnimPresenter { -public: - LegoLocomotionAnimPresenter(); - ~LegoLocomotionAnimPresenter() override; - - // FUNCTION: BETA10 0x1005c4e0 - static const char* HandlerClassName() - { - // STRING: LEGO1 0x100f06e4 - return "LegoLocomotionAnimPresenter"; - } - - // FUNCTION: LEGO1 0x1006ce50 - // FUNCTION: BETA10 0x1005c4b0 - const char* ClassName() const override // vtable+0x0c - { - return HandlerClassName(); - } - - // FUNCTION: LEGO1 0x1006ce60 - MxBool IsA(const char* p_name) const override // vtable+0x10 - { - return !strcmp(p_name, ClassName()) || LegoLoopingAnimPresenter::IsA(p_name); - } - - void ReadyTickle() override; // vtable+0x18 - void StartingTickle() override; // vtable+0x1c - void StreamingTickle() override; // vtable+0x20 - MxResult AddToManager() override; // vtable+0x34 - void Destroy() override; // vtable+0x38 - void EndAction() override; // vtable+0x40 - void PutFrame() override; // vtable+0x6c - MxResult CreateAnim(MxStreamChunk* p_chunk) override; // vtable+0x88 - - // SYNTHETIC: LEGO1 0x1006cfe0 - // LegoLocomotionAnimPresenter::`scalar deleting destructor' - - void FUN_1006d680(LegoAnimActor* p_actor, MxFloat p_value); - - void DecrementUnknown0xd4() - { - if (m_unk0xd4) { - --m_unk0xd4; - } - } - - undefined2 GetUnknown0xd4() { return m_unk0xd4; } - -private: - void Init(); - void Destroy(MxBool p_fromDestructor); - - undefined4 m_unk0xc0; // 0xc0 - undefined4* m_unk0xc4; // 0xc4 - LegoROIMapList* m_roiMapList; // 0xc8 - MxS32 m_unk0xcc; // 0xcc - MxS32 m_unk0xd0; // 0xd0 - undefined2 m_unk0xd4; // 0xd4 -}; - -#endif // LEGOLOCOMOTIONANIMPRESENTER_H diff --git a/LEGO1/lego/legoomni/include/legoloopinganimpresenter.h b/LEGO1/lego/legoomni/include/legoloopinganimpresenter.h deleted file mode 100644 index 8952cbba..00000000 --- a/LEGO1/lego/legoomni/include/legoloopinganimpresenter.h +++ /dev/null @@ -1,43 +0,0 @@ -#ifndef LEGOLOOPINGANIMPRESENTER_H -#define LEGOLOOPINGANIMPRESENTER_H - -#include "legoanimpresenter.h" - -// VTABLE: LEGO1 0x100d4900 -// SIZE 0xc0 -class LegoLoopingAnimPresenter : public LegoAnimPresenter { -public: - // FUNCTION: BETA10 0x1005c6f0 - static const char* HandlerClassName() - { - // STRING: LEGO1 0x100f0700 - return "LegoLoopingAnimPresenter"; - } - - // FUNCTION: LEGO1 0x1000c9a0 - // FUNCTION: BETA10 0x1005c6c0 - const char* ClassName() const override // vtable+0x0c - { - return HandlerClassName(); - } - - // FUNCTION: LEGO1 0x1000c9b0 - MxBool IsA(const char* p_name) const override // vtable+0x10 - { - return !strcmp(p_name, ClassName()) || LegoAnimPresenter::IsA(p_name); - } - - void StreamingTickle() override; // vtable+0x20 - void PutFrame() override; // vtable+0x6c - -private: - undefined4 m_unk0xbc; // 0xbc -}; - -// SYNTHETIC: LEGO1 0x1006d000 -// LegoLoopingAnimPresenter::~LegoLoopingAnimPresenter - -// SYNTHETIC: LEGO1 0x1000f440 -// LegoLoopingAnimPresenter::`scalar deleting destructor' - -#endif // LEGOLOOPINGANIMPRESENTER_H diff --git a/LEGO1/lego/legoomni/src/actors/act3actors.cpp b/LEGO1/lego/legoomni/src/actors/act3actors.cpp index 9a2acd16..0d89f2f4 100644 --- a/LEGO1/lego/legoomni/src/actors/act3actors.cpp +++ b/LEGO1/lego/legoomni/src/actors/act3actors.cpp @@ -4,9 +4,9 @@ #include "act3ammo.h" #include "anim/legoanim.h" #include "define.h" +#include "legoanimpresenter.h" #include "legobuildingmanager.h" #include "legocachesoundmanager.h" -#include "legolocomotionanimpresenter.h" #include "legopathedgecontainer.h" #include "legoplantmanager.h" #include "legoplants.h" diff --git a/LEGO1/lego/legoomni/src/common/legoanimationmanager.cpp b/LEGO1/lego/legoomni/src/common/legoanimationmanager.cpp index aab443e2..1b9dffcb 100644 --- a/LEGO1/lego/legoomni/src/common/legoanimationmanager.cpp +++ b/LEGO1/lego/legoomni/src/common/legoanimationmanager.cpp @@ -11,7 +11,6 @@ #include "legoentitylist.h" #include "legoextraactor.h" #include "legogamestate.h" -#include "legolocomotionanimpresenter.h" #include "legomain.h" #include "legonavcontroller.h" #include "legoroilist.h" diff --git a/LEGO1/lego/legoomni/src/common/legoobjectfactory.cpp b/LEGO1/lego/legoomni/src/common/legoobjectfactory.cpp index 68a3b35a..b8514020 100644 --- a/LEGO1/lego/legoomni/src/common/legoobjectfactory.cpp +++ b/LEGO1/lego/legoomni/src/common/legoobjectfactory.cpp @@ -10,7 +10,7 @@ #include "legoentity.h" #include "legopathactor.h" // The below header inclusions should be sound. -#include "legoloopinganimpresenter.h" +#include "legoanimpresenter.h" #include "mxcompositemediapresenter.h" #include "legoactorpresenter.h" #include "legomodelpresenter.h" @@ -71,10 +71,7 @@ #include "legoentity.h" #include "legoentitypresenter.h" #include "legoflctexturepresenter.h" -#include "legohideanimpresenter.h" #include "legoloadcachesoundpresenter.h" -#include "legolocomotionanimpresenter.h" -#include "legoloopinganimpresenter.h" #include "legometerpresenter.h" #include "legomodelpresenter.h" #include "legopalettepresenter.h" diff --git a/LEGO1/lego/legoomni/src/common/mxcompositemediapresenter.cpp b/LEGO1/lego/legoomni/src/common/mxcompositemediapresenter.cpp index 04437877..7c6a788d 100644 --- a/LEGO1/lego/legoomni/src/common/mxcompositemediapresenter.cpp +++ b/LEGO1/lego/legoomni/src/common/mxcompositemediapresenter.cpp @@ -27,6 +27,7 @@ MxCompositeMediaPresenter::~MxCompositeMediaPresenter() } // FUNCTION: LEGO1 0x10074090 +// FUNCTION: BETA10 0x100e9d37 MxResult MxCompositeMediaPresenter::StartAction(MxStreamController* p_controller, MxDSAction* p_action) { AUTOLOCK(m_criticalSection); diff --git a/LEGO1/lego/legoomni/src/entity/legoworld.cpp b/LEGO1/lego/legoomni/src/entity/legoworld.cpp index f038f3b0..f975f5b6 100644 --- a/LEGO1/lego/legoomni/src/entity/legoworld.cpp +++ b/LEGO1/lego/legoomni/src/entity/legoworld.cpp @@ -9,7 +9,6 @@ #include "legocontrolmanager.h" #include "legogamestate.h" #include "legoinputmanager.h" -#include "legolocomotionanimpresenter.h" #include "legonavcontroller.h" #include "legoplantmanager.h" #include "legosoundmanager.h" diff --git a/LEGO1/lego/legoomni/src/paths/legoanimactor.cpp b/LEGO1/lego/legoomni/src/paths/legoanimactor.cpp index f6ca062c..0a1e6d98 100644 --- a/LEGO1/lego/legoomni/src/paths/legoanimactor.cpp +++ b/LEGO1/lego/legoomni/src/paths/legoanimactor.cpp @@ -2,7 +2,7 @@ #include "anim/legoanim.h" #include "define.h" -#include "legolocomotionanimpresenter.h" +#include "legoanimpresenter.h" #include "legopathboundary.h" #include "legoworld.h" #include "misc.h" diff --git a/LEGO1/lego/legoomni/src/paths/legoextraactor.cpp b/LEGO1/lego/legoomni/src/paths/legoextraactor.cpp index b9cd25bb..13afdb32 100644 --- a/LEGO1/lego/legoomni/src/paths/legoextraactor.cpp +++ b/LEGO1/lego/legoomni/src/paths/legoextraactor.cpp @@ -1,8 +1,8 @@ #include "legoextraactor.h" #include "anim/legoanim.h" +#include "legoanimpresenter.h" #include "legocachesoundmanager.h" -#include "legolocomotionanimpresenter.h" #include "legosoundmanager.h" #include "legoworld.h" #include "misc.h" diff --git a/LEGO1/lego/legoomni/src/paths/legopathboundary.cpp b/LEGO1/lego/legoomni/src/paths/legopathboundary.cpp index 458a79b9..8ef3c323 100644 --- a/LEGO1/lego/legoomni/src/paths/legopathboundary.cpp +++ b/LEGO1/lego/legoomni/src/paths/legopathboundary.cpp @@ -2,7 +2,7 @@ #include "decomp.h" #include "geom/legoorientededge.h" -#include "legolocomotionanimpresenter.h" +#include "legoanimpresenter.h" #include "legopathactor.h" #include "legopathstruct.h" diff --git a/LEGO1/lego/legoomni/src/paths/legopathstruct.cpp b/LEGO1/lego/legoomni/src/paths/legopathstruct.cpp index e269f727..276b66ab 100644 --- a/LEGO1/lego/legoomni/src/paths/legopathstruct.cpp +++ b/LEGO1/lego/legoomni/src/paths/legopathstruct.cpp @@ -3,7 +3,7 @@ #include "isle.h" #include "jukebox.h" #include "jukebox_actions.h" -#include "legohideanimpresenter.h" +#include "legoanimpresenter.h" #include "legopathactor.h" #include "legoutils.h" #include "misc.h" diff --git a/LEGO1/lego/legoomni/src/race/carrace.cpp b/LEGO1/lego/legoomni/src/race/carrace.cpp index 6c36223a..fa52edba 100644 --- a/LEGO1/lego/legoomni/src/race/carrace.cpp +++ b/LEGO1/lego/legoomni/src/race/carrace.cpp @@ -5,8 +5,8 @@ #include "isle.h" #include "jukebox_actions.h" #include "legoanimationmanager.h" +#include "legoanimpresenter.h" #include "legocontrolmanager.h" -#include "legohideanimpresenter.h" #include "legomain.h" #include "legonavcontroller.h" #include "legopathstruct.h" diff --git a/LEGO1/lego/legoomni/src/race/jetskirace.cpp b/LEGO1/lego/legoomni/src/race/jetskirace.cpp index 79391c6d..54d6b4be 100644 --- a/LEGO1/lego/legoomni/src/race/jetskirace.cpp +++ b/LEGO1/lego/legoomni/src/race/jetskirace.cpp @@ -6,8 +6,8 @@ #include "jetski_actions.h" #include "jukebox_actions.h" #include "legoanimationmanager.h" +#include "legoanimpresenter.h" #include "legocontrolmanager.h" -#include "legohideanimpresenter.h" #include "legomain.h" #include "legopathstruct.h" #include "legoracers.h" diff --git a/LEGO1/lego/legoomni/src/video/legoanimpresenter.cpp b/LEGO1/lego/legoomni/src/video/legoanimpresenter.cpp index d3f38b22..c7fbc845 100644 --- a/LEGO1/lego/legoomni/src/video/legoanimpresenter.cpp +++ b/LEGO1/lego/legoomni/src/video/legoanimpresenter.cpp @@ -3,6 +3,7 @@ #include "3dmanager/lego3dmanager.h" #include "anim/legoanim.h" #include "define.h" +#include "legoanimactor.h" #include "legoanimationmanager.h" #include "legoanimmmpresenter.h" #include "legocameracontroller.h" @@ -27,6 +28,10 @@ #include "viewmanager/viewmanager.h" DECOMP_SIZE_ASSERT(LegoAnimPresenter, 0xbc) +DECOMP_SIZE_ASSERT(LegoLoopingAnimPresenter, 0xc0) +DECOMP_SIZE_ASSERT(LegoLocomotionAnimPresenter, 0xd8) +DECOMP_SIZE_ASSERT(LegoHideAnimPresenter, 0xc4) +DECOMP_SIZE_ASSERT(LegoHideAnimStruct, 0x08) // FUNCTION: LEGO1 0x10068420 // FUNCTION: BETA10 0x1004e5f0 @@ -1216,3 +1221,439 @@ MxResult LegoAnimPresenter::VTable0x98(LegoPathBoundary* p_boundary) return SUCCESS; } + +// FUNCTION: LEGO1 0x1006caa0 +// FUNCTION: BETA10 0x1005223d +void LegoLoopingAnimPresenter::StreamingTickle() +{ + if (m_subscriber->PeekData()) { + MxStreamChunk* chunk = m_subscriber->PopData(); + m_subscriber->FreeDataChunk(chunk); + } + + if (m_unk0x95) { + ProgressTickleState(e_done); + if (m_compositePresenter) { + if (m_compositePresenter->IsA("LegoAnimMMPresenter")) { + m_compositePresenter->VTable0x60(this); + } + } + } + else { + if (m_action->GetDuration() != -1) { + if (m_action->GetElapsedTime() > m_action->GetDuration() + m_action->GetStartTime()) { + m_unk0x95 = TRUE; + } + } + } +} + +// FUNCTION: LEGO1 0x1006cb40 +// FUNCTION: BETA10 0x1005239a +void LegoLoopingAnimPresenter::PutFrame() +{ + MxLong time; + + if (m_action->GetStartTime() <= m_action->GetElapsedTime()) { + time = (m_action->GetElapsedTime() - m_action->GetStartTime()) % m_anim->GetDuration(); + } + else { + time = 0; + } + + FUN_1006b9a0(m_anim, time, m_unk0x78); + + if (m_unk0x8c != NULL && m_currentWorld != NULL && m_currentWorld->GetCameraController() != NULL) { + for (MxS32 i = 0; i < m_unk0x94; i++) { + if (m_unk0x8c[i] != NULL) { + MxMatrix mat(m_unk0x8c[i]->GetLocal2World()); + + Vector3 pos(mat[0]); + Vector3 dir(mat[1]); + Vector3 up(mat[2]); + Vector3 und(mat[3]); + + float possqr = sqrt(pos.LenSquared()); + float dirsqr = sqrt(dir.LenSquared()); + float upsqr = sqrt(up.LenSquared()); + + up = und; + + up -= m_currentWorld->GetCameraController()->GetWorldLocation(); + dir /= dirsqr; + pos.EqualsCross(dir, up); + pos.Unitize(); + up.EqualsCross(pos, dir); + pos *= possqr; + dir *= dirsqr; + up *= upsqr; + + m_unk0x8c[i]->SetLocal2World(mat); + m_unk0x8c[i]->WrappedUpdateWorldData(); + } + } + } +} + +// FUNCTION: LEGO1 0x1006cdd0 +LegoLocomotionAnimPresenter::LegoLocomotionAnimPresenter() +{ + Init(); +} + +// FUNCTION: LEGO1 0x1006d050 +LegoLocomotionAnimPresenter::~LegoLocomotionAnimPresenter() +{ + Destroy(TRUE); +} + +// FUNCTION: LEGO1 0x1006d0b0 +void LegoLocomotionAnimPresenter::Init() +{ + m_unk0xc0 = 0; + m_unk0xc4 = NULL; + m_unk0xcc = -1; + m_unk0xd0 = -1; + m_roiMapList = NULL; + m_unk0xd4 = 0; +} + +// FUNCTION: LEGO1 0x1006d0e0 +void LegoLocomotionAnimPresenter::Destroy(MxBool p_fromDestructor) +{ + ENTER(m_criticalSection); + + if (m_unk0xc4) { + delete[] m_unk0xc4; + } + + if (m_roiMapList) { + delete m_roiMapList; + } + + m_roiMap = NULL; + Init(); + + m_criticalSection.Leave(); + + if (!p_fromDestructor) { + LegoLoopingAnimPresenter::Destroy(); + } +} + +// FUNCTION: LEGO1 0x1006d140 +MxResult LegoLocomotionAnimPresenter::CreateAnim(MxStreamChunk* p_chunk) +{ + MxResult result = LegoAnimPresenter::CreateAnim(p_chunk); + return result == SUCCESS ? SUCCESS : result; +} + +// FUNCTION: LEGO1 0x1006d160 +// FUNCTION: BETA10 0x100528c7 +MxResult LegoLocomotionAnimPresenter::AddToManager() +{ + m_roiMapList = new LegoROIMapList(); + + if (m_roiMapList == NULL) { + return FAILURE; + } + + return LegoAnimPresenter::AddToManager(); +} + +// FUNCTION: LEGO1 0x1006d5b0 +void LegoLocomotionAnimPresenter::Destroy() +{ + Destroy(FALSE); +} + +// FUNCTION: LEGO1 0x1006d5c0 +void LegoLocomotionAnimPresenter::PutFrame() +{ + // Empty +} + +// FUNCTION: LEGO1 0x1006d5d0 +void LegoLocomotionAnimPresenter::ReadyTickle() +{ + LegoLoopingAnimPresenter::ReadyTickle(); + + if (m_currentWorld != NULL && m_currentTickleState == e_starting) { + m_currentWorld->Add(this); + if (m_compositePresenter != NULL) { + SendToCompositePresenter(Lego()); + } + + m_unk0xd4++; + } +} + +// FUNCTION: LEGO1 0x1006d610 +// FUNCTION: BETA10 0x10052a34 +void LegoLocomotionAnimPresenter::StartingTickle() +{ + if (m_subscriber->PeekData()) { + MxStreamChunk* chunk = m_subscriber->PopData(); + m_subscriber->FreeDataChunk(chunk); + } + + if (m_roiMapList->GetNumElements() != 0) { + ProgressTickleState(e_streaming); + } +} + +// FUNCTION: LEGO1 0x1006d660 +void LegoLocomotionAnimPresenter::StreamingTickle() +{ + if (m_unk0xd4 == 0) { + EndAction(); + } +} + +// FUNCTION: LEGO1 0x1006d670 +void LegoLocomotionAnimPresenter::EndAction() +{ + if (m_action) { + MxVideoPresenter::EndAction(); + } +} + +// FUNCTION: LEGO1 0x1006d680 +// FUNCTION: BETA10 0x10052b3d +void LegoLocomotionAnimPresenter::FUN_1006d680(LegoAnimActor* p_actor, MxFloat p_value) +{ + // This asserts that LegoLocomotionAnimPresenter is contained in legoanimpresenter.cpp + AUTOLOCK(m_criticalSection); + + MxVariableTable* variableTable = VariableTable(); + + const char* key = ((LegoAnimNodeData*) m_anim->GetRoot()->GetData())->GetName(); + variableTable->SetVariable(key, p_actor->GetROI()->GetName()); + + FUN_100695c0(); + FUN_10069b10(); + + if (m_roiMap != NULL) { + m_roiMapList->Append(m_roiMap); + p_actor->FUN_1001c450(m_anim, p_value, m_roiMap, m_roiMapSize); + m_roiMap = NULL; + } + + variableTable->SetVariable(key, ""); + + if (m_sceneROIs != NULL) { + delete m_sceneROIs; + m_sceneROIs = NULL; + } +} + +// We do not have any hard evidence that `LegoHideAnimPresenter` is part of this file as well. +// However, since all of the other AnimPresenters are in the same file, it is reasonable to assume +// that the same holds here. + +// FUNCTION: LEGO1 0x1006d7e0 +LegoHideAnimPresenter::LegoHideAnimPresenter() +{ + Init(); +} + +// FUNCTION: LEGO1 0x1006d9f0 +LegoHideAnimPresenter::~LegoHideAnimPresenter() +{ + Destroy(TRUE); +} + +// FUNCTION: LEGO1 0x1006da50 +void LegoHideAnimPresenter::Init() +{ + m_boundaryMap = NULL; +} + +// FUNCTION: LEGO1 0x1006da60 +void LegoHideAnimPresenter::Destroy(MxBool p_fromDestructor) +{ + ENTER(m_criticalSection); + + if (m_boundaryMap) { + delete[] m_boundaryMap; + } + Init(); + + m_criticalSection.Leave(); + + // This appears to be a bug, since it results in an endless loop + if (!p_fromDestructor) { + LegoHideAnimPresenter::Destroy(); + } +} + +// FUNCTION: LEGO1 0x1006dab0 +MxResult LegoHideAnimPresenter::AddToManager() +{ + return LegoAnimPresenter::AddToManager(); +} + +// FUNCTION: LEGO1 0x1006dac0 +void LegoHideAnimPresenter::Destroy() +{ + Destroy(FALSE); +} + +// FUNCTION: LEGO1 0x1006dad0 +void LegoHideAnimPresenter::PutFrame() +{ +} + +// FUNCTION: LEGO1 0x1006dae0 +// FUNCTION: BETA10 0x100530f4 +void LegoHideAnimPresenter::ReadyTickle() +{ + LegoLoopingAnimPresenter::ReadyTickle(); + + if (m_currentWorld) { + if (m_currentTickleState == e_starting && m_compositePresenter != NULL) { + SendToCompositePresenter(Lego()); + } + + m_currentWorld->Add(this); + } +} + +// FUNCTION: LEGO1 0x1006db20 +// FUNCTION: BETA10 0x1005316b +void LegoHideAnimPresenter::StartingTickle() +{ + LegoLoopingAnimPresenter::StartingTickle(); + + if (m_currentTickleState == e_streaming) { + FUN_1006dc10(); + FUN_1006db40(0); + } +} + +// FUNCTION: LEGO1 0x1006db40 +// FUNCTION: BETA10 0x100531ab +void LegoHideAnimPresenter::FUN_1006db40(LegoTime p_time) +{ + FUN_1006db60(m_anim->GetRoot(), p_time); +} + +// FUNCTION: LEGO1 0x1006db60 +// FUNCTION: BETA10 0x100531de +void LegoHideAnimPresenter::FUN_1006db60(LegoTreeNode* p_node, LegoTime p_time) +{ + LegoAnimNodeData* data = (LegoAnimNodeData*) p_node->GetData(); + MxBool newB = FALSE; + MxBool previousB = FALSE; + + if (m_roiMap != NULL) { + LegoROI* roi = m_roiMap[data->GetROIIndex()]; + + if (roi != NULL) { + newB = data->GetVisibility(p_time); + previousB = roi->GetVisibility(); + roi->SetVisibility(newB); + } + } + + if (m_boundaryMap != NULL) { + LegoPathBoundary* boundary = m_boundaryMap[data->GetBoundaryIndex()]; + + if (boundary != NULL) { + newB = data->GetVisibility(p_time); + previousB = boundary->GetFlag0x10(); + boundary->SetFlag0x10(newB); + } + } + + for (MxS32 i = 0; i < p_node->GetNumChildren(); i++) { + FUN_1006db60(p_node->GetChild(i), p_time); + } +} + +// FUNCTION: LEGO1 0x1006dc10 +// FUNCTION: BETA10 0x100532fd +void LegoHideAnimPresenter::FUN_1006dc10() +{ + LegoHideAnimStructMap anims; + + FUN_1006e3f0(anims, m_anim->GetRoot()); + + if (m_boundaryMap != NULL) { + delete[] m_boundaryMap; + } + + m_boundaryMap = new LegoPathBoundary*[anims.size() + 1]; + m_boundaryMap[0] = NULL; + + for (LegoHideAnimStructMap::iterator it = anims.begin(); !(it == anims.end()); it++) { + m_boundaryMap[(*it).second.m_index] = (*it).second.m_boundary; + delete[] const_cast((*it).first); + } +} + +// FUNCTION: LEGO1 0x1006e3f0 +// FUNCTION: BETA10 0x1005345e +void LegoHideAnimPresenter::FUN_1006e3f0(LegoHideAnimStructMap& p_map, LegoTreeNode* p_node) +{ + LegoAnimNodeData* data = (LegoAnimNodeData*) p_node->GetData(); + const char* name = data->GetName(); + + if (name != NULL) { + LegoPathBoundary* boundary = m_currentWorld->FindPathBoundary(name); + + if (boundary != NULL) { + FUN_1006e470(p_map, data, name, boundary); + } + else { + data->SetBoundaryIndex(0); + } + } + + MxS32 count = p_node->GetNumChildren(); + for (MxS32 i = 0; i < count; i++) { + FUN_1006e3f0(p_map, p_node->GetChild(i)); + } +} + +// FUNCTION: LEGO1 0x1006e470 +// FUNCTION: BETA10 0x10053520 +void LegoHideAnimPresenter::FUN_1006e470( + LegoHideAnimStructMap& p_map, + LegoAnimNodeData* p_data, + const char* p_name, + LegoPathBoundary* p_boundary +) +{ + LegoHideAnimStructMap::iterator it; + + it = p_map.find(p_name); + if (it == p_map.end()) { + LegoHideAnimStruct animStruct; + animStruct.m_index = p_map.size() + 1; + animStruct.m_boundary = p_boundary; + + p_data->SetBoundaryIndex(animStruct.m_index); + + char* name = new char[strlen(p_name) + 1]; + strcpy(name, p_name); + + p_map[name] = animStruct; + } + else { + p_data->SetBoundaryIndex((*it).second.m_index); + } +} + +// FUNCTION: LEGO1 0x1006e9e0 +// FUNCTION: BETA10 0x100535ef +void LegoHideAnimPresenter::EndAction() +{ + if (m_action) { + MxVideoPresenter::EndAction(); + + if (m_currentWorld) { + m_currentWorld->Remove(this); + } + } +} diff --git a/LEGO1/lego/legoomni/src/video/legohideanimpresenter.cpp b/LEGO1/lego/legoomni/src/video/legohideanimpresenter.cpp deleted file mode 100644 index 03cc147e..00000000 --- a/LEGO1/lego/legoomni/src/video/legohideanimpresenter.cpp +++ /dev/null @@ -1,216 +0,0 @@ -#include "legohideanimpresenter.h" - -#include "anim/legoanim.h" -#include "legomain.h" -#include "legoworld.h" -#include "misc.h" - -DECOMP_SIZE_ASSERT(LegoHideAnimPresenter, 0xc4) -DECOMP_SIZE_ASSERT(LegoHideAnimStruct, 0x08) - -// FUNCTION: LEGO1 0x1006d7e0 -LegoHideAnimPresenter::LegoHideAnimPresenter() -{ - Init(); -} - -// FUNCTION: LEGO1 0x1006d9f0 -LegoHideAnimPresenter::~LegoHideAnimPresenter() -{ - Destroy(TRUE); -} - -// FUNCTION: LEGO1 0x1006da50 -void LegoHideAnimPresenter::Init() -{ - m_boundaryMap = NULL; -} - -// FUNCTION: LEGO1 0x1006da60 -void LegoHideAnimPresenter::Destroy(MxBool p_fromDestructor) -{ - ENTER(m_criticalSection); - - if (m_boundaryMap) { - delete[] m_boundaryMap; - } - Init(); - - m_criticalSection.Leave(); - - // This appears to be a bug, since it results in an endless loop - if (!p_fromDestructor) { - LegoHideAnimPresenter::Destroy(); - } -} - -// FUNCTION: LEGO1 0x1006dab0 -MxResult LegoHideAnimPresenter::AddToManager() -{ - return LegoAnimPresenter::AddToManager(); -} - -// FUNCTION: LEGO1 0x1006dac0 -void LegoHideAnimPresenter::Destroy() -{ - Destroy(FALSE); -} - -// FUNCTION: LEGO1 0x1006dad0 -void LegoHideAnimPresenter::PutFrame() -{ -} - -// FUNCTION: LEGO1 0x1006dae0 -// FUNCTION: BETA10 0x100530f4 -void LegoHideAnimPresenter::ReadyTickle() -{ - LegoLoopingAnimPresenter::ReadyTickle(); - - if (m_currentWorld) { - if (m_currentTickleState == e_starting && m_compositePresenter != NULL) { - SendToCompositePresenter(Lego()); - } - - m_currentWorld->Add(this); - } -} - -// FUNCTION: LEGO1 0x1006db20 -// FUNCTION: BETA10 0x1005316b -void LegoHideAnimPresenter::StartingTickle() -{ - LegoLoopingAnimPresenter::StartingTickle(); - - if (m_currentTickleState == e_streaming) { - FUN_1006dc10(); - FUN_1006db40(0); - } -} - -// FUNCTION: LEGO1 0x1006db40 -// FUNCTION: BETA10 0x100531ab -void LegoHideAnimPresenter::FUN_1006db40(LegoTime p_time) -{ - FUN_1006db60(m_anim->GetRoot(), p_time); -} - -// FUNCTION: LEGO1 0x1006db60 -// FUNCTION: BETA10 0x100531de -void LegoHideAnimPresenter::FUN_1006db60(LegoTreeNode* p_node, LegoTime p_time) -{ - LegoAnimNodeData* data = (LegoAnimNodeData*) p_node->GetData(); - MxBool newB = FALSE; - MxBool previousB = FALSE; - - if (m_roiMap != NULL) { - LegoROI* roi = m_roiMap[data->GetROIIndex()]; - - if (roi != NULL) { - newB = data->GetVisibility(p_time); - previousB = roi->GetVisibility(); - roi->SetVisibility(newB); - } - } - - if (m_boundaryMap != NULL) { - LegoPathBoundary* boundary = m_boundaryMap[data->GetBoundaryIndex()]; - - if (boundary != NULL) { - newB = data->GetVisibility(p_time); - previousB = boundary->GetFlag0x10(); - boundary->SetFlag0x10(newB); - } - } - - for (MxS32 i = 0; i < p_node->GetNumChildren(); i++) { - FUN_1006db60(p_node->GetChild(i), p_time); - } -} - -// FUNCTION: LEGO1 0x1006dc10 -// FUNCTION: BETA10 0x100532fd -void LegoHideAnimPresenter::FUN_1006dc10() -{ - LegoHideAnimStructMap anims; - - FUN_1006e3f0(anims, m_anim->GetRoot()); - - if (m_boundaryMap != NULL) { - delete[] m_boundaryMap; - } - - m_boundaryMap = new LegoPathBoundary*[anims.size() + 1]; - m_boundaryMap[0] = NULL; - - for (LegoHideAnimStructMap::iterator it = anims.begin(); !(it == anims.end()); it++) { - m_boundaryMap[(*it).second.m_index] = (*it).second.m_boundary; - delete[] const_cast((*it).first); - } -} - -// FUNCTION: LEGO1 0x1006e3f0 -// FUNCTION: BETA10 0x1005345e -void LegoHideAnimPresenter::FUN_1006e3f0(LegoHideAnimStructMap& p_map, LegoTreeNode* p_node) -{ - LegoAnimNodeData* data = (LegoAnimNodeData*) p_node->GetData(); - const char* name = data->GetName(); - - if (name != NULL) { - LegoPathBoundary* boundary = m_currentWorld->FindPathBoundary(name); - - if (boundary != NULL) { - FUN_1006e470(p_map, data, name, boundary); - } - else { - data->SetBoundaryIndex(0); - } - } - - MxS32 count = p_node->GetNumChildren(); - for (MxS32 i = 0; i < count; i++) { - FUN_1006e3f0(p_map, p_node->GetChild(i)); - } -} - -// FUNCTION: LEGO1 0x1006e470 -// FUNCTION: BETA10 0x10053520 -void LegoHideAnimPresenter::FUN_1006e470( - LegoHideAnimStructMap& p_map, - LegoAnimNodeData* p_data, - const char* p_name, - LegoPathBoundary* p_boundary -) -{ - LegoHideAnimStructMap::iterator it; - - it = p_map.find(p_name); - if (it == p_map.end()) { - LegoHideAnimStruct animStruct; - animStruct.m_index = p_map.size() + 1; - animStruct.m_boundary = p_boundary; - - p_data->SetBoundaryIndex(animStruct.m_index); - - char* name = new char[strlen(p_name) + 1]; - strcpy(name, p_name); - - p_map[name] = animStruct; - } - else { - p_data->SetBoundaryIndex((*it).second.m_index); - } -} - -// FUNCTION: LEGO1 0x1006e9e0 -// FUNCTION: BETA10 0x100535ef -void LegoHideAnimPresenter::EndAction() -{ - if (m_action) { - MxVideoPresenter::EndAction(); - - if (m_currentWorld) { - m_currentWorld->Remove(this); - } - } -} diff --git a/LEGO1/lego/legoomni/src/video/legolocomotionanimpresenter.cpp b/LEGO1/lego/legoomni/src/video/legolocomotionanimpresenter.cpp deleted file mode 100644 index 32e37669..00000000 --- a/LEGO1/lego/legoomni/src/video/legolocomotionanimpresenter.cpp +++ /dev/null @@ -1,164 +0,0 @@ -#include "legolocomotionanimpresenter.h" - -#include "anim/legoanim.h" -#include "legoanimactor.h" -#include "legomain.h" -#include "legoworld.h" -#include "misc.h" -#include "mxautolock.h" -#include "mxdssubscriber.h" -#include "mxmisc.h" -#include "mxvariabletable.h" - -DECOMP_SIZE_ASSERT(LegoLocomotionAnimPresenter, 0xd8) - -// FUNCTION: LEGO1 0x1006cdd0 -LegoLocomotionAnimPresenter::LegoLocomotionAnimPresenter() -{ - Init(); -} - -// FUNCTION: LEGO1 0x1006d050 -LegoLocomotionAnimPresenter::~LegoLocomotionAnimPresenter() -{ - Destroy(TRUE); -} - -// FUNCTION: LEGO1 0x1006d0b0 -void LegoLocomotionAnimPresenter::Init() -{ - m_unk0xc0 = 0; - m_unk0xc4 = NULL; - m_unk0xcc = -1; - m_unk0xd0 = -1; - m_roiMapList = NULL; - m_unk0xd4 = 0; -} - -// FUNCTION: LEGO1 0x1006d0e0 -void LegoLocomotionAnimPresenter::Destroy(MxBool p_fromDestructor) -{ - ENTER(m_criticalSection); - - if (m_unk0xc4) { - delete[] m_unk0xc4; - } - - if (m_roiMapList) { - delete m_roiMapList; - } - - m_roiMap = NULL; - Init(); - - m_criticalSection.Leave(); - - if (!p_fromDestructor) { - LegoLoopingAnimPresenter::Destroy(); - } -} - -// FUNCTION: LEGO1 0x1006d140 -MxResult LegoLocomotionAnimPresenter::CreateAnim(MxStreamChunk* p_chunk) -{ - MxResult result = LegoAnimPresenter::CreateAnim(p_chunk); - return result == SUCCESS ? SUCCESS : result; -} - -// FUNCTION: LEGO1 0x1006d160 -// FUNCTION: BETA10 0x100528c7 -MxResult LegoLocomotionAnimPresenter::AddToManager() -{ - m_roiMapList = new LegoROIMapList(); - - if (m_roiMapList == NULL) { - return FAILURE; - } - - return LegoAnimPresenter::AddToManager(); -} - -// FUNCTION: LEGO1 0x1006d5b0 -void LegoLocomotionAnimPresenter::Destroy() -{ - Destroy(FALSE); -} - -// FUNCTION: LEGO1 0x1006d5c0 -void LegoLocomotionAnimPresenter::PutFrame() -{ - // Empty -} - -// FUNCTION: LEGO1 0x1006d5d0 -void LegoLocomotionAnimPresenter::ReadyTickle() -{ - LegoLoopingAnimPresenter::ReadyTickle(); - - if (m_currentWorld != NULL && m_currentTickleState == e_starting) { - m_currentWorld->Add(this); - if (m_compositePresenter != NULL) { - SendToCompositePresenter(Lego()); - } - - m_unk0xd4++; - } -} - -// FUNCTION: LEGO1 0x1006d610 -// FUNCTION: BETA10 0x10052a34 -void LegoLocomotionAnimPresenter::StartingTickle() -{ - if (m_subscriber->PeekData()) { - MxStreamChunk* chunk = m_subscriber->PopData(); - m_subscriber->FreeDataChunk(chunk); - } - - if (m_roiMapList->GetNumElements() != 0) { - ProgressTickleState(e_streaming); - } -} - -// FUNCTION: LEGO1 0x1006d660 -void LegoLocomotionAnimPresenter::StreamingTickle() -{ - if (m_unk0xd4 == 0) { - EndAction(); - } -} - -// FUNCTION: LEGO1 0x1006d670 -void LegoLocomotionAnimPresenter::EndAction() -{ - if (m_action) { - MxVideoPresenter::EndAction(); - } -} - -// FUNCTION: LEGO1 0x1006d680 -// FUNCTION: BETA10 0x10052b3d -void LegoLocomotionAnimPresenter::FUN_1006d680(LegoAnimActor* p_actor, MxFloat p_value) -{ - AUTOLOCK(m_criticalSection); - - MxVariableTable* variableTable = VariableTable(); - - const char* key = ((LegoAnimNodeData*) m_anim->GetRoot()->GetData())->GetName(); - variableTable->SetVariable(key, p_actor->GetROI()->GetName()); - - FUN_100695c0(); - FUN_10069b10(); - - if (m_roiMap != NULL) { - m_roiMapList->Append(m_roiMap); - p_actor->FUN_1001c450(m_anim, p_value, m_roiMap, m_roiMapSize); - m_roiMap = NULL; - } - - variableTable->SetVariable(key, ""); - - if (m_sceneROIs != NULL) { - delete m_sceneROIs; - m_sceneROIs = NULL; - } -} diff --git a/LEGO1/lego/legoomni/src/video/legoloopinganimpresenter.cpp b/LEGO1/lego/legoomni/src/video/legoloopinganimpresenter.cpp deleted file mode 100644 index cf75ce47..00000000 --- a/LEGO1/lego/legoomni/src/video/legoloopinganimpresenter.cpp +++ /dev/null @@ -1,83 +0,0 @@ -#include "legoloopinganimpresenter.h" - -#include "anim/legoanim.h" -#include "legocameracontroller.h" -#include "legoworld.h" -#include "mxcompositepresenter.h" -#include "mxdsaction.h" -#include "mxdssubscriber.h" - -DECOMP_SIZE_ASSERT(LegoLoopingAnimPresenter, 0xc0) - -// FUNCTION: LEGO1 0x1006caa0 -// FUNCTION: BETA10 0x1005223d -void LegoLoopingAnimPresenter::StreamingTickle() -{ - if (m_subscriber->PeekData()) { - MxStreamChunk* chunk = m_subscriber->PopData(); - m_subscriber->FreeDataChunk(chunk); - } - - if (m_unk0x95) { - ProgressTickleState(e_done); - if (m_compositePresenter) { - if (m_compositePresenter->IsA("LegoAnimMMPresenter")) { - m_compositePresenter->VTable0x60(this); - } - } - } - else { - if (m_action->GetDuration() != -1) { - if (m_action->GetElapsedTime() > m_action->GetDuration() + m_action->GetStartTime()) { - m_unk0x95 = TRUE; - } - } - } -} - -// FUNCTION: LEGO1 0x1006cb40 -// FUNCTION: BETA10 0x1005239a -void LegoLoopingAnimPresenter::PutFrame() -{ - MxLong time; - - if (m_action->GetStartTime() <= m_action->GetElapsedTime()) { - time = (m_action->GetElapsedTime() - m_action->GetStartTime()) % m_anim->GetDuration(); - } - else { - time = 0; - } - - FUN_1006b9a0(m_anim, time, m_unk0x78); - - if (m_unk0x8c != NULL && m_currentWorld != NULL && m_currentWorld->GetCameraController() != NULL) { - for (MxS32 i = 0; i < m_unk0x94; i++) { - if (m_unk0x8c[i] != NULL) { - MxMatrix mat(m_unk0x8c[i]->GetLocal2World()); - - Vector3 pos(mat[0]); - Vector3 dir(mat[1]); - Vector3 up(mat[2]); - Vector3 und(mat[3]); - - float possqr = sqrt(pos.LenSquared()); - float dirsqr = sqrt(dir.LenSquared()); - float upsqr = sqrt(up.LenSquared()); - - up = und; - - up -= m_currentWorld->GetCameraController()->GetWorldLocation(); - dir /= dirsqr; - pos.EqualsCross(dir, up); - pos.Unitize(); - up.EqualsCross(pos, dir); - pos *= possqr; - dir *= dirsqr; - up *= upsqr; - - m_unk0x8c[i]->SetLocal2World(mat); - m_unk0x8c[i]->WrappedUpdateWorldData(); - } - } - } -} diff --git a/LEGO1/lego/legoomni/src/worlds/legoact2.cpp b/LEGO1/lego/legoomni/src/worlds/legoact2.cpp index a3c4f7c0..9d088e3b 100644 --- a/LEGO1/lego/legoomni/src/worlds/legoact2.cpp +++ b/LEGO1/lego/legoomni/src/worlds/legoact2.cpp @@ -7,10 +7,10 @@ #include "islepathactor.h" #include "jukebox_actions.h" #include "legoanimationmanager.h" +#include "legoanimpresenter.h" #include "legocachesoundmanager.h" #include "legogamestate.h" #include "legoinputmanager.h" -#include "legolocomotionanimpresenter.h" #include "legomain.h" #include "legopathstruct.h" #include "legosoundmanager.h" diff --git a/LEGO1/omni/src/common/mxcompositepresenter.cpp b/LEGO1/omni/src/common/mxcompositepresenter.cpp index 4e83d86c..b3d56875 100644 --- a/LEGO1/omni/src/common/mxcompositepresenter.cpp +++ b/LEGO1/omni/src/common/mxcompositepresenter.cpp @@ -25,7 +25,7 @@ MxCompositePresenter::~MxCompositePresenter() } // FUNCTION: LEGO1 0x100b6410 -// FUNCTION: BETA10 0x100e9d37 +// FUNCTION: BETA10 0x10137344 MxResult MxCompositePresenter::StartAction(MxStreamController* p_controller, MxDSAction* p_action) { AUTOLOCK(m_criticalSection); From e86fd71560d4e25b282bdb6260d4f23ccbb83f4c Mon Sep 17 00:00:00 2001 From: Christian Semmler Date: Sun, 20 Jul 2025 09:33:53 -0700 Subject: [PATCH 172/188] Handle `SDL_EVENT_FINGER_CANCELED` (#633) --- ISLE/emscripten/window.cpp | 3 ++- ISLE/isleapp.cpp | 4 +++- LEGO1/lego/legoomni/src/input/legoinputmanager.cpp | 3 +++ miniwin/src/d3drm/d3drmdevice.cpp | 3 ++- 4 files changed, 10 insertions(+), 3 deletions(-) diff --git a/ISLE/emscripten/window.cpp b/ISLE/emscripten/window.cpp index 6766490f..fd8b144c 100644 --- a/ISLE/emscripten/window.cpp +++ b/ISLE/emscripten/window.cpp @@ -84,7 +84,8 @@ void Emscripten_ConvertEventToRenderCoordinates(SDL_Event* event) } case SDL_EVENT_FINGER_MOTION: case SDL_EVENT_FINGER_DOWN: - case SDL_EVENT_FINGER_UP: { + case SDL_EVENT_FINGER_UP: + case SDL_EVENT_FINGER_CANCELED: { const float scale = std::min(g_fullWidth / g_targetWidth, g_fullHeight / g_targetHeight); const float widthRatio = (g_targetWidth * scale) / g_fullWidth; const float heightRatio = (g_targetHeight * scale) / g_fullHeight; diff --git a/ISLE/isleapp.cpp b/ISLE/isleapp.cpp index 16508553..ee440e8f 100644 --- a/ISLE/isleapp.cpp +++ b/ISLE/isleapp.cpp @@ -460,6 +460,7 @@ SDL_AppResult SDL_AppEvent(void* appstate, SDL_Event* event) case SDL_EVENT_FINGER_MOTION: case SDL_EVENT_FINGER_DOWN: case SDL_EVENT_FINGER_UP: + case SDL_EVENT_FINGER_CANCELED: IDirect3DRMMiniwinDevice* device = GetD3DRMMiniwinDevice(); if (device && !device->ConvertEventToRenderCoordinates(event)) { SDL_Log("Failed to convert event coordinates: %s", SDL_GetError()); @@ -754,7 +755,8 @@ SDL_AppResult SDL_AppEvent(void* appstate, SDL_Event* event) ); } break; - case SDL_EVENT_FINGER_UP: { + case SDL_EVENT_FINGER_UP: + case SDL_EVENT_FINGER_CANCELED: { g_mousedown = FALSE; float x = SDL_clamp(event->tfinger.x, 0, 1) * g_targetWidth; diff --git a/LEGO1/lego/legoomni/src/input/legoinputmanager.cpp b/LEGO1/lego/legoomni/src/input/legoinputmanager.cpp index f7fef27e..61dfa9bc 100644 --- a/LEGO1/lego/legoomni/src/input/legoinputmanager.cpp +++ b/LEGO1/lego/legoomni/src/input/legoinputmanager.cpp @@ -612,6 +612,7 @@ MxBool LegoInputManager::HandleTouchEvent(SDL_Event* p_event, TouchScheme p_touc case e_arrowKeys: switch (p_event->type) { case SDL_EVENT_FINGER_UP: + case SDL_EVENT_FINGER_CANCELED: m_touchFlags.erase(event.fingerID); break; case SDL_EVENT_FINGER_DOWN: @@ -647,6 +648,7 @@ MxBool LegoInputManager::HandleTouchEvent(SDL_Event* p_event, TouchScheme p_touc } break; case SDL_EVENT_FINGER_UP: + case SDL_EVENT_FINGER_CANCELED: if (event.fingerID == g_finger) { g_finger = 0; m_touchVirtualThumb = {0, 0}; @@ -795,6 +797,7 @@ void LegoInputManager::UpdateLastInputMethod(SDL_Event* p_event) case SDL_EVENT_FINGER_MOTION: case SDL_EVENT_FINGER_DOWN: case SDL_EVENT_FINGER_UP: + case SDL_EVENT_FINGER_CANCELED: m_lastInputMethod = SDL_TouchID_v{p_event->tfinger.touchID}; break; } diff --git a/miniwin/src/d3drm/d3drmdevice.cpp b/miniwin/src/d3drm/d3drmdevice.cpp index e3190692..2b062d50 100644 --- a/miniwin/src/d3drm/d3drmdevice.cpp +++ b/miniwin/src/d3drm/d3drmdevice.cpp @@ -185,7 +185,8 @@ bool Direct3DRMDevice2Impl::ConvertEventToRenderCoordinates(SDL_Event* event) } case SDL_EVENT_FINGER_MOTION: case SDL_EVENT_FINGER_DOWN: - case SDL_EVENT_FINGER_UP: { + case SDL_EVENT_FINGER_UP: + case SDL_EVENT_FINGER_CANCELED: { float x = (event->tfinger.x * m_windowWidth - m_viewportTransform.offsetX) / m_viewportTransform.scale; float y = (event->tfinger.y * m_windowHeight - m_viewportTransform.offsetY) / m_viewportTransform.scale; event->tfinger.x = x / m_virtualWidth; From 4edd8d1214d323b64ee18d4d19383037d033d7e3 Mon Sep 17 00:00:00 2001 From: MS Date: Sun, 20 Jul 2025 21:58:16 -0400 Subject: [PATCH 173/188] Beta match MxThread and MxSemaphore (#1644) --- LEGO1/omni/include/mxsemaphore.h | 5 +- LEGO1/omni/include/mxthread.h | 8 +++ .../omni/src/stream/mxdiskstreamprovider.cpp | 3 +- LEGO1/omni/src/system/mxsemaphore.cpp | 20 ++++++- LEGO1/omni/src/system/mxthread.cpp | 59 ++++++++++++++++--- 5 files changed, 82 insertions(+), 13 deletions(-) diff --git a/LEGO1/omni/include/mxsemaphore.h b/LEGO1/omni/include/mxsemaphore.h index a9c5f9d2..faee3b35 100644 --- a/LEGO1/omni/include/mxsemaphore.h +++ b/LEGO1/omni/include/mxsemaphore.h @@ -6,17 +6,20 @@ #include // VTABLE: LEGO1 0x100dccf0 +// VTABLE: BETA10 0x101c28ac // SIZE 0x08 class MxSemaphore { public: MxSemaphore(); // FUNCTION: LEGO1 0x100c87e0 + // FUNCTION: BETA10 0x101592a9 ~MxSemaphore() { CloseHandle(m_hSemaphore); } virtual MxResult Init(MxU32 p_initialCount, MxU32 p_maxCount); - void Wait(MxU32 p_timeoutMS); + void Acquire(MxU32 p_timeoutMS); + void TryAcquire(); void Release(MxU32 p_releaseCount); private: diff --git a/LEGO1/omni/include/mxthread.h b/LEGO1/omni/include/mxthread.h index 6d148edd..3c4f65ab 100644 --- a/LEGO1/omni/include/mxthread.h +++ b/LEGO1/omni/include/mxthread.h @@ -8,6 +8,7 @@ class MxCore; // VTABLE: LEGO1 0x100dc860 +// VTABLE: BETA10 0x101c23e8 // SIZE 0x1c class MxThread { public: @@ -19,9 +20,16 @@ class MxThread { void Terminate(); void Sleep(MxS32 p_milliseconds); + void ResumeThread(); + void SuspendThread(); + BOOL TerminateThread(MxU32 p_exitCode); + MxS32 GetThreadPriority(MxU16& p_priority); + BOOL SetThreadPriority(MxU16 p_priority); + MxBool IsRunning() { return m_running; } // SYNTHETIC: LEGO1 0x100bf580 + // SYNTHETIC: BETA10 0x10147880 // MxThread::`scalar deleting destructor' protected: diff --git a/LEGO1/omni/src/stream/mxdiskstreamprovider.cpp b/LEGO1/omni/src/stream/mxdiskstreamprovider.cpp index 8be19a46..3eee3477 100644 --- a/LEGO1/omni/src/stream/mxdiskstreamprovider.cpp +++ b/LEGO1/omni/src/stream/mxdiskstreamprovider.cpp @@ -161,10 +161,11 @@ void MxDiskStreamProvider::VTable0x20(MxDSAction* p_action) } // FUNCTION: LEGO1 0x100d1750 +// FUNCTION: BETA10 0x101632b8 MxResult MxDiskStreamProvider::WaitForWorkToComplete() { while (m_remainingWork) { - m_busySemaphore.Wait(INFINITE); + m_busySemaphore.Acquire(INFINITE); if (m_unk0x35) { PerformWork(); } diff --git a/LEGO1/omni/src/system/mxsemaphore.cpp b/LEGO1/omni/src/system/mxsemaphore.cpp index f67a4d70..59559b2e 100644 --- a/LEGO1/omni/src/system/mxsemaphore.cpp +++ b/LEGO1/omni/src/system/mxsemaphore.cpp @@ -6,30 +6,44 @@ DECOMP_SIZE_ASSERT(MxSemaphore, 0x08) // FUNCTION: LEGO1 0x100c87d0 +// FUNCTION: BETA10 0x10159260 MxSemaphore::MxSemaphore() { m_hSemaphore = NULL; } // FUNCTION: LEGO1 0x100c8800 +// FUNCTION: BETA10 0x101592d5 MxResult MxSemaphore::Init(MxU32 p_initialCount, MxU32 p_maxCount) { MxResult result = FAILURE; - if ((m_hSemaphore = CreateSemaphore(NULL, p_initialCount, p_maxCount, NULL))) { - result = SUCCESS; + m_hSemaphore = CreateSemaphore(NULL, p_initialCount, p_maxCount, NULL); + if (!m_hSemaphore) { + goto done; } + result = SUCCESS; + +done: return result; } // FUNCTION: LEGO1 0x100c8830 -void MxSemaphore::Wait(MxU32 p_timeoutMS) +// FUNCTION: BETA10 0x10159332 +void MxSemaphore::Acquire(MxU32 p_timeoutMS) { WaitForSingleObject(m_hSemaphore, p_timeoutMS); } +// FUNCTION: BETA10 0x10159385 +void MxSemaphore::TryAcquire() +{ + WaitForSingleObject(m_hSemaphore, 0); +} + // FUNCTION: LEGO1 0x100c8850 +// FUNCTION: BETA10 0x101593aa void MxSemaphore::Release(MxU32 p_releaseCount) { ReleaseSemaphore(m_hSemaphore, p_releaseCount, NULL); diff --git a/LEGO1/omni/src/system/mxthread.cpp b/LEGO1/omni/src/system/mxthread.cpp index f639f74d..aef11497 100644 --- a/LEGO1/omni/src/system/mxthread.cpp +++ b/LEGO1/omni/src/system/mxthread.cpp @@ -7,14 +7,16 @@ DECOMP_SIZE_ASSERT(MxThread, 0x1c) // FUNCTION: LEGO1 0x100bf510 +// FUNCTION: BETA10 0x10147540 MxThread::MxThread() { m_hThread = NULL; - m_running = TRUE; m_threadId = 0; + m_running = TRUE; } // FUNCTION: LEGO1 0x100bf5a0 +// FUNCTION: BETA10 0x101475d0 MxThread::~MxThread() { if (m_hThread) { @@ -25,41 +27,82 @@ MxThread::~MxThread() typedef unsigned(__stdcall* ThreadFunc)(void*); // FUNCTION: LEGO1 0x100bf610 +// FUNCTION: BETA10 0x10147655 MxResult MxThread::Start(MxS32 p_stackSize, MxS32 p_flag) { MxResult result = FAILURE; - if (m_semaphore.Init(0, 1) == SUCCESS) { - if ((m_hThread = - _beginthreadex(NULL, p_stackSize * 4, (ThreadFunc) &MxThread::ThreadProc, this, p_flag, &m_threadId) - )) { - result = SUCCESS; - } + if (m_semaphore.Init(0, 1) != SUCCESS) { + goto done; } + m_hThread = _beginthreadex(NULL, p_stackSize * 4, (ThreadFunc) &MxThread::ThreadProc, this, p_flag, &m_threadId); + if (!m_hThread) { + goto done; + } + + result = SUCCESS; + +done: return result; } // FUNCTION: LEGO1 0x100bf660 +// FUNCTION: BETA10 0x101476ee void MxThread::Sleep(MxS32 p_milliseconds) { ::Sleep(p_milliseconds); } +// FUNCTION: BETA10 0x10147710 +void MxThread::ResumeThread() +{ + ::ResumeThread((HANDLE) m_hThread); +} + +// FUNCTION: BETA10 0x10147733 +void MxThread::SuspendThread() +{ + ::SuspendThread((HANDLE) m_hThread); +} + +// FUNCTION: BETA10 0x10147756 +BOOL MxThread::TerminateThread(MxU32 p_exitCode) +{ + // TerminateThread returns nonzero for success, zero for failure + return ::TerminateThread((HANDLE) m_hThread, p_exitCode) == 0; +} + +// FUNCTION: BETA10 0x10147793 +MxS32 MxThread::GetThreadPriority(MxU16& p_priority) +{ + return (p_priority = ::GetThreadPriority((HANDLE) m_hThread)); +} + +// FUNCTION: BETA10 0x101477c8 +BOOL MxThread::SetThreadPriority(MxU16 p_priority) +{ + // SetThreadPriority returns nonzero for success, zero for failure + return ::SetThreadPriority((HANDLE) m_hThread, p_priority) == 0; +} + // FUNCTION: LEGO1 0x100bf670 +// FUNCTION: BETA10 0x1014780a void MxThread::Terminate() { m_running = FALSE; - m_semaphore.Wait(INFINITE); + m_semaphore.Acquire(INFINITE); } // FUNCTION: LEGO1 0x100bf680 +// FUNCTION: BETA10 0x1014783b unsigned MxThread::ThreadProc(void* p_thread) { return static_cast(p_thread)->Run(); } // FUNCTION: LEGO1 0x100bf690 +// FUNCTION: BETA10 0x10147855 MxResult MxThread::Run() { m_semaphore.Release(1); From b1dcc26d79c6ce8a24a0c7db6bc01ed072424115 Mon Sep 17 00:00:00 2001 From: MS Date: Sun, 20 Jul 2025 23:08:26 -0400 Subject: [PATCH 174/188] Beta functions for `MxVariable` (#1645) * Beta match MxVariable classes * CustomizeAnimFileVariable --- .../legoomni/include/legocharactermanager.h | 1 + LEGO1/lego/legoomni/include/legogamestate.h | 3 +++ LEGO1/lego/legoomni/include/legovariables.h | 19 ++++++++++++++ .../src/common/legocharactermanager.cpp | 2 ++ .../legoomni/src/common/legogamestate.cpp | 5 ++++ .../legoomni/src/common/legovariables.cpp | 26 +++++++++++++++---- LEGO1/omni/include/mxvariable.h | 5 ++++ 7 files changed, 56 insertions(+), 5 deletions(-) diff --git a/LEGO1/lego/legoomni/include/legocharactermanager.h b/LEGO1/lego/legoomni/include/legocharactermanager.h index c5a0c242..d024e2e0 100644 --- a/LEGO1/lego/legoomni/include/legocharactermanager.h +++ b/LEGO1/lego/legoomni/include/legocharactermanager.h @@ -46,6 +46,7 @@ struct LegoActorInfo; typedef map LegoCharacterMap; // VTABLE: LEGO1 0x100da878 +// VTABLE: BETA10 0x101bc028 // SIZE 0x24 class CustomizeAnimFileVariable : public MxVariable { public: diff --git a/LEGO1/lego/legoomni/include/legogamestate.h b/LEGO1/lego/legoomni/include/legogamestate.h index 99155ce0..4a797672 100644 --- a/LEGO1/lego/legoomni/include/legogamestate.h +++ b/LEGO1/lego/legoomni/include/legogamestate.h @@ -23,6 +23,7 @@ struct ColorStringStruct { }; // VTABLE: LEGO1 0x100d74a8 +// VTABLE: BETA10 0x101bc4f0 // SIZE 0x30 class LegoBackgroundColor : public MxVariable { public: @@ -43,9 +44,11 @@ class LegoBackgroundColor : public MxVariable { }; // VTABLE: LEGO1 0x100d74b8 +// VTABLE: BETA10 0x101bc500 // SIZE 0x24 class LegoFullScreenMovie : public MxVariable { public: + LegoFullScreenMovie(); LegoFullScreenMovie(const char* p_key, const char* p_value); void SetValue(const char* p_option) override; // vtable+0x04 diff --git a/LEGO1/lego/legoomni/include/legovariables.h b/LEGO1/lego/legoomni/include/legovariables.h index 9447a5f9..873ef34c 100644 --- a/LEGO1/lego/legoomni/include/legovariables.h +++ b/LEGO1/lego/legoomni/include/legovariables.h @@ -17,41 +17,60 @@ extern const char* g_varVISIBILITY; extern const char* g_varCAMERALOCATION; extern const char* g_varCURSOR; extern const char* g_varWHOAMI; +extern const char* g_varDEBUG; // VTABLE: LEGO1 0x100d86c8 +// VTABLE: BETA10 0x101bc980 // SIZE 0x24 class VisibilityVariable : public MxVariable { public: + // FUNCTION: BETA10 0x10093470 VisibilityVariable() { m_key = g_varVISIBILITY; } void SetValue(const char* p_value) override; // vtable+0x04 }; // VTABLE: LEGO1 0x100d86b8 +// VTABLE: BETA10 0x101bc990 // SIZE 0x24 class CameraLocationVariable : public MxVariable { public: + // FUNCTION: BETA10 0x10093510 CameraLocationVariable() { m_key = g_varCAMERALOCATION; } void SetValue(const char* p_value) override; // vtable+0x04 }; // VTABLE: LEGO1 0x100d86a8 +// VTABLE: BETA10 0x101bc9a0 // SIZE 0x24 class CursorVariable : public MxVariable { public: + // FUNCTION: BETA10 0x100935b0 CursorVariable() { m_key = g_varCURSOR; } void SetValue(const char* p_value) override; // vtable+0x04 }; // VTABLE: LEGO1 0x100d8698 +// VTABLE: BETA10 0x101bc9b0 // SIZE 0x24 class WhoAmIVariable : public MxVariable { public: + // FUNCTION: BETA10 0x10093650 WhoAmIVariable() { m_key = g_varWHOAMI; } void SetValue(const char* p_value) override; // vtable+0x04 }; +// VTABLE: BETA10 0x101bc9c0 +// SIZE 0x24 +class DebugVariable : public MxVariable { +public: + // FUNCTION: BETA10 0x100936f0 + DebugVariable() { m_key = g_varDEBUG; } + + void SetValue(const char* p_value) override; // vtable+0x04 +}; + #endif // LEGOVARIABLES_H diff --git a/LEGO1/lego/legoomni/src/common/legocharactermanager.cpp b/LEGO1/lego/legoomni/src/common/legocharactermanager.cpp index ff7cf23b..496bf844 100644 --- a/LEGO1/lego/legoomni/src/common/legocharactermanager.cpp +++ b/LEGO1/lego/legoomni/src/common/legocharactermanager.cpp @@ -1090,6 +1090,7 @@ LegoROI* LegoCharacterManager::FUN_10085a80(const char* p_name, const char* p_lo } // FUNCTION: LEGO1 0x10085aa0 +// FUNCTION: BETA10 0x1007703d CustomizeAnimFileVariable::CustomizeAnimFileVariable(const char* p_key) { m_key = p_key; @@ -1097,6 +1098,7 @@ CustomizeAnimFileVariable::CustomizeAnimFileVariable(const char* p_key) } // FUNCTION: LEGO1 0x10085b50 +// FUNCTION: BETA10 0x100770c8 void CustomizeAnimFileVariable::SetValue(const char* p_value) { // STRING: LEGO1 0x100fc4f4 diff --git a/LEGO1/lego/legoomni/src/common/legogamestate.cpp b/LEGO1/lego/legoomni/src/common/legogamestate.cpp index eae457ed..5096fbcf 100644 --- a/LEGO1/lego/legoomni/src/common/legogamestate.cpp +++ b/LEGO1/lego/legoomni/src/common/legogamestate.cpp @@ -1334,6 +1334,11 @@ void LegoBackgroundColor::SetLightColor() SetLightColor(convertedR, convertedG, convertedB); } +// FUNCTION: BETA10 0x10086a87 +LegoFullScreenMovie::LegoFullScreenMovie() +{ +} + // FUNCTION: LEGO1 0x1003c500 // FUNCTION: BETA10 0x10086af6 LegoFullScreenMovie::LegoFullScreenMovie(const char* p_key, const char* p_value) diff --git a/LEGO1/lego/legoomni/src/common/legovariables.cpp b/LEGO1/lego/legoomni/src/common/legovariables.cpp index 2c701074..f6d1618f 100644 --- a/LEGO1/lego/legoomni/src/common/legovariables.cpp +++ b/LEGO1/lego/legoomni/src/common/legovariables.cpp @@ -6,6 +6,7 @@ #include "legonavcontroller.h" #include "legovideomanager.h" #include "misc.h" +#include "mxdebug.h" #include "roi/legoroi.h" DECOMP_SIZE_ASSERT(VisibilityVariable, 0x24) @@ -101,6 +102,10 @@ const char* g_nick = "Nick"; // STRING: LEGO1 0x100f39e0 const char* g_laura = "Laura"; +// GLOBAL: BETA10 0x101f6ce4 +// STRING: BETA10 0x101f6d54 +const char* g_varDEBUG = "DEBUG"; + // FUNCTION: LEGO1 0x10037d00 // FUNCTION: BETA10 0x100d5620 void VisibilityVariable::SetValue(const char* p_value) @@ -130,6 +135,7 @@ void VisibilityVariable::SetValue(const char* p_value) } // FUNCTION: LEGO1 0x10037d80 +// FUNCTION: BETA10 0x100d56ee void CameraLocationVariable::SetValue(const char* p_value) { char buffer[256]; @@ -137,22 +143,25 @@ void CameraLocationVariable::SetValue(const char* p_value) strcpy(buffer, p_value); - char* location = strtok(buffer, ","); - NavController()->UpdateLocation(location); + char* token = strtok(buffer, ","); + assert(token); + NavController()->UpdateLocation(token); - location = strtok(NULL, ","); - if (location) { - MxFloat pov = (MxFloat) atof(location); + token = strtok(NULL, ","); + if (token) { + MxFloat pov = (MxFloat) atof(token); VideoManager()->Get3DManager()->SetFrustrum(pov, 0.1f, 250.0f); } } // FUNCTION: LEGO1 0x10037e30 +// FUNCTION: BETA10 0x100d57e2 void CursorVariable::SetValue(const char* p_value) { } // FUNCTION: LEGO1 0x10037e40 +// FUNCTION: BETA10 0x100d57fa void WhoAmIVariable::SetValue(const char* p_value) { MxVariable::SetValue(p_value); @@ -173,3 +182,10 @@ void WhoAmIVariable::SetValue(const char* p_value) GameState()->SetActorId(LegoActor::c_laura); } } + +// FUNCTION: BETA10 0x100d58fa +void DebugVariable::SetValue(const char* p_value) +{ + MxVariable::SetValue(p_value); + MxTrace("%s\n", p_value); +} diff --git a/LEGO1/omni/include/mxvariable.h b/LEGO1/omni/include/mxvariable.h index 8949833f..9eda281f 100644 --- a/LEGO1/omni/include/mxvariable.h +++ b/LEGO1/omni/include/mxvariable.h @@ -9,6 +9,7 @@ // SIZE 0x24 class MxVariable { public: + // FUNCTION: BETA10 0x1007b750 MxVariable() {} // FUNCTION: BETA10 0x1012a840 @@ -41,12 +42,16 @@ class MxVariable { // FUNCTION: BETA10 0x1012a7f0 const MxString* GetKey() const { return &m_key; } + // SYNTHETIC: BETA10 0x1007b8c0 + // MxVariable::`scalar deleting destructor' + protected: MxString m_key; // 0x04 MxString m_value; // 0x14 }; // SYNTHETIC: LEGO1 0x1003bf40 +// SYNTHETIC: BETA10 0x1007b910 // MxVariable::~MxVariable #endif // MXVARIABLE_H From fd299137ff1cd573a5315e1099da092645d295e8 Mon Sep 17 00:00:00 2001 From: Fabian Neundorf Date: Mon, 21 Jul 2025 22:18:59 +0200 Subject: [PATCH 175/188] Clear unknowns `LegoCameraController` (#1647) --- .../legoomni/include/legocameracontroller.h | 12 +++++----- LEGO1/lego/legoomni/src/actors/helicopter.cpp | 4 ++-- .../src/entity/legocameracontroller.cpp | 24 +++++++++---------- LEGO1/lego/legoomni/src/entity/legoentity.cpp | 2 +- .../legoomni/src/entity/legonavcontroller.cpp | 2 +- .../lego/legoomni/src/paths/legopathactor.cpp | 2 +- LEGO1/lego/legoomni/src/race/legoracers.cpp | 2 +- .../legoomni/src/video/legoanimpresenter.cpp | 2 +- 8 files changed, 25 insertions(+), 25 deletions(-) diff --git a/LEGO1/lego/legoomni/include/legocameracontroller.h b/LEGO1/lego/legoomni/include/legocameracontroller.h index 4dcb6c39..cf4e8f0a 100644 --- a/LEGO1/lego/legoomni/include/legocameracontroller.h +++ b/LEGO1/lego/legoomni/include/legocameracontroller.h @@ -38,17 +38,17 @@ class LegoCameraController : public LegoPointOfViewController { virtual MxResult Create(); // vtable+0x44 void SetWorldTransform(const Vector3& p_at, const Vector3& p_dir, const Vector3& p_up); - void FUN_10012290(float p_angle); - void FUN_10012320(float p_angle); - MxResult FUN_100123b0(Matrix4& p_matrix); - void FUN_100123e0(const Matrix4& p_transform, MxU32 p_und); + void RotateZ(float p_angle); + void RotateY(float p_angle); + MxResult GetPointOfView(Matrix4& p_matrix); + void TransformPointOfView(const Matrix4& p_transform, MxU32 p_multiply); Mx3DPointFloat GetWorldUp(); Mx3DPointFloat GetWorldLocation(); Mx3DPointFloat GetWorldDirection(); private: - MxMatrix m_matrix1; // 0x38 - MxMatrix m_matrix2; // 0x80 + MxMatrix m_currentTransform; // 0x38 + MxMatrix m_originalTransform; // 0x80 }; // SYNTHETIC: LEGO1 0x10011f50 diff --git a/LEGO1/lego/legoomni/src/actors/helicopter.cpp b/LEGO1/lego/legoomni/src/actors/helicopter.cpp index 358657b1..fc2addb3 100644 --- a/LEGO1/lego/legoomni/src/actors/helicopter.cpp +++ b/LEGO1/lego/legoomni/src/actors/helicopter.cpp @@ -426,7 +426,7 @@ void Helicopter::Animate(float p_time) v2 *= f2; v2 += v1; - m_world->GetCameraController()->FUN_100123e0(mat, 0); + m_world->GetCameraController()->TransformPointOfView(mat, 0); } else { if (m_state->m_unk0x08 == 4) { @@ -459,7 +459,7 @@ void Helicopter::FUN_100042a0(const Matrix4& p_matrix) // the typecast makes this function match for unknown reasons Vector3 vec6((const float*) m_unk0x1a8[3]); // locala0 // esp+0x28 - m_world->GetCameraController()->FUN_100123b0(local48); + m_world->GetCameraController()->GetPointOfView(local48); m_unk0x1a8.SetIdentity(); local90 = p_matrix; diff --git a/LEGO1/lego/legoomni/src/entity/legocameracontroller.cpp b/LEGO1/lego/legoomni/src/entity/legocameracontroller.cpp index 2f192058..c15b4aac 100644 --- a/LEGO1/lego/legoomni/src/entity/legocameracontroller.cpp +++ b/LEGO1/lego/legoomni/src/entity/legocameracontroller.cpp @@ -120,28 +120,28 @@ void LegoCameraController::OnMouseMove(MxU8 p_modifier, MxPoint32 p_point) // FUNCTION: LEGO1 0x10012260 void LegoCameraController::SetWorldTransform(const Vector3& p_at, const Vector3& p_dir, const Vector3& p_up) { - CalcLocalTransform(p_at, p_dir, p_up, m_matrix1); - m_matrix2 = m_matrix1; + CalcLocalTransform(p_at, p_dir, p_up, m_currentTransform); + m_originalTransform = m_currentTransform; } // FUNCTION: LEGO1 0x10012290 // FUNCTION: BETA10 0x10068c34 -void LegoCameraController::FUN_10012290(float p_angle) +void LegoCameraController::RotateZ(float p_angle) { - m_matrix1 = m_matrix2; - m_matrix1.RotateZ(p_angle); + m_currentTransform = m_originalTransform; + m_currentTransform.RotateZ(p_angle); } // FUNCTION: LEGO1 0x10012320 // FUNCTION: BETA10 0x10068c73 -void LegoCameraController::FUN_10012320(float p_angle) +void LegoCameraController::RotateY(float p_angle) { - m_matrix1 = m_matrix2; - m_matrix1.RotateY(p_angle); + m_currentTransform = m_originalTransform; + m_currentTransform.RotateY(p_angle); } // FUNCTION: LEGO1 0x100123b0 -MxResult LegoCameraController::FUN_100123b0(Matrix4& p_matrix) +MxResult LegoCameraController::GetPointOfView(Matrix4& p_matrix) { if (m_lego3DView) { ViewROI* pov = m_lego3DView->GetPointOfView(); @@ -156,7 +156,7 @@ MxResult LegoCameraController::FUN_100123b0(Matrix4& p_matrix) // FUNCTION: LEGO1 0x100123e0 // FUNCTION: BETA10 0x10068cb2 -void LegoCameraController::FUN_100123e0(const Matrix4& p_transform, MxU32 p_und) +void LegoCameraController::TransformPointOfView(const Matrix4& p_transform, MxU32 p_multiply) { if (m_lego3DView != NULL) { ViewROI* pov = m_lego3DView->GetPointOfView(); @@ -164,8 +164,8 @@ void LegoCameraController::FUN_100123e0(const Matrix4& p_transform, MxU32 p_und) if (pov != NULL) { MxMatrix mat; - if (p_und) { - MXM4(mat, m_matrix1, p_transform); + if (p_multiply) { + MXM4(mat, m_currentTransform, p_transform); } else { mat = p_transform; diff --git a/LEGO1/lego/legoomni/src/entity/legoentity.cpp b/LEGO1/lego/legoomni/src/entity/legoentity.cpp index 9ac9aad6..a87c9ebb 100644 --- a/LEGO1/lego/legoomni/src/entity/legoentity.cpp +++ b/LEGO1/lego/legoomni/src/entity/legoentity.cpp @@ -194,7 +194,7 @@ void LegoEntity::FUN_10010c30() LegoWorld* world = CurrentWorld(); if (m_cameraFlag && world && world->GetCameraController() && m_roi) { - world->GetCameraController()->FUN_100123e0(m_roi->GetLocal2World(), 1); + world->GetCameraController()->TransformPointOfView(m_roi->GetLocal2World(), 1); } } diff --git a/LEGO1/lego/legoomni/src/entity/legonavcontroller.cpp b/LEGO1/lego/legoomni/src/entity/legonavcontroller.cpp index 78d2e6d1..8410c63f 100644 --- a/LEGO1/lego/legoomni/src/entity/legonavcontroller.cpp +++ b/LEGO1/lego/legoomni/src/entity/legonavcontroller.cpp @@ -549,7 +549,7 @@ MxResult LegoNavController::ProcessJoystickInput(MxBool& p_und) LegoWorld* world = CurrentWorld(); if (world && world->GetCameraController()) { - world->GetCameraController()->FUN_10012320(DTOR(povPosition)); + world->GetCameraController()->RotateY(DTOR(povPosition)); p_und = TRUE; } } diff --git a/LEGO1/lego/legoomni/src/paths/legopathactor.cpp b/LEGO1/lego/legoomni/src/paths/legopathactor.cpp index 0a8f6878..728d5649 100644 --- a/LEGO1/lego/legoomni/src/paths/legopathactor.cpp +++ b/LEGO1/lego/legoomni/src/paths/legopathactor.cpp @@ -439,7 +439,7 @@ void LegoPathActor::Animate(float p_time) LegoWorld* world = CurrentWorld(); if (world) { - world->GetCameraController()->FUN_10012290(DTOR(m_unk0x14c)); + world->GetCameraController()->RotateZ(DTOR(m_unk0x14c)); } } } diff --git a/LEGO1/lego/legoomni/src/race/legoracers.cpp b/LEGO1/lego/legoomni/src/race/legoracers.cpp index da3baf53..7fbc9bfa 100644 --- a/LEGO1/lego/legoomni/src/race/legoracers.cpp +++ b/LEGO1/lego/legoomni/src/race/legoracers.cpp @@ -340,7 +340,7 @@ void LegoRaceCar::KickCamera(float p_param) a->GetAnimTreePtr()->GetCamAnim()->FUN_1009f490(deltaTime, transformationMatrix); if (r->GetCameraController()) { - r->GetCameraController()->FUN_100123e0(transformationMatrix, 0); + r->GetCameraController()->TransformPointOfView(transformationMatrix, 0); } m_roi->SetLocal2World(transformationMatrix); diff --git a/LEGO1/lego/legoomni/src/video/legoanimpresenter.cpp b/LEGO1/lego/legoomni/src/video/legoanimpresenter.cpp index c7fbc845..6a3bbf73 100644 --- a/LEGO1/lego/legoomni/src/video/legoanimpresenter.cpp +++ b/LEGO1/lego/legoomni/src/video/legoanimpresenter.cpp @@ -941,7 +941,7 @@ void LegoAnimPresenter::FUN_1006b9a0(LegoAnim* p_anim, MxLong p_time, Matrix4* p p_anim->GetCamAnim()->FUN_1009f490(p_time, transform); if (m_currentWorld != NULL && m_currentWorld->GetCameraController() != NULL) { - m_currentWorld->GetCameraController()->FUN_100123e0(transform, 0); + m_currentWorld->GetCameraController()->TransformPointOfView(transform, FALSE); } } From 36f6d963dce1926e1fedbce250229d802e5c3e84 Mon Sep 17 00:00:00 2001 From: jonschz <17198703+jonschz@users.noreply.github.com> Date: Mon, 21 Jul 2025 23:42:42 +0200 Subject: [PATCH 176/188] BETA matches for SoundManagers (#1646) --------- Co-authored-by: jonschz --- CMakeLists.txt | 2 +- .../legoomni/include/legocachesoundmanager.h | 6 ++ .../lego/legoomni/include/legosoundmanager.h | 8 +- .../src/audio/legocachesoundmanager.cpp | 2 + .../legoomni/src/audio/legosoundmanager.cpp | 96 ++++++++++--------- LEGO1/omni/include/mxaudiomanager.h | 7 +- LEGO1/omni/include/mxeventmanager.h | 4 +- ...mediamanager.h => mxpresentationmanager.h} | 16 ++-- LEGO1/omni/include/mxpresenterlist.h | 34 +++++-- LEGO1/omni/include/mxsoundmanager.h | 7 +- LEGO1/omni/include/mxvideomanager.h | 6 +- LEGO1/omni/src/audio/mxaudiomanager.cpp | 44 ++++++++- LEGO1/omni/src/audio/mxsoundmanager.cpp | 10 +- LEGO1/omni/src/common/mxmediapresenter.cpp | 1 + ...amanager.cpp => mxpresentationmanager.cpp} | 32 ++++--- LEGO1/omni/src/event/mxeventmanager.cpp | 4 +- LEGO1/omni/src/video/mxvideomanager.cpp | 14 ++- 17 files changed, 201 insertions(+), 92 deletions(-) rename LEGO1/omni/include/{mxmediamanager.h => mxpresentationmanager.h} (71%) rename LEGO1/omni/src/common/{mxmediamanager.cpp => mxpresentationmanager.cpp} (62%) diff --git a/CMakeLists.txt b/CMakeLists.txt index 0f6d1746..286e3b1a 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -240,7 +240,7 @@ function(add_lego_libraries NAME) LEGO1/omni/src/common/mxutilities.cpp LEGO1/omni/src/common/mxvariabletable.cpp LEGO1/omni/src/stream/mxdssubscriber.cpp - LEGO1/omni/src/common/mxmediamanager.cpp + LEGO1/omni/src/common/mxpresentationmanager.cpp LEGO1/omni/src/system/mxticklethread.cpp LEGO1/omni/src/audio/mxaudiomanager.cpp LEGO1/omni/src/system/mxautolock.cpp diff --git a/LEGO1/lego/legoomni/include/legocachesoundmanager.h b/LEGO1/lego/legoomni/include/legocachesoundmanager.h index dd9df741..33f5238a 100644 --- a/LEGO1/lego/legoomni/include/legocachesoundmanager.h +++ b/LEGO1/lego/legoomni/include/legocachesoundmanager.h @@ -46,10 +46,13 @@ typedef set Set100d6b4c; typedef list List100d6b4c; // VTABLE: LEGO1 0x100d6b4c +// VTABLE: BETA10 0x101becac // SIZE 0x20 class LegoCacheSoundManager { public: + // FUNCTION: BETA10 0x100d0a60 LegoCacheSoundManager() {} + ~LegoCacheSoundManager(); virtual MxResult Tickle(); // vtable+0x00 @@ -66,6 +69,9 @@ class LegoCacheSoundManager { List100d6b4c m_list; // 0x14 }; +// SYNTHETIC: BETA10 0x100d06b0 +// LegoCacheSoundManager::`scalar deleting destructor' + // TODO: Function names subject to change. // clang-format off diff --git a/LEGO1/lego/legoomni/include/legosoundmanager.h b/LEGO1/lego/legoomni/include/legosoundmanager.h index d0320320..e49ad34f 100644 --- a/LEGO1/lego/legoomni/include/legosoundmanager.h +++ b/LEGO1/lego/legoomni/include/legosoundmanager.h @@ -17,15 +17,15 @@ class LegoSoundManager : public MxSoundManager { void Destroy() override; // vtable+0x18 MxResult Create(MxU32 p_frequencyMS, MxBool p_createThread) override; // vtable+0x30 - // SYNTHETIC: LEGO1 0x10029920 - // SYNTHETIC: BETA10 0x100d0660 - // LegoSoundManager::`scalar deleting destructor' - void UpdateListener(const float* p_position, const float* p_direction, const float* p_up, const float* p_velocity); // FUNCTION: BETA10 0x1000f350 LegoCacheSoundManager* GetCacheSoundManager() { return m_cacheSoundManager; } + // SYNTHETIC: LEGO1 0x10029920 + // SYNTHETIC: BETA10 0x100d0660 + // LegoSoundManager::`scalar deleting destructor' + private: void Init(); void Destroy(MxBool p_fromDestructor); diff --git a/LEGO1/lego/legoomni/src/audio/legocachesoundmanager.cpp b/LEGO1/lego/legoomni/src/audio/legocachesoundmanager.cpp index 34be4519..cd235b61 100644 --- a/LEGO1/lego/legoomni/src/audio/legocachesoundmanager.cpp +++ b/LEGO1/lego/legoomni/src/audio/legocachesoundmanager.cpp @@ -7,6 +7,7 @@ DECOMP_SIZE_ASSERT(LegoCacheSoundEntry, 0x08) DECOMP_SIZE_ASSERT(LegoCacheSoundManager, 0x20) // FUNCTION: LEGO1 0x1003cf20 +// STUB: BETA10 0x100d0700 LegoCacheSoundManager::~LegoCacheSoundManager() { LegoCacheSound* sound; @@ -28,6 +29,7 @@ LegoCacheSoundManager::~LegoCacheSoundManager() } // FUNCTION: LEGO1 0x1003d050 +// STUB: BETA10 0x100652f0 MxResult LegoCacheSoundManager::Tickle() { #ifdef COMPAT_MODE diff --git a/LEGO1/lego/legoomni/src/audio/legosoundmanager.cpp b/LEGO1/lego/legoomni/src/audio/legosoundmanager.cpp index a1657e46..a8bf9a07 100644 --- a/LEGO1/lego/legoomni/src/audio/legosoundmanager.cpp +++ b/LEGO1/lego/legoomni/src/audio/legosoundmanager.cpp @@ -9,18 +9,21 @@ DECOMP_SIZE_ASSERT(LegoSoundManager, 0x44) // FUNCTION: LEGO1 0x100298a0 +// FUNCTION: BETA10 0x100cffb0 LegoSoundManager::LegoSoundManager() { Init(); } // FUNCTION: LEGO1 0x10029940 +// FUNCTION: BETA10 0x100d0027 LegoSoundManager::~LegoSoundManager() { Destroy(TRUE); } // FUNCTION: LEGO1 0x100299a0 +// FUNCTION: BETA10 0x100d0099 void LegoSoundManager::Init() { m_cacheSoundManager = NULL; @@ -28,6 +31,7 @@ void LegoSoundManager::Init() } // FUNCTION: LEGO1 0x100299b0 +// FUNCTION: BETA10 0x100d00c9 void LegoSoundManager::Destroy(MxBool p_fromDestructor) { delete m_cacheSoundManager; @@ -42,38 +46,45 @@ void LegoSoundManager::Destroy(MxBool p_fromDestructor) // FUNCTION: BETA10 0x100d0129 MxResult LegoSoundManager::Create(MxU32 p_frequencyMS, MxBool p_createThread) { - MxBool locked = FALSE; MxResult result = FAILURE; + MxBool locked = FALSE; - if (MxSoundManager::Create(10, FALSE) == SUCCESS) { - ENTER(m_criticalSection); - locked = TRUE; + if (MxSoundManager::Create(10, FALSE) != SUCCESS) { + goto done; + } - if (MxOmni::IsSound3D()) { - if (m_dsBuffer->QueryInterface(IID_IDirectSound3DListener, (LPVOID*) &m_listener) != DS_OK) { - goto done; - } + ENTER(m_criticalSection); + locked = TRUE; - MxOmni* omni = MxOmni::GetInstance(); - LPDIRECTSOUND sound; - - if (omni && omni->GetSoundManager() && (sound = omni->GetSoundManager()->GetDirectSound())) { - DSCAPS caps; - memset(&caps, 0, sizeof(DSCAPS)); - caps.dwSize = sizeof(DSCAPS); - - if (sound->GetCaps(&caps) == S_OK && caps.dwMaxHw3DAllBuffers == 0) { - m_listener->SetDistanceFactor(0.026315790f, 0); - m_listener->SetRolloffFactor(10, 0); - } - } + if (MxOmni::IsSound3D()) { + if (m_dsBuffer->QueryInterface(IID_IDirectSound3DListener, (LPVOID*) &m_listener) != DS_OK) { + goto done; } - m_cacheSoundManager = new LegoCacheSoundManager; - assert(m_cacheSoundManager); - result = SUCCESS; +#ifdef BETA10 + m_listener->SetDistanceFactor(0.026315790f, 0); + m_listener->SetRolloffFactor(10, 0); +#else + MxOmni* omni = MxOmni::GetInstance(); + LPDIRECTSOUND sound; + + if (omni && omni->GetSoundManager() && (sound = omni->GetSoundManager()->GetDirectSound())) { + DSCAPS caps; + memset(&caps, 0, sizeof(DSCAPS)); + caps.dwSize = sizeof(DSCAPS); + + if (sound->GetCaps(&caps) == S_OK && caps.dwMaxHw3DAllBuffers == 0) { + m_listener->SetDistanceFactor(0.026315790f, 0); + m_listener->SetRolloffFactor(10, 0); + } + } +#endif } + m_cacheSoundManager = new LegoCacheSoundManager; + assert(m_cacheSoundManager); + result = SUCCESS; + done: if (result != SUCCESS) { Destroy(); @@ -112,29 +123,24 @@ void LegoSoundManager::UpdateListener( const float* p_velocity ) { - if (m_listener != NULL) { - if (p_position != NULL) { - m_listener->SetPosition(p_position[0], p_position[1], p_position[2], DS3D_DEFERRED); - } + if (!m_listener) { + return; + } - if (p_direction != NULL && p_up != NULL) { - m_listener->SetOrientation( - p_direction[0], - p_direction[1], - p_direction[2], - p_up[0], - p_up[1], - p_up[2], - DS3D_DEFERRED - ); - } + if (p_position != NULL) { + m_listener->SetPosition(p_position[0], p_position[1], p_position[2], DS3D_DEFERRED); + } - if (p_velocity != NULL) { - m_listener->SetVelocity(p_velocity[0], p_velocity[1], p_velocity[2], DS3D_DEFERRED); - } + if (p_direction != NULL && p_up != NULL) { + m_listener + ->SetOrientation(p_direction[0], p_direction[1], p_direction[2], p_up[0], p_up[1], p_up[2], DS3D_DEFERRED); + } - if (p_position != NULL || (p_direction != NULL && p_up != NULL) || p_velocity != NULL) { - m_listener->CommitDeferredSettings(); - } + if (p_velocity != NULL) { + m_listener->SetVelocity(p_velocity[0], p_velocity[1], p_velocity[2], DS3D_DEFERRED); + } + + if (p_position != NULL || (p_direction != NULL && p_up != NULL) || p_velocity != NULL) { + m_listener->CommitDeferredSettings(); } } diff --git a/LEGO1/omni/include/mxaudiomanager.h b/LEGO1/omni/include/mxaudiomanager.h index f962a16d..bab1f553 100644 --- a/LEGO1/omni/include/mxaudiomanager.h +++ b/LEGO1/omni/include/mxaudiomanager.h @@ -2,11 +2,12 @@ #define MXAUDIOMANAGER_H #include "decomp.h" -#include "mxmediamanager.h" +#include "mxpresentationmanager.h" // VTABLE: LEGO1 0x100dc6e0 +// VTABLE: BETA10 0x101c2348 // SIZE 0x30 -class MxAudioManager : public MxMediaManager { +class MxAudioManager : public MxPresentationManager { public: MxAudioManager(); ~MxAudioManager() override; @@ -15,11 +16,13 @@ class MxAudioManager : public MxMediaManager { void Destroy() override; // vtable+18 // FUNCTION: LEGO1 0x10029910 + // FUNCTION: BETA10 0x100d0630 virtual MxS32 GetVolume() { return m_volume; } // vtable+28 virtual void SetVolume(MxS32 p_volume); // vtable+2c // SYNTHETIC: LEGO1 0x100b8d70 + // SYNTHETIC: BETA10 0x10145110 // MxAudioManager::`scalar deleting destructor' private: diff --git a/LEGO1/omni/include/mxeventmanager.h b/LEGO1/omni/include/mxeventmanager.h index b23a2ad0..f17f886d 100644 --- a/LEGO1/omni/include/mxeventmanager.h +++ b/LEGO1/omni/include/mxeventmanager.h @@ -2,11 +2,11 @@ #define MXEVENTMANAGER_H #include "decomp.h" -#include "mxmediamanager.h" +#include "mxpresentationmanager.h" // VTABLE: LEGO1 0x100dc900 // SIZE 0x2c -class MxEventManager : public MxMediaManager { +class MxEventManager : public MxPresentationManager { public: MxEventManager(); ~MxEventManager() override; diff --git a/LEGO1/omni/include/mxmediamanager.h b/LEGO1/omni/include/mxpresentationmanager.h similarity index 71% rename from LEGO1/omni/include/mxmediamanager.h rename to LEGO1/omni/include/mxpresentationmanager.h index 38f611eb..f0740e6e 100644 --- a/LEGO1/omni/include/mxmediamanager.h +++ b/LEGO1/omni/include/mxpresentationmanager.h @@ -1,5 +1,5 @@ -#ifndef MXMEDIAMANGER_H -#define MXMEDIAMANGER_H +#ifndef MXPRESENTATIONMANAGER_H +#define MXPRESENTATIONMANAGER_H #include "mxcore.h" #include "mxcriticalsection.h" @@ -9,11 +9,12 @@ class MxThread; // VTABLE: LEGO1 0x100dc6b0 +// VTABLE: BETA10 0x101c2318 // SIZE 0x2c -class MxMediaManager : public MxCore { +class MxPresentationManager : public MxCore { public: - MxMediaManager(); - ~MxMediaManager() override; + MxPresentationManager(); + ~MxPresentationManager() override; MxResult Tickle() override; // vtable+08 virtual MxResult Create(); // vtable+14 @@ -25,7 +26,8 @@ class MxMediaManager : public MxCore { MxResult Init(); // SYNTHETIC: LEGO1 0x100b8540 - // MxMediaManager::`scalar deleting destructor' + // SYNTHETIC: BETA10 0x10144db0 + // MxPresentationManager::`scalar deleting destructor' protected: MxPresenterList* m_presenters; // 0x08 @@ -33,4 +35,4 @@ class MxMediaManager : public MxCore { MxCriticalSection m_criticalSection; // 0x10 }; -#endif // MXMEDIAMANGER_H +#endif // MXPRESENTATIONMANAGER_H diff --git a/LEGO1/omni/include/mxpresenterlist.h b/LEGO1/omni/include/mxpresenterlist.h index 0e8bd316..7d3000cc 100644 --- a/LEGO1/omni/include/mxpresenterlist.h +++ b/LEGO1/omni/include/mxpresenterlist.h @@ -5,12 +5,15 @@ #include "mxpresenter.h" // VTABLE: LEGO1 0x100d62f0 +// VTABLE: BETA10 0x101bf070 // class MxPtrList // VTABLE: LEGO1 0x100d6308 +// VTABLE: BETA10 0x101bf050 // SIZE 0x18 class MxPresenterList : public MxPtrList { public: + // FUNCTION: BETA10 0x100dc900 MxPresenterList(MxBool p_ownership = FALSE) : MxPtrList(p_ownership) {} // FUNCTION: LEGO1 0x1001cd00 @@ -35,6 +38,13 @@ class MxPresenterListCursor : public MxPtrListCursor { public: // FUNCTION: BETA10 0x1007d130 MxPresenterListCursor(MxPresenterList* p_list) : MxPtrListCursor(p_list) {} + + // SYNTHETIC: LEGO1 0x1001eed0 + // MxPresenterListCursor::`scalar deleting destructor' + + // SYNTHETIC: LEGO1 0x1001f0c0 + // SYNTHETIC: BETA10 0x1007d510 + // MxPresenterListCursor::~MxPresenterListCursor }; // VTABLE: LEGO1 0x100d6350 @@ -58,7 +68,11 @@ class MxPresenterListCursor : public MxPtrListCursor { // TEMPLATE: LEGO1 0x1001ce20 // MxList::~MxList +// TEMPLATE: BETA10 0x100dc9f0 +// MxPtrList::MxPtrList + // TEMPLATE: LEGO1 0x1001cf20 +// TEMPLATE: BETA10 0x100dce70 // MxPtrList::~MxPtrList // SYNTHETIC: LEGO1 0x1001cf70 @@ -73,10 +87,8 @@ class MxPresenterListCursor : public MxPtrListCursor { // SYNTHETIC: LEGO1 0x1001d100 // MxPresenterList::~MxPresenterList -// SYNTHETIC: LEGO1 0x1001eed0 -// MxPresenterListCursor::`scalar deleting destructor' - // TEMPLATE: LEGO1 0x1001ef40 +// TEMPLATE: BETA10 0x1007d370 // MxPtrListCursor::~MxPtrListCursor // SYNTHETIC: LEGO1 0x1001ef90 @@ -86,11 +98,9 @@ class MxPresenterListCursor : public MxPtrListCursor { // MxPtrListCursor::`scalar deleting destructor' // TEMPLATE: LEGO1 0x1001f070 +// TEMPLATE: BETA10 0x1007d490 // MxListCursor::~MxListCursor -// FUNCTION: LEGO1 0x1001f0c0 -// MxPresenterListCursor::~MxPresenterListCursor - // TEMPLATE: LEGO1 0x10020760 // MxListCursor::MxListCursor @@ -106,6 +116,18 @@ class MxPresenterListCursor : public MxPtrListCursor { // TEMPLATE: BETA10 0x1007d270 // MxListCursor::MxListCursor +// TEMPLATE: BETA10 0x1007dc60 +// MxListCursor::Next + +// TEMPLATE: BETA10 0x100d8f20 +// MxListCursor::Reset + +// TEMPLATE: BETA10 0x1007e070 +// MxListEntry::GetNext + +// TEMPLATE: BETA10 0x1007e0a0 +// MxListEntry::GetValue + // TEMPLATE: BETA10 0x100d9420 // ?Prev@?$MxListCursor@PAVMxPresenter@@@@QAEEAAPAVMxPresenter@@@Z diff --git a/LEGO1/omni/include/mxsoundmanager.h b/LEGO1/omni/include/mxsoundmanager.h index 3d25856b..a0b1145e 100644 --- a/LEGO1/omni/include/mxsoundmanager.h +++ b/LEGO1/omni/include/mxsoundmanager.h @@ -27,6 +27,10 @@ class MxSoundManager : public MxAudioManager { MxPresenter* FindPresenter(const MxAtomId& p_atomId, MxU32 p_objectId); + // SYNTHETIC: LEGO1 0x100ae7b0 + // SYNTHETIC: BETA10 0x10133460 + // MxSoundManager::`scalar deleting destructor' + protected: void Init(); void Destroy(MxBool p_fromDestructor); @@ -36,7 +40,4 @@ class MxSoundManager : public MxAudioManager { undefined m_unk0x38[4]; }; -// SYNTHETIC: LEGO1 0x100ae7b0 -// MxSoundManager::`scalar deleting destructor' - #endif // MXSOUNDMANAGER_H diff --git a/LEGO1/omni/include/mxvideomanager.h b/LEGO1/omni/include/mxvideomanager.h index 2ecab25d..60349d13 100644 --- a/LEGO1/omni/include/mxvideomanager.h +++ b/LEGO1/omni/include/mxvideomanager.h @@ -1,7 +1,7 @@ #ifndef MXVIDEOMANAGER_H #define MXVIDEOMANAGER_H -#include "mxmediamanager.h" +#include "mxpresentationmanager.h" #include "mxvideoparam.h" #include @@ -11,8 +11,9 @@ class MxRect32; class MxRegion; // VTABLE: LEGO1 0x100dc810 +// VTABLE: BETA10 0x101c1bf8 // SIZE 0x64 -class MxVideoManager : public MxMediaManager { +class MxVideoManager : public MxPresentationManager { public: MxVideoManager(); ~MxVideoManager() override; @@ -48,6 +49,7 @@ class MxVideoManager : public MxMediaManager { MxRegion* GetRegion() { return this->m_region; } // SYNTHETIC: LEGO1 0x100be280 + // SYNTHETIC: BETA10 0x1012de00 // MxVideoManager::`scalar deleting destructor' protected: diff --git a/LEGO1/omni/src/audio/mxaudiomanager.cpp b/LEGO1/omni/src/audio/mxaudiomanager.cpp index 34b52eda..3cef829a 100644 --- a/LEGO1/omni/src/audio/mxaudiomanager.cpp +++ b/LEGO1/omni/src/audio/mxaudiomanager.cpp @@ -3,27 +3,32 @@ DECOMP_SIZE_ASSERT(MxAudioManager, 0x30); // GLOBAL: LEGO1 0x10102108 +// GLOBAL: BETA10 0x10203a60 MxS32 MxAudioManager::g_count = 0; // FUNCTION: LEGO1 0x100b8d00 +// FUNCTION: BETA10 0x10144e90 MxAudioManager::MxAudioManager() { Init(); } // FUNCTION: LEGO1 0x100b8d90 +// STUB: BETA10 0x10144f07 MxAudioManager::~MxAudioManager() { Destroy(TRUE); } // FUNCTION: LEGO1 0x100b8df0 +// FUNCTION: BETA10 0x10144f79 void MxAudioManager::Init() { m_volume = 100; } // FUNCTION: LEGO1 0x100b8e00 +// FUNCTION: BETA10 0x10144f9c void MxAudioManager::Destroy(MxBool p_fromDestructor) { ENTER(m_criticalSection); @@ -32,17 +37,49 @@ void MxAudioManager::Destroy(MxBool p_fromDestructor) m_criticalSection.Leave(); if (!p_fromDestructor) { - MxMediaManager::Destroy(); + MxPresentationManager::Destroy(); } } +#ifdef BETA10 +// FUNCTION: BETA10 0x10144ffe +MxResult MxAudioManager::Create() +{ + MxResult result = FAILURE; + MxBool success = FALSE; + + if (MxPresentationManager::Create() != SUCCESS) { + goto exit; + } + + ENTER(m_criticalSection); + success = TRUE; + + if (!g_count++) { + // This is correct. It was likely refactored later. + } + +exit: + result = SUCCESS; + + if (result) { + Destroy(); + } + + if (success) { + m_criticalSection.Leave(); + } + + return result; +} +#else // FUNCTION: LEGO1 0x100b8e40 MxResult MxAudioManager::Create() { MxResult result = FAILURE; MxBool success = FALSE; - if (MxMediaManager::Create() == SUCCESS) { + if (MxPresentationManager::Create() == SUCCESS) { ENTER(m_criticalSection); success = TRUE; result = SUCCESS; @@ -59,14 +96,17 @@ MxResult MxAudioManager::Create() return result; } +#endif // FUNCTION: LEGO1 0x100b8e90 +// FUNCTION: BETA10 0x101450a7 void MxAudioManager::Destroy() { Destroy(FALSE); } // FUNCTION: LEGO1 0x100b8ea0 +// FUNCTION: BETA10 0x101450c7 void MxAudioManager::SetVolume(MxS32 p_volume) { ENTER(m_criticalSection); diff --git a/LEGO1/omni/src/audio/mxsoundmanager.cpp b/LEGO1/omni/src/audio/mxsoundmanager.cpp index 1e2b28a1..5bfa851c 100644 --- a/LEGO1/omni/src/audio/mxsoundmanager.cpp +++ b/LEGO1/omni/src/audio/mxsoundmanager.cpp @@ -23,18 +23,21 @@ MxS32 g_volumeAttenuation[100] = {-6643, -5643, -5058, -4643, -4321, -4058, -383 -43, -29, -14, 0}; // FUNCTION: LEGO1 0x100ae740 +// FUNCTION: BETA10 0x10132c70 MxSoundManager::MxSoundManager() { Init(); } // FUNCTION: LEGO1 0x100ae7d0 +// FUNCTION: BETA10 0x10132ce7 MxSoundManager::~MxSoundManager() { Destroy(TRUE); } // FUNCTION: LEGO1 0x100ae830 +// FUNCTION: BETA10 0x10132d59 void MxSoundManager::Init() { m_directSound = NULL; @@ -42,6 +45,7 @@ void MxSoundManager::Init() } // FUNCTION: LEGO1 0x100ae840 +// FUNCTION: BETA10 0x10132d89 void MxSoundManager::Destroy(MxBool p_fromDestructor) { if (m_thread) { @@ -156,12 +160,14 @@ MxResult MxSoundManager::Create(MxU32 p_frequencyMS, MxBool p_createThread) } // FUNCTION: LEGO1 0x100aeab0 +// FUNCTION: BETA10 0x101331e3 void MxSoundManager::Destroy() { Destroy(FALSE); } // FUNCTION: LEGO1 0x100aeac0 +// FUNCTION: BETA10 0x10133203 void MxSoundManager::SetVolume(MxS32 p_volume) { MxAudioManager::SetVolume(p_volume); @@ -179,6 +185,7 @@ void MxSoundManager::SetVolume(MxS32 p_volume) } // FUNCTION: LEGO1 0x100aebd0 +// FUNCTION: BETA10 0x101332cf MxPresenter* MxSoundManager::FindPresenter(const MxAtomId& p_atomId, MxU32 p_objectId) { AUTOLOCK(m_criticalSection); @@ -187,8 +194,7 @@ MxPresenter* MxSoundManager::FindPresenter(const MxAtomId& p_atomId, MxU32 p_obj MxPresenterListCursor cursor(m_presenters); while (cursor.Next(presenter)) { - if (presenter->GetAction()->GetAtomId().GetInternal() == p_atomId.GetInternal() && - presenter->GetAction()->GetObjectId() == p_objectId) { + if (presenter->GetAction()->GetAtomId() == p_atomId && presenter->GetAction()->GetObjectId() == p_objectId) { return presenter; } } diff --git a/LEGO1/omni/src/common/mxmediapresenter.cpp b/LEGO1/omni/src/common/mxmediapresenter.cpp index c8b9abcd..11621f71 100644 --- a/LEGO1/omni/src/common/mxmediapresenter.cpp +++ b/LEGO1/omni/src/common/mxmediapresenter.cpp @@ -131,6 +131,7 @@ MxResult MxMediaPresenter::StartAction(MxStreamController* p_controller, MxDSAct } // FUNCTION: LEGO1 0x100b5bc0 +// STUB: BETA10 0x1013623c void MxMediaPresenter::EndAction() { AUTOLOCK(m_criticalSection); diff --git a/LEGO1/omni/src/common/mxmediamanager.cpp b/LEGO1/omni/src/common/mxpresentationmanager.cpp similarity index 62% rename from LEGO1/omni/src/common/mxmediamanager.cpp rename to LEGO1/omni/src/common/mxpresentationmanager.cpp index cc1611ae..6ef8448e 100644 --- a/LEGO1/omni/src/common/mxmediamanager.cpp +++ b/LEGO1/omni/src/common/mxpresentationmanager.cpp @@ -1,4 +1,4 @@ -#include "mxmediamanager.h" +#include "mxpresentationmanager.h" #include "decomp.h" #include "mxautolock.h" @@ -6,24 +6,27 @@ #include "mxpresenter.h" #include "mxticklemanager.h" -DECOMP_SIZE_ASSERT(MxMediaManager, 0x2c); +DECOMP_SIZE_ASSERT(MxPresentationManager, 0x2c); DECOMP_SIZE_ASSERT(MxPresenterList, 0x18); DECOMP_SIZE_ASSERT(MxPresenterListCursor, 0x10); // FUNCTION: LEGO1 0x100b84c0 -MxMediaManager::MxMediaManager() +// FUNCTION: BETA10 0x10144680 +MxPresentationManager::MxPresentationManager() { Init(); } // FUNCTION: LEGO1 0x100b8560 -MxMediaManager::~MxMediaManager() +// FUNCTION: BETA10 0x10144712 +MxPresentationManager::~MxPresentationManager() { Destroy(); } // FUNCTION: LEGO1 0x100b85d0 -MxResult MxMediaManager::Init() +// FUNCTION: BETA10 0x1014479b +MxResult MxPresentationManager::Init() { this->m_presenters = NULL; this->m_thread = NULL; @@ -31,8 +34,10 @@ MxResult MxMediaManager::Init() } // FUNCTION: LEGO1 0x100b85e0 -MxResult MxMediaManager::Create() +// FUNCTION: BETA10 0x101447c5 +MxResult MxPresentationManager::Create() { + // This validates the name of the source code file (and hence also the name of the class) AUTOLOCK(m_criticalSection); this->m_presenters = new MxPresenterList; @@ -46,7 +51,8 @@ MxResult MxMediaManager::Create() } // FUNCTION: LEGO1 0x100b8710 -void MxMediaManager::Destroy() +// FUNCTION: BETA10 0x101448e4 +void MxPresentationManager::Destroy() { AUTOLOCK(m_criticalSection); @@ -58,7 +64,8 @@ void MxMediaManager::Destroy() } // FUNCTION: LEGO1 0x100b8790 -MxResult MxMediaManager::Tickle() +// FUNCTION: BETA10 0x10144993 +MxResult MxPresentationManager::Tickle() { AUTOLOCK(m_criticalSection); MxPresenter* presenter; @@ -78,7 +85,8 @@ MxResult MxMediaManager::Tickle() } // FUNCTION: LEGO1 0x100b88c0 -void MxMediaManager::RegisterPresenter(MxPresenter& p_presenter) +// FUNCTION: BETA10 0x10144a8b +void MxPresentationManager::RegisterPresenter(MxPresenter& p_presenter) { AUTOLOCK(m_criticalSection); @@ -86,7 +94,8 @@ void MxMediaManager::RegisterPresenter(MxPresenter& p_presenter) } // FUNCTION: LEGO1 0x100b8980 -void MxMediaManager::UnregisterPresenter(MxPresenter& p_presenter) +// FUNCTION: BETA10 0x10144b0c +void MxPresentationManager::UnregisterPresenter(MxPresenter& p_presenter) { AUTOLOCK(m_criticalSection); MxPresenterListCursor cursor(this->m_presenters); @@ -97,7 +106,8 @@ void MxMediaManager::UnregisterPresenter(MxPresenter& p_presenter) } // FUNCTION: LEGO1 0x100b8ac0 -void MxMediaManager::StopPresenters() +// FUNCTION: BETA10 0x10144bc3 +void MxPresentationManager::StopPresenters() { AUTOLOCK(m_criticalSection); MxPresenter* presenter; diff --git a/LEGO1/omni/src/event/mxeventmanager.cpp b/LEGO1/omni/src/event/mxeventmanager.cpp index 2c38a123..91a22abb 100644 --- a/LEGO1/omni/src/event/mxeventmanager.cpp +++ b/LEGO1/omni/src/event/mxeventmanager.cpp @@ -35,7 +35,7 @@ void MxEventManager::Destroy(MxBool p_fromDestructor) } if (!p_fromDestructor) { - MxMediaManager::Destroy(); + MxPresentationManager::Destroy(); } } @@ -45,7 +45,7 @@ MxResult MxEventManager::Create(MxU32 p_frequencyMS, MxBool p_createThread) MxResult status = FAILURE; MxBool locked = FALSE; - MxResult result = MxMediaManager::Create(); + MxResult result = MxPresentationManager::Create(); if (result == SUCCESS) { if (p_createThread) { ENTER(this->m_criticalSection); diff --git a/LEGO1/omni/src/video/mxvideomanager.cpp b/LEGO1/omni/src/video/mxvideomanager.cpp index 9ad9005c..5649e2d1 100644 --- a/LEGO1/omni/src/video/mxvideomanager.cpp +++ b/LEGO1/omni/src/video/mxvideomanager.cpp @@ -19,17 +19,20 @@ MxVideoManager::MxVideoManager() } // FUNCTION: LEGO1 0x100be270 +// FUNCTION: BETA10 0x1012dde0 void MxVideoManager::UpdateView(MxU32 p_x, MxU32 p_y, MxU32 p_width, MxU32 p_height) { } // FUNCTION: LEGO1 0x100be2a0 +// FUNCTION: BETA10 0x1012cad8 MxVideoManager::~MxVideoManager() { Destroy(TRUE); } // FUNCTION: LEGO1 0x100be320 +// FUNCTION: BETA10 0x1012cb66 MxResult MxVideoManager::Init() { m_pDirectDraw = NULL; @@ -42,6 +45,7 @@ MxResult MxVideoManager::Init() } // FUNCTION: LEGO1 0x100be340 +// FUNCTION: BETA10 0x1012cbca void MxVideoManager::Destroy(MxBool p_fromDestructor) { if (m_thread) { @@ -79,7 +83,7 @@ void MxVideoManager::Destroy(MxBool p_fromDestructor) m_criticalSection.Leave(); if (!p_fromDestructor) { - MxMediaManager::Destroy(); + MxPresentationManager::Destroy(); } } @@ -132,6 +136,7 @@ void MxVideoManager::SortPresenterList() } // FUNCTION: LEGO1 0x100be600 +// STUB: BETA10 0x1012cfbc MxResult MxVideoManager::VTable0x28( MxVideoParam& p_videoParam, LPDIRECTDRAW p_pDirectDraw, @@ -148,7 +153,7 @@ MxResult MxVideoManager::VTable0x28( m_unk0x60 = FALSE; - if (MxMediaManager::Create() != SUCCESS) { + if (MxPresentationManager::Create() != SUCCESS) { goto done; } @@ -214,6 +219,7 @@ MxResult MxVideoManager::VTable0x28( } // FUNCTION: LEGO1 0x100be820 +// STUB: BETA10 0x1012d3f1 MxResult MxVideoManager::Create(MxVideoParam& p_videoParam, MxU32 p_frequencyMS, MxBool p_createThread) { MxBool locked = FALSE; @@ -221,7 +227,7 @@ MxResult MxVideoManager::Create(MxVideoParam& p_videoParam, MxU32 p_frequencyMS, m_unk0x60 = TRUE; - if (MxMediaManager::Create() != SUCCESS) { + if (MxPresentationManager::Create() != SUCCESS) { goto done; } @@ -292,6 +298,7 @@ MxResult MxVideoManager::Create(MxVideoParam& p_videoParam, MxU32 p_frequencyMS, } // FUNCTION: LEGO1 0x100bea50 +// FUNCTION: BETA10 0x1012d85f void MxVideoManager::Destroy() { Destroy(FALSE); @@ -310,6 +317,7 @@ void MxVideoManager::InvalidateRect(MxRect32& p_rect) } // FUNCTION: LEGO1 0x100bea90 +// FUNCTION: BETA10 0x1012d8e3 MxResult MxVideoManager::Tickle() { AUTOLOCK(m_criticalSection); From ed33541a2e88090cf6aa7ae23ab7b6927e8b1f3d Mon Sep 17 00:00:00 2001 From: Fabian Neundorf Date: Tue, 22 Jul 2025 19:45:50 +0200 Subject: [PATCH 177/188] Clear unknowns in `LegoAnimScene` (#1649) --- LEGO1/lego/legoomni/src/race/legoracers.cpp | 2 +- .../legoomni/src/video/legoanimpresenter.cpp | 2 +- LEGO1/lego/sources/anim/legoanim.cpp | 231 +++++++++--------- LEGO1/lego/sources/anim/legoanim.h | 36 +-- 4 files changed, 142 insertions(+), 129 deletions(-) diff --git a/LEGO1/lego/legoomni/src/race/legoracers.cpp b/LEGO1/lego/legoomni/src/race/legoracers.cpp index 7fbc9bfa..c45393e8 100644 --- a/LEGO1/lego/legoomni/src/race/legoracers.cpp +++ b/LEGO1/lego/legoomni/src/race/legoracers.cpp @@ -337,7 +337,7 @@ void LegoRaceCar::KickCamera(float p_param) transformationMatrix.SetIdentity(); // Possible bug in the original code: The first argument is not initialized - a->GetAnimTreePtr()->GetCamAnim()->FUN_1009f490(deltaTime, transformationMatrix); + a->GetAnimTreePtr()->GetCamAnim()->CalculateCameraTransform(deltaTime, transformationMatrix); if (r->GetCameraController()) { r->GetCameraController()->TransformPointOfView(transformationMatrix, 0); diff --git a/LEGO1/lego/legoomni/src/video/legoanimpresenter.cpp b/LEGO1/lego/legoomni/src/video/legoanimpresenter.cpp index 6a3bbf73..b36d95e1 100644 --- a/LEGO1/lego/legoomni/src/video/legoanimpresenter.cpp +++ b/LEGO1/lego/legoomni/src/video/legoanimpresenter.cpp @@ -938,7 +938,7 @@ void LegoAnimPresenter::FUN_1006b9a0(LegoAnim* p_anim, MxLong p_time, Matrix4* p if (p_anim->GetCamAnim() != NULL) { MxMatrix transform(mat); - p_anim->GetCamAnim()->FUN_1009f490(p_time, transform); + p_anim->GetCamAnim()->CalculateCameraTransform(p_time, transform); if (m_currentWorld != NULL && m_currentWorld->GetCameraController() != NULL) { m_currentWorld->GetCameraController()->TransformPointOfView(transform, FALSE); diff --git a/LEGO1/lego/sources/anim/legoanim.cpp b/LEGO1/lego/sources/anim/legoanim.cpp index 9026c245..e0fb90b6 100644 --- a/LEGO1/lego/sources/anim/legoanim.cpp +++ b/LEGO1/lego/sources/anim/legoanim.cpp @@ -10,20 +10,20 @@ DECOMP_SIZE_ASSERT(LegoTranslationKey, 0x14) DECOMP_SIZE_ASSERT(LegoRotationKey, 0x18) DECOMP_SIZE_ASSERT(LegoScaleKey, 0x14) DECOMP_SIZE_ASSERT(LegoMorphKey, 0x0c) -DECOMP_SIZE_ASSERT(LegoUnknownKey, 0x0c) +DECOMP_SIZE_ASSERT(LegoRotationZKey, 0x0c) DECOMP_SIZE_ASSERT(LegoAnimNodeData, 0x34) DECOMP_SIZE_ASSERT(LegoAnimActorEntry, 0x08) DECOMP_SIZE_ASSERT(LegoAnimScene, 0x24) DECOMP_SIZE_ASSERT(LegoAnim, 0x18) // FUNCTION: LEGO1 0x1009f000 -LegoUnknownKey::LegoUnknownKey() +LegoRotationZKey::LegoRotationZKey() { m_z = 0.0f; } // FUNCTION: LEGO1 0x1009f020 -LegoResult LegoUnknownKey::Read(LegoStorage* p_storage) +LegoResult LegoRotationZKey::Read(LegoStorage* p_storage) { LegoResult result; @@ -40,7 +40,7 @@ LegoResult LegoUnknownKey::Read(LegoStorage* p_storage) // FUNCTION: LEGO1 0x1009f060 // FUNCTION: BETA10 0x1018133f -LegoResult LegoUnknownKey::Write(LegoStorage* p_storage) +LegoResult LegoRotationZKey::Write(LegoStorage* p_storage) { LegoResult result; @@ -58,33 +58,33 @@ LegoResult LegoUnknownKey::Write(LegoStorage* p_storage) // FUNCTION: LEGO1 0x1009f0a0 LegoAnimScene::LegoAnimScene() { - m_unk0x00 = 0; - m_unk0x04 = NULL; - m_unk0x08 = 0; - m_unk0x0c = NULL; - m_unk0x10 = 0; - m_unk0x14 = NULL; - m_unk0x18 = 0; - m_unk0x1c = 0; - m_unk0x20 = 0; + m_translationKeysCount = 0; + m_translationKeys = NULL; + m_targetKeysCount = 0; + m_targetKeys = NULL; + m_rotationKeysCount = 0; + m_rotationKeys = NULL; + m_targetIndex = 0; + m_translationIndex = 0; + m_rotationIndex = 0; } // FUNCTION: LEGO1 0x1009f0d0 LegoAnimScene::~LegoAnimScene() { - if (m_unk0x04 != NULL) { - delete[] m_unk0x04; - m_unk0x04 = NULL; + if (m_translationKeys != NULL) { + delete[] m_translationKeys; + m_translationKeys = NULL; } - if (m_unk0x0c != NULL) { - delete[] m_unk0x0c; - m_unk0x0c = NULL; + if (m_targetKeys != NULL) { + delete[] m_targetKeys; + m_targetKeys = NULL; } - if (m_unk0x14 != NULL) { - delete[] m_unk0x14; - m_unk0x14 = NULL; + if (m_rotationKeys != NULL) { + delete[] m_rotationKeys; + m_rotationKeys = NULL; } } @@ -95,34 +95,34 @@ LegoResult LegoAnimScene::Write(LegoStorage* p_storage) LegoResult result; LegoS32 i; - if ((result = p_storage->Write(&m_unk0x00, sizeof(LegoU16))) != SUCCESS) { + if ((result = p_storage->Write(&m_translationKeysCount, sizeof(LegoU16))) != SUCCESS) { return result; } - if (m_unk0x00 != 0) { - for (i = 0; i < m_unk0x00; i++) { - if ((result = m_unk0x04[i].Write(p_storage)) != SUCCESS) { + if (m_translationKeysCount != 0) { + for (i = 0; i < m_translationKeysCount; i++) { + if ((result = m_translationKeys[i].Write(p_storage)) != SUCCESS) { return result; } } } - if ((result = p_storage->Write(&m_unk0x08, sizeof(LegoU16))) != SUCCESS) { + if ((result = p_storage->Write(&m_targetKeysCount, sizeof(LegoU16))) != SUCCESS) { return result; } - if (m_unk0x08 != 0) { - for (i = 0; i < m_unk0x08; i++) { - if ((result = m_unk0x0c[i].Write(p_storage)) != SUCCESS) { + if (m_targetKeysCount != 0) { + for (i = 0; i < m_targetKeysCount; i++) { + if ((result = m_targetKeys[i].Write(p_storage)) != SUCCESS) { return result; } } } - if ((result = p_storage->Write(&m_unk0x10, sizeof(LegoU16))) != SUCCESS) { + if ((result = p_storage->Write(&m_rotationKeysCount, sizeof(LegoU16))) != SUCCESS) { return result; } - if (m_unk0x10 != 0) { - for (i = 0; i < m_unk0x10; i++) { - if ((result = m_unk0x14[i].Write(p_storage)) != SUCCESS) { + if (m_rotationKeysCount != 0) { + for (i = 0; i < m_rotationKeysCount; i++) { + if ((result = m_rotationKeys[i].Write(p_storage)) != SUCCESS) { return result; } } @@ -137,37 +137,37 @@ LegoResult LegoAnimScene::Read(LegoStorage* p_storage) LegoResult result; LegoS32 i; - if ((result = p_storage->Read(&m_unk0x00, sizeof(LegoU16))) != SUCCESS) { + if ((result = p_storage->Read(&m_translationKeysCount, sizeof(LegoU16))) != SUCCESS) { return result; } - if (m_unk0x00 != 0) { - m_unk0x04 = new LegoTranslationKey[m_unk0x00]; - for (i = 0; i < m_unk0x00; i++) { - if ((result = m_unk0x04[i].Read(p_storage)) != SUCCESS) { + if (m_translationKeysCount != 0) { + m_translationKeys = new LegoTranslationKey[m_translationKeysCount]; + for (i = 0; i < m_translationKeysCount; i++) { + if ((result = m_translationKeys[i].Read(p_storage)) != SUCCESS) { goto done; } } } - if ((result = p_storage->Read(&m_unk0x08, sizeof(LegoU16))) != SUCCESS) { + if ((result = p_storage->Read(&m_targetKeysCount, sizeof(LegoU16))) != SUCCESS) { return result; } - if (m_unk0x08 != 0) { - m_unk0x0c = new LegoTranslationKey[m_unk0x08]; - for (i = 0; i < m_unk0x08; i++) { - if ((result = m_unk0x0c[i].Read(p_storage)) != SUCCESS) { + if (m_targetKeysCount != 0) { + m_targetKeys = new LegoTranslationKey[m_targetKeysCount]; + for (i = 0; i < m_targetKeysCount; i++) { + if ((result = m_targetKeys[i].Read(p_storage)) != SUCCESS) { goto done; } } } - if ((result = p_storage->Read(&m_unk0x10, sizeof(LegoU16))) != SUCCESS) { + if ((result = p_storage->Read(&m_rotationKeysCount, sizeof(LegoU16))) != SUCCESS) { return result; } - if (m_unk0x10 != 0) { - m_unk0x14 = new LegoUnknownKey[m_unk0x10]; - for (i = 0; i < m_unk0x10; i++) { - if ((result = m_unk0x14[i].Read(p_storage)) != SUCCESS) { + if (m_rotationKeysCount != 0) { + m_rotationKeys = new LegoRotationZKey[m_rotationKeysCount]; + for (i = 0; i < m_rotationKeysCount; i++) { + if ((result = m_rotationKeys[i].Read(p_storage)) != SUCCESS) { goto done; } } @@ -176,22 +176,22 @@ LegoResult LegoAnimScene::Read(LegoStorage* p_storage) return SUCCESS; done: - if (m_unk0x04 != NULL) { - delete[] m_unk0x04; - m_unk0x00 = 0; - m_unk0x04 = NULL; + if (m_translationKeys != NULL) { + delete[] m_translationKeys; + m_translationKeysCount = 0; + m_translationKeys = NULL; } - if (m_unk0x0c != NULL) { - delete[] m_unk0x0c; - m_unk0x08 = 0; - m_unk0x0c = NULL; + if (m_targetKeys != NULL) { + delete[] m_targetKeys; + m_targetKeysCount = 0; + m_targetKeys = NULL; } - if (m_unk0x14 != NULL) { - delete[] m_unk0x14; - m_unk0x10 = 0; - m_unk0x14 = NULL; + if (m_rotationKeys != NULL) { + delete[] m_rotationKeys; + m_rotationKeysCount = 0; + m_rotationKeys = NULL; } return result; @@ -199,82 +199,95 @@ LegoResult LegoAnimScene::Read(LegoStorage* p_storage) // FUNCTION: LEGO1 0x1009f490 // FUNCTION: BETA10 0x10181a83 -LegoResult LegoAnimScene::FUN_1009f490(LegoFloat p_time, Matrix4& p_matrix) +LegoResult LegoAnimScene::CalculateCameraTransform(LegoFloat p_time, Matrix4& p_matrix) { - MxMatrix localb0; - MxMatrix local4c; + MxMatrix tempMatrix; + MxMatrix original; - Vector3 local5c(localb0[0]); - Vector3 local68(localb0[1]); - Vector3 local54(localb0[2]); - Vector3 localb8(localb0[3]); + Vector3 column0(tempMatrix[0]); + Vector3 column1(tempMatrix[1]); + Vector3 column2(tempMatrix[2]); + Vector3 column3(tempMatrix[3]); - Mx3DPointFloat localcc; + Mx3DPointFloat tempTranslation; - localb0.SetIdentity(); + tempMatrix.SetIdentity(); - LegoU32 local60; - if (m_unk0x08 != 0) { - local60 = GetUnknown0x18(); - LegoAnimNodeData::GetTranslation(m_unk0x08, m_unk0x0c, p_time, localb0, local60); - SetUnknown0x18(local60); - localcc = localb8; - localb8.Clear(); + LegoU32 translationIndex; + if (m_targetKeysCount != 0) { + translationIndex = GetTargetIndex(); + LegoAnimNodeData::GetTranslation(m_targetKeysCount, m_targetKeys, p_time, tempMatrix, translationIndex); + SetTargetIndex(translationIndex); + tempTranslation = column3; + column3.Clear(); } - if (m_unk0x00 != 0) { - local60 = GetUnknown0x1c(); - LegoAnimNodeData::GetTranslation(m_unk0x00, m_unk0x04, p_time, localb0, local60); - SetUnknown0x1c(local60); + if (m_translationKeysCount != 0) { + translationIndex = GetTranslationIndex(); + LegoAnimNodeData::GetTranslation( + m_translationKeysCount, + m_translationKeys, + p_time, + tempMatrix, + translationIndex + ); + SetTranslationIndex(translationIndex); } - local54 = localcc; - local54 -= localb8; + column2 = tempTranslation; + column2 -= column3; - if (local54.Unitize() == 0) { - local5c.EqualsCross(local68, local54); + if (column2.Unitize() == 0) { + column0.EqualsCross(column1, column2); - if (local5c.Unitize() == 0) { - local68.EqualsCross(local54, local5c); + if (column0.Unitize() == 0) { + column1.EqualsCross(column2, column0); - localcc = p_matrix[3]; - localcc += localb0[3]; + tempTranslation = p_matrix[3]; + tempTranslation += tempMatrix[3]; - p_matrix[3][0] = p_matrix[3][1] = p_matrix[3][2] = localb0[3][0] = localb0[3][1] = localb0[3][2] = 0; + p_matrix[3][0] = p_matrix[3][1] = p_matrix[3][2] = tempMatrix[3][0] = tempMatrix[3][1] = tempMatrix[3][2] = + 0; - if (m_unk0x10 != 0) { - LegoU32 locald0 = -1; - LegoU32 locald8; - locald0 = GetUnknown0x20(); + if (m_rotationKeysCount != 0) { + LegoU32 old_index = -1; + LegoU32 i; + old_index = GetRotationIndex(); - LegoU32 localdc = - LegoAnimNodeData::FindKeys(p_time, m_unk0x10, m_unk0x14, sizeof(*m_unk0x14), locald8, locald0); + LegoU32 count = LegoAnimNodeData::FindKeys( + p_time, + m_rotationKeysCount, + m_rotationKeys, + sizeof(*m_rotationKeys), + i, + old_index + ); - SetUnknown0x20(locald0); + SetRotationIndex(old_index); - switch (localdc) { + switch (count) { case 1: - p_matrix.RotateZ(m_unk0x14[locald8].GetZ()); + p_matrix.RotateZ(m_rotationKeys[i].GetZ()); break; case 2: // Seems to be unused LegoFloat z = LegoAnimNodeData::Interpolate( p_time, - m_unk0x14[locald8], - m_unk0x14[locald8].GetZ(), - m_unk0x14[locald8 + 1], - m_unk0x14[locald8 + 1].GetZ() + m_rotationKeys[i], + m_rotationKeys[i].GetZ(), + m_rotationKeys[i + 1], + m_rotationKeys[i + 1].GetZ() ); - p_matrix.RotateZ(m_unk0x14[locald8].GetZ()); + p_matrix.RotateZ(m_rotationKeys[i].GetZ()); break; } } - local4c = p_matrix; - p_matrix.Product(local4c.GetData(), localb0.GetData()); - p_matrix[3][0] = localcc[0]; - p_matrix[3][1] = localcc[1]; - p_matrix[3][2] = localcc[2]; + original = p_matrix; + p_matrix.Product(original.GetData(), tempMatrix.GetData()); + p_matrix[3][0] = tempTranslation[0]; + p_matrix[3][1] = tempTranslation[1]; + p_matrix[3][2] = tempTranslation[2]; } } diff --git a/LEGO1/lego/sources/anim/legoanim.h b/LEGO1/lego/sources/anim/legoanim.h index 7a03d99c..1c297c4d 100644 --- a/LEGO1/lego/sources/anim/legoanim.h +++ b/LEGO1/lego/sources/anim/legoanim.h @@ -137,9 +137,9 @@ class LegoMorphKey : public LegoAnimKey { }; // SIZE 0x0c -class LegoUnknownKey : public LegoAnimKey { +class LegoRotationZKey : public LegoAnimKey { public: - LegoUnknownKey(); + LegoRotationZKey(); LegoResult Read(LegoStorage* p_storage); LegoResult Write(LegoStorage* p_storage); @@ -309,26 +309,26 @@ class LegoAnimScene { ~LegoAnimScene(); LegoResult Read(LegoStorage* p_storage); LegoResult Write(LegoStorage* p_storage); - LegoResult FUN_1009f490(LegoFloat p_time, Matrix4& p_matrix); + LegoResult CalculateCameraTransform(LegoFloat p_time, Matrix4& p_matrix); - LegoU32 GetUnknown0x18() { return m_unk0x18; } - LegoU32 GetUnknown0x1c() { return m_unk0x1c; } - LegoU32 GetUnknown0x20() { return m_unk0x20; } + LegoU32 GetTargetIndex() { return m_targetIndex; } + LegoU32 GetTranslationIndex() { return m_translationIndex; } + LegoU32 GetRotationIndex() { return m_rotationIndex; } - void SetUnknown0x18(LegoU32 p_unk0x18) { m_unk0x18 = p_unk0x18; } - void SetUnknown0x1c(LegoU32 p_unk0x1c) { m_unk0x1c = p_unk0x1c; } - void SetUnknown0x20(LegoU32 p_unk0x20) { m_unk0x20 = p_unk0x20; } + void SetTargetIndex(LegoU32 p_targetIndex) { m_targetIndex = p_targetIndex; } + void SetTranslationIndex(LegoU32 p_translationIndex) { m_translationIndex = p_translationIndex; } + void SetRotationIndex(LegoU32 p_rotationIndex) { m_rotationIndex = p_rotationIndex; } private: - LegoU16 m_unk0x00; // 0x00 - LegoTranslationKey* m_unk0x04; // 0x04 - LegoU16 m_unk0x08; // 0x08 - LegoTranslationKey* m_unk0x0c; // 0x0c - LegoU16 m_unk0x10; // 0x10 - LegoUnknownKey* m_unk0x14; // 0x14 - LegoU32 m_unk0x18; // 0x18 - LegoU32 m_unk0x1c; // 0x1c - LegoU32 m_unk0x20; // 0x20 + LegoU16 m_translationKeysCount; // 0x00 + LegoTranslationKey* m_translationKeys; // 0x04 + LegoU16 m_targetKeysCount; // 0x08 + LegoTranslationKey* m_targetKeys; // 0x0c + LegoU16 m_rotationKeysCount; // 0x10 + LegoRotationZKey* m_rotationKeys; // 0x14 + LegoU32 m_targetIndex; // 0x18 + LegoU32 m_translationIndex; // 0x1c + LegoU32 m_rotationIndex; // 0x20 }; // VTABLE: LEGO1 0x100db8d8 From eae038f6a9b13f4efdae9d83d07cd02e9cfbf963 Mon Sep 17 00:00:00 2001 From: Fabian Neundorf Date: Tue, 22 Jul 2025 20:58:00 +0200 Subject: [PATCH 178/188] Clear unknowns in `MxVideoParamFlags` (#1648) --- ISLE/isleapp.cpp | 2 +- LEGO1/lego/legoomni/src/video/legovideomanager.cpp | 8 ++++---- LEGO1/omni/include/mxvideoparamflags.h | 8 ++++---- LEGO1/omni/src/video/mxdisplaysurface.cpp | 6 +++--- 4 files changed, 12 insertions(+), 12 deletions(-) diff --git a/ISLE/isleapp.cpp b/ISLE/isleapp.cpp index 3f5f485a..7793ef16 100644 --- a/ISLE/isleapp.cpp +++ b/ISLE/isleapp.cpp @@ -221,7 +221,7 @@ void IsleApp::SetupVideoFlags( m_videoParam.Flags().SetLacksLightSupport(!hasLightSupport); m_videoParam.Flags().SetF1bit7(param_7); m_videoParam.Flags().SetWideViewAngle(wideViewAngle); - m_videoParam.Flags().SetF2bit1(1); + m_videoParam.Flags().SetEnabled(TRUE); m_videoParam.SetDeviceName(deviceId); if (using8bit) { m_videoParam.Flags().Set16Bit(0); diff --git a/LEGO1/lego/legoomni/src/video/legovideomanager.cpp b/LEGO1/lego/legoomni/src/video/legovideomanager.cpp index c9c79097..23e4c55d 100644 --- a/LEGO1/lego/legoomni/src/video/legovideomanager.cpp +++ b/LEGO1/lego/legoomni/src/video/legovideomanager.cpp @@ -592,14 +592,14 @@ void LegoVideoManager::EnableFullScreenMovie(MxBool p_enable, MxBool p_scale) m_palette = m_videoParam.GetPalette()->Clone(); OverrideSkyColor(FALSE); - m_displaySurface->GetVideoParam().Flags().SetF1bit3(p_scale); + m_displaySurface->GetVideoParam().Flags().SetDoubleScaling(p_scale); m_render3d = FALSE; m_fullScreenMovie = TRUE; } else { m_displaySurface->ClearScreen(); - m_displaySurface->GetVideoParam().Flags().SetF1bit3(FALSE); + m_displaySurface->GetVideoParam().Flags().SetDoubleScaling(FALSE); // restore previous pallete RealizePalette(m_palette); @@ -624,10 +624,10 @@ void LegoVideoManager::EnableFullScreenMovie(MxBool p_enable, MxBool p_scale) } if (p_enable) { - m_displaySurface->GetVideoParam().Flags().SetF1bit3(p_scale); + m_displaySurface->GetVideoParam().Flags().SetDoubleScaling(p_scale); } else { - m_displaySurface->GetVideoParam().Flags().SetF1bit3(FALSE); + m_displaySurface->GetVideoParam().Flags().SetDoubleScaling(FALSE); } } diff --git a/LEGO1/omni/include/mxvideoparamflags.h b/LEGO1/omni/include/mxvideoparamflags.h index 21ed6921..eed37cbf 100644 --- a/LEGO1/omni/include/mxvideoparamflags.h +++ b/LEGO1/omni/include/mxvideoparamflags.h @@ -19,7 +19,7 @@ class MxVideoParamFlags { void SetBackBuffers(MxBool p_e) { m_flags1.m_bit2 = p_e; } // FUNCTION: BETA10 0x100d9250 - void SetF1bit3(MxBool p_e) { m_flags1.m_bit3 = p_e; } + void SetDoubleScaling(MxBool p_e) { m_flags1.m_bit3 = p_e; } // inlined in ISLE void Set16Bit(MxBool p_e) { m_flags1.m_bit5 = p_e; } @@ -34,7 +34,7 @@ class MxVideoParamFlags { void SetLacksLightSupport(MxBool p_e) { m_flags2.m_bit0 = p_e; } // inlined in ISLE - void SetF2bit1(MxBool p_e) { m_flags2.m_bit1 = p_e; } + void SetEnabled(MxBool p_e) { m_flags2.m_bit1 = p_e; } // FUNCTION: BETA10 0x1009e770 MxBool GetFullScreen() { return m_flags1.m_bit0; } @@ -46,7 +46,7 @@ class MxVideoParamFlags { MxBool GetBackBuffers() { return m_flags1.m_bit2; } // FUNCTION: BETA10 0x10142010 - MxBool GetF1bit3() { return m_flags1.m_bit3; } + MxBool GetDoubleScaling() { return m_flags1.m_bit3; } // FUNCTION: BETA10 0x100d8150 MxBool Get16Bit() { return m_flags1.m_bit5; } @@ -58,7 +58,7 @@ class MxVideoParamFlags { MxBool GetLacksLightSupport() { return m_flags2.m_bit0; } // FUNCTION: BETA10 0x10142050 - MxBool GetF2bit1() { return m_flags2.m_bit1; } + MxBool GetEnabled() { return m_flags2.m_bit1; } private: FlagBitfield m_flags1; diff --git a/LEGO1/omni/src/video/mxdisplaysurface.cpp b/LEGO1/omni/src/video/mxdisplaysurface.cpp index ff46b5f4..6b6f4ce5 100644 --- a/LEGO1/omni/src/video/mxdisplaysurface.cpp +++ b/LEGO1/omni/src/video/mxdisplaysurface.cpp @@ -373,7 +373,7 @@ void MxDisplaySurface::VTable0x28( MxU8* data = p_bitmap->GetStart(p_left, p_top); - if (m_videoParam.Flags().GetF1bit3()) { + if (m_videoParam.Flags().GetDoubleScaling()) { p_bottom *= 2; p_right *= 2; @@ -825,7 +825,7 @@ void MxDisplaySurface::VTable0x34(MxU8* p_pixels, MxS32 p_bpp, MxS32 p_width, Mx // FUNCTION: LEGO1 0x100bba50 void MxDisplaySurface::Display(MxS32 p_left, MxS32 p_top, MxS32 p_left2, MxS32 p_top2, MxS32 p_width, MxS32 p_height) { - if (m_videoParam.Flags().GetF2bit1()) { + if (m_videoParam.Flags().GetEnabled()) { if (m_videoParam.Flags().GetFlipSurfaces()) { if (g_unk0x1010215c < 2) { g_unk0x1010215c++; @@ -1171,7 +1171,7 @@ void MxDisplaySurface::VTable0x24( MxU8* data = p_bitmap->GetStart(p_left, p_top); - if (m_videoParam.Flags().GetF1bit3()) { + if (m_videoParam.Flags().GetDoubleScaling()) { p_bottom *= 2; p_right *= 2; From 2451b041f6e9ad7f498bd8f5adb76da281e940d4 Mon Sep 17 00:00:00 2001 From: Fabian Neundorf Date: Wed, 23 Jul 2025 19:14:10 +0200 Subject: [PATCH 179/188] Clear remaining unknown in `Ambulance` (#1650) --- LEGO1/lego/legoomni/include/ambulance.h | 2 +- LEGO1/lego/legoomni/src/actors/ambulance.cpp | 12 ++++++------ 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/LEGO1/lego/legoomni/include/ambulance.h b/LEGO1/lego/legoomni/include/ambulance.h index cb6a396a..4f0a0623 100644 --- a/LEGO1/lego/legoomni/include/ambulance.h +++ b/LEGO1/lego/legoomni/include/ambulance.h @@ -211,7 +211,7 @@ class Ambulance : public IslePathActor { MxS16 m_atPoliceTask; // 0x16c MxS16 m_atBeachTask; // 0x16e MxS16 m_taskState; // 0x170 - MxS16 m_unk0x172; // 0x172 + MxS16 m_enableRandomAudio; // 0x172 IsleScript::Script m_lastAction; // 0x174 IsleScript::Script m_lastAnimation; // 0x178 MxFloat m_fuel; // 0x17c diff --git a/LEGO1/lego/legoomni/src/actors/ambulance.cpp b/LEGO1/lego/legoomni/src/actors/ambulance.cpp index 0bc2a9e6..7049009f 100644 --- a/LEGO1/lego/legoomni/src/actors/ambulance.cpp +++ b/LEGO1/lego/legoomni/src/actors/ambulance.cpp @@ -41,7 +41,7 @@ Ambulance::Ambulance() m_atBeachTask = 0; m_taskState = Ambulance::e_none; m_lastAction = IsleScript::c_noneIsle; - m_unk0x172 = 0; + m_enableRandomAudio = 0; m_lastAnimation = IsleScript::c_noneIsle; m_fuel = 1.0; } @@ -173,7 +173,7 @@ MxLong Ambulance::HandleEndAction(MxEndActionNotificationParam& p_param) m_state->m_state = AmbulanceMissionState::e_enteredAmbulance; CurrentWorld()->PlaceActor(UserActor()); HandleClick(); - m_unk0x172 = 0; + m_enableRandomAudio = 0; TickleManager()->RegisterClient(this, 40000); } else if (objectId == IsleScript::c_hpz047pe_RunAnim || objectId == IsleScript::c_hpz048pe_RunAnim || objectId == IsleScript::c_hpz049bd_RunAnim || objectId == IsleScript::c_hpz053pa_RunAnim) { @@ -198,7 +198,7 @@ MxLong Ambulance::HandleEndAction(MxEndActionNotificationParam& p_param) CurrentWorld()->PlaceActor(UserActor()); HandleClick(); SpawnPlayer(LegoGameState::e_pizzeriaExterior, TRUE, 0); - m_unk0x172 = 0; + m_enableRandomAudio = 0; TickleManager()->RegisterClient(this, 40000); if (m_atPoliceTask != 0) { @@ -222,7 +222,7 @@ MxLong Ambulance::HandleEndAction(MxEndActionNotificationParam& p_param) CurrentWorld()->PlaceActor(UserActor()); HandleClick(); SpawnPlayer(LegoGameState::e_policeExited, TRUE, 0); - m_unk0x172 = 0; + m_enableRandomAudio = 0; TickleManager()->RegisterClient(this, 40000); if (m_atBeachTask != 0) { @@ -513,8 +513,8 @@ void Ambulance::ActivateSceneActions() // FUNCTION: BETA10 0x100237df MxResult Ambulance::Tickle() { - if (m_unk0x172 == 0) { - m_unk0x172 = 1; + if (m_enableRandomAudio == 0) { + m_enableRandomAudio = 1; } else if (m_lastAction == IsleScript::c_noneIsle) { IsleScript::Script objectId; From 8f6bfe078b6523d04dd1cbefa4ef0acf135a6e25 Mon Sep 17 00:00:00 2001 From: VoxelTek <53562267+VoxelTek@users.noreply.github.com> Date: Thu, 24 Jul 2025 03:33:39 +1000 Subject: [PATCH 180/188] Add MSAA to `isle-config`, improve Exclusive Fullscreen resolution options. (#638) * Add dropdown for exclusive fullscreen resolutions * Add MSAA support * Make clang-format happy * Fix tab order * Make clang-format happy again --- CONFIG/MainDlg.cpp | 127 ++++++++++++++++++++++----- CONFIG/MainDlg.h | 9 +- CONFIG/config.cpp | 30 ++++++- CONFIG/config.h | 4 + CONFIG/res/maindialog.ui | 180 +++++++++++++++++++++++++++++++-------- ISLE/isleapp.cpp | 18 +++- ISLE/isleapp.h | 3 + 7 files changed, 307 insertions(+), 64 deletions(-) diff --git a/CONFIG/MainDlg.cpp b/CONFIG/MainDlg.cpp index 3f69d096..55f06cf3 100644 --- a/CONFIG/MainDlg.cpp +++ b/CONFIG/MainDlg.cpp @@ -13,6 +13,8 @@ #include #include #include +#include +#include #include #include @@ -54,15 +56,18 @@ CMainDialog::CMainDialog(QWidget* pParent) : QDialog(pParent) this, &CMainDialog::OnRadiobuttonTextureHighQuality ); + + connect(m_ui->windowedRadioButton, &QRadioButton::toggled, this, &CMainDialog::OnRadioWindowed); + connect(m_ui->fullscreenRadioButton, &QRadioButton::toggled, this, &CMainDialog::OnRadioFullscreen); + connect(m_ui->exFullscreenRadioButton, &QRadioButton::toggled, this, &CMainDialog::OnRadioExclusiveFullscreen); connect(m_ui->devicesList, &QListWidget::currentRowChanged, this, &CMainDialog::OnList3DevicesSelectionChanged); connect(m_ui->musicCheckBox, &QCheckBox::toggled, this, &CMainDialog::OnCheckboxMusic); connect(m_ui->sound3DCheckBox, &QCheckBox::toggled, this, &CMainDialog::OnCheckbox3DSound); - connect(m_ui->fullscreenCheckBox, &QCheckBox::toggled, this, &CMainDialog::OnCheckboxFullscreen); - connect(m_ui->exclusiveFullscreenCheckbox, &QCheckBox::toggled, this, &CMainDialog::OnCheckboxExclusiveFullscreen); connect(m_ui->rumbleCheckBox, &QCheckBox::toggled, this, &CMainDialog::OnCheckboxRumble); connect(m_ui->textureCheckBox, &QCheckBox::toggled, this, &CMainDialog::OnCheckboxTexture); connect(m_ui->touchComboBox, &QComboBox::currentIndexChanged, this, &CMainDialog::TouchControlsChanged); connect(m_ui->transitionTypeComboBox, &QComboBox::currentIndexChanged, this, &CMainDialog::TransitionTypeChanged); + connect(m_ui->exFullResComboBox, &QComboBox::currentIndexChanged, this, &CMainDialog::ExclusiveResolutionChanged); connect(m_ui->okButton, &QPushButton::clicked, this, &CMainDialog::accept); connect(m_ui->cancelButton, &QPushButton::clicked, this, &CMainDialog::reject); connect(m_ui->launchButton, &QPushButton::clicked, this, &CMainDialog::launch); @@ -81,6 +86,9 @@ CMainDialog::CMainDialog(QWidget* pParent) : QDialog(pParent) connect(m_ui->maxActorsSlider, &QSlider::valueChanged, this, &CMainDialog::MaxActorsChanged); connect(m_ui->maxActorsSlider, &QSlider::sliderMoved, this, &CMainDialog::MaxActorsChanged); + connect(m_ui->msaaSlider, &QSlider::valueChanged, this, &CMainDialog::MSAAChanged); + connect(m_ui->msaaSlider, &QSlider::sliderMoved, this, &CMainDialog::MSAAChanged); + connect(m_ui->aspectRatioComboBox, &QComboBox::currentIndexChanged, this, &CMainDialog::AspectRatioChanged); connect(m_ui->xResSpinBox, &QSpinBox::valueChanged, this, &CMainDialog::XResChanged); connect(m_ui->yResSpinBox, &QSpinBox::valueChanged, this, &CMainDialog::YResChanged); @@ -164,6 +172,23 @@ bool CMainDialog::OnInitDialog() m_ui->maxActorsSlider->setValue(currentConfigApp->m_max_actors); m_ui->maxActorsNum->setNum(currentConfigApp->m_max_actors); + m_ui->exFullResComboBox->clear(); + + int displayModeCount; + displayModes = SDL_GetFullscreenDisplayModes(SDL_GetPrimaryDisplay(), &displayModeCount); + + for (int i = 0; i < displayModeCount; ++i) { + QString mode = + QString("%1x%2 @ %3Hz").arg(displayModes[i]->w).arg(displayModes[i]->h).arg(displayModes[i]->refresh_rate); + m_ui->exFullResComboBox->addItem(mode); + + if ((displayModes[i]->w == currentConfigApp->m_exf_x_res) && + (displayModes[i]->h == currentConfigApp->m_exf_y_res) && + (displayModes[i]->refresh_rate == currentConfigApp->m_exf_fps)) { + m_ui->exFullResComboBox->setCurrentIndex(i); + } + } + UpdateInterface(); return true; } @@ -258,18 +283,30 @@ void CMainDialog::UpdateInterface() else { m_ui->textureQualityHighRadioButton->setChecked(true); } + if (currentConfigApp->m_exclusive_full_screen) { + m_ui->exFullscreenRadioButton->setChecked(true); + m_ui->resolutionBox->setEnabled(false); + m_ui->exFullResContainer->setEnabled(true); + } + else { + m_ui->resolutionBox->setEnabled(true); + m_ui->exFullResContainer->setEnabled(false); + if (currentConfigApp->m_full_screen) { + m_ui->fullscreenRadioButton->setChecked(true); + } + else { + m_ui->windowedRadioButton->setChecked(true); + } + } m_ui->musicCheckBox->setChecked(currentConfigApp->m_music); - m_ui->fullscreenCheckBox->setChecked(currentConfigApp->m_full_screen); - m_ui->exclusiveFullscreenCheckbox->setEnabled(currentConfigApp->m_full_screen); - m_ui->exclusiveFullscreenCheckbox->setChecked(currentConfigApp->m_exclusive_full_screen); m_ui->rumbleCheckBox->setChecked(currentConfigApp->m_haptic); m_ui->touchComboBox->setCurrentIndex(currentConfigApp->m_touch_scheme); m_ui->transitionTypeComboBox->setCurrentIndex(currentConfigApp->m_transition_type); m_ui->dataPath->setText(QString::fromStdString(currentConfigApp->m_cd_path)); m_ui->savePath->setText(QString::fromStdString(currentConfigApp->m_save_path)); + m_ui->textureCheckBox->setChecked(currentConfigApp->m_texture_load); m_ui->texturePath->setText(QString::fromStdString(currentConfigApp->m_texture_path)); - m_ui->texturePath->setEnabled(currentConfigApp->m_texture_load); m_ui->texturePathOpen->setEnabled(currentConfigApp->m_texture_load); @@ -277,6 +314,13 @@ void CMainDialog::UpdateInterface() m_ui->xResSpinBox->setValue(currentConfigApp->m_x_res); m_ui->yResSpinBox->setValue(currentConfigApp->m_y_res); m_ui->framerateSpinBox->setValue(static_cast(std::round(1000.0f / currentConfigApp->m_frame_delta))); + + m_ui->maxLoDSlider->setValue((int) (currentConfigApp->m_max_lod * 10)); + m_ui->LoDNum->setNum(currentConfigApp->m_max_lod); + m_ui->maxActorsSlider->setValue(currentConfigApp->m_max_actors); + m_ui->maxActorsNum->setNum(currentConfigApp->m_max_actors); + m_ui->msaaSlider->setValue(log2(currentConfigApp->m_msaa)); + m_ui->msaaNum->setNum(currentConfigApp->m_msaa); } // FUNCTION: CONFIG 0x004045e0 @@ -336,6 +380,42 @@ void CMainDialog::OnRadiobuttonTextureHighQuality(bool checked) } } +void CMainDialog::OnRadioWindowed(bool checked) +{ + if (checked) { + currentConfigApp->m_full_screen = false; + currentConfigApp->m_exclusive_full_screen = false; + m_ui->resolutionBox->setEnabled(true); + m_ui->exFullResContainer->setEnabled(false); + m_modified = true; + UpdateInterface(); + } +} + +void CMainDialog::OnRadioFullscreen(bool checked) +{ + if (checked) { + currentConfigApp->m_full_screen = true; + currentConfigApp->m_exclusive_full_screen = false; + m_ui->resolutionBox->setEnabled(true); + m_ui->exFullResContainer->setEnabled(false); + m_modified = true; + UpdateInterface(); + } +} + +void CMainDialog::OnRadioExclusiveFullscreen(bool checked) +{ + if (checked) { + currentConfigApp->m_full_screen = true; + currentConfigApp->m_exclusive_full_screen = true; + m_ui->resolutionBox->setEnabled(false); + m_ui->exFullResContainer->setEnabled(true); + m_modified = true; + UpdateInterface(); + } +} + // FUNCTION: CONFIG 0x004048c0 void CMainDialog::OnCheckboxMusic(bool checked) { @@ -344,21 +424,6 @@ void CMainDialog::OnCheckboxMusic(bool checked) UpdateInterface(); } -void CMainDialog::OnCheckboxFullscreen(bool checked) -{ - currentConfigApp->m_full_screen = checked; - m_ui->exclusiveFullscreenCheckbox->setEnabled(checked); - m_modified = true; - UpdateInterface(); -} - -void CMainDialog::OnCheckboxExclusiveFullscreen(bool checked) -{ - currentConfigApp->m_exclusive_full_screen = checked; - m_modified = true; - UpdateInterface(); -} - void CMainDialog::OnCheckboxRumble(bool checked) { currentConfigApp->m_haptic = checked; @@ -387,6 +452,15 @@ void CMainDialog::TransitionTypeChanged(int index) UpdateInterface(); } +void CMainDialog::ExclusiveResolutionChanged(int index) +{ + currentConfigApp->m_exf_x_res = displayModes[index]->w; + currentConfigApp->m_exf_y_res = displayModes[index]->h; + currentConfigApp->m_exf_fps = displayModes[index]->refresh_rate; + m_modified = true; + UpdateInterface(); +} + void CMainDialog::SelectDataPathDialog() { QString data_path = QString::fromStdString(currentConfigApp->m_cd_path); @@ -458,15 +532,22 @@ void CMainDialog::SavePathEdited() void CMainDialog::MaxLoDChanged(int value) { currentConfigApp->m_max_lod = static_cast(value) / 10.0f; - m_ui->LoDNum->setNum(value); m_modified = true; + UpdateInterface(); } void CMainDialog::MaxActorsChanged(int value) { currentConfigApp->m_max_actors = value; - m_ui->maxActorsNum->setNum(value); m_modified = true; + UpdateInterface(); +} + +void CMainDialog::MSAAChanged(int value) +{ + currentConfigApp->m_msaa = exp2(value); + m_modified = true; + UpdateInterface(); } void CMainDialog::SelectTexturePathDialog() diff --git a/CONFIG/MainDlg.h b/CONFIG/MainDlg.h index ece4dbfd..c453c785 100644 --- a/CONFIG/MainDlg.h +++ b/CONFIG/MainDlg.h @@ -7,6 +7,7 @@ #include #include +#include namespace Ui { @@ -29,6 +30,7 @@ class CMainDialog : public QDialog { bool m_modified = false; bool m_advanced = false; Ui::MainDialog* m_ui = nullptr; + SDL_DisplayMode** displayModes; void keyReleaseEvent(QKeyEvent* event) override; bool OnInitDialog(); @@ -40,13 +42,15 @@ private slots: void OnRadiobuttonModelHighQuality(bool checked); void OnRadiobuttonTextureLowQuality(bool checked); void OnRadiobuttonTextureHighQuality(bool checked); + void OnRadioWindowed(bool checked); + void OnRadioFullscreen(bool checked); + void OnRadioExclusiveFullscreen(bool checked); void OnCheckboxMusic(bool checked); - void OnCheckboxFullscreen(bool checked); - void OnCheckboxExclusiveFullscreen(bool checked); void OnCheckboxRumble(bool checked); void OnCheckboxTexture(bool checked); void TouchControlsChanged(int index); void TransitionTypeChanged(int index); + void ExclusiveResolutionChanged(int index); void accept() override; void reject() override; void launch(); @@ -56,6 +60,7 @@ private slots: void SavePathEdited(); void MaxLoDChanged(int value); void MaxActorsChanged(int value); + void MSAAChanged(int value); void SelectTexturePathDialog(); void TexturePathEdited(); void XResChanged(int i); diff --git a/CONFIG/config.cpp b/CONFIG/config.cpp index d0f1dda6..fd61fbb8 100644 --- a/CONFIG/config.cpp +++ b/CONFIG/config.cpp @@ -68,8 +68,9 @@ bool CConfigApp::InitInstance() } SDL_DestroyWindow(window); m_aspect_ratio = 0; - m_x_res = 640; - m_y_res = 480; + m_exf_x_res = m_x_res = 640; + m_exf_y_res = m_y_res = 480; + m_exf_fps = 60.00f; m_frame_delta = 10.0f; m_driver = NULL; m_device = NULL; @@ -83,6 +84,7 @@ bool CConfigApp::InitInstance() m_3d_video_ram = FALSE; m_joystick_index = -1; m_display_bit_depth = 16; + m_msaa = 1; m_haptic = TRUE; m_touch_scheme = 2; m_texture_load = TRUE; @@ -181,11 +183,15 @@ bool CConfigApp::ReadRegisterSettings() m_joystick_index = iniparser_getint(dict, "isle:JoystickIndex", m_joystick_index); m_max_lod = iniparser_getdouble(dict, "isle:Max LOD", m_max_lod); m_max_actors = iniparser_getint(dict, "isle:Max Allowed Extras", m_max_actors); + m_msaa = iniparser_getint(dict, "isle:MSAA", m_msaa); m_texture_load = iniparser_getboolean(dict, "extensions:texture loader", m_texture_load); m_texture_path = iniparser_getstring(dict, "texture loader:texture path", m_texture_path.c_str()); m_aspect_ratio = iniparser_getint(dict, "isle:Aspect Ratio", m_aspect_ratio); m_x_res = iniparser_getint(dict, "isle:Horizontal Resolution", m_x_res); m_y_res = iniparser_getint(dict, "isle:Vertical Resolution", m_y_res); + m_exf_x_res = iniparser_getint(dict, "isle:Exclusive X Resolution", m_exf_x_res); + m_exf_y_res = iniparser_getint(dict, "isle:Exclusive Y Resolution", m_exf_y_res); + m_exf_fps = iniparser_getdouble(dict, "isle:Exclusive Framerate", m_exf_fps); m_frame_delta = iniparser_getdouble(dict, "isle:Frame Delta", m_frame_delta); iniparser_freedict(dict); return true; @@ -259,6 +265,22 @@ bool CConfigApp::ValidateSettings() m_touch_scheme = 2; is_modified = TRUE; } + if (m_exclusive_full_screen && !m_full_screen) { + m_full_screen = TRUE; + is_modified = TRUE; + } + if (!(m_msaa & (m_msaa - 1))) { // Check if MSAA is power of 2 (1, 2, 4, 8, etc) + m_msaa = exp2(round(log2(m_msaa))); // Closest power of 2 + is_modified = TRUE; + } + if (m_msaa > 16) { + m_msaa = 16; + is_modified = TRUE; + } + else if (m_msaa < 1) { + m_msaa = 1; + is_modified = TRUE; + } return is_modified; } @@ -337,6 +359,7 @@ void CConfigApp::WriteRegisterSettings() const iniparser_set(dict, "isle:savepath", m_save_path.c_str()); SetIniInt(dict, "isle:Display Bit Depth", m_display_bit_depth); + SetIniInt(dict, "isle:MSAA", m_msaa); SetIniBool(dict, "isle:Flip Surfaces", m_flip_surfaces); SetIniBool(dict, "isle:Full Screen", m_full_screen); SetIniBool(dict, "isle:Exclusive Full Screen", m_exclusive_full_screen); @@ -367,6 +390,9 @@ void CConfigApp::WriteRegisterSettings() const SetIniInt(dict, "isle:Aspect Ratio", m_aspect_ratio); SetIniInt(dict, "isle:Horizontal Resolution", m_x_res); SetIniInt(dict, "isle:Vertical Resolution", m_y_res); + SetIniInt(dict, "isle:Exclusive X Resolution", m_exf_x_res); + SetIniInt(dict, "isle:Exclusive Y Resolution", m_exf_y_res); + iniparser_set(dict, "isle:Exclusive Framerate", std::to_string(m_exf_fps).c_str()); iniparser_set(dict, "isle:Frame Delta", std::to_string(m_frame_delta).c_str()); #undef SetIniBool diff --git a/CONFIG/config.h b/CONFIG/config.h index ad3321ac..964bad8c 100644 --- a/CONFIG/config.h +++ b/CONFIG/config.h @@ -62,11 +62,15 @@ class CConfigApp { int m_aspect_ratio; int m_x_res; int m_y_res; + int m_exf_x_res; + int m_exf_y_res; + float m_exf_fps; float m_frame_delta; LegoDeviceEnumerate* m_device_enumerator; MxDriver* m_driver; Direct3DDeviceInfo* m_device; int m_display_bit_depth; + int m_msaa; bool m_flip_surfaces; bool m_full_screen; bool m_exclusive_full_screen; diff --git a/CONFIG/res/maindialog.ui b/CONFIG/res/maindialog.ui index 5a0d3b31..e1a8bf3e 100644 --- a/CONFIG/res/maindialog.ui +++ b/CONFIG/res/maindialog.ui @@ -56,6 +56,9 @@ false + + fullscreenRadioContainer_2 + @@ -353,12 +356,18 @@ A higher setting will cause higher quality textures to be drawn regardless of di - 15 + 20 0 + + + 20 + 16777215 + + - 35 + 3.5 @@ -533,10 +542,16 @@ The game will gradually increase the number of actors until this maximum is reac - 15 + 20 0 + + + 20 + 16777215 + + 20 @@ -602,8 +617,59 @@ The game will gradually increase the number of actors until this maximum is reac General - - + + + + + + 0 + 0 + + + + <html><head/><body><p><span style=" font-weight:700;">Windowed:</span> Runs in a window, the initial resolution of which is dictated by Windowed Resolution. </p><p><span style=" font-weight:700;">Fullscreen:</span> Runs in a borderless window that consumes the entire screen. </p><p><span style=" font-weight:700;">Exclusive Fullscreen:</span> Grants the app full control of the display for better performance and lower input lag. May cause slower alt-tabbing.</p></body></html> + + + + 3 + + + 0 + + + 0 + + + 0 + + + 0 + + + + + Windowed + + + + + + + Fullscreen + + + + + + + Exclusive Fullscreen + + + + + + + <html><head/><body><p>Maximum framerate. Values above 100fps are untested.</p></body></html> @@ -622,33 +688,6 @@ The game will gradually increase the number of actors until this maximum is reac - - - - - - Toggle fullscreen display mode. - - - Fullscreen - - - - - - - false - - - Grants the app full control of the display for better performance and lower input lag. May cause slower alt-tabbing. - - - Exclusive Fullscreen - - - - - @@ -764,6 +803,72 @@ The game will gradually increase the number of actors until this maximum is reac + + + + false + + + Exclusive Fullscreen Resolution + + + + + + Resolution + + + + + + + + + + MSAA + + + + + + 0 + + + 4 + + + 1 + + + Qt::Orientation::Horizontal + + + QSlider::TickPosition::TicksBothSides + + + 1 + + + + + + + + 16 + 0 + + + + 1 + + + Qt::AlignmentFlag::AlignCenter + + + + + + @@ -772,7 +877,7 @@ The game will gradually increase the number of actors until this maximum is reac 20 - 40 + 0 @@ -860,7 +965,7 @@ The game will gradually increase the number of actors until this maximum is reac Settings for Texture Loader extension. - Texture Loader Extension + Texture Loader @@ -1007,12 +1112,15 @@ The game will gradually increase the number of actors until this maximum is reac maxLoDSlider maxActorsSlider devicesList - fullscreenCheckBox - exclusiveFullscreenCheckbox + windowedRadioButton + fullscreenRadioButton + exFullscreenRadioButton framerateSpinBox aspectRatioComboBox xResSpinBox yResSpinBox + exFullResComboBox + msaaSlider touchComboBox rumbleCheckBox textureCheckBox diff --git a/ISLE/isleapp.cpp b/ISLE/isleapp.cpp index ee440e8f..b2fab5fb 100644 --- a/ISLE/isleapp.cpp +++ b/ISLE/isleapp.cpp @@ -177,6 +177,9 @@ IsleApp::IsleApp() m_haptic = TRUE; m_xRes = 640; m_yRes = 480; + m_exclusiveXRes = m_xRes; + m_exclusiveYRes = m_yRes; + m_exclusiveFrameRate = 60.00f; m_frameRate = 100.0f; m_exclusiveFullScreen = FALSE; } @@ -915,7 +918,14 @@ MxResult IsleApp::SetupWindow() if (m_exclusiveFullScreen && m_fullScreen) { SDL_DisplayMode closestMode; SDL_DisplayID displayID = SDL_GetDisplayForWindow(window); - if (SDL_GetClosestFullscreenDisplayMode(displayID, m_xRes, m_yRes, m_frameRate, true, &closestMode)) { + if (SDL_GetClosestFullscreenDisplayMode( + displayID, + m_exclusiveXRes, + m_exclusiveYRes, + m_exclusiveFrameRate, + true, + &closestMode + )) { SDL_SetWindowFullscreenMode(window, &closestMode); } } @@ -1093,6 +1103,9 @@ bool IsleApp::LoadConfig() iniparser_set(dict, "isle:Haptic", m_haptic ? "true" : "false"); iniparser_set(dict, "isle:Horizontal Resolution", SDL_itoa(m_xRes, buf, 10)); iniparser_set(dict, "isle:Vertical Resolution", SDL_itoa(m_yRes, buf, 10)); + iniparser_set(dict, "isle:Exclusive X Resolution", SDL_itoa(m_exclusiveXRes, buf, 10)); + iniparser_set(dict, "isle:Exclusive Y Resolution", SDL_itoa(m_exclusiveYRes, buf, 10)); + iniparser_set(dict, "isle:Exclusive Framerate", SDL_itoa(m_exclusiveFrameRate, buf, 10)); iniparser_set(dict, "isle:Frame Delta", SDL_itoa(m_frameDelta, buf, 10)); #ifdef EXTENSIONS @@ -1167,6 +1180,9 @@ bool IsleApp::LoadConfig() m_haptic = iniparser_getboolean(dict, "isle:Haptic", m_haptic); m_xRes = iniparser_getint(dict, "isle:Horizontal Resolution", m_xRes); m_yRes = iniparser_getint(dict, "isle:Vertical Resolution", m_yRes); + m_exclusiveXRes = iniparser_getint(dict, "isle:Exclusive X Resolution", m_exclusiveXRes); + m_exclusiveYRes = iniparser_getint(dict, "isle:Exclusive Y Resolution", m_exclusiveXRes); + m_exclusiveFrameRate = iniparser_getdouble(dict, "isle:Exclusive Framerate", m_exclusiveFrameRate); if (!m_fullScreen) { m_videoParam.GetRect() = MxRect32(0, 0, (m_xRes - 1), (m_yRes - 1)); } diff --git a/ISLE/isleapp.h b/ISLE/isleapp.h index 4a23ea23..06c1b51f 100644 --- a/ISLE/isleapp.h +++ b/ISLE/isleapp.h @@ -109,6 +109,9 @@ class IsleApp { MxBool m_haptic; MxS32 m_xRes; MxS32 m_yRes; + MxS32 m_exclusiveXRes; + MxS32 m_exclusiveYRes; + MxFloat m_exclusiveFrameRate; MxFloat m_frameRate; MxBool m_exclusiveFullScreen; }; From a5a3c4ec83092f1bacb92e98715f5f2edc9a857d Mon Sep 17 00:00:00 2001 From: Christian Semmler Date: Wed, 23 Jul 2025 11:00:48 -0700 Subject: [PATCH 181/188] Upgrade OpenGL ES renderer to 3.0, add option for MSAA (#636) --- CMakeLists.txt | 4 +- ISLE/isleapp.cpp | 3 + ISLE/isleapp.h | 1 + LEGO1/mxdirectx/mxdirect3d.cpp | 20 + LEGO1/mxdirectx/mxdirectxinfo.cpp | 19 + LEGO1/omni/include/mxvideoparam.h | 6 + LEGO1/omni/src/video/mxvideoparam.cpp | 3 + miniwin/CMakeLists.txt | 16 +- miniwin/include/miniwin/miniwind3d.h | 8 + .../{opengles2 => opengles3}/renderer.cpp | 344 +++++++++--------- miniwin/src/d3drm/d3drm.cpp | 6 +- miniwin/src/d3drm/d3drmrenderer.cpp | 22 +- miniwin/src/ddraw/ddraw.cpp | 20 +- miniwin/src/internal/d3drmrenderer.h | 9 +- ..._opengles2.h => d3drmrenderer_opengles3.h} | 49 ++- miniwin/src/internal/ddraw_impl.h | 7 +- 16 files changed, 324 insertions(+), 213 deletions(-) create mode 100644 miniwin/include/miniwin/miniwind3d.h rename miniwin/src/d3drm/backends/{opengles2 => opengles3}/renderer.cpp (76%) rename miniwin/src/internal/{d3drmrenderer_opengles2.h => d3drmrenderer_opengles3.h} (71%) diff --git a/CMakeLists.txt b/CMakeLists.txt index f43617c0..24860101 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -14,7 +14,7 @@ endif() if (EMSCRIPTEN) add_compile_options(-pthread) - add_link_options(-sALLOW_MEMORY_GROWTH=1 -sMAXIMUM_MEMORY=2gb -sUSE_PTHREADS=1 -sPROXY_TO_PTHREAD=1 -sOFFSCREENCANVAS_SUPPORT=1 -sPTHREAD_POOL_SIZE_STRICT=0 -sFORCE_FILESYSTEM=1 -sWASMFS=1 -sEXIT_RUNTIME=1) + add_link_options(-sUSE_WEBGL2=1 -sMIN_WEBGL_VERSION=2 -sALLOW_MEMORY_GROWTH=1 -sMAXIMUM_MEMORY=2gb -sUSE_PTHREADS=1 -sPROXY_TO_PTHREAD=1 -sOFFSCREENCANVAS_SUPPORT=1 -sPTHREAD_POOL_SIZE_STRICT=0 -sFORCE_FILESYSTEM=1 -sWASMFS=1 -sEXIT_RUNTIME=1) set(SDL_PTHREADS ON CACHE BOOL "Enable SDL pthreads" FORCE) endif() @@ -181,6 +181,8 @@ target_include_directories(lego1 PUBLIC "$:DirectX5::DirectX5>) +# Allow unconditional include of miniwin/miniwind3d.h +target_link_libraries(lego1 PRIVATE miniwin-headers) if(WIN32) set_property(TARGET lego1 PROPERTY PREFIX "") endif() diff --git a/ISLE/isleapp.cpp b/ISLE/isleapp.cpp index b2fab5fb..b2a39dcc 100644 --- a/ISLE/isleapp.cpp +++ b/ISLE/isleapp.cpp @@ -182,6 +182,7 @@ IsleApp::IsleApp() m_exclusiveFrameRate = 60.00f; m_frameRate = 100.0f; m_exclusiveFullScreen = FALSE; + m_msaaSamples = 0; } // FUNCTION: ISLE 0x4011a0 @@ -914,6 +915,7 @@ MxResult IsleApp::SetupWindow() #endif window = SDL_CreateWindowWithProperties(props); + SDL_SetPointerProperty(SDL_GetWindowProperties(window), ISLE_PROP_WINDOW_CREATE_VIDEO_PARAM, &m_videoParam); if (m_exclusiveFullScreen && m_fullScreen) { SDL_DisplayMode closestMode; @@ -1188,6 +1190,7 @@ bool IsleApp::LoadConfig() } m_frameRate = (1000.0f / iniparser_getdouble(dict, "isle:Frame Delta", m_frameDelta)); m_frameDelta = static_cast(std::round(iniparser_getdouble(dict, "isle:Frame Delta", m_frameDelta))); + m_videoParam.SetMSAASamples(iniparser_getint(dict, "isle:MSAA", m_msaaSamples)); const char* deviceId = iniparser_getstring(dict, "isle:3D Device ID", NULL); if (deviceId != NULL) { diff --git a/ISLE/isleapp.h b/ISLE/isleapp.h index 06c1b51f..7346d817 100644 --- a/ISLE/isleapp.h +++ b/ISLE/isleapp.h @@ -114,6 +114,7 @@ class IsleApp { MxFloat m_exclusiveFrameRate; MxFloat m_frameRate; MxBool m_exclusiveFullScreen; + MxU32 m_msaaSamples; }; extern IsleApp* g_isle; diff --git a/LEGO1/mxdirectx/mxdirect3d.cpp b/LEGO1/mxdirectx/mxdirect3d.cpp index 7f12d147..5eacf824 100644 --- a/LEGO1/mxdirectx/mxdirect3d.cpp +++ b/LEGO1/mxdirectx/mxdirect3d.cpp @@ -1,7 +1,12 @@ +#ifndef MINIWIN +#include +#endif + #include "mxdirect3d.h" #include // for SDL_Log #include +#include DECOMP_SIZE_ASSERT(MxDirect3D, 0x894) @@ -44,6 +49,7 @@ BOOL MxDirect3D::Create( ) { BOOL success = FALSE; + IDirect3DMiniwin* miniwind3d = nullptr; assert(m_currentDeviceInfo); if (!MxDirectDraw::Create( @@ -64,6 +70,20 @@ BOOL MxDirect3D::Create( goto done; } + if (m_pDirect3d->QueryInterface(IID_IDirect3DMiniwin, (void**) &miniwind3d) == DD_OK) { + MxVideoParam* videoParam = (MxVideoParam*) SDL_GetPointerProperty( + SDL_GetWindowProperties(reinterpret_cast(hWnd)), + ISLE_PROP_WINDOW_CREATE_VIDEO_PARAM, + nullptr + ); +#ifndef MXDIRECTX_FOR_CONFIG + assert(videoParam); +#endif + if (videoParam) { + miniwind3d->RequestMSAA(videoParam->GetMSAASamples()); + } + } + if (!D3DSetMode()) { goto done; } diff --git a/LEGO1/mxdirectx/mxdirectxinfo.cpp b/LEGO1/mxdirectx/mxdirectxinfo.cpp index 3451b8fc..bbff9534 100644 --- a/LEGO1/mxdirectx/mxdirectxinfo.cpp +++ b/LEGO1/mxdirectx/mxdirectxinfo.cpp @@ -1,7 +1,10 @@ #include "mxdirectxinfo.h" +#include "omni/include/mxvideoparam.h" + #include #include +#include #include // for vsprintf DECOMP_SIZE_ASSERT(MxAssignedDevice, 0xe4) @@ -216,12 +219,28 @@ BOOL MxDeviceEnumerate::EnumDirectDrawCallback(LPGUID p_guid, LPSTR p_driverDesc LPDIRECTDRAW lpDD = NULL; MxDriver& newDevice = m_ddInfo.back(); HRESULT result = DirectDrawCreate(newDevice.m_guid, &lpDD, NULL); + IDirect3DMiniwin* miniwind3d = nullptr; if (result != DD_OK) { BuildErrorString("DirectDraw Create failed: %s\n", EnumerateErrorToString(result)); goto done; } + result = lpDD->QueryInterface(IID_IDirect3DMiniwin, (void**) &miniwind3d); + if (result == DD_OK) { + MxVideoParam* videoParam = (MxVideoParam*) SDL_GetPointerProperty( + SDL_GetWindowProperties(reinterpret_cast(m_hWnd)), + ISLE_PROP_WINDOW_CREATE_VIDEO_PARAM, + nullptr + ); +#ifndef MXDIRECTX_FOR_CONFIG + assert(videoParam); +#endif + if (videoParam) { + miniwind3d->RequestMSAA(videoParam->GetMSAASamples()); + } + } + result = lpDD->SetCooperativeLevel(m_hWnd, DDSCL_NORMAL); if (result != DD_OK) { BuildErrorString("SetCooperativeLevel failed: %s\n", EnumerateErrorToString(result)); diff --git a/LEGO1/omni/include/mxvideoparam.h b/LEGO1/omni/include/mxvideoparam.h index ccc073ab..af2fcddf 100644 --- a/LEGO1/omni/include/mxvideoparam.h +++ b/LEGO1/omni/include/mxvideoparam.h @@ -15,6 +15,8 @@ class MxPalette; +#define ISLE_PROP_WINDOW_CREATE_VIDEO_PARAM "ISLE.window.create.videoParam" + // SIZE 0x24 class MxVideoParam { public: @@ -51,6 +53,9 @@ class MxVideoParam { // FUNCTION: BETA10 0x10141fe0 void SetBackBuffers(MxU32 p_backBuffers) { m_backBuffers = p_backBuffers; } + void SetMSAASamples(MxU32 p_msaaSamples) { m_msaaSamples = p_msaaSamples; } + MxU32 GetMSAASamples() { return m_msaaSamples; } + private: MxRect32 m_rect; // 0x00 MxPalette* m_palette; // 0x10 @@ -58,6 +63,7 @@ class MxVideoParam { MxVideoParamFlags m_flags; // 0x18 int m_unk0x1c; // 0x1c char* m_deviceId; // 0x20 + MxU32 m_msaaSamples; }; #endif // MXVIDEOPARAM_H diff --git a/LEGO1/omni/src/video/mxvideoparam.cpp b/LEGO1/omni/src/video/mxvideoparam.cpp index 9a095c4e..7c029e52 100644 --- a/LEGO1/omni/src/video/mxvideoparam.cpp +++ b/LEGO1/omni/src/video/mxvideoparam.cpp @@ -28,6 +28,7 @@ MxVideoParam::MxVideoParam(MxRect32& p_rect, MxPalette* p_palette, MxULong p_bac m_flags = p_flags; m_unk0x1c = 0; m_deviceId = NULL; + m_msaaSamples = 0; } // FUNCTION: LEGO1 0x100becf0 @@ -41,6 +42,7 @@ MxVideoParam::MxVideoParam(MxVideoParam& p_videoParam) m_unk0x1c = p_videoParam.m_unk0x1c; m_deviceId = NULL; SetDeviceName(p_videoParam.m_deviceId); + m_msaaSamples = p_videoParam.m_msaaSamples; } // FUNCTION: LEGO1 0x100bed50 @@ -82,6 +84,7 @@ MxVideoParam& MxVideoParam::operator=(const MxVideoParam& p_videoParam) m_flags = p_videoParam.m_flags; m_unk0x1c = p_videoParam.m_unk0x1c; SetDeviceName(p_videoParam.m_deviceId); + m_msaaSamples = p_videoParam.m_msaaSamples; return *this; } diff --git a/miniwin/CMakeLists.txt b/miniwin/CMakeLists.txt index 3fe96ea3..615832cf 100644 --- a/miniwin/CMakeLists.txt +++ b/miniwin/CMakeLists.txt @@ -44,16 +44,16 @@ if(NOT WINDOWS_STORE) message(STATUS "🧩 OpenGL 1.x support not enabled — needs OpenGL") endif() - find_library(OPENGL_ES2_LIBRARY NAMES GLESv2) - if(EMSCRIPTEN OR OPENGL_ES2_LIBRARY) - message(STATUS "Found OpenGL: enabling OpenGL ES 2.x renderer") - target_sources(miniwin PRIVATE src/d3drm/backends/opengles2/renderer.cpp) - list(APPEND GRAPHICS_BACKENDS USE_OPENGLES2) - if(OPENGL_ES2_LIBRARY) - target_link_libraries(miniwin PRIVATE ${OPENGL_ES2_LIBRARY}) + find_library(OPENGL_ES3_LIBRARY NAMES GLESv2) + if(EMSCRIPTEN OR OPENGL_ES3_LIBRARY) + message(STATUS "Found OpenGL: enabling OpenGL ES 3.x renderer") + target_sources(miniwin PRIVATE src/d3drm/backends/opengles3/renderer.cpp) + list(APPEND GRAPHICS_BACKENDS USE_OPENGLES3) + if(OPENGL_ES3_LIBRARY) + target_link_libraries(miniwin PRIVATE ${OPENGL_ES3_LIBRARY}) endif() else() - message(STATUS "🧩 OpenGL ES 2.x support not enabled") + message(STATUS "🧩 OpenGL ES 3.x support not enabled") endif() endif() diff --git a/miniwin/include/miniwin/miniwind3d.h b/miniwin/include/miniwin/miniwind3d.h new file mode 100644 index 00000000..8204af17 --- /dev/null +++ b/miniwin/include/miniwin/miniwind3d.h @@ -0,0 +1,8 @@ +#pragma once + +DEFINE_GUID(IID_IDirect3DMiniwin, 0xf8a97f2d, 0x9b3a, 0x4f1c, 0x9e, 0x8d, 0x6a, 0x5b, 0x4c, 0x3d, 0x2e, 0x1f); + +struct IDirect3DMiniwin : virtual public IUnknown { + virtual HRESULT RequestMSAA(DWORD msaaSamples) = 0; + virtual DWORD GetMSAASamples() const = 0; +}; diff --git a/miniwin/src/d3drm/backends/opengles2/renderer.cpp b/miniwin/src/d3drm/backends/opengles3/renderer.cpp similarity index 76% rename from miniwin/src/d3drm/backends/opengles2/renderer.cpp rename to miniwin/src/d3drm/backends/opengles3/renderer.cpp index e1c86a62..5c3ac1e5 100644 --- a/miniwin/src/d3drm/backends/opengles2/renderer.cpp +++ b/miniwin/src/d3drm/backends/opengles3/renderer.cpp @@ -1,8 +1,8 @@ -#include "d3drmrenderer_opengles2.h" +#include "d3drmrenderer_opengles3.h" #include "meshutils.h" -#include #include +#include #include #include #include @@ -15,20 +15,29 @@ static GLuint CompileShader(GLenum type, const char* source) GLint success; glGetShaderiv(shader, GL_COMPILE_STATUS, &success); if (!success) { + GLint logLength = 0; + glGetShaderiv(shader, GL_INFO_LOG_LENGTH, &logLength); + if (logLength > 0) { + std::vector log(logLength); + glGetShaderInfoLog(shader, logLength, nullptr, log.data()); + SDL_Log("Shader compile error: %s", log.data()); + } + else { + SDL_Log("CompileShader (%s)", SDL_GetError()); + } glDeleteShader(shader); - SDL_Log("CompileShader (%s)", SDL_GetError()); return 0; } return shader; } -struct SceneLightGLES2 { +struct SceneLightGLES3 { float color[4]; float position[4]; float direction[4]; }; -Direct3DRMRenderer* OpenGLES2Renderer::Create(DWORD width, DWORD height) +Direct3DRMRenderer* OpenGLES3Renderer::Create(DWORD width, DWORD height, DWORD msaaSamples) { // We have to reset the attributes here after having enumerated the // OpenGL ES 2.0 renderer, or else SDL gets very confused by SDL_GL_DEPTH_SIZE @@ -37,7 +46,7 @@ Direct3DRMRenderer* OpenGLES2Renderer::Create(DWORD width, DWORD height) // But ResetAttributes resets it to 16. SDL_GL_SetAttribute(SDL_GL_DEPTH_SIZE, 24); SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_ES); - SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 2); + SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 3); SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 0); if (!DDWindow) { @@ -61,18 +70,20 @@ Direct3DRMRenderer* OpenGLES2Renderer::Create(DWORD width, DWORD height) glCullFace(GL_BACK); glFrontFace(GL_CW); - const char* vertexShaderSource = R"( - attribute vec3 a_position; - attribute vec3 a_normal; - attribute vec2 a_texCoord; + const char* vertexShaderSource = R"(#version 300 es + precision mediump float; + + in vec3 a_position; + in vec3 a_normal; + in vec2 a_texCoord; uniform mat4 u_modelViewMatrix; uniform mat3 u_normalMatrix; uniform mat4 u_projectionMatrix; - varying vec3 v_viewPos; - varying vec3 v_normal; - varying vec2 v_texCoord; + out vec3 v_viewPos; + out vec3 v_normal; + out vec2 v_texCoord; void main() { vec4 viewPos = u_modelViewMatrix * vec4(a_position, 1.0); @@ -83,7 +94,7 @@ Direct3DRMRenderer* OpenGLES2Renderer::Create(DWORD width, DWORD height) } )"; - const char* fragmentShaderSource = R"( + const char* fragmentShaderSource = R"(#version 300 es precision mediump float; struct SceneLight { @@ -95,22 +106,22 @@ Direct3DRMRenderer* OpenGLES2Renderer::Create(DWORD width, DWORD height) uniform SceneLight u_lights[3]; uniform int u_lightCount; - varying vec3 v_viewPos; - varying vec3 v_normal; - varying vec2 v_texCoord; + in vec3 v_viewPos; + in vec3 v_normal; + in vec2 v_texCoord; uniform float u_shininess; uniform vec4 u_color; - uniform int u_useTexture; + uniform bool u_useTexture; uniform sampler2D u_texture; + out vec4 fragColor; + void main() { vec3 diffuse = vec3(0.0); vec3 specular = vec3(0.0); - for (int i = 0; i < 3; ++i) { - if (i >= u_lightCount) break; - + for (int i = 0; i < u_lightCount; ++i) { vec3 lightColor = u_lights[i].color.rgb; if (u_lights[i].position.w == 0.0 && u_lights[i].direction.w == 0.0) { @@ -134,7 +145,7 @@ Direct3DRMRenderer* OpenGLES2Renderer::Create(DWORD width, DWORD height) // Specular if (u_shininess > 0.0 && u_lights[i].direction.w == 1.0) { - vec3 viewVec = normalize(-v_viewPos); // Assuming camera at origin + vec3 viewVec = normalize(-v_viewPos); vec3 H = normalize(lightVec + viewVec); float dotNH = max(dot(v_normal, H), 0.0); float spec = pow(dotNH, u_shininess); @@ -145,13 +156,13 @@ Direct3DRMRenderer* OpenGLES2Renderer::Create(DWORD width, DWORD height) vec4 finalColor = u_color; finalColor.rgb = clamp(diffuse * u_color.rgb + specular, 0.0, 1.0); - if (u_useTexture != 0) { - vec4 texel = texture2D(u_texture, v_texCoord); + if (u_useTexture) { + vec4 texel = texture(u_texture, v_texCoord); finalColor.rgb = clamp(texel.rgb * finalColor.rgb, 0.0, 1.0); finalColor.a = texel.a; } - gl_FragColor = finalColor; + fragColor = finalColor; } )"; @@ -168,12 +179,12 @@ Direct3DRMRenderer* OpenGLES2Renderer::Create(DWORD width, DWORD height) glDeleteShader(vs); glDeleteShader(fs); - return new OpenGLES2Renderer(width, height, context, shaderProgram); + return new OpenGLES3Renderer(width, height, msaaSamples, context, shaderProgram); } -GLES2MeshCacheEntry GLES2UploadMesh(const MeshGroup& meshGroup, bool forceUV = false) +GLES3MeshCacheEntry OpenGLES3Renderer::GLES3UploadMesh(const MeshGroup& meshGroup, bool forceUV) { - GLES2MeshCacheEntry cache{&meshGroup, meshGroup.version}; + GLES3MeshCacheEntry cache{&meshGroup, meshGroup.version}; cache.flat = meshGroup.quality == D3DRMRENDER_FLAT || meshGroup.quality == D3DRMRENDER_UNLITFLAT; @@ -211,18 +222,27 @@ GLES2MeshCacheEntry GLES2UploadMesh(const MeshGroup& meshGroup, bool forceUV = f std::vector normals(vertices.size()); std::transform(vertices.begin(), vertices.end(), normals.begin(), [](const D3DRMVERTEX& v) { return v.normal; }); + glGenVertexArrays(1, &cache.vao); + glBindVertexArray(cache.vao); + glGenBuffers(1, &cache.vboPositions); glBindBuffer(GL_ARRAY_BUFFER, cache.vboPositions); glBufferData(GL_ARRAY_BUFFER, positions.size() * sizeof(D3DVECTOR), positions.data(), GL_STATIC_DRAW); + glEnableVertexAttribArray(m_posLoc); + glVertexAttribPointer(m_posLoc, 3, GL_FLOAT, GL_FALSE, 0, nullptr); glGenBuffers(1, &cache.vboNormals); glBindBuffer(GL_ARRAY_BUFFER, cache.vboNormals); glBufferData(GL_ARRAY_BUFFER, normals.size() * sizeof(D3DVECTOR), normals.data(), GL_STATIC_DRAW); + glEnableVertexAttribArray(m_normLoc); + glVertexAttribPointer(m_normLoc, 3, GL_FLOAT, GL_FALSE, 0, nullptr); if (meshGroup.texture || forceUV) { glGenBuffers(1, &cache.vboTexcoords); glBindBuffer(GL_ARRAY_BUFFER, cache.vboTexcoords); glBufferData(GL_ARRAY_BUFFER, texcoords.size() * sizeof(TexCoord), texcoords.data(), GL_STATIC_DRAW); + glEnableVertexAttribArray(m_texLoc); + glVertexAttribPointer(m_texLoc, 2, GL_FLOAT, GL_FALSE, 0, nullptr); } glGenBuffers(1, &cache.ibo); @@ -234,6 +254,8 @@ GLES2MeshCacheEntry GLES2UploadMesh(const MeshGroup& meshGroup, bool forceUV = f GL_STATIC_DRAW ); + glBindVertexArray(0); + return cache; } @@ -262,7 +284,7 @@ bool UploadTexture(SDL_Surface* source, GLuint& outTexId, bool isUI) glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); - if (strstr((const char*) glGetString(GL_EXTENSIONS), "GL_EXT_texture_filter_anisotropic")) { + if (SDL_GL_ExtensionSupported("GL_EXT_texture_filter_anisotropic")) { GLfloat maxAniso = 0.0f; glGetFloatv(GL_MAX_TEXTURE_MAX_ANISOTROPY_EXT, &maxAniso); GLfloat desiredAniso = fminf(8.0f, maxAniso); @@ -278,12 +300,35 @@ bool UploadTexture(SDL_Surface* source, GLuint& outTexId, bool isUI) return true; } -OpenGLES2Renderer::OpenGLES2Renderer(DWORD width, DWORD height, SDL_GLContext context, GLuint shaderProgram) - : m_context(context), m_shaderProgram(shaderProgram) +OpenGLES3Renderer::OpenGLES3Renderer( + DWORD width, + DWORD height, + DWORD msaaSamples, + SDL_GLContext context, + GLuint shaderProgram +) + : m_context(context), m_shaderProgram(shaderProgram), m_msaa(msaaSamples) { glGenFramebuffers(1, &m_fbo); glBindFramebuffer(GL_FRAMEBUFFER, m_fbo); + GLint maxSamples; + glGetIntegerv(GL_MAX_SAMPLES, &maxSamples); + if (m_msaa > maxSamples) { + m_msaa = maxSamples; + } + SDL_Log( + "MSAA is %s. Requested samples: %d, active samples: %d, max samples: %d", + m_msaa > 1 ? "on" : "off", + msaaSamples, + m_msaa, + maxSamples + ); + + if (m_msaa > 1) { + glGenFramebuffers(1, &m_resolveFBO); + } + m_virtualWidth = width; m_virtualHeight = height; ViewportTransform viewportTransform = {1.0f, 0.0f, 0.0f}; @@ -310,14 +355,6 @@ OpenGLES2Renderer::OpenGLES2Renderer(DWORD width, DWORD height, SDL_GLContext co } SDL_DestroySurface(dummySurface); - m_uiMesh.vertices = { - {{0.0f, 0.0f, 0.0f}, {0, 0, -1}, {0.0f, 0.0f}}, - {{1.0f, 0.0f, 0.0f}, {0, 0, -1}, {1.0f, 0.0f}}, - {{1.0f, 1.0f, 0.0f}, {0, 0, -1}, {1.0f, 1.0f}}, - {{0.0f, 1.0f, 0.0f}, {0, 0, -1}, {0.0f, 1.0f}} - }; - m_uiMesh.indices = {0, 1, 2, 0, 2, 3}; - m_uiMeshCache = GLES2UploadMesh(m_uiMesh, true); m_posLoc = glGetAttribLocation(m_shaderProgram, "a_position"); m_normLoc = glGetAttribLocation(m_shaderProgram, "a_normal"); m_texLoc = glGetAttribLocation(m_shaderProgram, "a_texCoord"); @@ -336,17 +373,35 @@ OpenGLES2Renderer::OpenGLES2Renderer(DWORD width, DWORD height, SDL_GLContext co m_normalMatrixLoc = glGetUniformLocation(m_shaderProgram, "u_normalMatrix"); m_projectionMatrixLoc = glGetUniformLocation(m_shaderProgram, "u_projectionMatrix"); + m_uiMesh.vertices = { + {{0.0f, 0.0f, 0.0f}, {0, 0, -1}, {0.0f, 0.0f}}, + {{1.0f, 0.0f, 0.0f}, {0, 0, -1}, {1.0f, 0.0f}}, + {{1.0f, 1.0f, 0.0f}, {0, 0, -1}, {1.0f, 1.0f}}, + {{0.0f, 1.0f, 0.0f}, {0, 0, -1}, {0.0f, 1.0f}} + }; + m_uiMesh.indices = {0, 1, 2, 0, 2, 3}; + m_uiMeshCache = GLES3UploadMesh(m_uiMesh, true); + glUseProgram(m_shaderProgram); } -OpenGLES2Renderer::~OpenGLES2Renderer() +OpenGLES3Renderer::~OpenGLES3Renderer() { SDL_DestroySurface(m_renderedImage); + glDeleteTextures(1, &m_dummyTexture); glDeleteProgram(m_shaderProgram); + glDeleteRenderbuffers(1, &m_colorTarget); + glDeleteRenderbuffers(1, &m_depthTarget); + glDeleteFramebuffers(1, &m_fbo); + if (m_msaa > 1) { + glDeleteRenderbuffers(1, &m_resolveColor); + glDeleteFramebuffers(1, &m_resolveFBO); + } + SDL_GL_DestroyContext(m_context); } -void OpenGLES2Renderer::PushLights(const SceneLight* lightsArray, size_t count) +void OpenGLES3Renderer::PushLights(const SceneLight* lightsArray, size_t count) { if (count > 3) { SDL_Log("Unsupported number of lights (%d)", static_cast(count)); @@ -356,21 +411,21 @@ void OpenGLES2Renderer::PushLights(const SceneLight* lightsArray, size_t count) m_lights.assign(lightsArray, lightsArray + count); } -void OpenGLES2Renderer::SetFrustumPlanes(const Plane* frustumPlanes) +void OpenGLES3Renderer::SetFrustumPlanes(const Plane* frustumPlanes) { } -void OpenGLES2Renderer::SetProjection(const D3DRMMATRIX4D& projection, D3DVALUE front, D3DVALUE back) +void OpenGLES3Renderer::SetProjection(const D3DRMMATRIX4D& projection, D3DVALUE front, D3DVALUE back) { memcpy(&m_projection, projection, sizeof(D3DRMMATRIX4D)); } struct TextureDestroyContextGLS2 { - OpenGLES2Renderer* renderer; + OpenGLES3Renderer* renderer; Uint32 textureId; }; -void OpenGLES2Renderer::AddTextureDestroyCallback(Uint32 id, IDirect3DRMTexture* texture) +void OpenGLES3Renderer::AddTextureDestroyCallback(Uint32 id, IDirect3DRMTexture* texture) { auto* ctx = new TextureDestroyContextGLS2{this, id}; texture->AddDestroyCallback( @@ -388,7 +443,7 @@ void OpenGLES2Renderer::AddTextureDestroyCallback(Uint32 id, IDirect3DRMTexture* ); } -Uint32 OpenGLES2Renderer::GetTextureId(IDirect3DRMTexture* iTexture, bool isUI, float scaleX, float scaleY) +Uint32 OpenGLES3Renderer::GetTextureId(IDirect3DRMTexture* iTexture, bool isUI, float scaleX, float scaleY) { SDL_GL_MakeCurrent(DDWindow, m_context); auto texture = static_cast(iTexture); @@ -432,42 +487,43 @@ Uint32 OpenGLES2Renderer::GetTextureId(IDirect3DRMTexture* iTexture, bool isUI, return (Uint32) (m_textures.size() - 1); } -struct GLES2MeshDestroyContext { - OpenGLES2Renderer* renderer; +struct GLES3MeshDestroyContext { + OpenGLES3Renderer* renderer; Uint32 id; }; -void OpenGLES2Renderer::AddMeshDestroyCallback(Uint32 id, IDirect3DRMMesh* mesh) +void OpenGLES3Renderer::AddMeshDestroyCallback(Uint32 id, IDirect3DRMMesh* mesh) { - auto* ctx = new GLES2MeshDestroyContext{this, id}; + auto* ctx = new GLES3MeshDestroyContext{this, id}; mesh->AddDestroyCallback( [](IDirect3DRMObject*, void* arg) { - auto* ctx = static_cast(arg); + auto* ctx = static_cast(arg); auto& cache = ctx->renderer->m_meshs[ctx->id]; cache.meshGroup = nullptr; glDeleteBuffers(1, &cache.vboPositions); glDeleteBuffers(1, &cache.vboNormals); glDeleteBuffers(1, &cache.vboTexcoords); glDeleteBuffers(1, &cache.ibo); + glDeleteVertexArrays(1, &cache.vao); delete ctx; }, ctx ); } -Uint32 OpenGLES2Renderer::GetMeshId(IDirect3DRMMesh* mesh, const MeshGroup* meshGroup) +Uint32 OpenGLES3Renderer::GetMeshId(IDirect3DRMMesh* mesh, const MeshGroup* meshGroup) { for (Uint32 i = 0; i < m_meshs.size(); ++i) { auto& cache = m_meshs[i]; if (cache.meshGroup == meshGroup) { if (cache.version != meshGroup->version) { - cache = std::move(GLES2UploadMesh(*meshGroup)); + cache = std::move(GLES3UploadMesh(*meshGroup)); } return i; } } - auto newCache = GLES2UploadMesh(*meshGroup); + auto newCache = GLES3UploadMesh(*meshGroup); for (Uint32 i = 0; i < m_meshs.size(); ++i) { auto& cache = m_meshs[i]; @@ -483,7 +539,7 @@ Uint32 OpenGLES2Renderer::GetMeshId(IDirect3DRMMesh* mesh, const MeshGroup* mesh return (Uint32) (m_meshs.size() - 1); } -HRESULT OpenGLES2Renderer::BeginFrame() +HRESULT OpenGLES3Renderer::BeginFrame() { SDL_GL_MakeCurrent(DDWindow, m_context); m_dirty = true; @@ -495,7 +551,7 @@ HRESULT OpenGLES2Renderer::BeginFrame() glEnable(GL_DEPTH_TEST); glDepthMask(GL_TRUE); - SceneLightGLES2 lightData[3]; + SceneLightGLES3 lightData[3]; int lightCount = std::min(static_cast(m_lights.size()), 3); for (int i = 0; i < lightCount; ++i) { @@ -525,14 +581,14 @@ HRESULT OpenGLES2Renderer::BeginFrame() return DD_OK; } -void OpenGLES2Renderer::EnableTransparency() +void OpenGLES3Renderer::EnableTransparency() { glEnable(GL_BLEND); glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); glDepthMask(GL_FALSE); } -void OpenGLES2Renderer::SubmitDraw( +void OpenGLES3Renderer::SubmitDraw( DWORD meshId, const D3DRMMATRIX4D& modelViewMatrix, const D3DRMMATRIX4D& worldMatrix, @@ -570,28 +626,12 @@ void OpenGLES2Renderer::SubmitDraw( glUniform1i(m_textureLoc, 0); } - glBindBuffer(GL_ARRAY_BUFFER, mesh.vboPositions); - glEnableVertexAttribArray(m_posLoc); - glVertexAttribPointer(m_posLoc, 3, GL_FLOAT, GL_FALSE, 0, nullptr); - - glBindBuffer(GL_ARRAY_BUFFER, mesh.vboNormals); - glEnableVertexAttribArray(m_normLoc); - glVertexAttribPointer(m_normLoc, 3, GL_FLOAT, GL_FALSE, 0, nullptr); - - if (appearance.textureId != NO_TEXTURE_ID) { - glBindBuffer(GL_ARRAY_BUFFER, mesh.vboTexcoords); - glEnableVertexAttribArray(m_texLoc); - glVertexAttribPointer(m_texLoc, 2, GL_FLOAT, GL_FALSE, 0, nullptr); - } - - glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, mesh.ibo); + glBindVertexArray(mesh.vao); glDrawElements(GL_TRIANGLES, static_cast(mesh.indices.size()), GL_UNSIGNED_SHORT, nullptr); - - glDisableVertexAttribArray(m_normLoc); - glDisableVertexAttribArray(m_texLoc); + glBindVertexArray(0); } -HRESULT OpenGLES2Renderer::FinalizeFrame() +HRESULT OpenGLES3Renderer::FinalizeFrame() { glBindBuffer(GL_ARRAY_BUFFER, 0); glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0); @@ -599,7 +639,7 @@ HRESULT OpenGLES2Renderer::FinalizeFrame() return DD_OK; } -void OpenGLES2Renderer::Resize(int width, int height, const ViewportTransform& viewportTransform) +void OpenGLES3Renderer::Resize(int width, int height, const ViewportTransform& viewportTransform) { SDL_GL_MakeCurrent(DDWindow, m_context); m_width = width; @@ -610,37 +650,65 @@ void OpenGLES2Renderer::Resize(int width, int height, const ViewportTransform& v } m_renderedImage = SDL_CreateSurface(m_width, m_height, SDL_PIXELFORMAT_RGBA32); + if (m_colorTarget) { + glDeleteRenderbuffers(1, &m_colorTarget); + m_colorTarget = 0; + } + if (m_resolveColor) { + glDeleteRenderbuffers(1, &m_resolveColor); + m_resolveColor = 0; + } + if (m_depthTarget) { + glDeleteRenderbuffers(1, &m_depthTarget); + m_depthTarget = 0; + } + glBindFramebuffer(GL_FRAMEBUFFER, m_fbo); // Create color texture - glGenTextures(1, &m_colorTarget); - glBindTexture(GL_TEXTURE_2D, m_colorTarget); - glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, nullptr); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); - glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, m_colorTarget, 0); + glGenRenderbuffers(1, &m_colorTarget); + glBindRenderbuffer(GL_RENDERBUFFER, m_colorTarget); + if (m_msaa > 1) { + glRenderbufferStorageMultisample(GL_RENDERBUFFER, m_msaa, GL_RGBA8, width, height); + } + else { + glRenderbufferStorage(GL_RENDERBUFFER, GL_RGBA8, width, height); + } + glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, m_colorTarget); // Create depth renderbuffer glGenRenderbuffers(1, &m_depthTarget); glBindRenderbuffer(GL_RENDERBUFFER, m_depthTarget); - - if (SDL_GL_ExtensionSupported("GL_OES_depth24")) { - glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH_COMPONENT24_OES, width, height); - } - else if (SDL_GL_ExtensionSupported("GL_OES_depth32")) { - glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH_COMPONENT32_OES, width, height); + if (m_msaa > 1) { + glRenderbufferStorageMultisample(GL_RENDERBUFFER, m_msaa, GL_DEPTH_COMPONENT24, width, height); } else { - glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH_COMPONENT16, width, height); + glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH_COMPONENT24, width, height); } glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_RENDERBUFFER, m_depthTarget); + GLenum status = glCheckFramebufferStatus(GL_FRAMEBUFFER); + if (status != GL_FRAMEBUFFER_COMPLETE) { + SDL_Log("FBO incomplete: 0x%X", status); + } + + if (m_msaa > 1) { + glBindFramebuffer(GL_FRAMEBUFFER, m_resolveFBO); + glGenRenderbuffers(1, &m_resolveColor); + glBindRenderbuffer(GL_RENDERBUFFER, m_resolveColor); + glRenderbufferStorage(GL_RENDERBUFFER, GL_RGBA8, width, height); + glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, m_resolveColor); + GLenum status = glCheckFramebufferStatus(GL_FRAMEBUFFER); + if (status != GL_FRAMEBUFFER_COMPLETE) { + SDL_Log("Resolve FBO incomplete: 0x%X", status); + } + } + + glBindFramebuffer(GL_FRAMEBUFFER, m_fbo); glViewport(0, 0, m_width, m_height); } -void OpenGLES2Renderer::Clear(float r, float g, float b) +void OpenGLES3Renderer::Clear(float r, float g, float b) { SDL_GL_MakeCurrent(DDWindow, m_context); m_dirty = true; @@ -653,71 +721,22 @@ void OpenGLES2Renderer::Clear(float r, float g, float b) glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); } -void OpenGLES2Renderer::Flip() +void OpenGLES3Renderer::Flip() { SDL_GL_MakeCurrent(DDWindow, m_context); if (!m_dirty) { return; } - glBindFramebuffer(GL_FRAMEBUFFER, 0); - - glDisable(GL_DEPTH_TEST); - glFrontFace(GL_CCW); - glDepthMask(GL_FALSE); - - glUniform4f(m_colorLoc, 1.0f, 1.0f, 1.0f, 1.0f); - glUniform1f(m_shinLoc, 0.0f); - - float ambient[] = {1.0f, 1.0f, 1.0f, 1.0f}; - float blank[] = {0.0f, 0.0f, 0.0f, 0.0f}; - glUniform4fv(u_lightLocs[0][0], 1, ambient); - glUniform4fv(u_lightLocs[0][1], 1, blank); - glUniform4fv(u_lightLocs[0][2], 1, blank); - glUniform1i(m_lightCountLoc, 1); - - glActiveTexture(GL_TEXTURE0); - glBindTexture(GL_TEXTURE_2D, m_colorTarget); - glUniform1i(m_textureLoc, 0); - glUniform1i(m_useTextureLoc, 1); - - D3DRMMATRIX4D projection; - D3DRMMATRIX4D modelViewMatrix = { - {(float) m_width, 0.0f, 0.0f, 0.0f}, - {0.0f, (float) -m_height, 0.0f, 0.0f}, - {0.0f, 0.0f, 1.0f, 0.0f}, - {0.0f, (float) m_height, 0.0f, 1.0f} - }; - glUniformMatrix4fv(m_modelViewMatrixLoc, 1, GL_FALSE, &modelViewMatrix[0][0]); - Matrix3x3 identity = {{1.f, 0.f, 0.f}, {0.f, 1.f, 0.f}, {0.f, 0.f, 1.f}}; - glUniformMatrix3fv(m_normalMatrixLoc, 1, GL_FALSE, &identity[0][0]); - CreateOrthographicProjection((float) m_width, (float) m_height, projection); - glUniformMatrix4fv(m_projectionMatrixLoc, 1, GL_FALSE, &projection[0][0]); - - glDisable(GL_SCISSOR_TEST); - - glEnable(GL_BLEND); - glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); - - glBindBuffer(GL_ARRAY_BUFFER, m_uiMeshCache.vboPositions); - glEnableVertexAttribArray(m_posLoc); - glVertexAttribPointer(m_posLoc, 3, GL_FLOAT, GL_FALSE, 0, nullptr); - - glBindBuffer(GL_ARRAY_BUFFER, m_uiMeshCache.vboTexcoords); - glEnableVertexAttribArray(m_texLoc); - glVertexAttribPointer(m_texLoc, 2, GL_FLOAT, GL_FALSE, 0, nullptr); - - glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, m_uiMeshCache.ibo); - glDrawElements(GL_TRIANGLES, static_cast(m_uiMeshCache.indices.size()), GL_UNSIGNED_SHORT, nullptr); - - glDisableVertexAttribArray(m_texLoc); + glBindFramebuffer(GL_READ_FRAMEBUFFER, m_fbo); + glBindFramebuffer(GL_DRAW_FRAMEBUFFER, 0); + glBlitFramebuffer(0, 0, m_width, m_height, 0, 0, m_width, m_height, GL_COLOR_BUFFER_BIT, GL_NEAREST); SDL_GL_SwapWindow(DDWindow); - glFrontFace(GL_CW); m_dirty = false; } -void OpenGLES2Renderer::Draw2DImage(Uint32 textureId, const SDL_Rect& srcRect, const SDL_Rect& dstRect, FColor color) +void OpenGLES3Renderer::Draw2DImage(Uint32 textureId, const SDL_Rect& srcRect, const SDL_Rect& dstRect, FColor color) { SDL_GL_MakeCurrent(DDWindow, m_context); m_dirty = true; @@ -739,7 +758,7 @@ void OpenGLES2Renderer::Draw2DImage(Uint32 textureId, const SDL_Rect& srcRect, c SDL_Rect expandedDstRect; if (textureId != NO_TEXTURE_ID) { - const GLES2TextureCacheEntry& texture = m_textures[textureId]; + const GLES3TextureCacheEntry& texture = m_textures[textureId]; float scaleX = static_cast(dstRect.w) / srcRect.w; float scaleY = static_cast(dstRect.h) / srcRect.h; expandedDstRect = { @@ -790,26 +809,27 @@ void OpenGLES2Renderer::Draw2DImage(Uint32 textureId, const SDL_Rect& srcRect, c static_cast(std::round(dstRect.h * m_viewportTransform.scale)) ); - glBindBuffer(GL_ARRAY_BUFFER, m_uiMeshCache.vboPositions); - glEnableVertexAttribArray(m_posLoc); - glVertexAttribPointer(m_posLoc, 3, GL_FLOAT, GL_FALSE, 0, nullptr); - - glBindBuffer(GL_ARRAY_BUFFER, m_uiMeshCache.vboTexcoords); - glEnableVertexAttribArray(m_texLoc); - glVertexAttribPointer(m_texLoc, 2, GL_FLOAT, GL_FALSE, 0, nullptr); - - glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, m_uiMeshCache.ibo); + glBindVertexArray(m_uiMeshCache.vao); glDrawElements(GL_TRIANGLES, static_cast(m_uiMeshCache.indices.size()), GL_UNSIGNED_SHORT, nullptr); + glBindVertexArray(0); - glDisableVertexAttribArray(m_texLoc); glDisable(GL_SCISSOR_TEST); } -void OpenGLES2Renderer::Download(SDL_Surface* target) +void OpenGLES3Renderer::Download(SDL_Surface* target) { glFinish(); - glBindFramebuffer(GL_FRAMEBUFFER, m_fbo); + if (m_msaa > 1) { + glBindFramebuffer(GL_READ_FRAMEBUFFER, m_fbo); + glBindFramebuffer(GL_DRAW_FRAMEBUFFER, m_resolveFBO); + glBlitFramebuffer(0, 0, m_width, m_height, 0, 0, m_width, m_height, GL_COLOR_BUFFER_BIT, GL_NEAREST); + glBindFramebuffer(GL_FRAMEBUFFER, m_resolveFBO); + } + else { + glBindFramebuffer(GL_FRAMEBUFFER, m_fbo); + } + glReadPixels(0, 0, m_width, m_height, GL_RGBA, GL_UNSIGNED_BYTE, m_renderedImage->pixels); SDL_Rect srcRect = { @@ -839,7 +859,7 @@ void OpenGLES2Renderer::Download(SDL_Surface* target) SDL_DestroySurface(bufferClone); } -void OpenGLES2Renderer::SetDither(bool dither) +void OpenGLES3Renderer::SetDither(bool dither) { if (dither) { glEnable(GL_DITHER); diff --git a/miniwin/src/d3drm/d3drm.cpp b/miniwin/src/d3drm/d3drm.cpp index 7328d21c..3b4d5c9d 100644 --- a/miniwin/src/d3drm/d3drm.cpp +++ b/miniwin/src/d3drm/d3drm.cpp @@ -132,7 +132,11 @@ HRESULT Direct3DRMImpl::CreateDeviceFromSurface( DDSDesc.dwSize = sizeof(DDSURFACEDESC); surface->GetSurfaceDesc(&DDSDesc); - DDRenderer = CreateDirect3DRMRenderer(DDSDesc, guid); + IDirect3DMiniwin* miniwind3d = nullptr; + dd->QueryInterface(IID_IDirect3DMiniwin, (void**) &miniwind3d); + SDL_assert(miniwind3d); + + DDRenderer = CreateDirect3DRMRenderer(miniwind3d, DDSDesc, guid); if (!DDRenderer) { SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Device GUID not recognized"); return E_NOINTERFACE; diff --git a/miniwin/src/d3drm/d3drmrenderer.cpp b/miniwin/src/d3drm/d3drmrenderer.cpp index ca9b5541..913e55de 100644 --- a/miniwin/src/d3drm/d3drmrenderer.cpp +++ b/miniwin/src/d3drm/d3drmrenderer.cpp @@ -2,8 +2,8 @@ #ifdef USE_OPENGL1 #include "d3drmrenderer_opengl1.h" #endif -#ifdef USE_OPENGLES2 -#include "d3drmrenderer_opengles2.h" +#ifdef USE_OPENGLES3 +#include "d3drmrenderer_opengles3.h" #endif #ifdef USE_CITRO3D #include "d3drmrenderer_citro3d.h" @@ -18,7 +18,11 @@ #include "d3drmrenderer_software.h" #endif -Direct3DRMRenderer* CreateDirect3DRMRenderer(const DDSURFACEDESC& DDSDesc, const GUID* guid) +Direct3DRMRenderer* CreateDirect3DRMRenderer( + const IDirect3DMiniwin* d3d, + const DDSURFACEDESC& DDSDesc, + const GUID* guid +) { #ifdef USE_SDL_GPU if (SDL_memcmp(guid, &SDL3_GPU_GUID, sizeof(GUID)) == 0) { @@ -30,9 +34,9 @@ Direct3DRMRenderer* CreateDirect3DRMRenderer(const DDSURFACEDESC& DDSDesc, const return new Direct3DRMSoftwareRenderer(DDSDesc.dwWidth, DDSDesc.dwHeight); } #endif -#ifdef USE_OPENGLES2 - if (SDL_memcmp(guid, &OpenGLES2_GUID, sizeof(GUID)) == 0) { - return OpenGLES2Renderer::Create(DDSDesc.dwWidth, DDSDesc.dwHeight); +#ifdef USE_OPENGLES3 + if (SDL_memcmp(guid, &OpenGLES3_GUID, sizeof(GUID)) == 0) { + return OpenGLES3Renderer::Create(DDSDesc.dwWidth, DDSDesc.dwHeight, d3d->GetMSAASamples()); } #endif #ifdef USE_OPENGL1 @@ -53,13 +57,13 @@ Direct3DRMRenderer* CreateDirect3DRMRenderer(const DDSURFACEDESC& DDSDesc, const return nullptr; } -void Direct3DRMRenderer_EnumDevices(LPD3DENUMDEVICESCALLBACK cb, void* ctx) +void Direct3DRMRenderer_EnumDevices(const IDirect3DMiniwin* d3d, LPD3DENUMDEVICESCALLBACK cb, void* ctx) { #ifdef USE_SDL_GPU Direct3DRMSDL3GPU_EnumDevice(cb, ctx); #endif -#ifdef USE_OPENGLES2 - OpenGLES2Renderer_EnumDevice(cb, ctx); +#ifdef USE_OPENGLES3 + OpenGLES3Renderer_EnumDevice(d3d, cb, ctx); #endif #ifdef USE_OPENGL1 OpenGL1Renderer_EnumDevice(cb, ctx); diff --git a/miniwin/src/ddraw/ddraw.cpp b/miniwin/src/ddraw/ddraw.cpp index 264ceeb1..bb0e40ce 100644 --- a/miniwin/src/ddraw/ddraw.cpp +++ b/miniwin/src/ddraw/ddraw.cpp @@ -29,6 +29,11 @@ HRESULT DirectDrawImpl::QueryInterface(const GUID& riid, void** ppvObject) *ppvObject = static_cast(this); return S_OK; } + if (SDL_memcmp(&riid, &IID_IDirect3DMiniwin, sizeof(GUID)) == 0) { + this->IUnknown::AddRef(); + *ppvObject = static_cast(this); + return S_OK; + } MINIWIN_NOT_IMPLEMENTED(); return E_NOINTERFACE; } @@ -220,7 +225,7 @@ void EnumDevice( HRESULT DirectDrawImpl::EnumDevices(LPD3DENUMDEVICESCALLBACK cb, void* ctx) { - Direct3DRMRenderer_EnumDevices(cb, ctx); + Direct3DRMRenderer_EnumDevices(this, cb, ctx); return S_OK; } @@ -317,7 +322,7 @@ HRESULT DirectDrawImpl::CreateDevice( DDSDesc.dwSize = sizeof(DDSURFACEDESC); pBackBuffer->GetSurfaceDesc(&DDSDesc); - DDRenderer = CreateDirect3DRMRenderer(DDSDesc, &guid); + DDRenderer = CreateDirect3DRMRenderer(this, DDSDesc, &guid); if (!DDRenderer) { SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Device GUID not recognized"); return E_NOINTERFACE; @@ -326,6 +331,17 @@ HRESULT DirectDrawImpl::CreateDevice( return DD_OK; } +HRESULT DirectDrawImpl::RequestMSAA(DWORD msaaSamples) +{ + m_msaaSamples = msaaSamples; + return DD_OK; +} + +DWORD DirectDrawImpl::GetMSAASamples() const +{ + return m_msaaSamples; +} + HRESULT DirectDrawEnumerate(LPDDENUMCALLBACKA cb, void* context) { const char* driverName = SDL_GetCurrentVideoDriver(); diff --git a/miniwin/src/internal/d3drmrenderer.h b/miniwin/src/internal/d3drmrenderer.h index 85b2d1b9..7c19a8b4 100644 --- a/miniwin/src/internal/d3drmrenderer.h +++ b/miniwin/src/internal/d3drmrenderer.h @@ -3,6 +3,7 @@ #include "d3drmmesh_impl.h" #include "mathutils.h" #include "miniwin/d3drm.h" +#include "miniwin/miniwind3d.h" #include "miniwin/miniwindevice.h" #include "structs.h" @@ -61,5 +62,9 @@ class Direct3DRMRenderer : public IDirect3DDevice2 { ViewportTransform m_viewportTransform; }; -Direct3DRMRenderer* CreateDirect3DRMRenderer(const DDSURFACEDESC& DDSDesc, const GUID* guid); -void Direct3DRMRenderer_EnumDevices(LPD3DENUMDEVICESCALLBACK cb, void* ctx); +Direct3DRMRenderer* CreateDirect3DRMRenderer( + const IDirect3DMiniwin* d3d, + const DDSURFACEDESC& DDSDesc, + const GUID* guid +); +void Direct3DRMRenderer_EnumDevices(const IDirect3DMiniwin* d3d, LPD3DENUMDEVICESCALLBACK cb, void* ctx); diff --git a/miniwin/src/internal/d3drmrenderer_opengles2.h b/miniwin/src/internal/d3drmrenderer_opengles3.h similarity index 71% rename from miniwin/src/internal/d3drmrenderer_opengles2.h rename to miniwin/src/internal/d3drmrenderer_opengles3.h index d4985f32..1e029cd8 100644 --- a/miniwin/src/internal/d3drmrenderer_opengles2.h +++ b/miniwin/src/internal/d3drmrenderer_opengles3.h @@ -4,13 +4,13 @@ #include "d3drmtexture_impl.h" #include "ddraw_impl.h" -#include +#include #include #include -DEFINE_GUID(OpenGLES2_GUID, 0x682656F3, 0x0000, 0x0000, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04); +DEFINE_GUID(OpenGLES3_GUID, 0x682656F3, 0x0000, 0x0000, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04); -struct GLES2TextureCacheEntry { +struct GLES3TextureCacheEntry { IDirect3DRMTexture* texture; Uint32 version; GLuint glTextureId; @@ -18,7 +18,7 @@ struct GLES2TextureCacheEntry { uint16_t height; }; -struct GLES2MeshCacheEntry { +struct GLES3MeshCacheEntry { const MeshGroup* meshGroup; int version; bool flat; @@ -28,13 +28,14 @@ struct GLES2MeshCacheEntry { GLuint vboNormals; GLuint vboTexcoords; GLuint ibo; + GLuint vao; }; -class OpenGLES2Renderer : public Direct3DRMRenderer { +class OpenGLES3Renderer : public Direct3DRMRenderer { public: - static Direct3DRMRenderer* Create(DWORD width, DWORD height); - OpenGLES2Renderer(DWORD width, DWORD height, SDL_GLContext context, GLuint shaderProgram); - ~OpenGLES2Renderer() override; + static Direct3DRMRenderer* Create(DWORD width, DWORD height, DWORD msaaSamples); + OpenGLES3Renderer(DWORD width, DWORD height, DWORD msaaSamples, SDL_GLContext context, GLuint shaderProgram); + ~OpenGLES3Renderer() override; void PushLights(const SceneLight* lightsArray, size_t count) override; void SetProjection(const D3DRMMATRIX4D& projection, D3DVALUE front, D3DVALUE back) override; @@ -62,18 +63,22 @@ class OpenGLES2Renderer : public Direct3DRMRenderer { private: void AddTextureDestroyCallback(Uint32 id, IDirect3DRMTexture* texture); void AddMeshDestroyCallback(Uint32 id, IDirect3DRMMesh* mesh); + GLES3MeshCacheEntry GLES3UploadMesh(const MeshGroup& meshGroup, bool forceUV = false); MeshGroup m_uiMesh; - GLES2MeshCacheEntry m_uiMeshCache; - std::vector m_textures; - std::vector m_meshs; + GLES3MeshCacheEntry m_uiMeshCache; + std::vector m_textures; + std::vector m_meshs; D3DRMMATRIX4D m_projection; SDL_Surface* m_renderedImage = nullptr; bool m_dirty = false; std::vector m_lights; SDL_GLContext m_context; + uint32_t m_msaa; GLuint m_fbo; + GLuint m_resolveFBO; GLuint m_colorTarget; + GLuint m_resolveColor = 0; GLuint m_depthTarget; GLuint m_shaderProgram; GLuint m_dummyTexture; @@ -92,35 +97,25 @@ class OpenGLES2Renderer : public Direct3DRMRenderer { ViewportTransform m_viewportTransform; }; -inline static void OpenGLES2Renderer_EnumDevice(LPD3DENUMDEVICESCALLBACK cb, void* ctx) +inline static void OpenGLES3Renderer_EnumDevice(const IDirect3DMiniwin* d3d, LPD3DENUMDEVICESCALLBACK cb, void* ctx) { - Direct3DRMRenderer* device = OpenGLES2Renderer::Create(640, 480); + Direct3DRMRenderer* device = OpenGLES3Renderer::Create(640, 480, d3d->GetMSAASamples()); if (!device) { return; } + delete device; + D3DDEVICEDESC halDesc = {}; halDesc.dcmColorModel = D3DCOLOR_RGB; halDesc.dwFlags = D3DDD_DEVICEZBUFFERBITDEPTH; - halDesc.dwDeviceZBufferBitDepth = DDBD_16; + halDesc.dwDeviceZBufferBitDepth = DDBD_24; halDesc.dwDeviceRenderBitDepth = DDBD_32; halDesc.dpcTriCaps.dwTextureCaps = D3DPTEXTURECAPS_PERSPECTIVE; halDesc.dpcTriCaps.dwShadeCaps = D3DPSHADECAPS_ALPHAFLATBLEND; halDesc.dpcTriCaps.dwTextureFilterCaps = D3DPTFILTERCAPS_LINEAR; - const char* extensions = (const char*) glGetString(GL_EXTENSIONS); - if (extensions) { - if (strstr(extensions, "GL_OES_depth24")) { - halDesc.dwDeviceZBufferBitDepth |= DDBD_24; - } - if (strstr(extensions, "GL_OES_depth32")) { - halDesc.dwDeviceZBufferBitDepth |= DDBD_32; - } - } - - delete device; - D3DDEVICEDESC helDesc = {}; - EnumDevice(cb, ctx, "OpenGL ES 2.0 HAL", &halDesc, &helDesc, OpenGLES2_GUID); + EnumDevice(cb, ctx, "OpenGL ES 3.0 HAL", &halDesc, &helDesc, OpenGLES3_GUID); } diff --git a/miniwin/src/internal/ddraw_impl.h b/miniwin/src/internal/ddraw_impl.h index 4adfb4a5..ac4e01a9 100644 --- a/miniwin/src/internal/ddraw_impl.h +++ b/miniwin/src/internal/ddraw_impl.h @@ -4,6 +4,7 @@ #include "framebuffer_impl.h" #include "miniwin/d3d.h" #include "miniwin/ddraw.h" +#include "miniwin/miniwind3d.h" #include @@ -15,7 +16,7 @@ inline static SDL_Rect ConvertRect(const RECT* r) return {r->left, r->top, r->right - r->left, r->bottom - r->top}; } -struct DirectDrawImpl : public IDirectDraw2, public IDirect3D2 { +struct DirectDrawImpl : public IDirectDraw2, public IDirect3D2, public IDirect3DMiniwin { // IUnknown interface HRESULT QueryInterface(const GUID& riid, void** ppvObject) override; // IDirectDraw interface @@ -45,11 +46,15 @@ struct DirectDrawImpl : public IDirectDraw2, public IDirect3D2 { HRESULT CreateDevice(const GUID& guid, IDirectDrawSurface* pBackBuffer, IDirect3DDevice2** ppDirect3DDevice) override; HRESULT EnumDevices(LPD3DENUMDEVICESCALLBACK cb, void* ctx) override; + // IDirect3DMiniwin interface + HRESULT RequestMSAA(DWORD msaaSamples) override; + DWORD GetMSAASamples() const override; private: FrameBufferImpl* m_frameBuffer; int m_virtualWidth = 0; int m_virtualHeight = 0; + DWORD m_msaaSamples = 0; }; HRESULT DirectDrawEnumerate(LPDDENUMCALLBACKA cb, void* context); From a9db8d321e93fe082fee0218565a74c0c0ff8a89 Mon Sep 17 00:00:00 2001 From: Anders Jenbo Date: Wed, 23 Jul 2025 20:30:05 +0200 Subject: [PATCH 182/188] Add MSAA support to OpenGL 1.1 (#640) --- miniwin/src/d3drm/backends/opengl1/renderer.cpp | 7 ++++++- miniwin/src/d3drm/d3drmrenderer.cpp | 4 ++-- miniwin/src/internal/d3drmrenderer_opengl1.h | 6 +++--- 3 files changed, 11 insertions(+), 6 deletions(-) diff --git a/miniwin/src/d3drm/backends/opengl1/renderer.cpp b/miniwin/src/d3drm/backends/opengl1/renderer.cpp index 88b3b81b..2d4c14c9 100644 --- a/miniwin/src/d3drm/backends/opengl1/renderer.cpp +++ b/miniwin/src/d3drm/backends/opengl1/renderer.cpp @@ -16,7 +16,7 @@ static_assert(sizeof(GL11_BridgeTexCoord) == sizeof(TexCoord), "GL11_BridgeTexCo static_assert(sizeof(GL11_BridgeSceneLight) == sizeof(SceneLight), "GL11_BridgeSceneLight is wrong size"); static_assert(sizeof(GL11_BridgeSceneVertex) == sizeof(D3DRMVERTEX), "GL11_BridgeSceneVertex is wrong size"); -Direct3DRMRenderer* OpenGL1Renderer::Create(DWORD width, DWORD height) +Direct3DRMRenderer* OpenGL1Renderer::Create(DWORD width, DWORD height, DWORD msaaSamples) { // We have to reset the attributes here after having enumerated the // OpenGL ES 2.0 renderer, or else SDL gets very confused by SDL_GL_DEPTH_SIZE @@ -28,6 +28,11 @@ Direct3DRMRenderer* OpenGL1Renderer::Create(DWORD width, DWORD height) SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 1); SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 1); + if (msaaSamples > 1) { + SDL_GL_SetAttribute(SDL_GL_MULTISAMPLEBUFFERS, 1); + SDL_GL_SetAttribute(SDL_GL_MULTISAMPLESAMPLES, msaaSamples); + } + if (!DDWindow) { SDL_Log("No window handler"); return nullptr; diff --git a/miniwin/src/d3drm/d3drmrenderer.cpp b/miniwin/src/d3drm/d3drmrenderer.cpp index 913e55de..a19b31fb 100644 --- a/miniwin/src/d3drm/d3drmrenderer.cpp +++ b/miniwin/src/d3drm/d3drmrenderer.cpp @@ -41,7 +41,7 @@ Direct3DRMRenderer* CreateDirect3DRMRenderer( #endif #ifdef USE_OPENGL1 if (SDL_memcmp(guid, &OpenGL1_GUID, sizeof(GUID)) == 0) { - return OpenGL1Renderer::Create(DDSDesc.dwWidth, DDSDesc.dwHeight); + return OpenGL1Renderer::Create(DDSDesc.dwWidth, DDSDesc.dwHeight, d3d->GetMSAASamples()); } #endif #ifdef USE_CITRO3D @@ -66,7 +66,7 @@ void Direct3DRMRenderer_EnumDevices(const IDirect3DMiniwin* d3d, LPD3DENUMDEVICE OpenGLES3Renderer_EnumDevice(d3d, cb, ctx); #endif #ifdef USE_OPENGL1 - OpenGL1Renderer_EnumDevice(cb, ctx); + OpenGL1Renderer_EnumDevice(d3d, cb, ctx); #endif #ifdef USE_CITRO3D Citro3DRenderer_EnumDevice(cb, ctx); diff --git a/miniwin/src/internal/d3drmrenderer_opengl1.h b/miniwin/src/internal/d3drmrenderer_opengl1.h index 2f1c8173..894fa10c 100644 --- a/miniwin/src/internal/d3drmrenderer_opengl1.h +++ b/miniwin/src/internal/d3drmrenderer_opengl1.h @@ -11,7 +11,7 @@ DEFINE_GUID(OpenGL1_GUID, 0x682656F3, 0x0000, 0x0000, 0x00, 0x00, 0x00, 0x00, 0x class OpenGL1Renderer : public Direct3DRMRenderer { public: - static Direct3DRMRenderer* Create(DWORD width, DWORD height); + static Direct3DRMRenderer* Create(DWORD width, DWORD height, DWORD msaaSamples); OpenGL1Renderer(DWORD width, DWORD height, SDL_GLContext context); ~OpenGL1Renderer() override; @@ -54,9 +54,9 @@ class OpenGL1Renderer : public Direct3DRMRenderer { ViewportTransform m_viewportTransform; }; -inline static void OpenGL1Renderer_EnumDevice(LPD3DENUMDEVICESCALLBACK cb, void* ctx) +inline static void OpenGL1Renderer_EnumDevice(const IDirect3DMiniwin* d3d, LPD3DENUMDEVICESCALLBACK cb, void* ctx) { - Direct3DRMRenderer* device = OpenGL1Renderer::Create(640, 480); + Direct3DRMRenderer* device = OpenGL1Renderer::Create(640, 480, d3d->GetMSAASamples()); if (!device) { return; } From 1a2e03de4715c2eac30a7442128f2598f80b3d28 Mon Sep 17 00:00:00 2001 From: Christian Semmler Date: Wed, 23 Jul 2025 13:18:05 -0700 Subject: [PATCH 183/188] Make Anisotropic filtering configurable (#641) --- ISLE/isleapp.cpp | 4 ++- ISLE/isleapp.h | 1 + LEGO1/mxdirectx/mxdirect3d.cpp | 1 + LEGO1/mxdirectx/mxdirectxinfo.cpp | 1 + LEGO1/omni/include/mxvideoparam.h | 4 +++ LEGO1/omni/src/video/mxvideoparam.cpp | 3 ++ miniwin/include/miniwin/miniwind3d.h | 2 ++ .../src/d3drm/backends/opengles3/renderer.cpp | 32 +++++++++++++------ miniwin/src/d3drm/d3drmrenderer.cpp | 7 +++- miniwin/src/ddraw/ddraw.cpp | 11 ------- .../src/internal/d3drmrenderer_opengles3.h | 15 +++++++-- miniwin/src/internal/ddraw_impl.h | 15 +++++++-- 12 files changed, 69 insertions(+), 27 deletions(-) diff --git a/ISLE/isleapp.cpp b/ISLE/isleapp.cpp index b2a39dcc..c8e9f980 100644 --- a/ISLE/isleapp.cpp +++ b/ISLE/isleapp.cpp @@ -183,6 +183,7 @@ IsleApp::IsleApp() m_frameRate = 100.0f; m_exclusiveFullScreen = FALSE; m_msaaSamples = 0; + m_anisotropic = 0.0f; } // FUNCTION: ISLE 0x4011a0 @@ -1190,7 +1191,8 @@ bool IsleApp::LoadConfig() } m_frameRate = (1000.0f / iniparser_getdouble(dict, "isle:Frame Delta", m_frameDelta)); m_frameDelta = static_cast(std::round(iniparser_getdouble(dict, "isle:Frame Delta", m_frameDelta))); - m_videoParam.SetMSAASamples(iniparser_getint(dict, "isle:MSAA", m_msaaSamples)); + m_videoParam.SetMSAASamples((m_msaaSamples = iniparser_getint(dict, "isle:MSAA", m_msaaSamples))); + m_videoParam.SetAnisotropic((m_anisotropic = iniparser_getdouble(dict, "isle:Anisotropic", m_anisotropic))); const char* deviceId = iniparser_getstring(dict, "isle:3D Device ID", NULL); if (deviceId != NULL) { diff --git a/ISLE/isleapp.h b/ISLE/isleapp.h index 7346d817..66e8e6e1 100644 --- a/ISLE/isleapp.h +++ b/ISLE/isleapp.h @@ -115,6 +115,7 @@ class IsleApp { MxFloat m_frameRate; MxBool m_exclusiveFullScreen; MxU32 m_msaaSamples; + MxFloat m_anisotropic; }; extern IsleApp* g_isle; diff --git a/LEGO1/mxdirectx/mxdirect3d.cpp b/LEGO1/mxdirectx/mxdirect3d.cpp index 5eacf824..84ff77d3 100644 --- a/LEGO1/mxdirectx/mxdirect3d.cpp +++ b/LEGO1/mxdirectx/mxdirect3d.cpp @@ -81,6 +81,7 @@ BOOL MxDirect3D::Create( #endif if (videoParam) { miniwind3d->RequestMSAA(videoParam->GetMSAASamples()); + miniwind3d->RequestAnisotropic(videoParam->GetAnisotropic()); } } diff --git a/LEGO1/mxdirectx/mxdirectxinfo.cpp b/LEGO1/mxdirectx/mxdirectxinfo.cpp index bbff9534..765d32f7 100644 --- a/LEGO1/mxdirectx/mxdirectxinfo.cpp +++ b/LEGO1/mxdirectx/mxdirectxinfo.cpp @@ -238,6 +238,7 @@ BOOL MxDeviceEnumerate::EnumDirectDrawCallback(LPGUID p_guid, LPSTR p_driverDesc #endif if (videoParam) { miniwind3d->RequestMSAA(videoParam->GetMSAASamples()); + miniwind3d->RequestAnisotropic(videoParam->GetAnisotropic()); } } diff --git a/LEGO1/omni/include/mxvideoparam.h b/LEGO1/omni/include/mxvideoparam.h index af2fcddf..cd425ba9 100644 --- a/LEGO1/omni/include/mxvideoparam.h +++ b/LEGO1/omni/include/mxvideoparam.h @@ -56,6 +56,9 @@ class MxVideoParam { void SetMSAASamples(MxU32 p_msaaSamples) { m_msaaSamples = p_msaaSamples; } MxU32 GetMSAASamples() { return m_msaaSamples; } + void SetAnisotropic(MxFloat p_anisotropic) { m_anisotropic = p_anisotropic; } + MxFloat GetAnisotropic() { return m_anisotropic; } + private: MxRect32 m_rect; // 0x00 MxPalette* m_palette; // 0x10 @@ -64,6 +67,7 @@ class MxVideoParam { int m_unk0x1c; // 0x1c char* m_deviceId; // 0x20 MxU32 m_msaaSamples; + MxFloat m_anisotropic; }; #endif // MXVIDEOPARAM_H diff --git a/LEGO1/omni/src/video/mxvideoparam.cpp b/LEGO1/omni/src/video/mxvideoparam.cpp index 7c029e52..45a9a688 100644 --- a/LEGO1/omni/src/video/mxvideoparam.cpp +++ b/LEGO1/omni/src/video/mxvideoparam.cpp @@ -29,6 +29,7 @@ MxVideoParam::MxVideoParam(MxRect32& p_rect, MxPalette* p_palette, MxULong p_bac m_unk0x1c = 0; m_deviceId = NULL; m_msaaSamples = 0; + m_anisotropic = 0.0f; } // FUNCTION: LEGO1 0x100becf0 @@ -43,6 +44,7 @@ MxVideoParam::MxVideoParam(MxVideoParam& p_videoParam) m_deviceId = NULL; SetDeviceName(p_videoParam.m_deviceId); m_msaaSamples = p_videoParam.m_msaaSamples; + m_anisotropic = p_videoParam.m_anisotropic; } // FUNCTION: LEGO1 0x100bed50 @@ -85,6 +87,7 @@ MxVideoParam& MxVideoParam::operator=(const MxVideoParam& p_videoParam) m_unk0x1c = p_videoParam.m_unk0x1c; SetDeviceName(p_videoParam.m_deviceId); m_msaaSamples = p_videoParam.m_msaaSamples; + m_anisotropic = p_videoParam.m_anisotropic; return *this; } diff --git a/miniwin/include/miniwin/miniwind3d.h b/miniwin/include/miniwin/miniwind3d.h index 8204af17..aea2e25c 100644 --- a/miniwin/include/miniwin/miniwind3d.h +++ b/miniwin/include/miniwin/miniwind3d.h @@ -5,4 +5,6 @@ DEFINE_GUID(IID_IDirect3DMiniwin, 0xf8a97f2d, 0x9b3a, 0x4f1c, 0x9e, 0x8d, 0x6a, struct IDirect3DMiniwin : virtual public IUnknown { virtual HRESULT RequestMSAA(DWORD msaaSamples) = 0; virtual DWORD GetMSAASamples() const = 0; + virtual HRESULT RequestAnisotropic(float anisotropic) = 0; + virtual float GetAnisotropic() const = 0; }; diff --git a/miniwin/src/d3drm/backends/opengles3/renderer.cpp b/miniwin/src/d3drm/backends/opengles3/renderer.cpp index 5c3ac1e5..e2304ce5 100644 --- a/miniwin/src/d3drm/backends/opengles3/renderer.cpp +++ b/miniwin/src/d3drm/backends/opengles3/renderer.cpp @@ -37,7 +37,7 @@ struct SceneLightGLES3 { float direction[4]; }; -Direct3DRMRenderer* OpenGLES3Renderer::Create(DWORD width, DWORD height, DWORD msaaSamples) +Direct3DRMRenderer* OpenGLES3Renderer::Create(DWORD width, DWORD height, DWORD msaaSamples, float anisotropic) { // We have to reset the attributes here after having enumerated the // OpenGL ES 2.0 renderer, or else SDL gets very confused by SDL_GL_DEPTH_SIZE @@ -179,7 +179,7 @@ Direct3DRMRenderer* OpenGLES3Renderer::Create(DWORD width, DWORD height, DWORD m glDeleteShader(vs); glDeleteShader(fs); - return new OpenGLES3Renderer(width, height, msaaSamples, context, shaderProgram); + return new OpenGLES3Renderer(width, height, msaaSamples, anisotropic, context, shaderProgram); } GLES3MeshCacheEntry OpenGLES3Renderer::GLES3UploadMesh(const MeshGroup& meshGroup, bool forceUV) @@ -259,7 +259,7 @@ GLES3MeshCacheEntry OpenGLES3Renderer::GLES3UploadMesh(const MeshGroup& meshGrou return cache; } -bool UploadTexture(SDL_Surface* source, GLuint& outTexId, bool isUI) +bool OpenGLES3Renderer::UploadTexture(SDL_Surface* source, GLuint& outTexId, bool isUI) { SDL_Surface* surf = source; if (source->format != SDL_PIXELFORMAT_RGBA32) { @@ -284,11 +284,8 @@ bool UploadTexture(SDL_Surface* source, GLuint& outTexId, bool isUI) glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); - if (SDL_GL_ExtensionSupported("GL_EXT_texture_filter_anisotropic")) { - GLfloat maxAniso = 0.0f; - glGetFloatv(GL_MAX_TEXTURE_MAX_ANISOTROPY_EXT, &maxAniso); - GLfloat desiredAniso = fminf(8.0f, maxAniso); - glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAX_ANISOTROPY_EXT, desiredAniso); + if (m_anisotropic > 1.0f) { + glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAX_ANISOTROPY_EXT, m_anisotropic); } glGenerateMipmap(GL_TEXTURE_2D); } @@ -304,10 +301,11 @@ OpenGLES3Renderer::OpenGLES3Renderer( DWORD width, DWORD height, DWORD msaaSamples, + float anisotropic, SDL_GLContext context, GLuint shaderProgram ) - : m_context(context), m_shaderProgram(shaderProgram), m_msaa(msaaSamples) + : m_context(context), m_shaderProgram(shaderProgram), m_msaa(msaaSamples), m_anisotropic(anisotropic) { glGenFramebuffers(1, &m_fbo); glBindFramebuffer(GL_FRAMEBUFFER, m_fbo); @@ -329,6 +327,22 @@ OpenGLES3Renderer::OpenGLES3Renderer( glGenFramebuffers(1, &m_resolveFBO); } + bool anisoAvailable = SDL_GL_ExtensionSupported("GL_EXT_texture_filter_anisotropic"); + GLfloat maxAniso = 0.0f; + if (anisoAvailable) { + glGetFloatv(GL_MAX_TEXTURE_MAX_ANISOTROPY_EXT, &maxAniso); + } + if (m_anisotropic > maxAniso) { + m_anisotropic = maxAniso; + } + SDL_Log( + "Anisotropic is %s. Requested: %f, active: %f, max aniso: %f", + m_anisotropic > 1.0f ? "on" : "off", + anisotropic, + m_anisotropic, + maxAniso + ); + m_virtualWidth = width; m_virtualHeight = height; ViewportTransform viewportTransform = {1.0f, 0.0f, 0.0f}; diff --git a/miniwin/src/d3drm/d3drmrenderer.cpp b/miniwin/src/d3drm/d3drmrenderer.cpp index a19b31fb..8fe210b5 100644 --- a/miniwin/src/d3drm/d3drmrenderer.cpp +++ b/miniwin/src/d3drm/d3drmrenderer.cpp @@ -36,7 +36,12 @@ Direct3DRMRenderer* CreateDirect3DRMRenderer( #endif #ifdef USE_OPENGLES3 if (SDL_memcmp(guid, &OpenGLES3_GUID, sizeof(GUID)) == 0) { - return OpenGLES3Renderer::Create(DDSDesc.dwWidth, DDSDesc.dwHeight, d3d->GetMSAASamples()); + return OpenGLES3Renderer::Create( + DDSDesc.dwWidth, + DDSDesc.dwHeight, + d3d->GetMSAASamples(), + d3d->GetAnisotropic() + ); } #endif #ifdef USE_OPENGL1 diff --git a/miniwin/src/ddraw/ddraw.cpp b/miniwin/src/ddraw/ddraw.cpp index bb0e40ce..b28a105b 100644 --- a/miniwin/src/ddraw/ddraw.cpp +++ b/miniwin/src/ddraw/ddraw.cpp @@ -331,17 +331,6 @@ HRESULT DirectDrawImpl::CreateDevice( return DD_OK; } -HRESULT DirectDrawImpl::RequestMSAA(DWORD msaaSamples) -{ - m_msaaSamples = msaaSamples; - return DD_OK; -} - -DWORD DirectDrawImpl::GetMSAASamples() const -{ - return m_msaaSamples; -} - HRESULT DirectDrawEnumerate(LPDDENUMCALLBACKA cb, void* context) { const char* driverName = SDL_GetCurrentVideoDriver(); diff --git a/miniwin/src/internal/d3drmrenderer_opengles3.h b/miniwin/src/internal/d3drmrenderer_opengles3.h index 1e029cd8..e3f3f59d 100644 --- a/miniwin/src/internal/d3drmrenderer_opengles3.h +++ b/miniwin/src/internal/d3drmrenderer_opengles3.h @@ -33,8 +33,15 @@ struct GLES3MeshCacheEntry { class OpenGLES3Renderer : public Direct3DRMRenderer { public: - static Direct3DRMRenderer* Create(DWORD width, DWORD height, DWORD msaaSamples); - OpenGLES3Renderer(DWORD width, DWORD height, DWORD msaaSamples, SDL_GLContext context, GLuint shaderProgram); + static Direct3DRMRenderer* Create(DWORD width, DWORD height, DWORD msaaSamples, float anisotropic); + OpenGLES3Renderer( + DWORD width, + DWORD height, + DWORD msaaSamples, + float anisotropic, + SDL_GLContext context, + GLuint shaderProgram + ); ~OpenGLES3Renderer() override; void PushLights(const SceneLight* lightsArray, size_t count) override; @@ -64,6 +71,7 @@ class OpenGLES3Renderer : public Direct3DRMRenderer { void AddTextureDestroyCallback(Uint32 id, IDirect3DRMTexture* texture); void AddMeshDestroyCallback(Uint32 id, IDirect3DRMMesh* mesh); GLES3MeshCacheEntry GLES3UploadMesh(const MeshGroup& meshGroup, bool forceUV = false); + bool UploadTexture(SDL_Surface* source, GLuint& outTexId, bool isUI); MeshGroup m_uiMesh; GLES3MeshCacheEntry m_uiMeshCache; @@ -75,6 +83,7 @@ class OpenGLES3Renderer : public Direct3DRMRenderer { std::vector m_lights; SDL_GLContext m_context; uint32_t m_msaa; + float m_anisotropic; GLuint m_fbo; GLuint m_resolveFBO; GLuint m_colorTarget; @@ -99,7 +108,7 @@ class OpenGLES3Renderer : public Direct3DRMRenderer { inline static void OpenGLES3Renderer_EnumDevice(const IDirect3DMiniwin* d3d, LPD3DENUMDEVICESCALLBACK cb, void* ctx) { - Direct3DRMRenderer* device = OpenGLES3Renderer::Create(640, 480, d3d->GetMSAASamples()); + Direct3DRMRenderer* device = OpenGLES3Renderer::Create(640, 480, d3d->GetMSAASamples(), d3d->GetAnisotropic()); if (!device) { return; } diff --git a/miniwin/src/internal/ddraw_impl.h b/miniwin/src/internal/ddraw_impl.h index ac4e01a9..0da2d883 100644 --- a/miniwin/src/internal/ddraw_impl.h +++ b/miniwin/src/internal/ddraw_impl.h @@ -47,14 +47,25 @@ struct DirectDrawImpl : public IDirectDraw2, public IDirect3D2, public IDirect3D override; HRESULT EnumDevices(LPD3DENUMDEVICESCALLBACK cb, void* ctx) override; // IDirect3DMiniwin interface - HRESULT RequestMSAA(DWORD msaaSamples) override; - DWORD GetMSAASamples() const override; + HRESULT RequestMSAA(DWORD msaaSamples) override + { + m_msaaSamples = msaaSamples; + return DD_OK; + } + DWORD GetMSAASamples() const override { return m_msaaSamples; } + HRESULT RequestAnisotropic(float anisotropic) override + { + m_anisotropic = anisotropic; + return DD_OK; + } + float GetAnisotropic() const override { return m_anisotropic; } private: FrameBufferImpl* m_frameBuffer; int m_virtualWidth = 0; int m_virtualHeight = 0; DWORD m_msaaSamples = 0; + float m_anisotropic = 0.0f; }; HRESULT DirectDrawEnumerate(LPDDENUMCALLBACKA cb, void* context); From b69687d2f10646af57d3ea2cbebdff4bfcbad25b Mon Sep 17 00:00:00 2001 From: Christian Semmler Date: Wed, 23 Jul 2025 15:24:21 -0700 Subject: [PATCH 184/188] Update renderer.cpp (#643) --- miniwin/src/d3drm/backends/opengles3/renderer.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/miniwin/src/d3drm/backends/opengles3/renderer.cpp b/miniwin/src/d3drm/backends/opengles3/renderer.cpp index e2304ce5..af9e14f7 100644 --- a/miniwin/src/d3drm/backends/opengles3/renderer.cpp +++ b/miniwin/src/d3drm/backends/opengles3/renderer.cpp @@ -71,7 +71,7 @@ Direct3DRMRenderer* OpenGLES3Renderer::Create(DWORD width, DWORD height, DWORD m glFrontFace(GL_CW); const char* vertexShaderSource = R"(#version 300 es - precision mediump float; + precision highp float; in vec3 a_position; in vec3 a_normal; From d83a729c772b1a6604175496b6915971d2ccbeb2 Mon Sep 17 00:00:00 2001 From: VoxelTek <53562267+VoxelTek@users.noreply.github.com> Date: Thu, 24 Jul 2025 14:23:43 +1000 Subject: [PATCH 185/188] Add Anisotropic filtering (#644) --- CONFIG/MainDlg.cpp | 11 +++ CONFIG/MainDlg.h | 1 + CONFIG/config.cpp | 15 ++++ CONFIG/config.h | 1 + CONFIG/res/maindialog.ui | 151 ++++++++++++++++++++++++++------------- 5 files changed, 130 insertions(+), 49 deletions(-) diff --git a/CONFIG/MainDlg.cpp b/CONFIG/MainDlg.cpp index 55f06cf3..5ae140c3 100644 --- a/CONFIG/MainDlg.cpp +++ b/CONFIG/MainDlg.cpp @@ -88,6 +88,8 @@ CMainDialog::CMainDialog(QWidget* pParent) : QDialog(pParent) connect(m_ui->msaaSlider, &QSlider::valueChanged, this, &CMainDialog::MSAAChanged); connect(m_ui->msaaSlider, &QSlider::sliderMoved, this, &CMainDialog::MSAAChanged); + connect(m_ui->AFSlider, &QSlider::valueChanged, this, &CMainDialog::AFChanged); + connect(m_ui->AFSlider, &QSlider::sliderMoved, this, &CMainDialog::AFChanged); connect(m_ui->aspectRatioComboBox, &QComboBox::currentIndexChanged, this, &CMainDialog::AspectRatioChanged); connect(m_ui->xResSpinBox, &QSpinBox::valueChanged, this, &CMainDialog::XResChanged); @@ -321,6 +323,8 @@ void CMainDialog::UpdateInterface() m_ui->maxActorsNum->setNum(currentConfigApp->m_max_actors); m_ui->msaaSlider->setValue(log2(currentConfigApp->m_msaa)); m_ui->msaaNum->setNum(currentConfigApp->m_msaa); + m_ui->AFSlider->setValue(log2(currentConfigApp->m_anisotropy)); + m_ui->AFNum->setNum(currentConfigApp->m_anisotropy); } // FUNCTION: CONFIG 0x004045e0 @@ -550,6 +554,13 @@ void CMainDialog::MSAAChanged(int value) UpdateInterface(); } +void CMainDialog::AFChanged(int value) +{ + currentConfigApp->m_anisotropy = exp2(value); + m_modified = true; + UpdateInterface(); +} + void CMainDialog::SelectTexturePathDialog() { QString texture_path = QString::fromStdString(currentConfigApp->m_texture_path); diff --git a/CONFIG/MainDlg.h b/CONFIG/MainDlg.h index c453c785..0aaeeb12 100644 --- a/CONFIG/MainDlg.h +++ b/CONFIG/MainDlg.h @@ -61,6 +61,7 @@ private slots: void MaxLoDChanged(int value); void MaxActorsChanged(int value); void MSAAChanged(int value); + void AFChanged(int value); void SelectTexturePathDialog(); void TexturePathEdited(); void XResChanged(int i); diff --git a/CONFIG/config.cpp b/CONFIG/config.cpp index fd61fbb8..766cecd4 100644 --- a/CONFIG/config.cpp +++ b/CONFIG/config.cpp @@ -85,6 +85,7 @@ bool CConfigApp::InitInstance() m_joystick_index = -1; m_display_bit_depth = 16; m_msaa = 1; + m_anisotropy = 1; m_haptic = TRUE; m_touch_scheme = 2; m_texture_load = TRUE; @@ -184,6 +185,7 @@ bool CConfigApp::ReadRegisterSettings() m_max_lod = iniparser_getdouble(dict, "isle:Max LOD", m_max_lod); m_max_actors = iniparser_getint(dict, "isle:Max Allowed Extras", m_max_actors); m_msaa = iniparser_getint(dict, "isle:MSAA", m_msaa); + m_anisotropy = iniparser_getint(dict, "isle:Anisotropic", m_anisotropy); m_texture_load = iniparser_getboolean(dict, "extensions:texture loader", m_texture_load); m_texture_path = iniparser_getstring(dict, "texture loader:texture path", m_texture_path.c_str()); m_aspect_ratio = iniparser_getint(dict, "isle:Aspect Ratio", m_aspect_ratio); @@ -281,6 +283,18 @@ bool CConfigApp::ValidateSettings() m_msaa = 1; is_modified = TRUE; } + if (!(m_anisotropy & (m_anisotropy - 1))) { // Check if anisotropy is power of 2 (1, 2, 4, 8, etc) + m_anisotropy = exp2(round(log2(m_anisotropy))); // Closest power of 2 + is_modified = TRUE; + } + if (m_anisotropy > 16) { + m_anisotropy = 16; + is_modified = TRUE; + } + else if (m_anisotropy < 1) { + m_anisotropy = 1; + is_modified = TRUE; + } return is_modified; } @@ -360,6 +374,7 @@ void CConfigApp::WriteRegisterSettings() const SetIniInt(dict, "isle:Display Bit Depth", m_display_bit_depth); SetIniInt(dict, "isle:MSAA", m_msaa); + SetIniInt(dict, "isle:Anisotropic", m_anisotropy); SetIniBool(dict, "isle:Flip Surfaces", m_flip_surfaces); SetIniBool(dict, "isle:Full Screen", m_full_screen); SetIniBool(dict, "isle:Exclusive Full Screen", m_exclusive_full_screen); diff --git a/CONFIG/config.h b/CONFIG/config.h index 964bad8c..c0dcb851 100644 --- a/CONFIG/config.h +++ b/CONFIG/config.h @@ -71,6 +71,7 @@ class CConfigApp { Direct3DDeviceInfo* m_device; int m_display_bit_depth; int m_msaa; + int m_anisotropy; bool m_flip_surfaces; bool m_full_screen; bool m_exclusive_full_screen; diff --git a/CONFIG/res/maindialog.ui b/CONFIG/res/maindialog.ui index e1a8bf3e..e395159b 100644 --- a/CONFIG/res/maindialog.ui +++ b/CONFIG/res/maindialog.ui @@ -631,7 +631,7 @@ The game will gradually increase the number of actors until this maximum is reac - 3 + 6 0 @@ -823,65 +823,117 @@ The game will gradually increase the number of actors until this maximum is reac - - - MSAA - - + + + + 0 + + + 0 + + + 0 + + + 0 + - - - 0 - - - 4 - - - 1 - - - Qt::Orientation::Horizontal - - - QSlider::TickPosition::TicksBothSides - - - 1 + + + MSAA + + + + + 0 + + + 4 + + + 1 + + + Qt::Orientation::Horizontal + + + QSlider::TickPosition::TicksBothSides + + + 1 + + + + + + + + 16 + 0 + + + + 1 + + + Qt::AlignmentFlag::AlignCenter + + + + - - - - 16 - 0 - - - - 1 - - - Qt::AlignmentFlag::AlignCenter + + + Anisotropic Filtering + + + + + 0 + + + 4 + + + 1 + + + Qt::Orientation::Horizontal + + + QSlider::TickPosition::TicksBothSides + + + 1 + + + + + + + + 16 + 0 + + + + 1 + + + Qt::AlignmentFlag::AlignCenter + + + + - - - - Qt::Orientation::Vertical - - - - 20 - 0 - - - - @@ -1121,6 +1173,7 @@ The game will gradually increase the number of actors until this maximum is reac yResSpinBox exFullResComboBox msaaSlider + AFSlider touchComboBox rumbleCheckBox textureCheckBox From fd36f0753764f0d1f3277d53720005562e438485 Mon Sep 17 00:00:00 2001 From: Christian Semmler Date: Thu, 24 Jul 2025 08:42:47 -0700 Subject: [PATCH 186/188] Workaround for WebGL driver bug (#645) --- miniwin/src/d3drm/backends/opengles3/renderer.cpp | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/miniwin/src/d3drm/backends/opengles3/renderer.cpp b/miniwin/src/d3drm/backends/opengles3/renderer.cpp index af9e14f7..98cf6db5 100644 --- a/miniwin/src/d3drm/backends/opengles3/renderer.cpp +++ b/miniwin/src/d3drm/backends/opengles3/renderer.cpp @@ -744,6 +744,17 @@ void OpenGLES3Renderer::Flip() glBindFramebuffer(GL_READ_FRAMEBUFFER, m_fbo); glBindFramebuffer(GL_DRAW_FRAMEBUFFER, 0); + + // This is a workaround for what is (presumably) a driver bug in some WebGL environments (Android Chrome) + // During transitions, the screen would sometimes (randomly) briefly be cleared / flicker black when using + // glBlitFramebuffer. Any write to the back buffer before that fixes this issue. The following should have minimal + // performance impact. + glEnable(GL_SCISSOR_TEST); + glScissor(0, 0, 1, 1); + glClearColor(0.0f, 0.0f, 0.0f, 0.0f); + glClear(GL_COLOR_BUFFER_BIT); + glDisable(GL_SCISSOR_TEST); + glBlitFramebuffer(0, 0, m_width, m_height, 0, 0, m_width, m_height, GL_COLOR_BUFFER_BIT, GL_NEAREST); SDL_GL_SwapWindow(DDWindow); From 7473330e479a6250a1d5e0abeeceabe6d22908af Mon Sep 17 00:00:00 2001 From: Christian Semmler Date: Thu, 24 Jul 2025 15:10:15 -0700 Subject: [PATCH 187/188] (Web port) Add HD textures option (#647) --- ISLE/emscripten/filesystem.cpp | 18 +++++++++++++++- ISLE/islefiles.cpp | 21 +++++++++++++++++++ extensions/include/extensions/textureloader.h | 2 +- extensions/src/textureloader.cpp | 4 ++-- 4 files changed, 41 insertions(+), 4 deletions(-) diff --git a/ISLE/emscripten/filesystem.cpp b/ISLE/emscripten/filesystem.cpp index 50cd9012..1c21cb03 100644 --- a/ISLE/emscripten/filesystem.cpp +++ b/ISLE/emscripten/filesystem.cpp @@ -1,5 +1,6 @@ #include "filesystem.h" +#include "extensions/textureloader.h" #include "legogamestate.h" #include "misc.h" #include "mxomni.h" @@ -13,6 +14,7 @@ static backend_t opfs = nullptr; static backend_t fetchfs = nullptr; extern const char* g_files[46]; +extern const char* g_textures[120]; bool Emscripten_OPFSDisabled() { @@ -41,7 +43,7 @@ bool Emscripten_SetupConfig(const char* p_iniConfig) void Emscripten_SetupFilesystem() { - fetchfs = wasmfs_create_fetch_backend((MxString(Emscripten_streamHost) + MxString("/LEGO")).GetData(), 512 * 1024); + fetchfs = wasmfs_create_fetch_backend((MxString(Emscripten_streamHost) + "/LEGO").GetData(), 512 * 1024); wasmfs_create_directory("/LEGO", 0644, fetchfs); wasmfs_create_directory("/LEGO/Scripts", 0644, fetchfs); @@ -75,6 +77,20 @@ void Emscripten_SetupFilesystem() registerFile(file); } +#ifdef EXTENSIONS + if (Extensions::TextureLoader::enabled) { + MxString directory = + MxString("/LEGO") + Extensions::TextureLoader::options["texture loader:texture path"].c_str(); + Extensions::TextureLoader::options["texture loader:texture path"] = directory.GetData(); + wasmfs_create_directory(directory.GetData(), 0644, fetchfs); + + for (const char* file : g_textures) { + MxString path = directory + "/" + file + ".bmp"; + registerFile(path.GetData()); + } + } +#endif + if (GameState()->GetSavePath() && *GameState()->GetSavePath() && !Emscripten_OPFSDisabled()) { if (!opfs) { opfs = wasmfs_create_opfs_backend(); diff --git a/ISLE/islefiles.cpp b/ISLE/islefiles.cpp index 6e0bb482..c6f4566d 100644 --- a/ISLE/islefiles.cpp +++ b/ISLE/islefiles.cpp @@ -46,3 +46,24 @@ const char* g_files[46] = { "/LEGO/data/WORLD.WDB", "/LEGO/data/testinf.dta", }; + +const char* g_textures[120] = { + "bank01.gif", "beach.gif", "black.gif", "bowtie.gif", "brela_01.gif", "bth1chst.gif", "bth2chst.gif", + "capch.gif", "capdb.gif", "capjs.gif", "capmd.gif", "caprc.gif", "cave_24x.gif", "caverocx.gif", + "caverokx.gif", "cheker01.gif", "construct.gif", "copchest.gif", "dbfrfn.gif", "doctor.gif", "dogface.gif", + "dummy.gif", "e.gif", "flowers.gif", "fruit.gif", "gasroad.gif", "gdface.gif", "g.gif", + "grassx.gif", "infochst.gif", "infoface.gif", "jailpad.gif", "jfrnt.gif", "jsfrnt4.gif", "jsfrnt.gif", + "jside.gif", "jswnsh5.gif", "jswnsh.gif", "l6.gif", "l.gif", "mamachst.gif", "mamaface.gif", + "mamamap.gif", "mech.gif", "medic01.gif", "mitesx.gif", "mustache.gif", "nickchst.gif", "nickface.gif", + "nickmap.gif", "nopizza.gif", "norachst.gif", "noraface.gif", "noramap.gif", "nwcurve.gif", "octan01.gif", + "octsq01.gif", "o.gif", "papachst.gif", "papaface.gif", "papamap.gif", "pebblesx.gif", "pepperha.gif", + "peppizza.gif", "peppmap.gif", "peprchst.gif", "peprface.gif", "pianokys.gif", "pizcurve.gif", "pizza01.gif", + "pizza.gif", "polbar01.gif", "polbla01.gif", "polkadot.gif", "polwhi01.gif", "postchst.gif", "post.gif", + "rac1chst.gif", "rac2chst.gif", "radar.gif", "raddis01.gif", "rcback.gif", "rc-butn.gif", "rcfrnt5.gif", + "rcfrnt6.gif", "rcfrnt7.gif", "rcfrnt.gif", "rcside1.gif", "rcside2.gif", "rcside3.gif", "rctail.gif", + "redskul.gif", "relrel01.gif", "road1way.gif", "road3wa2.gif", "road3wa3.gif", "road3way.gif", "road4way.gif", + "roadstr8.gif", "rockx.gif", "roofpiz.gif", "sandredx.gif", "se_curve.gif", "shftchst.gif", "shftface2.gif", + "shftface.gif", "shldwn02.gif", "skull.gif", "smile.gif", "smileshd.gif", "supr2_01.gif", "tightcrv.gif", + "unkchst.gif", "val_02.gif", "vest.gif", "water2x.gif", "w_curve.gif", "wnbars.gif", "woman.gif", + "womanshd.gif" +}; diff --git a/extensions/include/extensions/textureloader.h b/extensions/include/extensions/textureloader.h index c476c7ff..38cfc639 100644 --- a/extensions/include/extensions/textureloader.h +++ b/extensions/include/extensions/textureloader.h @@ -17,7 +17,7 @@ class TextureLoader { static bool enabled; static constexpr std::array, 1> defaults = { - {{"texture loader:texture path", "/textures/"}} + {{"texture loader:texture path", "/textures"}} }; private: diff --git a/extensions/src/textureloader.cpp b/extensions/src/textureloader.cpp index 07b53253..1c6ed9b3 100644 --- a/extensions/src/textureloader.cpp +++ b/extensions/src/textureloader.cpp @@ -99,11 +99,11 @@ SDL_Surface* TextureLoader::FindTexture(const char* p_name) { SDL_Surface* surface; const char* texturePath = options["texture loader:texture path"].c_str(); - MxString path = MxString(MxOmni::GetHD()) + texturePath + p_name + ".bmp"; + MxString path = MxString(MxOmni::GetHD()) + texturePath + "/" + p_name + ".bmp"; path.MapPathToFilesystem(); if (!(surface = SDL_LoadBMP(path.GetData()))) { - path = MxString(MxOmni::GetCD()) + texturePath + p_name + ".bmp"; + path = MxString(MxOmni::GetCD()) + texturePath + "/" + p_name + ".bmp"; path.MapPathToFilesystem(); surface = SDL_LoadBMP(path.GetData()); } From 89f2f5cefee1a107330bbf93048f7da73f5754f7 Mon Sep 17 00:00:00 2001 From: Christian Semmler Date: Fri, 25 Jul 2025 16:08:55 -0700 Subject: [PATCH 188/188] (Web port) Improve loading UX for HD Textures (#648) --- ISLE/emscripten/events.cpp | 8 ++++++++ ISLE/emscripten/events.h | 1 + ISLE/emscripten/filesystem.cpp | 18 ++++++++++++++++++ extensions/include/extensions/textureloader.h | 2 ++ extensions/src/textureloader.cpp | 5 +++++ 5 files changed, 34 insertions(+) diff --git a/ISLE/emscripten/events.cpp b/ISLE/emscripten/events.cpp index 5cf67e51..436c1b64 100644 --- a/ISLE/emscripten/events.cpp +++ b/ISLE/emscripten/events.cpp @@ -36,3 +36,11 @@ void Emscripten_SendPresenterProgress(MxDSAction* p_action, MxPresenter::TickleS Emscripten_SendEvent("presenterProgress", buf); } + +void Emscripten_SendExtensionProgress(const char* p_extension, MxU32 p_progress) +{ + char buf[128]; + SDL_snprintf(buf, sizeof(buf), "{\"name\": \"%s\", \"progress\": %d}", p_extension, p_progress); + + Emscripten_SendEvent("extensionProgress", buf); +} diff --git a/ISLE/emscripten/events.h b/ISLE/emscripten/events.h index fac59cad..a22d7b55 100644 --- a/ISLE/emscripten/events.h +++ b/ISLE/emscripten/events.h @@ -4,5 +4,6 @@ #include "mxpresenter.h" void Emscripten_SendPresenterProgress(MxDSAction* p_action, MxPresenter::TickleState p_tickleState); +void Emscripten_SendExtensionProgress(const char* p_extension, MxU32 p_progress); #endif // EMSCRIPTEN_EVENTS_H diff --git a/ISLE/emscripten/filesystem.cpp b/ISLE/emscripten/filesystem.cpp index 1c21cb03..740802b8 100644 --- a/ISLE/emscripten/filesystem.cpp +++ b/ISLE/emscripten/filesystem.cpp @@ -1,5 +1,6 @@ #include "filesystem.h" +#include "events.h" #include "extensions/textureloader.h" #include "legogamestate.h" #include "misc.h" @@ -73,6 +74,15 @@ void Emscripten_SetupFilesystem() } }; + const auto preloadFile = [](const char* p_path) -> bool { + size_t length = 0; + void* data = SDL_LoadFile(p_path, &length); + if (data) { + SDL_free(data); + } + return length > 0; + }; + for (const char* file : g_files) { registerFile(file); } @@ -84,9 +94,17 @@ void Emscripten_SetupFilesystem() Extensions::TextureLoader::options["texture loader:texture path"] = directory.GetData(); wasmfs_create_directory(directory.GetData(), 0644, fetchfs); + MxU32 i = 0; + Emscripten_SendExtensionProgress("HD Textures", 0); for (const char* file : g_textures) { MxString path = directory + "/" + file + ".bmp"; registerFile(path.GetData()); + + if (!preloadFile(path.GetData())) { + Extensions::TextureLoader::excludedFiles.emplace_back(file); + } + + Emscripten_SendExtensionProgress("HD Textures", (++i * 100) / sizeOfArray(g_textures)); } } #endif diff --git a/extensions/include/extensions/textureloader.h b/extensions/include/extensions/textureloader.h index 38cfc639..a318c9b8 100644 --- a/extensions/include/extensions/textureloader.h +++ b/extensions/include/extensions/textureloader.h @@ -5,6 +5,7 @@ #include #include +#include namespace Extensions { @@ -14,6 +15,7 @@ class TextureLoader { static bool PatchTexture(LegoTextureInfo* p_textureInfo); static std::map options; + static std::vector excludedFiles; static bool enabled; static constexpr std::array, 1> defaults = { diff --git a/extensions/src/textureloader.cpp b/extensions/src/textureloader.cpp index 1c6ed9b3..3fbf0f7d 100644 --- a/extensions/src/textureloader.cpp +++ b/extensions/src/textureloader.cpp @@ -3,6 +3,7 @@ using namespace Extensions; std::map TextureLoader::options; +std::vector TextureLoader::excludedFiles; bool TextureLoader::enabled = false; void TextureLoader::Initialize() @@ -97,6 +98,10 @@ bool TextureLoader::PatchTexture(LegoTextureInfo* p_textureInfo) SDL_Surface* TextureLoader::FindTexture(const char* p_name) { + if (std::find(excludedFiles.begin(), excludedFiles.end(), p_name) != excludedFiles.end()) { + return nullptr; + } + SDL_Surface* surface; const char* texturePath = options["texture loader:texture path"].c_str(); MxString path = MxString(MxOmni::GetHD()) + texturePath + "/" + p_name + ".bmp";

oxVE^qx=y)Hxc+tBaNT#^ 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 literal 0 HcmV?d00001 diff --git a/ISLE/res/3ds/isle.png b/ISLE/res/3ds/icon.png similarity index 100% rename from ISLE/res/3ds/isle.png rename to ISLE/res/3ds/icon.png diff --git a/ISLE/res/3ds/logo.bcma.lz b/ISLE/res/3ds/logo.bcma.lz new file mode 100644 index 0000000000000000000000000000000000000000..dd9db8cda2c5fa0ba767e5f25dda002579af78b8 GIT binary patch literal 8192 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 D6|7Yq literal 0 HcmV?d00001 diff --git a/ISLE/res/3ds/template.rsf b/ISLE/res/3ds/template.rsf new file mode 100644 index 00000000..bbde6448 --- /dev/null +++ b/ISLE/res/3ds/template.rsf @@ -0,0 +1,283 @@ +BasicInfo: + Title : "Lego Island" + ProductCode : "CTR-P-ISLE" + Logo : Homebrew + +TitleInfo: + Category : Application + UniqueId : 0x76E7E + +Option: + UseOnSD : true # true if App is to be installed to SD + FreeProductCode : true # Removes limitations on ProductCode + MediaFootPadding : false # If true CCI files are created with padding + EnableCrypt : false # Enables encryption for NCCH and CIA + EnableCompress : true # Compresses where applicable (currently only exefs:/.code) + +AccessControlInfo: + CoreVersion : 2 + + # Exheader Format Version + DescVersion : 2 + + # Minimum Required Kernel Version (below is for 4.5.0) + ReleaseKernelMajor : "02" + ReleaseKernelMinor : "33" + + # ExtData + UseExtSaveData : false # enables ExtData + #ExtSaveDataId : 0x300 # only set this when the ID is different to the UniqueId + + # FS:USER Archive Access Permissions + # Uncomment as required + FileSystemAccess: + #- CategorySystemApplication + #- CategoryHardwareCheck + #- CategoryFileSystemTool + #- Debug + #- TwlCardBackup + #- TwlNandData + #- Boss + - DirectSdmc + - Core + #- CtrNandRo + #- CtrNandRw + #- CtrNandRoWrite + #- CategorySystemSettings + #- CardBoard + #- ExportImportIvs + - DirectSdmcWrite + #- SwitchCleanup + #- SaveDataMove + #- Shop + #- Shell + #- CategoryHomeMenu + #- SeedDB + IoAccessControl: + #- FsMountNand + #- FsMountNandRoWrite + #- FsMountTwln + #- FsMountWnand + #- FsMountCardSpi + #- UseSdif3 + #- CreateSeed + #- UseCardSpi + + # Process Settings + MemoryType : Application # Application/System/Base + SystemMode : 64MB # 64MB(Default)/96MB/80MB/72MB/32MB + IdealProcessor : 0 + AffinityMask : 1 + Priority : 16 + MaxCpu : 0x9E # Default + HandleTableSize : 0x200 + DisableDebug : false + EnableForceDebug : false + CanWriteSharedPage : true + CanUsePrivilegedPriority : false + CanUseNonAlphabetAndNumber : true + PermitMainFunctionArgument : true + CanShareDeviceMemory : true + RunnableOnSleep : false + SpecialMemoryArrange : true + + # New3DS Exclusive Process Settings + SystemModeExt : Legacy # Legacy(Default)/124MB/178MB Legacy:Use Old3DS SystemMode + CpuSpeed : 804MHz # 268MHz(Default)/804MHz + EnableL2Cache : true # false(default)/true + CanAccessCore2 : true + + # Virtual Address Mappings + IORegisterMapping: + - 1ff00000-1ff7ffff # DSP memory + MemoryMapping: + - 1f000000-1f5fffff:r # VRAM + + # Accessible SVCs, : + SystemCallAccess: + ControlMemory: 1 + QueryMemory: 2 + ExitProcess: 3 + GetProcessAffinityMask: 4 + SetProcessAffinityMask: 5 + GetProcessIdealProcessor: 6 + SetProcessIdealProcessor: 7 + CreateThread: 8 + ExitThread: 9 + SleepThread: 10 + GetThreadPriority: 11 + SetThreadPriority: 12 + GetThreadAffinityMask: 13 + SetThreadAffinityMask: 14 + GetThreadIdealProcessor: 15 + SetThreadIdealProcessor: 16 + GetCurrentProcessorNumber: 17 + Run: 18 + CreateMutex: 19 + ReleaseMutex: 20 + CreateSemaphore: 21 + ReleaseSemaphore: 22 + CreateEvent: 23 + SignalEvent: 24 + ClearEvent: 25 + CreateTimer: 26 + SetTimer: 27 + CancelTimer: 28 + ClearTimer: 29 + CreateMemoryBlock: 30 + MapMemoryBlock: 31 + UnmapMemoryBlock: 32 + CreateAddressArbiter: 33 + ArbitrateAddress: 34 + CloseHandle: 35 + WaitSynchronization1: 36 + WaitSynchronizationN: 37 + SignalAndWait: 38 + DuplicateHandle: 39 + GetSystemTick: 40 + GetHandleInfo: 41 + GetSystemInfo: 42 + GetProcessInfo: 43 + GetThreadInfo: 44 + ConnectToPort: 45 + SendSyncRequest1: 46 + SendSyncRequest2: 47 + SendSyncRequest3: 48 + SendSyncRequest4: 49 + SendSyncRequest: 50 + OpenProcess: 51 + OpenThread: 52 + GetProcessId: 53 + GetProcessIdOfThread: 54 + GetThreadId: 55 + GetResourceLimit: 56 + GetResourceLimitLimitValues: 57 + GetResourceLimitCurrentValues: 58 + GetThreadContext: 59 + Break: 60 + OutputDebugString: 61 + ControlPerformanceCounter: 62 + CreatePort: 71 + CreateSessionToPort: 72 + CreateSession: 73 + AcceptSession: 74 + ReplyAndReceive1: 75 + ReplyAndReceive2: 76 + ReplyAndReceive3: 77 + ReplyAndReceive4: 78 + ReplyAndReceive: 79 + BindInterrupt: 80 + UnbindInterrupt: 81 + InvalidateProcessDataCache: 82 + StoreProcessDataCache: 83 + FlushProcessDataCache: 84 + StartInterProcessDma: 85 + StopDma: 86 + GetDmaState: 87 + RestartDma: 88 + DebugActiveProcess: 96 + BreakDebugProcess: 97 + TerminateDebugProcess: 98 + GetProcessDebugEvent: 99 + ContinueDebugEvent: 100 + GetProcessList: 101 + GetThreadList: 102 + GetDebugThreadContext: 103 + SetDebugThreadContext: 104 + QueryDebugProcessMemory: 105 + ReadProcessMemory: 106 + WriteProcessMemory: 107 + SetHardwareBreakPoint: 108 + GetDebugThreadParam: 109 + ControlProcessMemory: 112 + MapProcessMemory: 113 + UnmapProcessMemory: 114 + CreateCodeSet: 115 + CreateProcess: 117 + TerminateProcess: 118 + SetProcessResourceLimits: 119 + CreateResourceLimit: 120 + SetResourceLimitValues: 121 + AddCodeSegment: 122 + Backdoor: 123 + KernelSetState: 124 + QueryProcessMemory: 125 + + # Service List + # Maximum 34 services (32 if firmware is prior to 9.6.0) + ServiceAccessControl: + - APT:U + - ac:u + - am:net + - boss:U + - cam:u + - cecd:u + - cfg:nor + - cfg:u + - csnd:SND + - dsp::DSP + - frd:u + - fs:USER + - gsp::Gpu + - gsp::Lcd + - hid:USER + #- http:C + - ir:rst + - ir:u + - ir:USER + #- mic:u + - mcu::HWC + - ndm:u + - news:s + - nwm::EXT + - nwm::UDS + - ptm:sysm + - ptm:u + - pxi:dev + - soc:U + - ssl:C + - y2r:u + + +SystemControlInfo: + SaveDataSize: 0KB # Change if the app uses savedata + RemasterVersion: $(APP_VERSION_MAJOR) + StackSize: 0x40000 + + # Modules that run services listed above should be included below + # Maximum 48 dependencies + # : + Dependency: + ac: 0x0004013000002402 + #act: 0x0004013000003802 + am: 0x0004013000001502 + boss: 0x0004013000003402 + camera: 0x0004013000001602 + cecd: 0x0004013000002602 + cfg: 0x0004013000001702 + codec: 0x0004013000001802 + csnd: 0x0004013000002702 + dlp: 0x0004013000002802 + dsp: 0x0004013000001a02 + friends: 0x0004013000003202 + gpio: 0x0004013000001b02 + gsp: 0x0004013000001c02 + hid: 0x0004013000001d02 + #http: 0x0004013000002902 + i2c: 0x0004013000001e02 + ir: 0x0004013000003302 + mcu: 0x0004013000001f02 + #mic: 0x0004013000002002 + ndm: 0x0004013000002b02 + news: 0x0004013000003502 + #nfc: 0x0004013000004002 + nim: 0x0004013000002c02 + nwm: 0x0004013000002d02 + pdn: 0x0004013000002102 + ps: 0x0004013000003102 + ptm: 0x0004013000002202 + #qtm: 0x0004013020004202 + ro: 0x0004013000003702 + socket: 0x0004013000002e02 + spi: 0x0004013000002302 + ssl: 0x0004013000002f02 From c7fda26cf4676cbafee9986f39ef3ddbee41bed0 Mon Sep 17 00:00:00 2001 From: Fabian Neundorf Date: Tue, 8 Jul 2025 02:35:21 +0200 Subject: [PATCH 097/188] Clear unknowns in `Infocenter` and `InfocenterMapEntry` (#1609) --- LEGO1/lego/legoomni/include/infocenter.h | 20 +++- LEGO1/lego/legoomni/src/worlds/infocenter.cpp | 92 +++++++++---------- 2 files changed, 61 insertions(+), 51 deletions(-) diff --git a/LEGO1/lego/legoomni/include/infocenter.h b/LEGO1/lego/legoomni/include/infocenter.h index 24d14aa8..3c58b325 100644 --- a/LEGO1/lego/legoomni/include/infocenter.h +++ b/LEGO1/lego/legoomni/include/infocenter.h @@ -78,10 +78,20 @@ class InfocenterState : public LegoState { // SIZE 0x18 struct InfocenterMapEntry { + enum { + e_infocenter = 3, + e_jetrace = 10, + e_carrace = 11, + e_pizzeria = 12, + e_garage = 13, + e_hospital = 14, + e_police = 15, + }; + InfocenterMapEntry(); MxStillPresenter* m_destCtl; // 0x00 - undefined4 m_unk0x04; // 0x04 + MxU32 m_target; // 0x04 MxRect m_area; // 0x08 }; @@ -154,7 +164,7 @@ class Infocenter : public LegoWorld { void PlayCutscene(Cutscene p_entityId, MxBool p_scale); void StopCutscene(); - void FUN_10070d10(MxS32 p_x, MxS32 p_y); + void UpdateEnabledGlowControl(MxS32 p_x, MxS32 p_y); void StartCredits(); void StopCredits(); @@ -173,12 +183,12 @@ class Infocenter : public LegoWorld { Radio m_radio; // 0x10c MxStillPresenter* m_dragPresenter; // 0x11c InfocenterMapEntry m_glowInfo[7]; // 0x120 - MxS16 m_unk0x1c8; // 0x1c8 + MxS16 m_enabledGlowControl; // 0x1c8 MxStillPresenter* m_frame; // 0x1cc MxS16 m_infoManDialogueTimer; // 0x1d0 MxS16 m_bookAnimationTimer; // 0x1d2 - MxU16 m_unk0x1d4; // 0x1d4 - MxS16 m_unk0x1d6; // 0x1d6 + MxU16 m_playingMovieCounter; // 0x1d4 + MxS16 m_bigInfoBlinkTimer; // 0x1d6 }; #endif // INFOCENTER_H diff --git a/LEGO1/lego/legoomni/src/worlds/infocenter.cpp b/LEGO1/lego/legoomni/src/worlds/infocenter.cpp index 40463c97..0a8d5f1c 100644 --- a/LEGO1/lego/legoomni/src/worlds/infocenter.cpp +++ b/LEGO1/lego/legoomni/src/worlds/infocenter.cpp @@ -138,14 +138,14 @@ Infocenter::Infocenter() memset(&m_glowInfo, 0, sizeof(m_glowInfo)); - m_unk0x1c8 = -1; + m_enabledGlowControl = -1; SetAppCursor(e_cursorBusy); NotificationManager()->Register(this); m_infoManDialogueTimer = 0; m_bookAnimationTimer = 0; - m_unk0x1d4 = 0; - m_unk0x1d6 = 0; + m_playingMovieCounter = 0; + m_bigInfoBlinkTimer = 0; } // FUNCTION: LEGO1 0x1006ec80 @@ -296,11 +296,11 @@ MxLong Infocenter::HandleEndAction(MxEndActionNotificationParam& p_param) action->GetObjectId() == InfomainScript::c_Pepper_All_Movie || action->GetObjectId() == InfomainScript::c_Nick_All_Movie || action->GetObjectId() == InfomainScript::c_Laura_All_Movie)) { - if (m_unk0x1d4) { - m_unk0x1d4--; + if (m_playingMovieCounter) { + m_playingMovieCounter--; } - if (!m_unk0x1d4) { + if (!m_playingMovieCounter) { PlayMusic(JukeboxScript::c_InformationCenter_Music); GameState()->SetActor(m_selectedCharacter); @@ -336,7 +336,7 @@ MxLong Infocenter::HandleEndAction(MxEndActionNotificationParam& p_param) if (action->GetObjectId() == InfomainScript::c_iicx26in_RunAnim) { ControlManager()->UpdateEnabledChild(InfomainScript::c_BigInfo_Ctl, action->GetAtomId().GetInternal(), 0); - m_unk0x1d6 = 0; + m_bigInfoBlinkTimer = 0; } switch (m_infocenterState->m_unk0x74) { @@ -441,8 +441,8 @@ void Infocenter::ReadyWorld() { m_infoManDialogueTimer = 0; m_bookAnimationTimer = 0; - m_unk0x1d4 = 0; - m_unk0x1d6 = 0; + m_playingMovieCounter = 0; + m_bigInfoBlinkTimer = 0; MxStillPresenter* bg = (MxStillPresenter*) Find("MxStillPresenter", "Background_Bitmap"); MxStillPresenter* bgRed = (MxStillPresenter*) Find("MxStillPresenter", "BackgroundRed_Bitmap"); @@ -474,8 +474,8 @@ void Infocenter::ReadyWorld() InfomainScript::Script script = m_infocenterState->GetNextReturnDialogue(); PlayAction(script); - if (script == InfomainScript::c_iicx26in_RunAnim) { - m_unk0x1d6 = 1; + if (script == InfomainScript::c_iicx26in_RunAnim) { // want to get back? Click on I! + m_bigInfoBlinkTimer = 1; } FUN_10015820(FALSE, LegoOmni::c_disableInput | LegoOmni::c_disable3d | LegoOmni::c_clearScreen); @@ -631,37 +631,37 @@ void Infocenter::InitializeBitmaps() m_glowInfo[0].m_destCtl = (MxStillPresenter*) Find("MxStillPresenter", "Info_A_Bitmap"); assert(m_glowInfo[0].m_destCtl); m_glowInfo[0].m_area = MxRect(391, 182, 427, 230); - m_glowInfo[0].m_unk0x04 = 3; + m_glowInfo[0].m_target = InfocenterMapEntry::e_infocenter; m_glowInfo[1].m_destCtl = (MxStillPresenter*) Find("MxStillPresenter", "Boat_A_Bitmap"); assert(m_glowInfo[1].m_destCtl); m_glowInfo[1].m_area = MxRect(304, 225, 350, 268); - m_glowInfo[1].m_unk0x04 = 10; + m_glowInfo[1].m_target = InfocenterMapEntry::e_jetrace; m_glowInfo[2].m_destCtl = (MxStillPresenter*) Find("MxStillPresenter", "Race_A_Bitmap"); assert(m_glowInfo[1].m_destCtl); // DECOMP: intentional typo m_glowInfo[2].m_area = MxRect(301, 133, 347, 181); - m_glowInfo[2].m_unk0x04 = 11; + m_glowInfo[2].m_target = InfocenterMapEntry::e_carrace; m_glowInfo[3].m_destCtl = (MxStillPresenter*) Find("MxStillPresenter", "Pizza_A_Bitmap"); assert(m_glowInfo[3].m_destCtl); m_glowInfo[3].m_area = MxRect(289, 182, 335, 225); - m_glowInfo[3].m_unk0x04 = 12; + m_glowInfo[3].m_target = InfocenterMapEntry::e_pizzeria; m_glowInfo[4].m_destCtl = (MxStillPresenter*) Find("MxStillPresenter", "Gas_A_Bitmap"); assert(m_glowInfo[4].m_destCtl); m_glowInfo[4].m_area = MxRect(350, 161, 391, 209); - m_glowInfo[4].m_unk0x04 = 13; + m_glowInfo[4].m_target = InfocenterMapEntry::e_garage; m_glowInfo[5].m_destCtl = (MxStillPresenter*) Find("MxStillPresenter", "Med_A_Bitmap"); assert(m_glowInfo[5].m_destCtl); m_glowInfo[5].m_area = MxRect(392, 130, 438, 176); - m_glowInfo[5].m_unk0x04 = 14; + m_glowInfo[5].m_target = InfocenterMapEntry::e_hospital; m_glowInfo[6].m_destCtl = (MxStillPresenter*) Find("MxStillPresenter", "Cop_A_Bitmap"); assert(m_glowInfo[6].m_destCtl); m_glowInfo[6].m_area = MxRect(396, 229, 442, 272); - m_glowInfo[6].m_unk0x04 = 15; + m_glowInfo[6].m_target = InfocenterMapEntry::e_police; m_frame = (MxStillPresenter*) Find("MxStillPresenter", "FrameHot_Bitmap"); assert(m_frame); @@ -687,7 +687,7 @@ MxU8 Infocenter::HandleMouseMove(MxS32 p_x, MxS32 p_y) m_dragPresenter->SetPosition(p_x, p_y); } - FUN_10070d10(p_x, p_y); + UpdateEnabledGlowControl(p_x, p_y); return 1; } @@ -776,7 +776,7 @@ MxU8 Infocenter::HandleButtonUp(MxS32 p_x, MxS32 p_y) m_radio.Stop(); BackgroundAudioManager()->Stop(); PlayAction(InfomainScript::c_Pepper_All_Movie); - m_unk0x1d4++; + m_playingMovieCounter++; } break; case InfomainScript::c_Mama_Ctl: @@ -784,7 +784,7 @@ MxU8 Infocenter::HandleButtonUp(MxS32 p_x, MxS32 p_y) m_radio.Stop(); BackgroundAudioManager()->Stop(); PlayAction(InfomainScript::c_Mama_All_Movie); - m_unk0x1d4++; + m_playingMovieCounter++; } break; case InfomainScript::c_Papa_Ctl: @@ -792,7 +792,7 @@ MxU8 Infocenter::HandleButtonUp(MxS32 p_x, MxS32 p_y) m_radio.Stop(); BackgroundAudioManager()->Stop(); PlayAction(InfomainScript::c_Papa_All_Movie); - m_unk0x1d4++; + m_playingMovieCounter++; } break; case InfomainScript::c_Nick_Ctl: @@ -800,7 +800,7 @@ MxU8 Infocenter::HandleButtonUp(MxS32 p_x, MxS32 p_y) m_radio.Stop(); BackgroundAudioManager()->Stop(); PlayAction(InfomainScript::c_Nick_All_Movie); - m_unk0x1d4++; + m_playingMovieCounter++; } break; case InfomainScript::c_Laura_Ctl: @@ -808,17 +808,17 @@ MxU8 Infocenter::HandleButtonUp(MxS32 p_x, MxS32 p_y) m_radio.Stop(); BackgroundAudioManager()->Stop(); PlayAction(InfomainScript::c_Laura_All_Movie); - m_unk0x1d4++; + m_playingMovieCounter++; } break; } } else { - if (m_unk0x1c8 != -1) { + if (m_enabledGlowControl != -1) { m_infoManDialogueTimer = 0; - switch (m_glowInfo[m_unk0x1c8].m_unk0x04) { - case 3: + switch (m_glowInfo[m_enabledGlowControl].m_target) { + case InfocenterMapEntry::e_infocenter: GameState()->SetActor(m_selectedCharacter); switch (m_selectedCharacter) { @@ -839,37 +839,37 @@ MxU8 Infocenter::HandleButtonUp(MxS32 p_x, MxS32 p_y) break; } break; - case 10: + case InfocenterMapEntry::e_jetrace: if (m_selectedCharacter) { m_destLocation = LegoGameState::e_jetraceExterior; m_infocenterState->m_unk0x74 = 5; } break; - case 11: + case InfocenterMapEntry::e_carrace: if (m_selectedCharacter) { m_destLocation = LegoGameState::e_carraceExterior; m_infocenterState->m_unk0x74 = 5; } break; - case 12: + case InfocenterMapEntry::e_pizzeria: if (m_selectedCharacter) { m_destLocation = LegoGameState::e_pizzeriaExterior; m_infocenterState->m_unk0x74 = 5; } break; - case 13: + case InfocenterMapEntry::e_garage: if (m_selectedCharacter) { m_destLocation = LegoGameState::e_garageExterior; m_infocenterState->m_unk0x74 = 5; } break; - case 14: + case InfocenterMapEntry::e_hospital: if (m_selectedCharacter) { m_destLocation = LegoGameState::e_hospitalExterior; m_infocenterState->m_unk0x74 = 5; } break; - case 15: + case InfocenterMapEntry::e_police: if (m_selectedCharacter) { m_destLocation = LegoGameState::e_policeExterior; m_infocenterState->m_unk0x74 = 5; @@ -931,7 +931,7 @@ MxU8 Infocenter::HandleButtonUp(MxS32 p_x, MxS32 p_y) } UpdateFrameHot(TRUE); - FUN_10070d10(0, 0); + UpdateEnabledGlowControl(0, 0); } return FALSE; @@ -1232,21 +1232,21 @@ MxResult Infocenter::Tickle() m_bookAnimationTimer = 1; } - if (m_unk0x1d6 != 0) { - m_unk0x1d6 += 100; + if (m_bigInfoBlinkTimer != 0) { + m_bigInfoBlinkTimer += 100; - if (m_unk0x1d6 > 3400 && m_unk0x1d6 < 3650) { + if (m_bigInfoBlinkTimer > 3400 && m_bigInfoBlinkTimer < 3650) { ControlManager()->UpdateEnabledChild(InfomainScript::c_BigInfo_Ctl, m_atomId.GetInternal(), 1); } - else if (m_unk0x1d6 > 3650 && m_unk0x1d6 < 3900) { + else if (m_bigInfoBlinkTimer > 3650 && m_bigInfoBlinkTimer < 3900) { ControlManager()->UpdateEnabledChild(InfomainScript::c_BigInfo_Ctl, m_atomId.GetInternal(), 0); } - else if (m_unk0x1d6 > 3900 && m_unk0x1d6 < 4150) { + else if (m_bigInfoBlinkTimer > 3900 && m_bigInfoBlinkTimer < 4150) { ControlManager()->UpdateEnabledChild(InfomainScript::c_BigInfo_Ctl, m_atomId.GetInternal(), 1); } - else if (m_unk0x1d6 > 4400) { + else if (m_bigInfoBlinkTimer > 4400) { ControlManager()->UpdateEnabledChild(InfomainScript::c_BigInfo_Ctl, m_atomId.GetInternal(), 0); - m_unk0x1d6 = 0; + m_bigInfoBlinkTimer = 0; } } @@ -1295,7 +1295,7 @@ MxBool Infocenter::VTable0x5c() // FUNCTION: LEGO1 0x10070d10 // FUNCTION: BETA10 0x100307d4 -void Infocenter::FUN_10070d10(MxS32 p_x, MxS32 p_y) +void Infocenter::UpdateEnabledGlowControl(MxS32 p_x, MxS32 p_y) { MxS16 i; for (i = 0; i < (MxS32) (sizeof(m_glowInfo) / sizeof(m_glowInfo[0])); i++) { @@ -1313,12 +1313,12 @@ void Infocenter::FUN_10070d10(MxS32 p_x, MxS32 p_y) i = -1; } - if (i != m_unk0x1c8) { - if (m_unk0x1c8 != -1) { - m_glowInfo[m_unk0x1c8].m_destCtl->Enable(FALSE); + if (i != m_enabledGlowControl) { + if (m_enabledGlowControl != -1) { + m_glowInfo[m_enabledGlowControl].m_destCtl->Enable(FALSE); } - m_unk0x1c8 = i; + m_enabledGlowControl = i; if (i != -1) { m_glowInfo[i].m_destCtl->Enable(TRUE); } From da2a06ed2be0a4638290c8f3d0f126a55538dd57 Mon Sep 17 00:00:00 2001 From: Anders Jenbo Date: Tue, 8 Jul 2025 05:16:48 +0200 Subject: [PATCH 098/188] Use minimal test for SDL_GPU on macOS (#552) --- miniwin/src/internal/d3drmrenderer_sdl3gpu.h | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/miniwin/src/internal/d3drmrenderer_sdl3gpu.h b/miniwin/src/internal/d3drmrenderer_sdl3gpu.h index 7f948a37..1e528232 100644 --- a/miniwin/src/internal/d3drmrenderer_sdl3gpu.h +++ b/miniwin/src/internal/d3drmrenderer_sdl3gpu.h @@ -120,11 +120,19 @@ class Direct3DRMSDL3GPURenderer : public Direct3DRMRenderer { inline static void Direct3DRMSDL3GPU_EnumDevice(LPD3DENUMDEVICESCALLBACK cb, void* ctx) { +#ifdef __APPLE__ + SDL_GPUDevice* device = SDL_CreateGPUDevice(SDL_GPU_SHADERFORMAT_MSL, false, nullptr); + if (!device) { + return; + } + SDL_DestroyGPUDevice(device); +#else Direct3DRMRenderer* device = Direct3DRMSDL3GPURenderer::Create(640, 480); if (!device) { return; } delete device; +#endif D3DDEVICEDESC halDesc = {}; halDesc.dcmColorModel = D3DCOLOR_RGB; From 5d3b6884e0c412f7e4033e25c430604f7d3ae2dc Mon Sep 17 00:00:00 2001 From: Fabian Neundorf Date: Tue, 8 Jul 2025 16:26:32 +0200 Subject: [PATCH 099/188] Clear unknowns in `legoutils.h` (#1610) --- LEGO1/lego/legoomni/include/legoutils.h | 10 ++-- LEGO1/lego/legoomni/src/actors/bike.cpp | 2 +- LEGO1/lego/legoomni/src/actors/buildings.cpp | 12 ++--- LEGO1/lego/legoomni/src/actors/dunebuggy.cpp | 2 +- LEGO1/lego/legoomni/src/actors/helicopter.cpp | 2 +- .../legoomni/src/actors/islepathactor.cpp | 6 +-- LEGO1/lego/legoomni/src/actors/jetski.cpp | 2 +- .../legoomni/src/actors/jukeboxentity.cpp | 2 +- LEGO1/lego/legoomni/src/actors/motorcycle.cpp | 2 +- LEGO1/lego/legoomni/src/actors/pizzeria.cpp | 2 +- LEGO1/lego/legoomni/src/actors/racecar.cpp | 2 +- LEGO1/lego/legoomni/src/actors/skateboard.cpp | 2 +- .../lego/legoomni/src/build/legocarbuild.cpp | 10 ++-- LEGO1/lego/legoomni/src/common/legoutils.cpp | 46 +++++++++---------- LEGO1/lego/legoomni/src/entity/legoworld.cpp | 2 +- LEGO1/lego/legoomni/src/worlds/isle.cpp | 18 ++++---- 16 files changed, 61 insertions(+), 61 deletions(-) diff --git a/LEGO1/lego/legoomni/include/legoutils.h b/LEGO1/lego/legoomni/include/legoutils.h index 3780e700..59c34825 100644 --- a/LEGO1/lego/legoomni/include/legoutils.h +++ b/LEGO1/lego/legoomni/include/legoutils.h @@ -45,28 +45,28 @@ LegoROI* PickROI(MxLong p_x, MxLong p_y); LegoROI* PickRootROI(MxLong p_x, MxLong p_y); void RotateY(LegoROI* p_roi, MxFloat p_angle); MxBool SpheresIntersect(const BoundingSphere& p_sphere1, const BoundingSphere& p_sphere2); -MxBool FUN_1003ded0(MxFloat p_param1[2], MxFloat p_param2[3], MxFloat p_param3[3]); +MxBool CalculateRayOriginDirection(MxFloat p_coordinates[2], MxFloat p_direction[3], MxFloat p_origin[3]); MxBool TransformWorldToScreen(const MxFloat p_world[3], MxFloat p_screen[4]); MxS16 CountTotalTreeNodes(LegoTreeNode* p_node); LegoTreeNode* GetTreeNode(LegoTreeNode* p_node, MxU32 p_index); -void FUN_1003e050(LegoAnimPresenter* p_presenter); +void CalculateViewFromAnimation(LegoAnimPresenter* p_presenter); Extra::ActionType MatchActionString(const char*); void InvokeAction(Extra::ActionType p_actionId, const MxAtomId& p_pAtom, MxS32 p_streamId, LegoEntity* p_sender); void SetCameraControllerFromIsle(); void ConvertHSVToRGB(float p_h, float p_s, float p_v, float* p_rOut, float* p_bOut, float* p_gOut); void PlayCamAnim(LegoPathActor* p_actor, MxBool p_unused, MxU32 p_location, MxBool p_bool); -void FUN_1003eda0(); +void ResetViewVelocity(); MxBool RemoveFromCurrentWorld(const MxAtomId& p_atomId, MxS32 p_id); void EnableAnimations(MxBool p_enable); void SetAppCursor(Cursor p_cursor); -MxBool FUN_1003ef60(); +MxBool CanExit(); MxBool RemoveFromWorld(MxAtomId& p_entityAtom, MxS32 p_entityId, MxAtomId& p_worldAtom, MxS32 p_worldEntityId); MxS32 UpdateLightPosition(MxS32 p_increase); void SetLightPosition(MxS32 p_index); LegoNamedTexture* ReadNamedTexture(LegoStorage* p_storage); void WriteDefaultTexture(LegoStorage* p_storage, const char* p_name); void WriteNamedTexture(LegoStorage* p_storage, LegoNamedTexture* p_namedTexture); -void FUN_1003f930(LegoNamedTexture* p_namedTexture); +void LoadFromNamedTexture(LegoNamedTexture* p_namedTexture); // FUNCTION: BETA10 0x100260a0 inline void StartIsleAction(IsleScript::Script p_objectId) diff --git a/LEGO1/lego/legoomni/src/actors/bike.cpp b/LEGO1/lego/legoomni/src/actors/bike.cpp index 0d480ad0..6c82cb0d 100644 --- a/LEGO1/lego/legoomni/src/actors/bike.cpp +++ b/LEGO1/lego/legoomni/src/actors/bike.cpp @@ -52,7 +52,7 @@ void Bike::Exit() // FUNCTION: LEGO1 0x100769a0 MxLong Bike::HandleClick() { - if (FUN_1003ef60()) { + if (CanExit()) { Act1State* state = (Act1State*) GameState()->GetState("Act1State"); FUN_10015820(TRUE, 0); diff --git a/LEGO1/lego/legoomni/src/actors/buildings.cpp b/LEGO1/lego/legoomni/src/actors/buildings.cpp index ea7069cc..833287bc 100644 --- a/LEGO1/lego/legoomni/src/actors/buildings.cpp +++ b/LEGO1/lego/legoomni/src/actors/buildings.cpp @@ -80,7 +80,7 @@ MxLong InfoCenterEntity::HandleClick(LegoEventNotificationParam& p_param) // FUNCTION: LEGO1 0x100151d0 MxLong GasStationEntity::HandleClick(LegoEventNotificationParam& p_param) { - if (FUN_1003ef60()) { + if (CanExit()) { Act1State* state = (Act1State*) GameState()->GetState("Act1State"); if (state->GetUnknown18() != 8) { @@ -104,7 +104,7 @@ MxLong GasStationEntity::HandleClick(LegoEventNotificationParam& p_param) // FUNCTION: LEGO1 0x10015270 MxLong HospitalEntity::HandleClick(LegoEventNotificationParam& p_param) { - if (FUN_1003ef60()) { + if (CanExit()) { Act1State* act1State = (Act1State*) GameState()->GetState("Act1State"); if (act1State->GetUnknown18() != 10) { @@ -128,7 +128,7 @@ MxLong HospitalEntity::HandleClick(LegoEventNotificationParam& p_param) // FUNCTION: LEGO1 0x10015310 MxLong PoliceEntity::HandleClick(LegoEventNotificationParam& p_param) { - if (FUN_1003ef60()) { + if (CanExit()) { Act1State* state = (Act1State*) GameState()->GetState("Act1State"); if (state->GetUnknown18() != 10) { @@ -152,7 +152,7 @@ MxLong PoliceEntity::HandleClick(LegoEventNotificationParam& p_param) // FUNCTION: LEGO1 0x100153b0 MxLong BeachHouseEntity::HandleClick(LegoEventNotificationParam& p_param) { - if (FUN_1003ef60()) { + if (CanExit()) { Act1State* state = (Act1State*) GameState()->GetState("Act1State"); state->SetUnknown18(0); @@ -173,7 +173,7 @@ MxLong BeachHouseEntity::HandleClick(LegoEventNotificationParam& p_param) // FUNCTION: LEGO1 0x10015450 MxLong RaceStandsEntity::HandleClick(LegoEventNotificationParam& p_param) { - if (FUN_1003ef60()) { + if (CanExit()) { Act1State* state = (Act1State*) GameState()->GetState("Act1State"); state->SetUnknown18(0); @@ -195,7 +195,7 @@ MxLong RaceStandsEntity::HandleClick(LegoEventNotificationParam& p_param) // FUNCTION: BETA10 0x100256e8 MxLong JailEntity::HandleClick(LegoEventNotificationParam& p_param) { - if (FUN_1003ef60()) { + if (CanExit()) { PlayCamAnim(UserActor(), FALSE, 18, TRUE); } diff --git a/LEGO1/lego/legoomni/src/actors/dunebuggy.cpp b/LEGO1/lego/legoomni/src/actors/dunebuggy.cpp index e475b6a6..e9ec7817 100644 --- a/LEGO1/lego/legoomni/src/actors/dunebuggy.cpp +++ b/LEGO1/lego/legoomni/src/actors/dunebuggy.cpp @@ -88,7 +88,7 @@ void DuneBuggy::Exit() // FUNCTION: LEGO1 0x10068060 MxLong DuneBuggy::HandleClick() { - if (!FUN_1003ef60()) { + if (!CanExit()) { return 1; } diff --git a/LEGO1/lego/legoomni/src/actors/helicopter.cpp b/LEGO1/lego/legoomni/src/actors/helicopter.cpp index bf9eb9d0..c8c465f7 100644 --- a/LEGO1/lego/legoomni/src/actors/helicopter.cpp +++ b/LEGO1/lego/legoomni/src/actors/helicopter.cpp @@ -121,7 +121,7 @@ void Helicopter::Exit() // FUNCTION: BETA10 0x1002a3db MxLong Helicopter::HandleClick() { - if (!FUN_1003ef60()) { + if (!CanExit()) { return 1; } diff --git a/LEGO1/lego/legoomni/src/actors/islepathactor.cpp b/LEGO1/lego/legoomni/src/actors/islepathactor.cpp index fbd57206..9d5a3a22 100644 --- a/LEGO1/lego/legoomni/src/actors/islepathactor.cpp +++ b/LEGO1/lego/legoomni/src/actors/islepathactor.cpp @@ -153,7 +153,7 @@ void IslePathActor::Exit() FUN_1001b660(); FUN_10010c30(); - FUN_1003eda0(); + ResetViewVelocity(); } // GLOBAL: LEGO1 0x10102b28 @@ -598,7 +598,7 @@ void IslePathActor::SpawnPlayer(LegoGameState::Area p_area, MxBool p_enter, MxU8 } if (m_cameraFlag) { - FUN_1003eda0(); + ResetViewVelocity(); } if (p_flags & c_playMusic && g_spawnLocations[i].m_music != JukeboxScript::c_noneJukebox) { @@ -632,7 +632,7 @@ void IslePathActor::VTable0xec(MxMatrix p_transform, LegoPathBoundary* p_boundar m_roi->SetLocal2World(p_transform); if (m_cameraFlag) { - FUN_1003eda0(); + ResetViewVelocity(); FUN_10010c30(); } } diff --git a/LEGO1/lego/legoomni/src/actors/jetski.cpp b/LEGO1/lego/legoomni/src/actors/jetski.cpp index 3e049920..11ff7d18 100644 --- a/LEGO1/lego/legoomni/src/actors/jetski.cpp +++ b/LEGO1/lego/legoomni/src/actors/jetski.cpp @@ -81,7 +81,7 @@ void Jetski::Exit() MxLong Jetski::HandleClick() { #ifndef BETA10 - if (!FUN_1003ef60()) { + if (!CanExit()) { return 1; } diff --git a/LEGO1/lego/legoomni/src/actors/jukeboxentity.cpp b/LEGO1/lego/legoomni/src/actors/jukeboxentity.cpp index ecf0bec8..c92aec79 100644 --- a/LEGO1/lego/legoomni/src/actors/jukeboxentity.cpp +++ b/LEGO1/lego/legoomni/src/actors/jukeboxentity.cpp @@ -36,7 +36,7 @@ MxLong JukeBoxEntity::Notify(MxParam& p_param) MxNotificationParam& param = (MxNotificationParam&) p_param; if (param.GetNotification() == c_notificationClick) { - if (!FUN_1003ef60()) { + if (!CanExit()) { return 1; } diff --git a/LEGO1/lego/legoomni/src/actors/motorcycle.cpp b/LEGO1/lego/legoomni/src/actors/motorcycle.cpp index 1ada7611..5819598e 100644 --- a/LEGO1/lego/legoomni/src/actors/motorcycle.cpp +++ b/LEGO1/lego/legoomni/src/actors/motorcycle.cpp @@ -84,7 +84,7 @@ void Motocycle::Exit() // FUNCTION: LEGO1 0x10035c50 MxLong Motocycle::HandleClick() { - if (!FUN_1003ef60()) { + if (!CanExit()) { return 1; } diff --git a/LEGO1/lego/legoomni/src/actors/pizzeria.cpp b/LEGO1/lego/legoomni/src/actors/pizzeria.cpp index a040f337..3b5f8923 100644 --- a/LEGO1/lego/legoomni/src/actors/pizzeria.cpp +++ b/LEGO1/lego/legoomni/src/actors/pizzeria.cpp @@ -69,7 +69,7 @@ void Pizzeria::CreateState() // FUNCTION: BETA10 0x100efc91 MxLong Pizzeria::HandleClick() { - if (FUN_1003ef60() && m_pizzaMissionState->m_unk0x0c == 0) { + if (CanExit() && m_pizzaMissionState->m_unk0x0c == 0) { if (UserActor()->GetActorId() != GameState()->GetActorId()) { if (!UserActor()->IsA("SkateBoard")) { ((IslePathActor*) UserActor())->Exit(); diff --git a/LEGO1/lego/legoomni/src/actors/racecar.cpp b/LEGO1/lego/legoomni/src/actors/racecar.cpp index f511f524..ff9eb293 100644 --- a/LEGO1/lego/legoomni/src/actors/racecar.cpp +++ b/LEGO1/lego/legoomni/src/actors/racecar.cpp @@ -40,7 +40,7 @@ MxResult RaceCar::Create(MxDSAction& p_dsAction) // FUNCTION: LEGO1 0x100284d0 MxLong RaceCar::HandleClick() { - if (!FUN_1003ef60()) { + if (!CanExit()) { return 1; } diff --git a/LEGO1/lego/legoomni/src/actors/skateboard.cpp b/LEGO1/lego/legoomni/src/actors/skateboard.cpp index 18d2810d..0998ba58 100644 --- a/LEGO1/lego/legoomni/src/actors/skateboard.cpp +++ b/LEGO1/lego/legoomni/src/actors/skateboard.cpp @@ -75,7 +75,7 @@ MxLong SkateBoard::HandleClick() { Act1State* state = (Act1State*) GameState()->GetState("Act1State"); - if (!FUN_1003ef60() && state->m_unk0x018 != 3) { + if (!CanExit() && state->m_unk0x018 != 3) { return 1; } diff --git a/LEGO1/lego/legoomni/src/build/legocarbuild.cpp b/LEGO1/lego/legoomni/src/build/legocarbuild.cpp index 0cea0c0d..5f99393e 100644 --- a/LEGO1/lego/legoomni/src/build/legocarbuild.cpp +++ b/LEGO1/lego/legoomni/src/build/legocarbuild.cpp @@ -371,7 +371,7 @@ void LegoCarBuild::FUN_10023130(MxLong p_x, MxLong p_y) pfVar3[0] = p_x; pfVar3[1] = p_y; - if (FUN_1003ded0(pfVar3, local30, local84)) { + if (CalculateRayOriginDirection(pfVar3, local30, local84)) { MxFloat local18[3]; MxFloat local8c[2]; @@ -421,7 +421,7 @@ void LegoCarBuild::VTable0x74(MxFloat p_param1[2], MxFloat p_param2[3]) MxFloat local20[3]; MxFloat local14[3]; - FUN_1003ded0(p_param1, local14, local20); + CalculateRayOriginDirection(p_param1, local14, local20); fVar1 = (m_unk0x2a4[2] - local20[2]) / local14[2]; p_param2[0] = (fVar1 * local14[0] + local20[0]) - m_unk0x2a4[0]; @@ -437,7 +437,7 @@ void LegoCarBuild::VTable0x78(MxFloat p_param1[2], MxFloat p_param2[3]) MxFloat local18[3]; MxFloat localc[3]; - FUN_1003ded0(p_param1, local18, localc); + CalculateRayOriginDirection(p_param1, local18, localc); p_param2[2] = m_unk0x2a4[2] + (m_unk0x2bc[2] - m_unk0x2a4[2]) * ((p_param1[1] - m_unk0x290[1]) / (m_unk0x298[1] - m_unk0x290[1])); @@ -453,7 +453,7 @@ void LegoCarBuild::VTable0x7c(MxFloat p_param1[2], MxFloat p_param2[3]) { MxFloat local18[3]; MxFloat localc[3]; - FUN_1003ded0(p_param1, local18, localc); + CalculateRayOriginDirection(p_param1, local18, localc); MxFloat fVar1 = (m_unk0x2bc[1] - localc[1]) / local18[1]; p_param2[0] = fVar1 * local18[0] - m_unk0x2a4[0] + localc[0]; @@ -1249,7 +1249,7 @@ undefined4 LegoCarBuild::FUN_10024c20(MxNotificationParam* p_param) // FUNCTION: LEGO1 0x10024ef0 void LegoCarBuild::FUN_10024ef0() { - FUN_1003eda0(); + ResetViewVelocity(); m_buildState->m_animationState = LegoVehicleBuildState::e_cutscene; FUN_10025720(FUN_10025d70()); m_buildState->m_unk0x4c += 1; diff --git a/LEGO1/lego/legoomni/src/common/legoutils.cpp b/LEGO1/lego/legoomni/src/common/legoutils.cpp index 1e0dbba5..d5ecb1ad 100644 --- a/LEGO1/lego/legoomni/src/common/legoutils.cpp +++ b/LEGO1/lego/legoomni/src/common/legoutils.cpp @@ -99,29 +99,29 @@ MxBool SpheresIntersect(const BoundingSphere& p_sphere1, const BoundingSphere& p // FUNCTION: LEGO1 0x1003ded0 // FUNCTION: BETA10 0x100d3802 -MxBool FUN_1003ded0(MxFloat p_param1[2], MxFloat p_param2[3], MxFloat p_param3[3]) +MxBool CalculateRayOriginDirection(MxFloat p_coordinates[2], MxFloat p_direction[3], MxFloat p_origin[3]) { - MxFloat local1c[4]; - MxFloat local10[3]; + MxFloat screenPoint[4]; + MxFloat farPoint[3]; Tgl::View* view = VideoManager()->Get3DManager()->GetLego3DView()->GetView(); - local1c[0] = p_param1[0]; - local1c[1] = p_param1[1]; - local1c[2] = 1.0f; - local1c[3] = 1.0f; + screenPoint[0] = p_coordinates[0]; + screenPoint[1] = p_coordinates[1]; + screenPoint[2] = 1.0f; + screenPoint[3] = 1.0f; - view->TransformScreenToWorld(local1c, p_param3); + view->TransformScreenToWorld(screenPoint, p_origin); - local1c[0] *= 2.0; - local1c[1] *= 2.0; - local1c[3] = 2.0; + screenPoint[0] *= 2.0; + screenPoint[1] *= 2.0; + screenPoint[3] = 2.0; - view->TransformScreenToWorld(local1c, local10); + view->TransformScreenToWorld(screenPoint, farPoint); - p_param2[0] = local10[0] - p_param3[0]; - p_param2[1] = local10[1] - p_param3[1]; - p_param2[2] = local10[2] - p_param3[2]; + p_direction[0] = farPoint[0] - p_origin[0]; + p_direction[1] = farPoint[1] - p_origin[1]; + p_direction[2] = farPoint[2] - p_origin[2]; return TRUE; } @@ -173,7 +173,7 @@ LegoTreeNode* GetTreeNode(LegoTreeNode* p_node, MxU32 p_index) // FUNCTION: LEGO1 0x1003e050 // FUNCTION: BETA10 0x100d3abc -void FUN_1003e050(LegoAnimPresenter* p_presenter) +void CalculateViewFromAnimation(LegoAnimPresenter* p_presenter) { MxMatrix viewMatrix; LegoTreeNode* rootNode = p_presenter->GetAnimation()->GetRoot(); @@ -181,7 +181,7 @@ void FUN_1003e050(LegoAnimPresenter* p_presenter) LegoAnimNodeData* targetData = NULL; MxS16 nodesCount = CountTotalTreeNodes(rootNode); - MxFloat cam; + MxFloat fov; for (MxS16 i = 0; i < nodesCount; i++) { if (camData && targetData) { break; @@ -191,7 +191,7 @@ void FUN_1003e050(LegoAnimPresenter* p_presenter) if (!strnicmp(data->GetName(), "CAM", strlen("CAM"))) { camData = data; - cam = atof(&data->GetName()[strlen(data->GetName()) - 2]); + fov = atof(&data->GetName()[strlen(data->GetName()) - 2]); } else if (!strcmpi(data->GetName(), "TARGET")) { targetData = data; @@ -220,8 +220,8 @@ void FUN_1003e050(LegoAnimPresenter* p_presenter) roi->WrappedSetLocal2WorldWithWorldDataUpdate(viewMatrix); view->Moved(*roi); - FUN_1003eda0(); - video->Get3DManager()->SetFrustrum(cam, 0.1, 250.0); + ResetViewVelocity(); + video->Get3DManager()->SetFrustrum(fov, 0.1, 250.0); } // FUNCTION: LEGO1 0x1003e300 @@ -473,7 +473,7 @@ void PlayCamAnim(LegoPathActor* p_actor, MxBool p_unused, MxU32 p_location, MxBo // FUNCTION: LEGO1 0x1003eda0 // FUNCTION: BETA10 0x100d4bf4 -void FUN_1003eda0() +void ResetViewVelocity() { Mx3DPointFloat vec; vec.Clear(); @@ -569,7 +569,7 @@ void SetAppCursor(Cursor p_cursor) } // FUNCTION: LEGO1 0x1003ef60 -MxBool FUN_1003ef60() +MxBool CanExit() { Act1State* act1State = (Act1State*) GameState()->GetState("Act1State"); @@ -765,7 +765,7 @@ void WriteNamedTexture(LegoStorage* p_storage, LegoNamedTexture* p_namedTexture) } // FUNCTION: LEGO1 0x1003f930 -void FUN_1003f930(LegoNamedTexture* p_namedTexture) +void LoadFromNamedTexture(LegoNamedTexture* p_namedTexture) { LegoTextureInfo* textureInfo = TextureContainer()->Get(p_namedTexture->GetName()->GetData()); diff --git a/LEGO1/lego/legoomni/src/entity/legoworld.cpp b/LEGO1/lego/legoomni/src/entity/legoworld.cpp index c34a985b..cc82fe92 100644 --- a/LEGO1/lego/legoomni/src/entity/legoworld.cpp +++ b/LEGO1/lego/legoomni/src/entity/legoworld.cpp @@ -424,7 +424,7 @@ void LegoWorld::Add(MxCore* p_object) #ifndef BETA10 if (p_object->IsA("LegoAnimPresenter")) { if (!strcmpi(((LegoAnimPresenter*) p_object)->GetAction()->GetObjectName(), "ConfigAnimation")) { - FUN_1003e050((LegoAnimPresenter*) p_object); + CalculateViewFromAnimation((LegoAnimPresenter*) p_object); ((LegoAnimPresenter*) p_object) ->GetAction() ->SetDuration(((LegoAnimPresenter*) p_object)->GetAnimation()->GetDuration()); diff --git a/LEGO1/lego/legoomni/src/worlds/isle.cpp b/LEGO1/lego/legoomni/src/worlds/isle.cpp index 2eb47874..7865969b 100644 --- a/LEGO1/lego/legoomni/src/worlds/isle.cpp +++ b/LEGO1/lego/legoomni/src/worlds/isle.cpp @@ -1653,19 +1653,19 @@ void Act1State::PlaceActors() m_helicopter = NULL; if (m_helicopterWindshield != NULL) { - FUN_1003f930(m_helicopterWindshield); + LoadFromNamedTexture(m_helicopterWindshield); delete m_helicopterWindshield; m_helicopterWindshield = NULL; } if (m_helicopterJetLeft != NULL) { - FUN_1003f930(m_helicopterJetLeft); + LoadFromNamedTexture(m_helicopterJetLeft); delete m_helicopterJetLeft; m_helicopterJetLeft = NULL; } if (m_helicopterJetRight != NULL) { - FUN_1003f930(m_helicopterJetRight); + LoadFromNamedTexture(m_helicopterJetRight); delete m_helicopterJetRight; m_helicopterJetRight = NULL; } @@ -1689,13 +1689,13 @@ void Act1State::PlaceActors() m_jetski = NULL; if (m_jetskiFront != NULL) { - FUN_1003f930(m_jetskiFront); + LoadFromNamedTexture(m_jetskiFront); delete m_jetskiFront; m_jetskiFront = NULL; } if (m_jetskiWindshield != NULL) { - FUN_1003f930(m_jetskiWindshield); + LoadFromNamedTexture(m_jetskiWindshield); delete m_jetskiWindshield; m_jetskiWindshield = NULL; } @@ -1723,7 +1723,7 @@ void Act1State::PlaceActors() m_dunebuggy = NULL; if (m_dunebuggyFront != NULL) { - FUN_1003f930(m_dunebuggyFront); + LoadFromNamedTexture(m_dunebuggyFront); delete m_dunebuggyFront; m_dunebuggyFront = NULL; } @@ -1751,19 +1751,19 @@ void Act1State::PlaceActors() m_racecar = NULL; if (m_racecarFront != NULL) { - FUN_1003f930(m_racecarFront); + LoadFromNamedTexture(m_racecarFront); delete m_racecarFront; m_racecarFront = NULL; } if (m_racecarBack != NULL) { - FUN_1003f930(m_racecarBack); + LoadFromNamedTexture(m_racecarBack); delete m_racecarBack; m_racecarBack = NULL; } if (m_racecarTail != NULL) { - FUN_1003f930(m_racecarTail); + LoadFromNamedTexture(m_racecarTail); delete m_racecarTail; m_racecarTail = NULL; } From 2761d9985aceb52f3e22fdb78c1e8e16f0416440 Mon Sep 17 00:00:00 2001 From: Helloyunho Date: Wed, 9 Jul 2025 02:09:11 +0900 Subject: [PATCH 100/188] Create .app when building/packing macOS (#555) --- .github/workflows/ci.yml | 15 +++- CMakeLists.txt | 4 ++ packaging/CMakeLists.txt | 4 ++ packaging/macos/CMakeLists.txt | 99 +++++++++++++++++++++++++++ packaging/macos/config/AppIcon.icns | Bin 0 -> 49433 bytes packaging/macos/config/Info.plist.in | 80 ++++++++++++++++++++++ packaging/macos/isle/AppIcon.icns | Bin 0 -> 60473 bytes packaging/macos/isle/Info.plist.in | 82 ++++++++++++++++++++++ 8 files changed, 283 insertions(+), 1 deletion(-) create mode 100644 packaging/macos/CMakeLists.txt create mode 100644 packaging/macos/config/AppIcon.icns create mode 100644 packaging/macos/config/Info.plist.in create mode 100644 packaging/macos/isle/AppIcon.icns create mode 100644 packaging/macos/isle/Info.plist.in diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 0c28ea84..090b9e1f 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -122,7 +122,20 @@ jobs: if: ${{ !matrix.n3ds }} run: | cd build - cpack . + success=0 + max_tries=10 + for i in $(seq $max_tries); do + cpack . && success=1 + if test $success = 1; then + break + fi + echo "Package creation failed. Sleep 1 second and try again." + sleep 1 + done + if test $success = 0; then + echo "Package creation failed after $max_tries attempts." + exit 1 + fi - name: Install linuxdeploy if: ${{ matrix.linux }} diff --git a/CMakeLists.txt b/CMakeLists.txt index d5f77cde..82360dec 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -707,6 +707,7 @@ if (NOT (NINTENDO_3DS OR WINDOWS_STORE)) install(TARGETS isle ${install_extra_targets} RUNTIME DESTINATION "${CMAKE_INSTALL_BINDIR}" LIBRARY DESTINATION "${CMAKE_INSTALL_LIBDIR}" + BUNDLE DESTINATION "." ) endif() if (ISLE_BUILD_CONFIG) @@ -731,6 +732,7 @@ if (ISLE_BUILD_CONFIG) endif() install(TARGETS isle-config RUNTIME DESTINATION "${CMAKE_INSTALL_BINDIR}" + BUNDLE DESTINATION "." ) endif() if(EMSCRIPTEN) @@ -802,6 +804,8 @@ if(WINDOWS_STORE) endif() if(MSVC) set(CPACK_GENERATOR ZIP) +elseif(APPLE AND NOT IOS) + set(CPACK_GENERATOR DragNDrop) else() set(CPACK_GENERATOR TGZ) endif() diff --git a/packaging/CMakeLists.txt b/packaging/CMakeLists.txt index 3ec9d174..1b178b63 100644 --- a/packaging/CMakeLists.txt +++ b/packaging/CMakeLists.txt @@ -24,3 +24,7 @@ endif() if(WINDOWS_STORE) add_subdirectory(UWP) endif() + +if(APPLE AND NOT IOS) + add_subdirectory(macos) +endif() diff --git a/packaging/macos/CMakeLists.txt b/packaging/macos/CMakeLists.txt new file mode 100644 index 00000000..d6e93db8 --- /dev/null +++ b/packaging/macos/CMakeLists.txt @@ -0,0 +1,99 @@ +set(_icon_file AppIcon) +set(MACOSX_BUNDLE_GUI_IDENTIFIER ${APP_ID}) +set(MACOSX_BUNDLE_COPYRIGHT ${APP_SPDX}) +set(ISLE_TARGET_NAME isle) +set(MACOSX_ISLE_BUNDLE_NAME ${APP_NAME}) # Do note that it can be up to 15 characters long +set(MACOSX_ISLE_BUNDLE_DISPLAY_NAME ${APP_NAME}) +set(CONFIG_TARGET_NAME isle-config) +set(MACOSX_CONFIG_BUNDLE_NAME "Config Isle") # Do note that it can be up to 15 characters long +set(MACOSX_CONFIG_BUNDLE_DISPLAY_NAME "Configure ${APP_NAME}") +set(MACOSX_BUNDLE_INFO_STRING ${PROJECT_VERSION}) +set(MACOSX_BUNDLE_BUNDLE_VERSION ${PROJECT_VERSION}) +set(MACOSX_BUNDLE_SHORT_VERSION_STRING ${PROJECT_VERSION}) +set(MACOSX_BUNDLE_LONG_VERSION_STRING "Version ${PROJECT_VERSION}") + +# TODO: darwin < 9 +set(MACOSX_BUNDLE_REQUIRED_PLATFORM Carbon) + +set(CPACK_DMG_VOLUME_NAME "Isle Portable") + +if(ISLE_BUILD_APP) + configure_file( + "${CMAKE_CURRENT_SOURCE_DIR}/isle/Info.plist.in" + "${CMAKE_CURRENT_BINARY_DIR}/isle/Info.plist" + @ONLY + ) + set(RESOURCE_FILES "${CMAKE_CURRENT_SOURCE_DIR}/isle/${_icon_file}.icns") + target_sources(${ISLE_TARGET_NAME} PRIVATE ${RESOURCE_FILES}) + set_target_properties(${ISLE_TARGET_NAME} PROPERTIES + MACOSX_BUNDLE TRUE + MACOSX_BUNDLE_ICON_FILE "${_icon_file}.icns" + MACOSX_BUNDLE_INFO_PLIST "${CMAKE_CURRENT_BINARY_DIR}/isle/Info.plist" + RESOURCE ${RESOURCE_FILES}) + install(TARGETS ${ISLE_TARGET_NAME} DESTINATION ./) + install(CODE " + include(BundleUtilities) + fixup_bundle(${CMAKE_BINARY_DIR}/${ISLE_TARGET_NAME}.app \"\" \"\") + " + COMPONENT Runtime) + install(CODE " + execute_process(COMMAND /usr/bin/codesign + --force --deep --sign - --timestamp + \"\$\{CMAKE_INSTALL_PREFIX\}/${ISLE_TARGET_NAME}.app/Contents/MacOS/${ISLE_TARGET_NAME}\") + ") + install(CODE " + file(RENAME + \"\$\{CMAKE_INSTALL_PREFIX\}/${ISLE_TARGET_NAME}.app\" + \"\$\{CMAKE_INSTALL_PREFIX\}/${MACOSX_ISLE_BUNDLE_DISPLAY_NAME}.app\") + ") +endif() +if(ISLE_BUILD_CONFIG) + configure_file( + "${CMAKE_CURRENT_SOURCE_DIR}/config/Info.plist.in" + "${CMAKE_CURRENT_BINARY_DIR}/config/Info.plist" + @ONLY + ) + set(RESOURCE_FILES "${CMAKE_CURRENT_SOURCE_DIR}/config/${_icon_file}.icns") + target_sources(${CONFIG_TARGET_NAME} PRIVATE ${RESOURCE_FILES}) + set_target_properties(${CONFIG_TARGET_NAME} PROPERTIES + MACOSX_BUNDLE TRUE + MACOSX_BUNDLE_ICON_FILE "${_icon_file}.icns" + MACOSX_BUNDLE_INFO_PLIST "${CMAKE_CURRENT_BINARY_DIR}/config/Info.plist" + RESOURCE ${RESOURCE_FILES}) + install(TARGETS ${CONFIG_TARGET_NAME} DESTINATION ./) + install(CODE " + include(BundleUtilities) + fixup_bundle(${CMAKE_BINARY_DIR}/${CONFIG_TARGET_NAME}.app \"\" \"\") + " + COMPONENT Runtime) + qt_generate_deploy_app_script( + TARGET ${CONFIG_TARGET_NAME} + OUTPUT_SCRIPT deploy_script + NO_COMPILER_RUNTIME + NO_TRANSLATIONS + ) + install(SCRIPT "${deploy_script}") + install(CODE " + execute_process(COMMAND /usr/bin/install_name_tool + -add_rpath \"@executable_path/../Frameworks\" + \"\$\{CMAKE_INSTALL_PREFIX\}/${CONFIG_TARGET_NAME}.app/Contents/MacOS/${CONFIG_TARGET_NAME}\") + ") + install(CODE " + execute_process(COMMAND /usr/bin/codesign + --force --deep --sign - --timestamp + \"\$\{CMAKE_INSTALL_PREFIX\}/${CONFIG_TARGET_NAME}.app/Contents/MacOS/${CONFIG_TARGET_NAME}\") + ") + install(CODE " + file(RENAME + \"\$\{CMAKE_INSTALL_PREFIX\}/${CONFIG_TARGET_NAME}.app\" + \"\$\{CMAKE_INSTALL_PREFIX\}/${MACOSX_CONFIG_BUNDLE_DISPLAY_NAME}.app\") + ") +endif() + +install(CODE " + if(IS_DIRECTORY \"\$\{CMAKE_INSTALL_PREFIX\}/bin\" OR IS_DIRECTORY \"\$\{CMAKE_INSTALL_PREFIX\}/lib\" OR EXISTS \"\$\{CMAKE_INSTALL_PREFIX\}/AppIcon.icns\") + execute_process(COMMAND /bin/rm + -rf \"\$\{CMAKE_INSTALL_PREFIX\}/bin\" \"\$\{CMAKE_INSTALL_PREFIX\}/lib\" \"\$\{CMAKE_INSTALL_PREFIX\}/AppIcon.icns\" + ) + endif() +") diff --git a/packaging/macos/config/AppIcon.icns b/packaging/macos/config/AppIcon.icns new file mode 100644 index 0000000000000000000000000000000000000000..5b67c48af96ceeb90c216c37f42e7d84ce87fa53 GIT binary patch literal 49433 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 XUby+}B`BnTp#v&NK|lpU!O2emX=a=* literal 0 HcmV?d00001 diff --git a/packaging/macos/config/Info.plist.in b/packaging/macos/config/Info.plist.in new file mode 100644 index 00000000..d128adc0 --- /dev/null +++ b/packaging/macos/config/Info.plist.in @@ -0,0 +1,80 @@ + + + + + CFBundleDevelopmentRegion + English + CFBundleExecutable + ${MACOSX_BUNDLE_EXECUTABLE_NAME} + CFBundleGetInfoString + @MACOSX_BUNDLE_INFO_STRING@ + CFBundleIconFile + @MACOSX_BUNDLE_ICON_FILE@ + CFBundleIdentifier + @MACOSX_BUNDLE_GUI_IDENTIFIER@ + CFBundleInfoDictionaryVersion + 6.0 + CFBundleLongVersionString + @MACOSX_BUNDLE_LONG_VERSION_STRING@ + CFBundleName + @MACOSX_CONFIG_BUNDLE_NAME@ + CFBundleDisplayName + @MACOSX_CONFIG_BUNDLE_DISPLAY_NAME@ + CFBundlePackageType + APPL + CFBundleShortVersionString + @MACOSX_BUNDLE_SHORT_VERSION_STRING@ + CFBundleSignature + ???? + CFBundleVersion + @MACOSX_BUNDLE_BUNDLE_VERSION@ + UILaunchStoryboardName + LaunchScreen + NSHighResolutionCapable + + CSResourcesFileMapped + + LSRequires@MACOSX_BUNDLE_REQUIRED_PLATFORM@ + + NSHumanReadableCopyright + @MACOSX_BUNDLE_COPYRIGHT@ + SDL_FILESYSTEM_BASE_DIR_TYPE + resource + NSSupportsAutomaticGraphicsSwitching + + UIApplicationSupportsIndirectInputEvents + + LSSupportsOpeningDocumentsInPlace + + UIFileSharingEnabled + + CADisableMinimumFrameDurationOnPhone + + UIDeviceFamily + + 1 + 2 + + UIRequiresFullScreen + + UIStatusBarHidden + + UISupportedInterfaceOrientations + + UIInterfaceOrientationLandscapeLeft + UIInterfaceOrientationLandscapeRight + + UISupportedInterfaceOrientations~ipad + + UIInterfaceOrientationLandscapeLeft + UIInterfaceOrientationLandscapeRight + + UIApplicationSceneManifest + + UIApplicationSupportsMultipleScenes + + + CFBundleAllowMixedLocalizations + + + \ No newline at end of file diff --git a/packaging/macos/isle/AppIcon.icns b/packaging/macos/isle/AppIcon.icns new file mode 100644 index 0000000000000000000000000000000000000000..61aa735eb7310cff8089b0f0c1d1451c829bcd16 GIT binary patch literal 60473 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 LEJpr}{_gt^%~|(j literal 0 HcmV?d00001 diff --git a/packaging/macos/isle/Info.plist.in b/packaging/macos/isle/Info.plist.in new file mode 100644 index 00000000..2ca3d26e --- /dev/null +++ b/packaging/macos/isle/Info.plist.in @@ -0,0 +1,82 @@ + + + + + CFBundleDevelopmentRegion + English + CFBundleExecutable + ${MACOSX_BUNDLE_EXECUTABLE_NAME} + CFBundleGetInfoString + @MACOSX_BUNDLE_INFO_STRING@ + CFBundleIconFile + @MACOSX_BUNDLE_ICON_FILE@ + CFBundleIdentifier + @MACOSX_BUNDLE_GUI_IDENTIFIER@ + CFBundleInfoDictionaryVersion + 6.0 + CFBundleLongVersionString + @MACOSX_BUNDLE_LONG_VERSION_STRING@ + CFBundleName + @MACOSX_ISLE_BUNDLE_NAME@ + CFBundleDisplayName + @MACOSX_ISLE_BUNDLE_DISPLAY_NAME@ + CFBundlePackageType + APPL + CFBundleShortVersionString + @MACOSX_BUNDLE_SHORT_VERSION_STRING@ + CFBundleSignature + ???? + CFBundleVersion + @MACOSX_BUNDLE_BUNDLE_VERSION@ + UILaunchStoryboardName + LaunchScreen + NSHighResolutionCapable + + CSResourcesFileMapped + + LSRequires@MACOSX_BUNDLE_REQUIRED_PLATFORM@ + + NSHumanReadableCopyright + @MACOSX_BUNDLE_COPYRIGHT@ + SDL_FILESYSTEM_BASE_DIR_TYPE + resource + NSSupportsAutomaticGraphicsSwitching + + UIApplicationSupportsIndirectInputEvents + + LSSupportsOpeningDocumentsInPlace + + UIFileSharingEnabled + + CADisableMinimumFrameDurationOnPhone + + UIDeviceFamily + + 1 + 2 + + UIRequiresFullScreen + + UIStatusBarHidden + + UISupportedInterfaceOrientations + + UIInterfaceOrientationLandscapeLeft + UIInterfaceOrientationLandscapeRight + + UISupportedInterfaceOrientations~ipad + + UIInterfaceOrientationLandscapeLeft + UIInterfaceOrientationLandscapeRight + + UIApplicationSceneManifest + + UIApplicationSupportsMultipleScenes + + + CFBundleAllowMixedLocalizations + + LSApplicationCategoryType + public.app-category.games + + \ No newline at end of file From 37c6abe3b57a2c657fb0e30f3d91185512509d22 Mon Sep 17 00:00:00 2001 From: Christian Semmler Date: Tue, 8 Jul 2025 12:43:34 -0700 Subject: [PATCH 101/188] Disable offscreen canvases in case of no WebGL support (#559) --- CMakeLists.txt | 1 + ISLE/emscripten/config.cpp | 17 +++++++++++++++++ ISLE/emscripten/config.h | 8 ++++++++ ISLE/emscripten/emscripten.patch | 26 ++++++++++++++++---------- ISLE/isleapp.cpp | 21 ++++----------------- 5 files changed, 46 insertions(+), 27 deletions(-) create mode 100644 ISLE/emscripten/config.cpp create mode 100644 ISLE/emscripten/config.h diff --git a/CMakeLists.txt b/CMakeLists.txt index 82360dec..1f26a28b 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -532,6 +532,7 @@ if (ISLE_BUILD_APP) endif() if(EMSCRIPTEN) target_sources(isle PRIVATE + ISLE/emscripten/config.cpp ISLE/emscripten/events.cpp ISLE/emscripten/filesystem.cpp ISLE/emscripten/messagebox.cpp diff --git a/ISLE/emscripten/config.cpp b/ISLE/emscripten/config.cpp new file mode 100644 index 00000000..45d4ea65 --- /dev/null +++ b/ISLE/emscripten/config.cpp @@ -0,0 +1,17 @@ +#include "config.h" + +#include "filesystem.h" + +#include +#include + +void Emscripten_SetupDefaultConfigOverrides(dictionary* p_dictionary) +{ + SDL_Log("Overriding default config for Emscripten"); + + iniparser_set(p_dictionary, "isle:diskpath", Emscripten_bundledPath); + iniparser_set(p_dictionary, "isle:cdpath", Emscripten_streamPath); + iniparser_set(p_dictionary, "isle:savepath", Emscripten_savePath); + iniparser_set(p_dictionary, "isle:Full Screen", "false"); + iniparser_set(p_dictionary, "isle:Flip Surfaces", "true"); +} diff --git a/ISLE/emscripten/config.h b/ISLE/emscripten/config.h new file mode 100644 index 00000000..255888c0 --- /dev/null +++ b/ISLE/emscripten/config.h @@ -0,0 +1,8 @@ +#ifndef EMSCRIPTEN_CONFIG_H +#define EMSCRIPTEN_CONFIG_H + +#include "dictionary.h" + +void Emscripten_SetupDefaultConfigOverrides(dictionary* p_dictionary); + +#endif // EMSCRIPTEN_CONFIG_H diff --git a/ISLE/emscripten/emscripten.patch b/ISLE/emscripten/emscripten.patch index 0bc8222d..7e9ad611 100644 --- a/ISLE/emscripten/emscripten.patch +++ b/ISLE/emscripten/emscripten.patch @@ -140,20 +140,26 @@ index e8c9f7e21..caf1971d2 100644 - }); diff --git a/src/preamble.js b/src/preamble.js -index 572694517..0d2f4421b 100644 +index 572694517..44e65c823 100644 --- a/src/preamble.js +++ b/src/preamble.js -@@ -1062,3 +1062,13 @@ function getCompilerSetting(name) { +@@ -1062,3 +1062,19 @@ function getCompilerSetting(name) { // dynamic linker as symbols are loaded. var asyncifyStubs = {}; #endif + -+(async () => { -+ try { -+ await navigator.storage.getDirectory(); -+ Module["disableOpfs"] = false; -+ } catch (e) { -+ Module["disableOpfs"] = true; -+ } -+})(); ++if (typeof document !== "undefined") { ++ (async () => { ++ try { ++ await navigator.storage.getDirectory(); ++ Module["disableOpfs"] = false; ++ } catch (e) { ++ Module["disableOpfs"] = true; ++ } ++ console.log("disableOpfs: " + Module["disableOpfs"]); ++ })(); ++ ++ Module["disableOffscreenCanvases"] ||= !document.createElement('canvas').getContext('webgl'); ++ console.log("disableOffscreenCanvases: " + Module["disableOffscreenCanvases"]); ++} + diff --git a/ISLE/isleapp.cpp b/ISLE/isleapp.cpp index 5cc6c9c2..89743a51 100644 --- a/ISLE/isleapp.cpp +++ b/ISLE/isleapp.cpp @@ -48,6 +48,7 @@ #include #ifdef __EMSCRIPTEN__ +#include "emscripten/config.h" #include "emscripten/events.h" #include "emscripten/filesystem.h" #include "emscripten/messagebox.h" @@ -1003,19 +1004,15 @@ bool IsleApp::LoadConfig() } #ifdef __EMSCRIPTEN__ - const char* hdPath = Emscripten_bundledPath; -#else - const char* hdPath = iniparser_getstring(dict, "isle:diskpath", SDL_GetBasePath()); + Emscripten_SetupDefaultConfigOverrides(dict); #endif + + const char* hdPath = iniparser_getstring(dict, "isle:diskpath", SDL_GetBasePath()); m_hdPath = new char[strlen(hdPath) + 1]; strcpy(m_hdPath, hdPath); MxOmni::SetHD(m_hdPath); -#ifdef __EMSCRIPTEN__ - const char* cdPath = Emscripten_streamPath; -#else const char* cdPath = iniparser_getstring(dict, "isle:cdpath", MxOmni::GetCD()); -#endif m_cdPath = new char[strlen(cdPath) + 1]; strcpy(m_cdPath, cdPath); MxOmni::SetCD(m_cdPath); @@ -1025,13 +1022,7 @@ bool IsleApp::LoadConfig() strcpy(m_mediaPath, mediaPath); m_flipSurfaces = iniparser_getboolean(dict, "isle:Flip Surfaces", m_flipSurfaces); - -#ifdef __EMSCRIPTEN__ - m_fullScreen = FALSE; -#else m_fullScreen = iniparser_getboolean(dict, "isle:Full Screen", m_fullScreen); -#endif - m_wideViewAngle = iniparser_getboolean(dict, "isle:Wide View Angle", m_wideViewAngle); m_use3dSound = iniparser_getboolean(dict, "isle:3DSound", m_use3dSound); m_useMusic = iniparser_getboolean(dict, "isle:Music", m_useMusic); @@ -1071,11 +1062,7 @@ bool IsleApp::LoadConfig() // [library:config] // The original game does not save any data if no savepath is given. // Instead, we use SDLs prefPath as a default fallback and always save data. -#ifdef __EMSCRIPTEN__ - const char* savePath = Emscripten_savePath; -#else const char* savePath = iniparser_getstring(dict, "isle:savepath", prefPath); -#endif m_savePath = new char[strlen(savePath) + 1]; strcpy(m_savePath, savePath); From 4dc8bfc0ace8d849b4585eeb6a8690904c154a5b Mon Sep 17 00:00:00 2001 From: Christian Semmler Date: Tue, 8 Jul 2025 15:17:20 -0700 Subject: [PATCH 102/188] Add full screen support (#560) --- ISLE/emscripten/config.cpp | 5 +++++ miniwin/src/ddraw/ddraw.cpp | 4 ++-- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/ISLE/emscripten/config.cpp b/ISLE/emscripten/config.cpp index 45d4ea65..7d9cdb31 100644 --- a/ISLE/emscripten/config.cpp +++ b/ISLE/emscripten/config.cpp @@ -3,6 +3,7 @@ #include "filesystem.h" #include +#include #include void Emscripten_SetupDefaultConfigOverrides(dictionary* p_dictionary) @@ -14,4 +15,8 @@ void Emscripten_SetupDefaultConfigOverrides(dictionary* p_dictionary) iniparser_set(p_dictionary, "isle:savepath", Emscripten_savePath); iniparser_set(p_dictionary, "isle:Full Screen", "false"); iniparser_set(p_dictionary, "isle:Flip Surfaces", "true"); + + // clang-format off + MAIN_THREAD_EM_ASM({JSEvents.fullscreenEnabled = function() { return false; }}); +// clang-format on } diff --git a/miniwin/src/ddraw/ddraw.cpp b/miniwin/src/ddraw/ddraw.cpp index f9dabada..e6603f17 100644 --- a/miniwin/src/ddraw/ddraw.cpp +++ b/miniwin/src/ddraw/ddraw.cpp @@ -311,11 +311,11 @@ HRESULT DirectDrawImpl::SetCooperativeLevel(HWND hWnd, DDSCLFlags dwFlags) return DDERR_INVALIDPARAMS; } - if (!SDL_SetWindowFullscreen(sdlWindow, fullscreen)) { #ifndef __EMSCRIPTEN__ + if (!SDL_SetWindowFullscreen(sdlWindow, fullscreen)) { return DDERR_GENERIC; -#endif } +#endif DDWindow = sdlWindow; } return DD_OK; From b8e7c8f7748c9ad96cd8064eb6b4de9369983d7c Mon Sep 17 00:00:00 2001 From: Anders Jenbo Date: Wed, 9 Jul 2025 07:40:24 +0200 Subject: [PATCH 103/188] Fix uninitialized ddpfPixelFormat in VTable0x44 (#563) --- LEGO1/omni/src/video/mxdisplaysurface.cpp | 1 + miniwin/include/miniwin/ddraw.h | 3 +++ miniwin/src/ddraw/ddraw.cpp | 22 +++++++++++++--------- miniwin/src/ddraw/ddsurface.cpp | 2 +- miniwin/src/ddraw/framebuffer.cpp | 2 +- 5 files changed, 19 insertions(+), 11 deletions(-) diff --git a/LEGO1/omni/src/video/mxdisplaysurface.cpp b/LEGO1/omni/src/video/mxdisplaysurface.cpp index b36cbd2a..2902c4fe 100644 --- a/LEGO1/omni/src/video/mxdisplaysurface.cpp +++ b/LEGO1/omni/src/video/mxdisplaysurface.cpp @@ -827,6 +827,7 @@ LPDIRECTDRAWSURFACE MxDisplaySurface::VTable0x44( ddsd.dwFlags = DDSD_CAPS | DDSD_HEIGHT | DDSD_WIDTH | DDSD_PIXELFORMAT; ddsd.dwWidth = p_bitmap->GetBmiWidth(); ddsd.dwHeight = p_bitmap->GetBmiHeightAbs(); + ddsd.ddpfPixelFormat = m_surfaceDesc.ddpfPixelFormat; ddsd.ddsCaps.dwCaps = DDSCAPS_OFFSCREENPLAIN; *p_ret = 0; ddsd.ddsCaps.dwCaps |= DDSCAPS_SYSTEMMEMORY; diff --git a/miniwin/include/miniwin/ddraw.h b/miniwin/include/miniwin/ddraw.h index 0250c099..912a1b9f 100644 --- a/miniwin/include/miniwin/ddraw.h +++ b/miniwin/include/miniwin/ddraw.h @@ -143,9 +143,12 @@ enum class DDBltFlags : uint32_t { }; ENABLE_BITMASK_OPERATORS(DDBltFlags) +#define DDPF_ALPHAPIXELS DDPixelFormatFlags::ALPHAPIXELS #define DDPF_PALETTEINDEXED8 DDPixelFormatFlags::PALETTEINDEXED8 #define DDPF_RGB DDPixelFormatFlags::RGB +#define DDPF_ALPHAPIXELS DDPixelFormatFlags::ALPHAPIXELS enum class DDPixelFormatFlags : uint32_t { + ALPHAPIXELS = 1 << 0, // dwRGBAlphaBitMask is valid PALETTEINDEXED8 = 1 << 5, // The texture uses an 8 bit palette RGB = 1 << 6, // dwRGBBitCount, dwRBitMask, dwGBitMask, and dwBBitMask is valid }; diff --git a/miniwin/src/ddraw/ddraw.cpp b/miniwin/src/ddraw/ddraw.cpp index e6603f17..d2ce8743 100644 --- a/miniwin/src/ddraw/ddraw.cpp +++ b/miniwin/src/ddraw/ddraw.cpp @@ -106,13 +106,17 @@ HRESULT DirectDrawImpl::CreateSurface( #endif if ((lpDDSurfaceDesc->dwFlags & DDSD_PIXELFORMAT) == DDSD_PIXELFORMAT) { if ((lpDDSurfaceDesc->ddpfPixelFormat.dwFlags & DDPF_RGB) == DDPF_RGB) { - switch (lpDDSurfaceDesc->ddpfPixelFormat.dwRGBBitCount) { - case 8: - format = SDL_PIXELFORMAT_INDEX8; - break; - case 16: - format = SDL_PIXELFORMAT_RGB565; - break; + int bpp = lpDDSurfaceDesc->ddpfPixelFormat.dwRGBBitCount; + Uint32 rMask = lpDDSurfaceDesc->ddpfPixelFormat.dwRBitMask; + Uint32 gMask = lpDDSurfaceDesc->ddpfPixelFormat.dwGBitMask; + Uint32 bMask = lpDDSurfaceDesc->ddpfPixelFormat.dwBBitMask; + Uint32 aMask = (lpDDSurfaceDesc->ddpfPixelFormat.dwFlags & DDPF_ALPHAPIXELS) == DDPF_ALPHAPIXELS + ? lpDDSurfaceDesc->ddpfPixelFormat.dwRGBAlphaBitMask + : 0; + + format = SDL_GetPixelFormatForMasks(bpp, rMask, gMask, bMask, aMask); + if (format == SDL_PIXELFORMAT_UNKNOWN) { + return DDERR_INVALIDPIXELFORMAT; } } } @@ -168,7 +172,7 @@ HRESULT DirectDrawImpl::EnumDisplayModes( ddsd.dwWidth = modes[i]->w; ddsd.dwHeight = modes[i]->h; ddsd.ddpfPixelFormat.dwSize = sizeof(DDPIXELFORMAT); - ddsd.ddpfPixelFormat.dwFlags = DDPF_RGB; + ddsd.ddpfPixelFormat.dwFlags = DDPF_RGB | DDPF_ALPHAPIXELS; ddsd.ddpfPixelFormat.dwRGBBitCount = details->bits_per_pixel; if (details->bits_per_pixel == 8) { ddsd.ddpfPixelFormat.dwFlags |= DDPF_PALETTEINDEXED8; @@ -271,7 +275,7 @@ HRESULT DirectDrawImpl::GetDisplayMode(LPDDSURFACEDESC lpDDSurfaceDesc) lpDDSurfaceDesc->dwWidth = mode->w; lpDDSurfaceDesc->dwHeight = mode->h; lpDDSurfaceDesc->ddpfPixelFormat.dwRGBBitCount = details->bits_per_pixel; - lpDDSurfaceDesc->ddpfPixelFormat.dwFlags = DDPF_RGB; + lpDDSurfaceDesc->ddpfPixelFormat.dwFlags = DDPF_RGB | DDPF_ALPHAPIXELS; if (details->bits_per_pixel == 8) { lpDDSurfaceDesc->ddpfPixelFormat.dwFlags |= DDPF_PALETTEINDEXED8; } diff --git a/miniwin/src/ddraw/ddsurface.cpp b/miniwin/src/ddraw/ddsurface.cpp index 056912fd..9e710596 100644 --- a/miniwin/src/ddraw/ddsurface.cpp +++ b/miniwin/src/ddraw/ddsurface.cpp @@ -145,7 +145,7 @@ HRESULT DirectDrawSurfaceImpl::GetPalette(LPDIRECTDRAWPALETTE* lplpDDPalette) HRESULT DirectDrawSurfaceImpl::GetPixelFormat(LPDDPIXELFORMAT lpDDPixelFormat) { memset(lpDDPixelFormat, 0, sizeof(*lpDDPixelFormat)); - lpDDPixelFormat->dwFlags = DDPF_RGB; + lpDDPixelFormat->dwFlags = DDPF_RGB | DDPF_ALPHAPIXELS; const SDL_PixelFormatDetails* details = SDL_GetPixelFormatDetails(m_surface->format); if (details->bits_per_pixel == 8) { lpDDPixelFormat->dwFlags |= DDPF_PALETTEINDEXED8; diff --git a/miniwin/src/ddraw/framebuffer.cpp b/miniwin/src/ddraw/framebuffer.cpp index 13caa434..d3d8a3b8 100644 --- a/miniwin/src/ddraw/framebuffer.cpp +++ b/miniwin/src/ddraw/framebuffer.cpp @@ -143,7 +143,7 @@ HRESULT FrameBufferImpl::GetPalette(LPDIRECTDRAWPALETTE* lplpDDPalette) HRESULT FrameBufferImpl::GetPixelFormat(LPDDPIXELFORMAT lpDDPixelFormat) { memset(lpDDPixelFormat, 0, sizeof(*lpDDPixelFormat)); - lpDDPixelFormat->dwFlags = DDPF_RGB; + lpDDPixelFormat->dwFlags = DDPF_RGB | DDPF_ALPHAPIXELS; const SDL_PixelFormatDetails* details = SDL_GetPixelFormatDetails(m_transferBuffer->m_surface->format); if (details->bits_per_pixel == 8) { lpDDPixelFormat->dwFlags |= DDPF_PALETTEINDEXED8; From 4446aaaf53bc0119de9b931d3da59d93f3ec1403 Mon Sep 17 00:00:00 2001 From: olebeck <31539311+olebeck@users.noreply.github.com> Date: Wed, 9 Jul 2025 14:55:26 +0200 Subject: [PATCH 104/188] translate and scale touch coordinates to the viewport (#565) --- ISLE/isleapp.cpp | 3 +++ miniwin/src/d3drm/d3drmdevice.cpp | 24 +++++++++++++++++------- miniwin/src/internal/d3drmdevice_impl.h | 2 ++ 3 files changed, 22 insertions(+), 7 deletions(-) diff --git a/ISLE/isleapp.cpp b/ISLE/isleapp.cpp index 89743a51..2788b8a7 100644 --- a/ISLE/isleapp.cpp +++ b/ISLE/isleapp.cpp @@ -428,6 +428,9 @@ SDL_AppResult SDL_AppEvent(void* appstate, SDL_Event* event) case SDL_EVENT_MOUSE_MOTION: case SDL_EVENT_MOUSE_BUTTON_DOWN: case SDL_EVENT_MOUSE_BUTTON_UP: + case SDL_EVENT_FINGER_MOTION: + case SDL_EVENT_FINGER_DOWN: + case SDL_EVENT_FINGER_UP: IDirect3DRMMiniwinDevice* device = GetD3DRMMiniwinDevice(); if (device && !device->ConvertEventToRenderCoordinates(event)) { SDL_Log("Failed to convert event coordinates: %s", SDL_GetError()); diff --git a/miniwin/src/d3drm/d3drmdevice.cpp b/miniwin/src/d3drm/d3drmdevice.cpp index bae67e94..9f517426 100644 --- a/miniwin/src/d3drm/d3drmdevice.cpp +++ b/miniwin/src/d3drm/d3drmdevice.cpp @@ -150,14 +150,13 @@ ViewportTransform CalculateViewportTransform(int virtualW, int virtualH, int win void Direct3DRMDevice2Impl::Resize() { - int width, height; - SDL_GetWindowSizeInPixels(DDWindow, &width, &height); + SDL_GetWindowSizeInPixels(DDWindow, &m_windowWidth, &m_windowHeight); #ifdef __3DS__ - width = 320; // We are on the lower screen - height = 240; + m_windowWidth = 320; // We are on the lower screen + m_windowHeight = 240; #endif - m_viewportTransform = CalculateViewportTransform(m_virtualWidth, m_virtualHeight, width, height); - m_renderer->Resize(width, height, m_viewportTransform); + m_viewportTransform = CalculateViewportTransform(m_virtualWidth, m_virtualHeight, m_windowWidth, m_windowHeight); + m_renderer->Resize(m_windowWidth, m_windowHeight, m_viewportTransform); m_renderer->Clear(0, 0, 0); for (int i = 0; i < m_viewports->GetSize(); i++) { IDirect3DRMViewport* viewport; @@ -183,7 +182,18 @@ bool Direct3DRMDevice2Impl::ConvertEventToRenderCoordinates(SDL_Event* event) event->motion.x = static_cast(x); event->motion.y = static_cast(y); break; - } break; + } + case SDL_EVENT_FINGER_MOTION: + case SDL_EVENT_FINGER_DOWN: + case SDL_EVENT_FINGER_UP: { + int rawX = event->tfinger.x * m_windowWidth; + int rawY = event->tfinger.y * m_windowHeight; + float x = (rawX - m_viewportTransform.offsetX) / m_viewportTransform.scale; + float y = (rawY - m_viewportTransform.offsetY) / m_viewportTransform.scale; + event->tfinger.x = x / m_virtualWidth; + event->tfinger.y = y / m_virtualHeight; + break; + } } return true; diff --git a/miniwin/src/internal/d3drmdevice_impl.h b/miniwin/src/internal/d3drmdevice_impl.h index daab564a..5679ce0d 100644 --- a/miniwin/src/internal/d3drmdevice_impl.h +++ b/miniwin/src/internal/d3drmdevice_impl.h @@ -40,6 +40,8 @@ struct Direct3DRMDevice2Impl : public Direct3DRMObjectBaseImpl Date: Wed, 9 Jul 2025 14:09:49 -0400 Subject: [PATCH 105/188] Improve match for `Act3Ammo::Animate` (#1612) * Improve Act3Ammo * Early return --- LEGO1/lego/legoomni/include/act3ammo.h | 2 +- .../lego/legoomni/include/legopathboundary.h | 8 +- LEGO1/lego/legoomni/src/actors/act3ammo.cpp | 144 ++++++++++-------- 3 files changed, 86 insertions(+), 68 deletions(-) diff --git a/LEGO1/lego/legoomni/include/act3ammo.h b/LEGO1/lego/legoomni/include/act3ammo.h index ee4e3dc1..e80aa6b0 100644 --- a/LEGO1/lego/legoomni/include/act3ammo.h +++ b/LEGO1/lego/legoomni/include/act3ammo.h @@ -90,7 +90,7 @@ class Act3Ammo : public LegoPathActor { // Act3Ammo::`scalar deleting destructor' private: - MxResult FUN_10053db0(float p_param1, const Matrix4& p_param2); + MxResult FUN_10053db0(float p_param1, Matrix4& p_param2); static Mx3DPointFloat g_unk0x10104f08; diff --git a/LEGO1/lego/legoomni/include/legopathboundary.h b/LEGO1/lego/legoomni/include/legopathboundary.h index 05d0487c..2bc304f0 100644 --- a/LEGO1/lego/legoomni/include/legopathboundary.h +++ b/LEGO1/lego/legoomni/include/legopathboundary.h @@ -75,7 +75,7 @@ class LegoPathBoundary : public LegoWEGEdge { // _Tree >::_Kfn,LegoPathActorSetCompare,allocator >::erase // TEMPLATE: LEGO1 0x1002c440 -// TEMPLATE: BETA10 0x100b6480 +// TEMPLATE: BETA10 0x10020480 // _Tree >::_Kfn,LegoPathActorSetCompare,allocator >::find // TEMPLATE: LEGO1 0x1002c4c0 @@ -190,6 +190,12 @@ class LegoPathBoundary : public LegoWEGEdge { // TEMPLATE: BETA10 0x10082b40 // _Tree >::_Kfn,LegoAnimPresenterSetCompare,allocator >::const_iterator::operator* +// TEMPLATE: BETA10 0x100b6440 +// set >::find + +// TEMPLATE: BETA10 0x100b6480 +// _Tree >::_Kfn,LegoAnimPresenterSetCompare,allocator >::find + // TEMPLATE: BETA10 0x10021dc0 // ??0?$Set@PAVLegoPathActor@@ULegoPathActorSetCompare@@@@QAE@ABV0@@Z diff --git a/LEGO1/lego/legoomni/src/actors/act3ammo.cpp b/LEGO1/lego/legoomni/src/actors/act3ammo.cpp index d88a7bee..4ab1a27b 100644 --- a/LEGO1/lego/legoomni/src/actors/act3ammo.cpp +++ b/LEGO1/lego/legoomni/src/actors/act3ammo.cpp @@ -200,7 +200,7 @@ MxResult Act3Ammo::FUN_10053d30(LegoPathController* p_p, MxFloat p_unk0x19c) // FUNCTION: LEGO1 0x10053db0 // FUNCTION: BETA10 0x1001e0f0 -MxResult Act3Ammo::FUN_10053db0(float p_param1, const Matrix4& p_param2) +MxResult Act3Ammo::FUN_10053db0(float p_param1, Matrix4& p_param2) { float local34 = p_param1 * p_param1; @@ -384,6 +384,8 @@ void Act3Ammo::Animate(float p_time) m_world->RemoveDonut(*this); m_world->TriggerHitSound(4); } + + return; } else { if (IsPizza()) { @@ -394,89 +396,99 @@ void Act3Ammo::Animate(float p_time) assert(SoundManager()->GetCacheSoundManager()); SoundManager()->GetCacheSoundManager()->Play("stickdn", NULL, FALSE); } + } - LegoPathActorSet& plpas = m_boundary->GetActors(); - LegoPathActorSet lpas(plpas); + LegoPathActorSet& plpas = m_boundary->GetActors(); + LegoPathActorSet lpas(plpas); - for (LegoPathActorSet::iterator itpa = lpas.begin(); itpa != lpas.end(); itpa++) { - if (plpas.find(*itpa) != plpas.end() && this != *itpa) { - LegoROI* r = (*itpa)->GetROI(); - assert(r); + for (LegoPathActorSet::iterator itpa = lpas.begin(); itpa != lpas.end(); itpa++) { + if (plpas.find(*itpa) == plpas.end()) { + continue; + } - if (!strncmp(r->GetName(), "pammo", 5)) { - Mx3DPointFloat local1c8; - Mx3DPointFloat local1b4; + if (this == *itpa) { + continue; + } - local1c8 = r->GetLocal2World()[3]; - local1b4 = m_roi->GetLocal2World()[3]; + LegoROI* r = (*itpa)->GetROI(); + assert(r); - local1b4 -= local1c8; + if (!strncmp(r->GetName(), "pammo", 5)) { + Mx3DPointFloat local1c8; + Mx3DPointFloat local1b4; - float radius = r->GetWorldBoundingSphere().Radius(); - if (local1b4.LenSquared() <= radius * radius) { - MxS32 index = -1; - if (sscanf(r->GetName(), "pammo%d", &index) != 1) { - assert(0); - } + local1c8 = r->GetLocal2World()[3]; + local1b4 = m_roi->GetLocal2World()[3]; - assert(m_world); + local1b4 -= local1c8; - if (m_world->m_pizzas[index].IsValid() && !m_world->m_pizzas[index].IsSharkFood()) { - m_world->EatPizza(index); - m_world->m_brickster->FUN_100417c0(); - } - - if (IsDonut()) { - assert(SoundManager()->GetCacheSoundManager()); - SoundManager()->GetCacheSoundManager()->Play("dnhitpz", NULL, FALSE); - m_world->RemoveDonut(*this); - local14 = TRUE; - break; - } - } + float radius = r->GetWorldBoundingSphere().Radius(); + if (local1b4.LenSquared() <= radius * radius) { + MxS32 index = -1; + if (sscanf(r->GetName(), "pammo%d", &index) != 1) { + assert(0); } - else if (!strncmp(r->GetName(), "dammo", 5)) { - Mx3DPointFloat local1f8; - Mx3DPointFloat local1e4; - local1f8 = r->GetLocal2World()[3]; - local1e4 = m_roi->GetLocal2World()[3]; + assert(m_world); - local1e4 -= local1f8; +#ifdef BETA10 + m_world->EatPizza(index); +#else + if (m_world->m_pizzas[index].IsValid() && !m_world->m_pizzas[index].IsSharkFood()) { + m_world->EatPizza(index); + m_world->m_brickster->FUN_100417c0(); + } +#endif - float radius = r->GetWorldBoundingSphere().Radius(); - if (local1e4.LenSquared() <= radius * radius) { - MxS32 index = -1; - if (sscanf(r->GetName(), "dammo%d", &index) != 1) { - assert(0); - } - - assert(m_world); - - m_world->EatDonut(index); - - if (IsPizza()) { - assert(SoundManager()->GetCacheSoundManager()); - SoundManager()->GetCacheSoundManager()->Play("pzhitdn", NULL, FALSE); - m_world->RemovePizza(*this); - local14 = TRUE; - break; - } - } + if (IsDonut()) { + assert(SoundManager()->GetCacheSoundManager()); + SoundManager()->GetCacheSoundManager()->Play("dnhitpz", NULL, FALSE); + m_world->RemoveDonut(*this); + local14 = TRUE; + break; } } } + else if (!strncmp(r->GetName(), "dammo", 5)) { + Mx3DPointFloat local1f8; + Mx3DPointFloat local1e4; - if (!local14) { - if (IsPizza()) { - m_world->FUN_10073360(*this, local68); - } - else { - m_world->FUN_10073390(*this, local68); - } + local1f8 = r->GetLocal2World()[3]; + local1e4 = m_roi->GetLocal2World()[3]; - m_worldSpeed = -1.0f; + local1e4 -= local1f8; + + float radius = r->GetWorldBoundingSphere().Radius(); + if (local1e4.LenSquared() <= radius * radius) { + MxS32 index = -1; + if (sscanf(r->GetName(), "dammo%d", &index) != 1) { + assert(0); + } + + assert(m_world); + + m_world->EatDonut(index); + + if (IsPizza()) { + assert(SoundManager()->GetCacheSoundManager()); + SoundManager()->GetCacheSoundManager()->Play("pzhitdn", NULL, FALSE); + m_world->RemovePizza(*this); + local14 = TRUE; + break; + } + } } } + + if (!local14) { + if (IsPizza()) { + m_world->FUN_10073360(*this, local68); + } + else { + m_world->FUN_10073390(*this, local68); + } + + m_worldSpeed = -1.0f; + } } } From f0f771f3f46c7be97a922c2be6c84a999346c89f Mon Sep 17 00:00:00 2001 From: Fabian Neundorf Date: Thu, 10 Jul 2025 06:28:59 +0200 Subject: [PATCH 106/188] Clear global unknowns in `LegoROI` (#1611) --- LEGO1/lego/sources/roi/legoroi.cpp | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/LEGO1/lego/sources/roi/legoroi.cpp b/LEGO1/lego/sources/roi/legoroi.cpp index afd5f711..25d4362e 100644 --- a/LEGO1/lego/sources/roi/legoroi.cpp +++ b/LEGO1/lego/sources/roi/legoroi.cpp @@ -43,13 +43,13 @@ ROIColorAlias g_roiColorAliases[22] = { int g_roiConfig = 100; // GLOBAL: LEGO1 0x10101370 -const char* g_unk0x10101370[] = {"bike", "moto", NULL}; +const char* g_sharedModelsHigh[] = {"bike", "moto", NULL}; // GLOBAL: LEGO1 0x10101380 -const char* g_unk0x10101380[] = {"bike", "moto", "haus", NULL}; +const char* g_sharedModelsLow[] = {"bike", "moto", "haus", NULL}; // GLOBAL: LEGO1 0x10101390 -const char* g_unk0x10101390[] = {"rcuser", "jsuser", "dunebugy", "chtrblad", "chtrbody", "chtrshld", NULL}; +const char* g_alwaysLoadNames[] = {"rcuser", "jsuser", "dunebugy", "chtrblad", "chtrbody", "chtrshld", NULL}; // GLOBAL: LEGO1 0x101013ac ColorOverride g_colorOverride = NULL; @@ -224,30 +224,30 @@ LegoResult LegoROI::Read( } if (g_roiConfig <= 2) { - for (i = 0; g_unk0x10101380[i] != NULL; i++) { - if (!strnicmp(m_name, g_unk0x10101380[i], 4)) { - roiName = g_unk0x10101380[i]; + for (i = 0; g_sharedModelsLow[i] != NULL; i++) { + if (!strnicmp(m_name, g_sharedModelsLow[i], 4)) { + roiName = g_sharedModelsLow[i]; break; } } } else { - for (i = 0; g_unk0x10101370[i] != NULL; i++) { - if (!strnicmp(m_name, g_unk0x10101370[i], 4)) { - roiName = g_unk0x10101370[i]; + for (i = 0; g_sharedModelsHigh[i] != NULL; i++) { + if (!strnicmp(m_name, g_sharedModelsHigh[i], 4)) { + roiName = g_sharedModelsHigh[i]; break; } } } if ((lodList = p_viewLODListManager->Lookup(roiName))) { - for (j = 0; g_unk0x10101390[j] != NULL; j++) { - if (!strcmpi(g_unk0x10101390[j], roiName)) { + for (j = 0; g_alwaysLoadNames[j] != NULL; j++) { + if (!strcmpi(g_alwaysLoadNames[j], roiName)) { break; } } - if (g_unk0x10101390[j] != NULL) { + if (g_alwaysLoadNames[j] != NULL) { while (lodList->Size()) { delete const_cast(lodList->PopBack()); } From c784fc32f1f8718f120554ae6b1cae3414c27741 Mon Sep 17 00:00:00 2001 From: MS Date: Thu, 10 Jul 2025 00:29:22 -0400 Subject: [PATCH 107/188] Move g_skeletonKickPhases to .data (#1614) --- LEGO1/lego/legoomni/src/race/legoracers.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/LEGO1/lego/legoomni/src/race/legoracers.cpp b/LEGO1/lego/legoomni/src/race/legoracers.cpp index 0a9fa1e2..da3baf53 100644 --- a/LEGO1/lego/legoomni/src/race/legoracers.cpp +++ b/LEGO1/lego/legoomni/src/race/legoracers.cpp @@ -56,7 +56,7 @@ EdgeReference g_skBMap[] = { // GLOBAL: LEGO1 0x100f0a50 // GLOBAL: BETA10 0x101f5e60 -const SkeletonKickPhase g_skeletonKickPhases[] = { +SkeletonKickPhase g_skeletonKickPhases[] = { {&g_skBMap[0], 0.1, 0.2, LEGORACECAR_KICK2}, {&g_skBMap[1], 0.2, 0.3, LEGORACECAR_KICK2}, {&g_skBMap[2], 0.3, 0.4, LEGORACECAR_KICK2}, From 9d8cb64a191905121c2cdceb3e413a8f735bb193 Mon Sep 17 00:00:00 2001 From: Helloyunho Date: Thu, 10 Jul 2025 15:34:52 +0900 Subject: [PATCH 108/188] Add iOS Port (#566) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * ✨ feat: add ios support * ⚗️ chore: trying some experiments to make ci working * ⚗️ chore: is it really ci version problem? * 💚 fix: it really is just a ci version issue * 🩹 fix: go as low as possible * 🩹 fix: support ipad --- .github/workflows/ci.yml | 1 + CMakeLists.txt | 17 +++- ISLE/ios/config.cpp | 25 +++++ ISLE/ios/config.h | 8 ++ ISLE/isleapp.cpp | 13 +++ packaging/CMakeLists.txt | 4 + packaging/ios/CMakeLists.txt | 52 +++++++++++ .../AppIcon.appiconset/AppIcon.png | Bin 0 -> 24327 bytes .../AppIcon.appiconset/Contents.json | 36 ++++++++ .../ios/isle/Assets.xcassets/Contents.json | 6 ++ packaging/ios/isle/Info.plist.in | 86 ++++++++++++++++++ packaging/ios/isle/LaunchScreen.storyboard | 48 ++++++++++ 12 files changed, 295 insertions(+), 1 deletion(-) create mode 100644 ISLE/ios/config.cpp create mode 100644 ISLE/ios/config.h create mode 100644 packaging/ios/CMakeLists.txt create mode 100644 packaging/ios/isle/Assets.xcassets/AppIcon.appiconset/AppIcon.png create mode 100644 packaging/ios/isle/Assets.xcassets/AppIcon.appiconset/Contents.json create mode 100644 packaging/ios/isle/Assets.xcassets/Contents.json create mode 100644 packaging/ios/isle/Info.plist.in create mode 100644 packaging/ios/isle/LaunchScreen.storyboard diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 090b9e1f..1d3b5e5f 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -41,6 +41,7 @@ jobs: - { name: 'msys2 mingw32', os: 'windows-latest', generator: 'Ninja', dx5: false, config: false, mingw: true, werror: true, clang-tidy: true, msystem: 'mingw32', msys-env: 'mingw-w64-i686', shell: 'msys2 {0}' } - { name: 'msys2 mingw64', os: 'windows-latest', generator: 'Ninja', dx5: false, config: true, mingw: true, werror: true, clang-tidy: true, msystem: 'mingw64', msys-env: 'mingw-w64-x86_64', shell: 'msys2 {0}' } - { name: 'macOS', os: 'macos-latest', generator: 'Ninja', dx5: false, config: true, brew: true, werror: true, clang-tidy: false } + - { name: 'iOS', os: 'macos-15', generator: 'Xcode', dx5: false, config: false, brew: true, werror: true, clang-tidy: false, cmake-args: '-DCMAKE_SYSTEM_NAME=iOS' } - { name: 'Emscripten', os: 'ubuntu-latest', generator: 'Ninja', dx5: false, config: false, emsdk: true, werror: true, clang-tidy: false, cmake-wrapper: 'emcmake' } - { name: 'Nintendo 3DS', os: 'ubuntu-latest', generator: 'Ninja', dx5: false, config: false, n3ds: true, werror: true, clang-tidy: false, container: 'devkitpro/devkitarm:latest', cmake-args: '-DCMAKE_TOOLCHAIN_FILE=/opt/devkitpro/cmake/3DS.cmake' } - { name: 'Xbox One', os: 'windows-latest', generator: 'Visual Studio 17 2022', dx5: false, config: false, msvc: true, werror: false, clang-tidy: false, vc-arch: 'amd64', cmake-args: '-DCMAKE_SYSTEM_NAME=WindowsStore -DCMAKE_SYSTEM_VERSION=10.0.26100.0', xbox-one: true} diff --git a/CMakeLists.txt b/CMakeLists.txt index 1f26a28b..70ddba15 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -2,6 +2,12 @@ cmake_minimum_required(VERSION 3.25...4.0 FATAL_ERROR) project(isle LANGUAGES CXX C VERSION 0.1) +if (IOS) + set(CMAKE_XCODE_ATTRIBUTE_CODE_SIGNING_ALLOWED NO) + set(CMAKE_OSX_DEPLOYMENT_TARGET "12.0") + add_compile_definitions(IOS) +endif() + if (WINDOWS_STORE) add_compile_definitions(WINDOWS_STORE) endif() @@ -551,6 +557,11 @@ if (ISLE_BUILD_APP) ISLE/xbox_one_series/config.cpp ) endif() + if (IOS) + target_sources(isle PRIVATE + ISLE/ios/config.cpp + ) + endif() if(Python3_FOUND) if(NOT DEFINED PYTHON_PIL_AVAILABLE) execute_process( @@ -803,8 +814,12 @@ if(WINDOWS_STORE) PATTERN "*/*.msix" PATTERN "*/*.msixbundle") endif() -if(MSVC) +if(MSVC OR IOS) set(CPACK_GENERATOR ZIP) + if(IOS) + set(CPACK_ARCHIVE_FILE_EXTENSION ".ipa") + set(CPACK_INCLUDE_TOPLEVEL_DIRECTORY OFF) + endif() elseif(APPLE AND NOT IOS) set(CPACK_GENERATOR DragNDrop) else() diff --git a/ISLE/ios/config.cpp b/ISLE/ios/config.cpp new file mode 100644 index 00000000..ee9349fa --- /dev/null +++ b/ISLE/ios/config.cpp @@ -0,0 +1,25 @@ +#include "config.h" + +#include +#include +#include + +void IOS_SetupDefaultConfigOverrides(dictionary* p_dictionary) +{ + SDL_Log("Overriding default config for iOS"); + + // Use DevelopmentFiles path for disk and cd paths + // It's good to use that path since user can easily + // connect through SMB and copy the files + const char* documentFolder = SDL_GetUserFolder(SDL_FOLDER_DOCUMENTS); + char* diskPath = new char[strlen(documentFolder) + strlen("isle") + 1](); + strcpy(diskPath, documentFolder); + strcat(diskPath, "isle"); + + if (!SDL_GetPathInfo(diskPath, NULL)) { + SDL_CreateDirectory(diskPath); + } + + iniparser_set(p_dictionary, "isle:diskpath", diskPath); + iniparser_set(p_dictionary, "isle:cdpath", diskPath); +} diff --git a/ISLE/ios/config.h b/ISLE/ios/config.h new file mode 100644 index 00000000..53caf824 --- /dev/null +++ b/ISLE/ios/config.h @@ -0,0 +1,8 @@ +#ifndef IOS_CONFIG_H +#define IOS_CONFIG_H + +#include "dictionary.h" + +void IOS_SetupDefaultConfigOverrides(dictionary* p_dictionary); + +#endif // IOS_CONFIG_H diff --git a/ISLE/isleapp.cpp b/ISLE/isleapp.cpp index 2788b8a7..f8f80e3a 100644 --- a/ISLE/isleapp.cpp +++ b/ISLE/isleapp.cpp @@ -63,6 +63,10 @@ #include "xbox_one_series/config.h" #endif +#ifdef IOS +#include "ios/config.h" +#endif + DECOMP_SIZE_ASSERT(IsleApp, 0x8c) // GLOBAL: ISLE 0x410030 @@ -917,7 +921,11 @@ MxResult IsleApp::SetupWindow() // FUNCTION: ISLE 0x4028d0 bool IsleApp::LoadConfig() { +#ifdef IOS + const char* prefPath = SDL_GetUserFolder(SDL_FOLDER_DOCUMENTS); +#else char* prefPath = SDL_GetPrefPath("isledecomp", "isle"); +#endif char* iniConfig; #ifdef __EMSCRIPTEN__ @@ -1000,6 +1008,9 @@ bool IsleApp::LoadConfig() #endif #ifdef WINDOWS_STORE XBONE_SetupDefaultConfigOverrides(dict); +#endif +#ifdef IOS + IOS_SetupDefaultConfigOverrides(dict); #endif iniparser_dump_ini(dict, iniFP); SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION, "New config written at '%s'", iniConfig); @@ -1071,7 +1082,9 @@ bool IsleApp::LoadConfig() iniparser_freedict(dict); delete[] iniConfig; +#ifndef IOS SDL_free(prefPath); +#endif return true; } diff --git a/packaging/CMakeLists.txt b/packaging/CMakeLists.txt index 1b178b63..6f5769e9 100644 --- a/packaging/CMakeLists.txt +++ b/packaging/CMakeLists.txt @@ -28,3 +28,7 @@ endif() if(APPLE AND NOT IOS) add_subdirectory(macos) endif() + +if(IOS) + add_subdirectory(ios) +endif() diff --git a/packaging/ios/CMakeLists.txt b/packaging/ios/CMakeLists.txt new file mode 100644 index 00000000..418ad518 --- /dev/null +++ b/packaging/ios/CMakeLists.txt @@ -0,0 +1,52 @@ +set(MACOSX_BUNDLE_GUI_IDENTIFIER ${APP_ID}) +set(MACOSX_BUNDLE_COPYRIGHT ${APP_SPDX}) +set(ISLE_TARGET_NAME isle) +set(MACOSX_ISLE_BUNDLE_NAME ${APP_NAME}) # Do note that it can be up to 15 characters long +set(MACOSX_ISLE_BUNDLE_DISPLAY_NAME ${APP_NAME}) +set(MACOSX_BUNDLE_INFO_STRING ${PROJECT_VERSION}) +set(MACOSX_BUNDLE_BUNDLE_VERSION ${PROJECT_VERSION}) +set(MACOSX_BUNDLE_SHORT_VERSION_STRING ${PROJECT_VERSION}) +set(MACOSX_BUNDLE_LONG_VERSION_STRING "Version ${PROJECT_VERSION}") +set(MACOSX_BUNDLE_REQUIRED_PLATFORM IPhoneOS) + +if(ISLE_BUILD_APP) + configure_file( + "${CMAKE_CURRENT_SOURCE_DIR}/isle/Info.plist.in" + "${CMAKE_CURRENT_BINARY_DIR}/isle/Info.plist" + @ONLY + ) + set(RESOURCE_FILES "${CMAKE_CURRENT_SOURCE_DIR}/isle/LaunchScreen.storyboard" "${CMAKE_CURRENT_SOURCE_DIR}/isle/Assets.xcassets") + target_sources(${ISLE_TARGET_NAME} PRIVATE ${RESOURCE_FILES}) + set_source_files_properties(${RESOURCE_FILES} + TARGET_DIRECTORY isle + PROPERTIES MACOSX_PACKAGE_LOCATION Resources) + set_target_properties(${ISLE_TARGET_NAME} PROPERTIES + MACOSX_BUNDLE TRUE + MACOSX_BUNDLE_INFO_PLIST "${CMAKE_CURRENT_BINARY_DIR}/isle/Info.plist" + XCODE_ATTRIBUTE_ASSETCATALOG_COMPILER_APPICON_NAME "AppIcon" + XCODE_ATTRIBUTE_TARGETED_DEVICE_FAMILY "1,2") + install(TARGETS ${ISLE_TARGET_NAME} DESTINATION ./) + install(CODE " + file(COPY + \"\$\" + \"\$\" + DESTINATION \"\$\{CMAKE_INSTALL_PREFIX\}/${ISLE_TARGET_NAME}.app/Frameworks\") + execute_process(COMMAND /usr/bin/install_name_tool + -add_rpath @executable_path/Frameworks + \"\$\{CMAKE_INSTALL_PREFIX\}/${ISLE_TARGET_NAME}.app/${ISLE_TARGET_NAME}\" + ) + file(MAKE_DIRECTORY + \"\$\{CMAKE_INSTALL_PREFIX\}/Payload\") + file(RENAME + \"\$\{CMAKE_INSTALL_PREFIX\}/${ISLE_TARGET_NAME}.app\" + \"\$\{CMAKE_INSTALL_PREFIX\}/Payload/${ISLE_TARGET_NAME}.app\") + ") +endif() + +install(CODE " + if(IS_DIRECTORY \"\$\{CMAKE_INSTALL_PREFIX\}/bin\" OR IS_DIRECTORY \"\$\{CMAKE_INSTALL_PREFIX\}/lib\" OR EXISTS \"\$\{CMAKE_INSTALL_PREFIX\}/AppIcon.icns\") + execute_process(COMMAND /bin/rm + -rf \"\$\{CMAKE_INSTALL_PREFIX\}/bin\" \"\$\{CMAKE_INSTALL_PREFIX\}/lib\" \"\$\{CMAKE_INSTALL_PREFIX\}/AppIcon.icns\" + ) + endif() +") diff --git a/packaging/ios/isle/Assets.xcassets/AppIcon.appiconset/AppIcon.png b/packaging/ios/isle/Assets.xcassets/AppIcon.appiconset/AppIcon.png new file mode 100644 index 0000000000000000000000000000000000000000..f51e706908f30270d2e641e2a80fcb7e472f96ce GIT binary patch literal 24327 zcmeHP3sh5A*1id$2B;QAQB+I>A5?{)wFM(W6cj0=BDR7c4=bn)LU=!fkiei9qh1f7be4tNo|ry0BQeIrrRs z_SyH_kF)nV>y|7*rolB8 zu_cW_Y~YFnzX)k8f>MJk8 za({0MBD5L2MjyM>>aYVo?9vvm%0v+7$><*`Bz%$@LCCX{K8nnWT=FO8T6T)pngsSI ziC%drY3NgkUmg=KQxdb*(DPC@re-qp=DC@BFyR_)X1LMKU9ytrxkWAsqX)7x66qg! zd3$-g`P8I0WAT(8{syx1A*88he1nZxj&#h5kA z6MA@N=BH+@$@5Ijbhi-crxTQzxi%vyEh~wgN=NCg`GlRFHP6ir9cbXk!e?S$(%?v` znXje=6J(%w7&E=R8UNd3SxM{u#bf9l%VXwwS%&jNi7~^H@)9?$3Q9^zOw9z={AYU4 zn(b#E=bvs3a%%NxK6XdK=wv<#)`BxYZLuvz8cM{$*MQXIdfS?QX&Wy6&>3B zpW0sa_hX=PwUV>tC7N5|E&AL0F$NIfZ@&;xBq9h42??72Q68ziVSehQkQ~Y#p~Pv! zbkboSJ!Z?tK93hgJ-5H-diMS_cJ1?IYE;6JtuNZW)-Aow3iZj{Dn`gR4v}g_Vp`0? zu@Z$x%#kB?++yARnD?|L_nu!FL1*z0{h3yi95*MLMk;C)Z;sYy%3r-=90FpH=h3+r5OyL&MOtA}oakK;gV^@4Zu*?I1wm(a&uV7;1uAgDhCfFhig-M1Ve!kuUT{t7sy+?yS2-i|44jd z48QJNFusDXjvn6f@CY=Xmv!C|%uf?XOM|Ct*LNls_H+sar_&2{2HC;p zyLS3mo^01J4bAwq$#~Xeys)vz*z*0c(klu5HGLzqS`Qjjrqo_9l_YlljVPDMnp)$`jL=T;;{Hn_E%CDoKZT zZX0c1T|K@cU9IbIGb1X5-$1RBol@$kMakRhCqo;*j2B0G_A(S>c1h&nBwNPpXn+pI9cpSkl(k<^P#VQzq?cZLRFwl-D+e#bYg(a}s2#2R&;k z+~jQ~jhZ95$y*DInw8pXHOA`Q?mMQi;@5+{z11MowF}bq6?^UcKk%C0m?1VB41VN;XPR9{6OR0v8UnY zkq;4EThpn}!BtvGgTI|emd}4|gPn(OIkZ++WzWJgrv~3e2-zaN*Aud&9yFp?yQ24R zx*b)rvSLa0}y|9(kazFHe8+^DI>WFCs@w zzU&JW)5R6lMMjNunrWrCDRK?zf6)ZE>*^{4 zu9~$;bxOUzz>q&W*l|fbn=gIPRrgL@#eR-Broevn)ibhP!EB=<+OL;(*QooxT!O&c z-eA|bNNq@sxk>X^trzL5RY%524Z4!JahC$#QyE33Xy9EP0rp<@@!R0`M^Qo?ZTWQCqWgb?iiU9fp6L)Crx)fVMoFzU{Y z66yN3>za&5TLUu0dtLP&Pb-douj1;|W~jtR^Oe4RfNt3$b;(NY?RNGRee}%e*(7qH za3!**y}nWgdUGhCu=#eOi{^YAt+hU-)KnBv*7Y}&W_aVT<)u2tf(Df-ZcmH584f!@ zTke&gIV&FeXRayTq!H&E#iP2!j&#RS;A@truED)tcUT`(hN8N+!e0>o;#;OIL zdIrkG>(MFHAV59A>YFFk zyuIJrxu1rhY|o1`w|u)oM4X}t0WII%mMH7%(>cbwQ_YAuy-ku``UB*kQXCQ?J1RE; zaP;EY#*+6O_R##Koclv%)8zrF_^aNOdrZcT2$j#; zT%cnge)*MB%++y4Lv@^sd)(PNnMtOqP>KD!9_VOes!RrxRJA3*X@`S$6wmhp3kP+` z%0Ia|8B>Ko_>~a2EcM!lh58eKdVc0kEY#^x0od`m11=?-4_ev0+6}9fJ0O~Ac|yi% zWh`i=k6bHrI_o3lLG4c0Uo7xE97G9Jo40u;|%> zuX@FIX4xNo$R%;Uyr!ACxSL_(jt7ldk-N2iT9wDmyGE7iWyW~vy+>C@L@t0d|K+i7 zFL$imbE9HG*|h+Qm!jT~(*Y^3I+3hwo~@VSp{C*7HSJh(8+fs^0kNp3zn4e23TGk z;r59>uH80Z>DxWhT(*m#403eT41q)d(UtZyH7;r|jf7lnODhu>Uuj2#A%!8@53xpj zDY!1*t_;_`ShrDV=YfbO72@Kysjyxx&AU*H3&%vTV2v9g8er1ANas6&=U7_3IvAG) zjsq{ju#3Yw6v<9-Q&VHhj^d&>1|)|CTg$Cq8l5G-3?!Tw(z}>!01u==mWS3xVAP+U z_6d;KDNt^}(Flbs_vFXZvBtvp43M~7pFJB(Lp>SXJ0WI^1Yc9k@>XTwOJ!SIy31GM zW0@v5Rqu-oeeY;)^K(U8dUc5n)JL*iF5NU#-0~FkLoj|Bzri=ByRAg2eXP1TwPpp^ zuDXb`nClu)C|mT@kdsaEa_>FmZY_FY!edY0oAe%e( z)DT>{CmX;^bbci&J|PV&^Z2_;BldwyVtI&viGowAg~CKV)p&fA*MJJZ()pw$<(R>F zi9YjTwqBiY=r%oBmc*B439GkD5(_sK=zqdg(r!3a?g>VVjSwQ~MuGw?b2x&tk4a!- zPsE(sg0rzFz~&`AcEVZ0Q_y<$`)qHVB^(I_G0#FvMSeiNPM}rCoO8o)Zs9LrrcZ2J zHqgy{AwLVP_sbq!!ZEi3?D1Lk&VCbMc?+em`cM1iXC^KKmIsmEU!47M^SBSKORJ)W z;QY-pu>bdKEg6l0>{?nBv?fI;<8jQr3suJ`ZIBfPKrBnqs$<8WUGY^%5GNsIOZNTs zgsgs{f3_kV))QXtDnY{2@Kq*GHO(|Dbu@pdZJAV2Df0rb-j>X{Z6GhAZ-&*kX86q20D!)SClBUU!pnr4@PQ> zT(%cAsO80fl6$Fh8e@$&E0EYviESgGg2WD&f9Jj-^R^Mx;$F6GmX#=qs+eS{GQCk1 z!%)TLc2|kHF`R#ZX*vlZ^z-T7s8OsX3+mIWYqkYmnMd*WpFpe7%MHphjz&}dy?Fyt zHY|$6;`k(-SuF%RSGd*=YvzQI{yAwW>RA=f;zbZ2iOx{47C#J9O@EJwN3gBlaV(Oz zERu{)VX!1;#jaKBL52`H$ew}QQ1W-Mo;{<+U@a~628?#Y=fHrSwp=_1yuP%58`t_2 zg!6P7y7x{VWEH*7zX-Wu5!nXU+6SZcT^(UH1L(|x{k4R6 z3Gu$dAc0=9;uFTfY$XWeU@^CZ?KLl7{t(7NlFT97YIqnV@qld*_Rrj~ z`Egbh6kMGJ!Ps!w9wJrpMeIQ^`aa0f6Rf^@QkB2&>QRFFm+NGesxDm|uBQeytpl{G30Zbbh4PS43mfwH;WWyMZiKpIpC^bm=rAHl6wf1PiUU?PEs9j7u#nIl`-h6xPz86 zU30&~MilgEu!Sqb0axsBg@~dqW*Ro4*a30*UmI(!Fpeg)f?@Z4x&XHu;I9x-cvRqu zK6l{kV^-sFTzUT_>^8gZiK~fZK{569Sp9s(^*96>_YL~L0RDs6CRnx3#`1q7Fc4BD zq-tfR{?LYtz$$^&-xRBP9hW~u_uKIgNqRPor_s)7JGQVZYV4}HO$L?GnB8+6Js3V% zx}!QYauIbPYfMaIphTzXKeBzyR&P&a6^LBTzcZ~Ad!PS;+Gs1{j<0KTa3B}hH}?9S zUjguyR}S0K<;mMEIY1V3Rl4)7cU?D4gc9|F`iDth&}2ExQ$M~6GjsV3w{^xxaE6FP z&WC(6aom77#6g@lv2r+c7IBIPR;$E;qc`us5pm*-ILAjEIebG7a!O3+*48aTYP}sP z9Ulr!|0NueHcGI;>A24?>i!z%1s;h$bu_LxTEim=eIjs;f5e`W&d+yIiX|1DnGEN_ z9POu6zvS}VYvs+j84UKo_;PGvm$-$^u2{Ag5u!71FT!qDB?M)ih9aavNP{q*zquCh dlgI&Qj~+-d&KR*C{<}L8vT$io*@F1Z{{!mFakl^f literal 0 HcmV?d00001 diff --git a/packaging/ios/isle/Assets.xcassets/AppIcon.appiconset/Contents.json b/packaging/ios/isle/Assets.xcassets/AppIcon.appiconset/Contents.json new file mode 100644 index 00000000..ce8e776e --- /dev/null +++ b/packaging/ios/isle/Assets.xcassets/AppIcon.appiconset/Contents.json @@ -0,0 +1,36 @@ +{ + "images" : [ + { + "filename" : "AppIcon.png", + "idiom" : "universal", + "platform" : "ios", + "size" : "1024x1024" + }, + { + "appearances" : [ + { + "appearance" : "luminosity", + "value" : "dark" + } + ], + "idiom" : "universal", + "platform" : "ios", + "size" : "1024x1024" + }, + { + "appearances" : [ + { + "appearance" : "luminosity", + "value" : "tinted" + } + ], + "idiom" : "universal", + "platform" : "ios", + "size" : "1024x1024" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/packaging/ios/isle/Assets.xcassets/Contents.json b/packaging/ios/isle/Assets.xcassets/Contents.json new file mode 100644 index 00000000..73c00596 --- /dev/null +++ b/packaging/ios/isle/Assets.xcassets/Contents.json @@ -0,0 +1,6 @@ +{ + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/packaging/ios/isle/Info.plist.in b/packaging/ios/isle/Info.plist.in new file mode 100644 index 00000000..714324a3 --- /dev/null +++ b/packaging/ios/isle/Info.plist.in @@ -0,0 +1,86 @@ + + + + + CFBundleDevelopmentRegion + English + CFBundleExecutable + ${MACOSX_BUNDLE_EXECUTABLE_NAME} + CFBundleGetInfoString + @MACOSX_BUNDLE_INFO_STRING@ + CFBundleIdentifier + @MACOSX_BUNDLE_GUI_IDENTIFIER@ + CFBundleInfoDictionaryVersion + 6.0 + CFBundleLongVersionString + @MACOSX_BUNDLE_LONG_VERSION_STRING@ + CFBundleName + @MACOSX_ISLE_BUNDLE_NAME@ + CFBundleDisplayName + @MACOSX_ISLE_BUNDLE_DISPLAY_NAME@ + CFBundlePackageType + APPL + CFBundleShortVersionString + @MACOSX_BUNDLE_SHORT_VERSION_STRING@ + CFBundleSignature + ???? + CFBundleVersion + @MACOSX_BUNDLE_BUNDLE_VERSION@ + UILaunchStoryboardName + LaunchScreen + NSHighResolutionCapable + + CSResourcesFileMapped + + LSRequires@MACOSX_BUNDLE_REQUIRED_PLATFORM@ + + NSHumanReadableCopyright + @MACOSX_BUNDLE_COPYRIGHT@ + SDL_FILESYSTEM_BASE_DIR_TYPE + resource + NSSupportsAutomaticGraphicsSwitching + + UIApplicationSupportsIndirectInputEvents + + LSSupportsOpeningDocumentsInPlace + + UIFileSharingEnabled + + CADisableMinimumFrameDurationOnPhone + + UIDeviceFamily + + 1 + 2 + + UIRequiresFullScreen + + UIStatusBarHidden + + UISupportedInterfaceOrientations + + UIInterfaceOrientationLandscapeLeft + UIInterfaceOrientationLandscapeRight + + UISupportedInterfaceOrientations~ipad + + UIInterfaceOrientationLandscapeLeft + UIInterfaceOrientationLandscapeRight + + UIApplicationSceneManifest + + UIApplicationSupportsMultipleScenes + + + CFBundleAllowMixedLocalizations + + LSApplicationCategoryType + public.app-category.games + DTPlatformName + iphoneos + UIFileSharingEnabled + + LSSupportsOpeningDocumentsInPlace + + + \ No newline at end of file diff --git a/packaging/ios/isle/LaunchScreen.storyboard b/packaging/ios/isle/LaunchScreen.storyboard new file mode 100644 index 00000000..ad167a8a --- /dev/null +++ b/packaging/ios/isle/LaunchScreen.storyboard @@ -0,0 +1,48 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + From 4e8ac864154cecb0cef95217035b5a300d909f61 Mon Sep 17 00:00:00 2001 From: Helloyunho Date: Thu, 10 Jul 2025 15:58:17 +0900 Subject: [PATCH 109/188] Dynamically on/off virtual cursor (#567) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * ✨ feat: eliminate need of draw_cursor config * 🩹 fix: type mismatch --- ISLE/isleapp.cpp | 54 ++++++++++++++++--------- ISLE/isleapp.h | 1 + miniwin/include/miniwin/miniwindevice.h | 1 + miniwin/src/d3drm/d3drmdevice.cpp | 8 ++++ miniwin/src/internal/d3drmdevice_impl.h | 1 + 5 files changed, 45 insertions(+), 20 deletions(-) diff --git a/ISLE/isleapp.cpp b/ISLE/isleapp.cpp index f8f80e3a..49a939c2 100644 --- a/ISLE/isleapp.cpp +++ b/ISLE/isleapp.cpp @@ -103,6 +103,7 @@ MxFloat g_lastJoystickMouseX = 0; MxFloat g_lastJoystickMouseY = 0; MxFloat g_lastMouseX = 320; MxFloat g_lastMouseY = 240; +MxBool g_mouseWarped = FALSE; bool g_dpadUp = false; bool g_dpadDown = false; @@ -603,6 +604,10 @@ SDL_AppResult SDL_AppEvent(void* appstate, SDL_Event* event) break; } case SDL_EVENT_MOUSE_MOTION: + if (g_mouseWarped) { + g_mouseWarped = FALSE; + break; + } #ifdef __EMSCRIPTEN__ if (detectedTouchEvents) { break; @@ -619,9 +624,13 @@ SDL_AppResult SDL_AppEvent(void* appstate, SDL_Event* event) 0 ); } + g_lastMouseX = event->motion.x; + g_lastMouseY = event->motion.y; - if (g_isle->GetDrawCursor()) { - VideoManager()->MoveCursor(Min((MxS32) event->motion.x, 639), Min((MxS32) event->motion.y, 479)); + SDL_ShowCursor(); + g_isle->SetDrawCursor(FALSE); + if (VideoManager()) { + VideoManager()->SetCursorBitmap(NULL); } break; case SDL_EVENT_FINGER_MOTION: { @@ -633,12 +642,17 @@ SDL_AppResult SDL_AppEvent(void* appstate, SDL_Event* event) float x = SDL_clamp(event->tfinger.x, 0, 1) * 640; float y = SDL_clamp(event->tfinger.y, 0, 1) * 480; + g_lastMouseX = x; + g_lastMouseY = y; + if (InputManager()) { InputManager()->QueueEvent(c_notificationMouseMove, LegoEventNotificationParam::c_lButtonState, x, y, 0); } - if (g_isle->GetDrawCursor()) { - VideoManager()->MoveCursor(Min((MxS32) x, 639), Min((MxS32) y, 479)); + SDL_HideCursor(); + g_isle->SetDrawCursor(FALSE); + if (VideoManager()) { + VideoManager()->SetCursorBitmap(NULL); } break; } @@ -808,12 +822,9 @@ MxResult IsleApp::SetupWindow() m_cursorBusy = SDL_CreateSystemCursor(SDL_SYSTEM_CURSOR_WAIT); m_cursorNo = SDL_CreateSystemCursor(SDL_SYSTEM_CURSOR_NOT_ALLOWED); SDL_SetCursor(m_cursorCurrent); - if (g_isle->GetDrawCursor()) { - SDL_HideCursor(); - m_cursorCurrentBitmap = m_cursorArrowBitmap = &arrow_cursor; - m_cursorBusyBitmap = &busy_cursor; - m_cursorNoBitmap = &no_cursor; - } + m_cursorCurrentBitmap = m_cursorArrowBitmap = &arrow_cursor; + m_cursorBusyBitmap = &busy_cursor; + m_cursorNoBitmap = &no_cursor; SDL_PropertiesID props = SDL_CreateProperties(); SDL_SetNumberProperty(props, SDL_PROP_WINDOW_CREATE_WIDTH_NUMBER, g_targetWidth); @@ -897,7 +908,7 @@ MxResult IsleApp::SetupWindow() LegoOmni::GetInstance()->GetInputManager()->SetUseJoystick(m_useJoystick); LegoOmni::GetInstance()->GetInputManager()->SetJoystickIndex(m_joystickIndex); } - if (LegoOmni::GetInstance()->GetVideoManager() && g_isle->GetDrawCursor()) { + if (LegoOmni::GetInstance()->GetVideoManager()) { LegoOmni::GetInstance()->GetVideoManager()->SetCursorBitmap(m_cursorCurrentBitmap); } MxDirect3D* d3d = LegoOmni::GetInstance()->GetVideoManager()->GetDirect3D(); @@ -990,7 +1001,6 @@ bool IsleApp::LoadConfig() iniparser_set(dict, "isle:UseJoystick", m_useJoystick ? "true" : "false"); iniparser_set(dict, "isle:JoystickIndex", SDL_itoa(m_joystickIndex, buf, 10)); - iniparser_set(dict, "isle:Draw Cursor", m_drawCursor ? "true" : "false"); SDL_snprintf(buf, sizeof(buf), "%f", m_cursorSensitivity); iniparser_set(dict, "isle:Cursor Sensitivity", buf); @@ -1042,7 +1052,6 @@ bool IsleApp::LoadConfig() m_useMusic = iniparser_getboolean(dict, "isle:Music", m_useMusic); m_useJoystick = iniparser_getboolean(dict, "isle:UseJoystick", m_useJoystick); m_joystickIndex = iniparser_getint(dict, "isle:JoystickIndex", m_joystickIndex); - m_drawCursor = iniparser_getboolean(dict, "isle:Draw Cursor", m_drawCursor); m_cursorSensitivity = iniparser_getdouble(dict, "isle:Cursor Sensitivity", m_cursorSensitivity); MxS32 backBuffersInVRAM = iniparser_getboolean(dict, "isle:Back Buffers in Video RAM", -1); @@ -1213,12 +1222,7 @@ void IsleApp::SetupCursor(Cursor p_cursor) } if (g_isle->GetDrawCursor()) { - if (m_cursorCurrentBitmap == NULL) { - VideoManager()->SetCursorBitmap(NULL); - } - else { - VideoManager()->SetCursorBitmap(m_cursorCurrentBitmap); - } + VideoManager()->SetCursorBitmap(m_cursorCurrentBitmap); } else { if (m_cursorCurrent != NULL) { @@ -1392,8 +1396,18 @@ void IsleApp::MoveVirtualMouseViaJoystick() ); } - if (g_isle->GetDrawCursor()) { + SDL_HideCursor(); + g_isle->SetDrawCursor(TRUE); + if (VideoManager()) { + VideoManager()->SetCursorBitmap(m_cursorCurrentBitmap); VideoManager()->MoveCursor(Min((MxS32) g_lastMouseX, 639), Min((MxS32) g_lastMouseY, 479)); } + IDirect3DRMMiniwinDevice* device = GetD3DRMMiniwinDevice(); + if (device) { + Sint32 x, y; + device->ConvertRenderToWindowCoordinates(g_lastMouseX, g_lastMouseY, x, y); + g_mouseWarped = TRUE; + SDL_WarpMouseInWindow(window, x, y); + } } } diff --git a/ISLE/isleapp.h b/ISLE/isleapp.h index 70445d05..7054f6d1 100644 --- a/ISLE/isleapp.h +++ b/ISLE/isleapp.h @@ -56,6 +56,7 @@ class IsleApp { void SetWindowActive(MxS32 p_windowActive) { m_windowActive = p_windowActive; } void SetGameStarted(MxS32 p_gameStarted) { m_gameStarted = p_gameStarted; } + void SetDrawCursor(MxS32 p_drawCursor) { m_drawCursor = p_drawCursor; } MxResult ParseArguments(int argc, char** argv); MxResult VerifyFilesystem(); diff --git a/miniwin/include/miniwin/miniwindevice.h b/miniwin/include/miniwin/miniwindevice.h index 00329393..de28e76a 100644 --- a/miniwin/include/miniwin/miniwindevice.h +++ b/miniwin/include/miniwin/miniwindevice.h @@ -6,4 +6,5 @@ DEFINE_GUID(IID_IDirect3DRMMiniwinDevice, 0x6eb09673, 0x8d30, 0x4d8a, 0x8d, 0x81 struct IDirect3DRMMiniwinDevice : virtual public IUnknown { virtual bool ConvertEventToRenderCoordinates(SDL_Event* event) = 0; + virtual bool ConvertRenderToWindowCoordinates(Sint32 inX, Sint32 inY, Sint32& outX, Sint32& outY) = 0; }; diff --git a/miniwin/src/d3drm/d3drmdevice.cpp b/miniwin/src/d3drm/d3drmdevice.cpp index 9f517426..987efb6e 100644 --- a/miniwin/src/d3drm/d3drmdevice.cpp +++ b/miniwin/src/d3drm/d3drmdevice.cpp @@ -198,3 +198,11 @@ bool Direct3DRMDevice2Impl::ConvertEventToRenderCoordinates(SDL_Event* event) return true; } + +bool Direct3DRMDevice2Impl::ConvertRenderToWindowCoordinates(Sint32 inX, Sint32 inY, Sint32& outX, Sint32& outY) +{ + outX = static_cast(inX * m_viewportTransform.scale + m_viewportTransform.offsetX); + outY = static_cast(inY * m_viewportTransform.scale + m_viewportTransform.offsetY); + + return true; +} diff --git a/miniwin/src/internal/d3drmdevice_impl.h b/miniwin/src/internal/d3drmdevice_impl.h index 5679ce0d..46dc5ed2 100644 --- a/miniwin/src/internal/d3drmdevice_impl.h +++ b/miniwin/src/internal/d3drmdevice_impl.h @@ -34,6 +34,7 @@ struct Direct3DRMDevice2Impl : public Direct3DRMObjectBaseImpl Date: Thu, 10 Jul 2025 00:24:59 -0700 Subject: [PATCH 110/188] Add extensions, `TextureLoader` (#570) * Add extensions, `TextureLoader` * Fix wording * Add to default ini * Add folder to flatpak * Use different enable strategy --- CMakeLists.txt | 11 +++ CONTRIBUTING.md | 7 +- ISLE/isleapp.cpp | 20 ++++ .../legoomni/src/common/legotextureinfo.cpp | 7 ++ README.md | 2 +- extensions/include/extensions/extensions.h | 28 ++++++ extensions/include/extensions/textureloader.h | 24 +++++ extensions/src/extensions.cpp | 19 ++++ extensions/src/textureloader.cpp | 98 +++++++++++++++++++ .../linux/flatpak/org.legoisland.Isle.json | 5 + 10 files changed, 214 insertions(+), 7 deletions(-) create mode 100644 extensions/include/extensions/extensions.h create mode 100644 extensions/include/extensions/textureloader.h create mode 100644 extensions/src/extensions.cpp create mode 100644 extensions/src/textureloader.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index 70ddba15..724ca120 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -48,6 +48,7 @@ option(ISLE_WERROR "Treat warnings as errors" OFF) option(ISLE_DEBUG "Enable imgui debug" ON) cmake_dependent_option(ISLE_USE_DX5 "Build with internal DirectX 5 SDK" "${NOT_MINGW}" "WIN32;CMAKE_SIZEOF_VOID_P EQUAL 4" OFF) cmake_dependent_option(ISLE_MINIWIN "Use miniwin" ON "NOT ISLE_USE_DX5" OFF) +cmake_dependent_option(ISLE_EXTENSIONS "Use extensions" ON "NOT ISLE_USE_DX5" OFF) cmake_dependent_option(ISLE_BUILD_CONFIG "Build CONFIG.EXE application" ON "MSVC OR ISLE_MINIWIN;NOT NINTENDO_3DS;NOT WINDOWS_STORE" OFF) cmake_dependent_option(ISLE_COMPILE_SHADERS "Compile shaders" ON "SDL_SHADERCROSS_BIN;TARGET Python3::Interpreter" OFF) option(CMAKE_POSITION_INDEPENDENT_CODE "Build with -fPIC" ON) @@ -61,6 +62,7 @@ message(STATUS "Isle app: ${ISLE_BUILD_APP}") message(STATUS "Config app: ${ISLE_BUILD_CONFIG}") message(STATUS "Internal DirectX5 SDK: ${ISLE_USE_DX5}") message(STATUS "Internal miniwin: ${ISLE_MINIWIN}") +message(STATUS "Isle extensions: ${ISLE_EXTENSIONS}") message(STATUS "Isle debugging: ${ISLE_DEBUG}") message(STATUS "Compile shaders: ${ISLE_COMPILE_SHADERS}") @@ -170,6 +172,7 @@ add_library(lego1 target_precompile_headers(lego1 PRIVATE "LEGO1/lego1_pch.h") set_property(TARGET lego1 PROPERTY DEFINE_SYMBOL "LEGO1_DLL") target_include_directories(lego1 PUBLIC "$") +target_include_directories(lego1 PUBLIC "$") target_include_directories(lego1 PUBLIC "$") target_include_directories(lego1 PUBLIC "$") target_include_directories(lego1 PUBLIC "$") @@ -487,6 +490,14 @@ if (NOT ISLE_MINIWIN) target_compile_definitions(lego1 PRIVATE DIRECTINPUT_VERSION=0x0500) endif() +if (ISLE_EXTENSIONS) + target_compile_definitions(lego1 PUBLIC EXTENSIONS) + target_sources(lego1 PRIVATE + extensions/src/extensions.cpp + extensions/src/textureloader.cpp + ) +endif() + if (ISLE_BUILD_APP) add_executable(isle WIN32 ISLE/res/isle.rc diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 61b8cf68..8be43bce 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -6,12 +6,7 @@ If you feel fit to contribute, feel free to create a pull request! Someone will Please keep your pull requests small and understandable; you may be able to shoot ahead and make a lot of progress in a short amount of time, but this is a collaborative project, so you must allow others to catch up and follow along. Large pull requests become significantly more unwieldy to review, and as such make it exponentially more likely for a mistake or error to go undetected. They also make it harder to merge other pull requests because the more files you modify, the more likely it is for a merge conflict to occur. A general guideline is to keep submissions limited to one class at a time. Sometimes two or more classes may be too interlinked for this to be feasible, so this is not a hard rule, however if your PR is starting to modify more than 10 or so files, it's probably getting too big. -This repository has a single goal: achieving platform independence. Consequently, contributions that do not support this goal cannot be accepted. Examples of out-of-scope contributions include: - -* Improving code efficiency or performance without addressing platform compatibility -* Replacing `MxString` with `std::string` -* Fixing gameplay bugs -* Enhancing audio or video quality +This repository has achieving platform independence as its primary goal, with limited support for light modding (using [`extensions`](/extensions)). Any changes that modify code in `LEGO1` are unlikely to be accepted, unless they directly serve to increase platform compatibility. If in doubt, please contact us in the [Matrix chatroom](https://matrix.to/#/#isledecomp:matrix.org). diff --git a/ISLE/isleapp.cpp b/ISLE/isleapp.cpp index 49a939c2..ddbeb6c4 100644 --- a/ISLE/isleapp.cpp +++ b/ISLE/isleapp.cpp @@ -37,6 +37,7 @@ #include "tgl/d3drm/impl.h" #include "viewmanager/viewmanager.h" +#include #include #define SDL_MAIN_USE_CALLBACKS @@ -1013,6 +1014,13 @@ bool IsleApp::LoadConfig() iniparser_set(dict, "isle:Max Allowed Extras", SDL_itoa(m_maxAllowedExtras, buf, 10)); iniparser_set(dict, "isle:Transition Type", SDL_itoa(m_transitionType, buf, 10)); +#ifdef EXTENSIONS + iniparser_set(dict, "extensions", NULL); + for (const char* key : Extensions::availableExtensions) { + iniparser_set(dict, key, "false"); + } +#endif + #ifdef __3DS__ N3DS_SetupDefaultConfigOverrides(dict); #endif @@ -1089,6 +1097,18 @@ bool IsleApp::LoadConfig() m_savePath = new char[strlen(savePath) + 1]; strcpy(m_savePath, savePath); +#ifdef EXTENSIONS + std::vector keys; + keys.resize(iniparser_getsecnkeys(dict, "extensions")); + iniparser_getseckeys(dict, "extensions", keys.data()); + + for (const char* key : keys) { + if (iniparser_getboolean(dict, key, 0)) { + Extensions::Enable(key); + } + } +#endif + iniparser_freedict(dict); delete[] iniConfig; #ifndef IOS diff --git a/LEGO1/lego/legoomni/src/common/legotextureinfo.cpp b/LEGO1/lego/legoomni/src/common/legotextureinfo.cpp index eb639d84..a0be25b7 100644 --- a/LEGO1/lego/legoomni/src/common/legotextureinfo.cpp +++ b/LEGO1/lego/legoomni/src/common/legotextureinfo.cpp @@ -1,5 +1,6 @@ #include "legotextureinfo.h" +#include "extensions/textureloader.h" #include "legovideomanager.h" #include "misc.h" #include "misc/legoimage.h" @@ -9,6 +10,8 @@ DECOMP_SIZE_ASSERT(LegoTextureInfo, 0x10) +using namespace Extensions; + // FUNCTION: LEGO1 0x10065bf0 LegoTextureInfo::LegoTextureInfo() { @@ -56,6 +59,10 @@ LegoTextureInfo* LegoTextureInfo::Create(const char* p_name, LegoTexture* p_text strcpy(textureInfo->m_name, p_name); } + if (Extension::Call(PatchTexture, textureInfo).value_or(false)) { + return textureInfo; + } + LPDIRECTDRAW pDirectDraw = VideoManager()->GetDirect3D()->DirectDraw(); LegoImage* image = p_texture->GetImage(); diff --git a/README.md b/README.md index 765c74d2..70ea1aa4 100644 --- a/README.md +++ b/README.md @@ -4,7 +4,7 @@ This initiative is a portable version of LEGO Island (Version 1.1, English) based on the [decompilation project](https://github.com/isledecomp/isle). Our primary goal is to transform the codebase to achieve platform independence, thereby enhancing compatibility across various systems while preserving the original game's experience as faithfully as possible. -Please note: this project is dedicated to achieving platform independence without altering the core gameplay, adding new features, enhancing visual quality, or rewriting code for improvement's sake. While those are worthwhile objectives, they are not within the scope of this project. +Please note: this project is primarily dedicated to achieving platform independence without altering the core gameplay or rewriting code for improvement's sake. While those are worthwhile objectives, they are not within the scope of this project. `isle-portable` offers support for light modding using [`extensions`](/extensions). ## Status diff --git a/extensions/include/extensions/extensions.h b/extensions/include/extensions/extensions.h new file mode 100644 index 00000000..215bf80a --- /dev/null +++ b/extensions/include/extensions/extensions.h @@ -0,0 +1,28 @@ +#pragma once + +#include "lego1_export.h" + +#include +#include +#include + +namespace Extensions +{ +constexpr const char* availableExtensions[] = {"extensions:texture loader"}; + +LEGO1_EXPORT void Enable(const char* p_key); + +template +struct Extension { + template + static auto Call(Function&& function, Args&&... args) -> std::optional> + { +#ifdef EXTENSIONS + if (T::enabled) { + return std::invoke(std::forward(function), std::forward(args)...); + } +#endif + return std::nullopt; + } +}; +}; // namespace Extensions diff --git a/extensions/include/extensions/textureloader.h b/extensions/include/extensions/textureloader.h new file mode 100644 index 00000000..649c6112 --- /dev/null +++ b/extensions/include/extensions/textureloader.h @@ -0,0 +1,24 @@ +#pragma once + +#include "extensions/extensions.h" +#include "legotextureinfo.h" + +namespace Extensions +{ +class TextureLoader { +public: + static bool PatchTexture(LegoTextureInfo* p_textureInfo); + static bool enabled; + +private: + static constexpr const char* texturePath = "/textures/"; + + static SDL_Surface* FindTexture(const char* p_name); +}; + +#ifdef EXTENSIONS +constexpr auto PatchTexture = &TextureLoader::PatchTexture; +#else +constexpr decltype(&TextureLoader::PatchTexture) PatchTexture = nullptr; +#endif +}; // namespace Extensions diff --git a/extensions/src/extensions.cpp b/extensions/src/extensions.cpp new file mode 100644 index 00000000..e1d0c389 --- /dev/null +++ b/extensions/src/extensions.cpp @@ -0,0 +1,19 @@ +#include "extensions/extensions.h" + +#include "extensions/textureloader.h" + +#include + +void Extensions::Enable(const char* p_key) +{ + for (const char* key : availableExtensions) { + if (!SDL_strcasecmp(p_key, key)) { + if (!SDL_strcasecmp(p_key, "extensions:texture loader")) { + TextureLoader::enabled = true; + } + + SDL_Log("Enabled extension: %s", p_key); + break; + } + } +} diff --git a/extensions/src/textureloader.cpp b/extensions/src/textureloader.cpp new file mode 100644 index 00000000..ee057997 --- /dev/null +++ b/extensions/src/textureloader.cpp @@ -0,0 +1,98 @@ +#include "extensions/textureloader.h" + +using namespace Extensions; + +bool TextureLoader::enabled = false; + +bool TextureLoader::PatchTexture(LegoTextureInfo* p_textureInfo) +{ + SDL_Surface* surface = FindTexture(p_textureInfo->m_name); + if (!surface) { + return false; + } + + const SDL_PixelFormatDetails* details = SDL_GetPixelFormatDetails(surface->format); + + DDSURFACEDESC desc; + memset(&desc, 0, sizeof(desc)); + desc.dwSize = sizeof(desc); + desc.dwFlags = DDSD_PIXELFORMAT | DDSD_WIDTH | DDSD_HEIGHT | DDSD_CAPS; + desc.ddsCaps.dwCaps = DDSCAPS_TEXTURE | DDSCAPS_SYSTEMMEMORY; + desc.ddpfPixelFormat.dwSize = sizeof(desc.ddpfPixelFormat); + desc.dwWidth = surface->w; + desc.dwHeight = surface->h; + desc.ddpfPixelFormat.dwFlags = DDPF_RGB | DDPF_ALPHAPIXELS; + desc.ddpfPixelFormat.dwRGBBitCount = details->bits_per_pixel == 8 ? 24 : details->bits_per_pixel; + desc.ddpfPixelFormat.dwRBitMask = details->Rmask; + desc.ddpfPixelFormat.dwGBitMask = details->Gmask; + desc.ddpfPixelFormat.dwBBitMask = details->Bmask; + desc.ddpfPixelFormat.dwRGBAlphaBitMask = details->Amask; + + if (VideoManager()->GetDirect3D()->DirectDraw()->CreateSurface(&desc, &p_textureInfo->m_surface, NULL) != DD_OK) { + SDL_DestroySurface(surface); + return false; + } + + memset(&desc, 0, sizeof(desc)); + desc.dwSize = sizeof(desc); + + if (p_textureInfo->m_surface->Lock(NULL, &desc, DDLOCK_SURFACEMEMORYPTR | DDLOCK_WRITEONLY, NULL) != DD_OK) { + SDL_DestroySurface(surface); + return false; + } + + MxU8* dst = (MxU8*) desc.lpSurface; + Uint8* srcPixels = (Uint8*) surface->pixels; + + if (details->bits_per_pixel == 8) { + SDL_Palette* palette = SDL_GetSurfacePalette(surface); + if (palette) { + for (int y = 0; y < surface->h; ++y) { + Uint8* srcRow = srcPixels + y * surface->pitch; + Uint8* dstRow = dst + y * desc.lPitch; + for (int x = 0; x < surface->w; ++x) { + SDL_Color color = palette->colors[srcRow[x]]; + dstRow[x * 3 + 0] = color.r; + dstRow[x * 3 + 1] = color.g; + dstRow[x * 3 + 2] = color.b; + } + } + } + else { + p_textureInfo->m_surface->Unlock(desc.lpSurface); + SDL_DestroySurface(surface); + return false; + } + } + else { + memcpy(dst, srcPixels, surface->pitch * surface->h); + } + + p_textureInfo->m_surface->Unlock(desc.lpSurface); + p_textureInfo->m_palette = NULL; + + if (((TglImpl::RendererImpl*) VideoManager()->GetRenderer()) + ->CreateTextureFromSurface(p_textureInfo->m_surface, &p_textureInfo->m_texture) != D3DRM_OK) { + SDL_DestroySurface(surface); + return false; + } + + p_textureInfo->m_texture->SetAppData((LPD3DRM_APPDATA) p_textureInfo); + SDL_DestroySurface(surface); + return true; +} + +SDL_Surface* TextureLoader::FindTexture(const char* p_name) +{ + SDL_Surface* surface; + MxString path = MxString(MxOmni::GetHD()) + texturePath + p_name + ".bmp"; + + path.MapPathToFilesystem(); + if (!(surface = SDL_LoadBMP(path.GetData()))) { + path = MxString(MxOmni::GetCD()) + texturePath + p_name + ".bmp"; + path.MapPathToFilesystem(); + surface = SDL_LoadBMP(path.GetData()); + } + + return surface; +} diff --git a/packaging/linux/flatpak/org.legoisland.Isle.json b/packaging/linux/flatpak/org.legoisland.Isle.json index 178434fa..229ba424 100644 --- a/packaging/linux/flatpak/org.legoisland.Isle.json +++ b/packaging/linux/flatpak/org.legoisland.Isle.json @@ -55,6 +55,11 @@ "path": "../../../miniwin", "dest": "miniwin/" }, + { + "type": "dir", + "path": "../../../extensions", + "dest": "extensions/" + }, { "type": "dir", "path": "../../../packaging", From e61f9209f2b64e03b3bd41d0ecbb90a760ff7c4a Mon Sep 17 00:00:00 2001 From: Christian Semmler Date: Thu, 10 Jul 2025 00:28:35 -0700 Subject: [PATCH 111/188] Update README.md [skip ci] --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 70ea1aa4..95237c85 100644 --- a/README.md +++ b/README.md @@ -16,10 +16,10 @@ Please note: this project is primarily dedicated to achieving platform independe | [Web](https://isle.pizza) | [![CI](https://github.com/isledecomp/isle-portable/actions/workflows/ci.yml/badge.svg)](https://github.com/isledecomp/isle-portable/actions/workflows/ci.yml) | | Nintendo 3DS | [![CI](https://github.com/isledecomp/isle-portable/actions/workflows/ci.yml/badge.svg)](https://github.com/isledecomp/isle-portable/actions/workflows/ci.yml) | | Xbox One | [![CI](https://github.com/isledecomp/isle-portable/actions/workflows/ci.yml/badge.svg)](https://github.com/isledecomp/isle-portable/actions/workflows/ci.yml) | +| iOS | [![CI](https://github.com/isledecomp/isle-portable/actions/workflows/ci.yml/badge.svg)](https://github.com/isledecomp/isle-portable/actions/workflows/ci.yml) | We are actively working to support more platforms. If you have experience with a particular platform, we encourage you to contribute to `isle-portable`. You can find a [list of ongoing efforts](https://github.com/isledecomp/isle-portable/wiki/Work%E2%80%90in%E2%80%90progress-ports) in our Wiki. - ## Usage **An existing copy of LEGO Island is required to use this project.** From 078b1b52d0103c4c0ffbba320e3197ad69f02180 Mon Sep 17 00:00:00 2001 From: Christian Semmler Date: Thu, 10 Jul 2025 00:32:55 -0700 Subject: [PATCH 112/188] Update Dockerfile --- docker/emscripten/Dockerfile | 1 + 1 file changed, 1 insertion(+) diff --git a/docker/emscripten/Dockerfile b/docker/emscripten/Dockerfile index a213093c..707b3c7a 100644 --- a/docker/emscripten/Dockerfile +++ b/docker/emscripten/Dockerfile @@ -28,6 +28,7 @@ COPY --chown=emscripten:emscripten miniwin/ ./miniwin/ COPY --chown=emscripten:emscripten util/ ./util/ COPY --chown=emscripten:emscripten CMake/ ./CMake/ COPY --chown=emscripten:emscripten packaging/ ./packaging/ +COPY --chown=emscripten:emscripten extensions/ ./extensions/ COPY --chown=emscripten:emscripten CMakeLists.txt . RUN emcmake cmake -S . -B build -DISLE_BUILD_CONFIG=OFF -DISLE_DEBUG=OFF -DCMAKE_BUILD_TYPE=Release -DISLE_EMSCRIPTEN_HOST=/assets && \ From 96f60cb683c5e5f2008f8996be535123abcd0298 Mon Sep 17 00:00:00 2001 From: Helloyunho Date: Thu, 10 Jul 2025 19:43:46 +0900 Subject: [PATCH 113/188] =?UTF-8?q?=F0=9F=A9=B9=20fix:=20touch=20screens?= =?UTF-8?q?=20go=20beep=20and=20done=20(#573)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ISLE/isleapp.cpp | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/ISLE/isleapp.cpp b/ISLE/isleapp.cpp index ddbeb6c4..dbb3c9ff 100644 --- a/ISLE/isleapp.cpp +++ b/ISLE/isleapp.cpp @@ -625,6 +625,7 @@ SDL_AppResult SDL_AppEvent(void* appstate, SDL_Event* event) 0 ); } + g_lastMouseX = event->motion.x; g_lastMouseY = event->motion.y; @@ -643,13 +644,13 @@ SDL_AppResult SDL_AppEvent(void* appstate, SDL_Event* event) float x = SDL_clamp(event->tfinger.x, 0, 1) * 640; float y = SDL_clamp(event->tfinger.y, 0, 1) * 480; - g_lastMouseX = x; - g_lastMouseY = y; - if (InputManager()) { InputManager()->QueueEvent(c_notificationMouseMove, LegoEventNotificationParam::c_lButtonState, x, y, 0); } + g_lastMouseX = x; + g_lastMouseY = y; + SDL_HideCursor(); g_isle->SetDrawCursor(FALSE); if (VideoManager()) { @@ -687,6 +688,15 @@ SDL_AppResult SDL_AppEvent(void* appstate, SDL_Event* event) if (InputManager()) { InputManager()->QueueEvent(c_notificationButtonDown, LegoEventNotificationParam::c_lButtonState, x, y, 0); } + + g_lastMouseX = x; + g_lastMouseY = y; + + SDL_HideCursor(); + g_isle->SetDrawCursor(FALSE); + if (VideoManager()) { + VideoManager()->SetCursorBitmap(NULL); + } break; } case SDL_EVENT_MOUSE_BUTTON_UP: From 28b026f6053f9cbb8c8cffc0ebfe494342636a6a Mon Sep 17 00:00:00 2001 From: Anders Jenbo Date: Thu, 10 Jul 2025 18:07:03 +0200 Subject: [PATCH 114/188] Maintain paletted textures (#574) --- extensions/src/textureloader.cpp | 43 +++++++++++++++++--------------- 1 file changed, 23 insertions(+), 20 deletions(-) diff --git a/extensions/src/textureloader.cpp b/extensions/src/textureloader.cpp index ee057997..dd96105e 100644 --- a/extensions/src/textureloader.cpp +++ b/extensions/src/textureloader.cpp @@ -22,13 +22,14 @@ bool TextureLoader::PatchTexture(LegoTextureInfo* p_textureInfo) desc.dwWidth = surface->w; desc.dwHeight = surface->h; desc.ddpfPixelFormat.dwFlags = DDPF_RGB | DDPF_ALPHAPIXELS; - desc.ddpfPixelFormat.dwRGBBitCount = details->bits_per_pixel == 8 ? 24 : details->bits_per_pixel; + desc.ddpfPixelFormat.dwRGBBitCount = details->bits_per_pixel; desc.ddpfPixelFormat.dwRBitMask = details->Rmask; desc.ddpfPixelFormat.dwGBitMask = details->Gmask; desc.ddpfPixelFormat.dwBBitMask = details->Bmask; desc.ddpfPixelFormat.dwRGBAlphaBitMask = details->Amask; - if (VideoManager()->GetDirect3D()->DirectDraw()->CreateSurface(&desc, &p_textureInfo->m_surface, NULL) != DD_OK) { + LPDIRECTDRAW pDirectDraw = VideoManager()->GetDirect3D()->DirectDraw(); + if (pDirectDraw->CreateSurface(&desc, &p_textureInfo->m_surface, NULL) != DD_OK) { SDL_DestroySurface(surface); return false; } @@ -45,29 +46,31 @@ bool TextureLoader::PatchTexture(LegoTextureInfo* p_textureInfo) Uint8* srcPixels = (Uint8*) surface->pixels; if (details->bits_per_pixel == 8) { - SDL_Palette* palette = SDL_GetSurfacePalette(surface); - if (palette) { - for (int y = 0; y < surface->h; ++y) { - Uint8* srcRow = srcPixels + y * surface->pitch; - Uint8* dstRow = dst + y * desc.lPitch; - for (int x = 0; x < surface->w; ++x) { - SDL_Color color = palette->colors[srcRow[x]]; - dstRow[x * 3 + 0] = color.r; - dstRow[x * 3 + 1] = color.g; - dstRow[x * 3 + 2] = color.b; - } - } - } - else { - p_textureInfo->m_surface->Unlock(desc.lpSurface); + SDL_Palette* sdlPalette = SDL_GetSurfacePalette(surface); + if (!sdlPalette) { SDL_DestroySurface(surface); return false; } - } - else { - memcpy(dst, srcPixels, surface->pitch * surface->h); + + PALETTEENTRY entries[256]; + for (int i = 0; i < sdlPalette->ncolors; ++i) { + entries[i].peRed = sdlPalette->colors[i].r; + entries[i].peGreen = sdlPalette->colors[i].g; + entries[i].peBlue = sdlPalette->colors[i].b; + entries[i].peFlags = PC_NONE; + } + + LPDIRECTDRAWPALETTE ddPalette = nullptr; + if (pDirectDraw->CreatePalette(DDPCAPS_8BIT | DDPCAPS_ALLOW256, entries, &ddPalette, NULL) != DD_OK) { + SDL_DestroySurface(surface); + return false; + } + + p_textureInfo->m_surface->SetPalette(ddPalette); + ddPalette->Release(); } + memcpy(dst, srcPixels, surface->pitch * surface->h); p_textureInfo->m_surface->Unlock(desc.lpSurface); p_textureInfo->m_palette = NULL; From b26a707db286868b558922966db77335e8ef3b3d Mon Sep 17 00:00:00 2001 From: olebeck <31539311+olebeck@users.noreply.github.com> Date: Thu, 10 Jul 2025 18:19:21 +0200 Subject: [PATCH 115/188] put ifdefs around every d3drm backend so that any can be disabled (#569) * put ifdefs around every d3drm backend so that any can be disabled * make clang-format happy * move backend selection and enum to its own functions --- miniwin/CMakeLists.txt | 74 +++++++++++++++++---------- miniwin/src/d3drm/d3drm.cpp | 43 +--------------- miniwin/src/d3drm/d3drmrenderer.cpp | 76 ++++++++++++++++++++++++++++ miniwin/src/ddraw/ddraw.cpp | 61 ++-------------------- miniwin/src/internal/d3drmrenderer.h | 3 ++ 5 files changed, 133 insertions(+), 124 deletions(-) create mode 100644 miniwin/src/d3drm/d3drmrenderer.cpp diff --git a/miniwin/CMakeLists.txt b/miniwin/CMakeLists.txt index 1ed5e909..3fe96ea3 100644 --- a/miniwin/CMakeLists.txt +++ b/miniwin/CMakeLists.txt @@ -19,42 +19,44 @@ add_library(miniwin STATIC EXCLUDE_FROM_ALL src/d3drm/d3drmmesh.cpp src/d3drm/d3drmtexture.cpp src/d3drm/d3drmviewport.cpp + src/d3drm/d3drmrenderer.cpp src/internal/meshutils.cpp - - # D3DRM backends - src/d3drm/backends/sdl3gpu/renderer.cpp - src/d3drm/backends/sdl3gpu/shaders/generated/ShaderIndex.cpp - src/d3drm/backends/software/renderer.cpp ) target_compile_definitions(miniwin PRIVATE $<$:DEBUG> ) -find_package(OpenGL) -if(OpenGL_FOUND AND NOT WINDOWS_STORE) - message(STATUS "Found OpenGL: enabling OpenGL 1.x renderer") - target_sources(miniwin PRIVATE - src/d3drm/backends/opengl1/actual.cpp - src/d3drm/backends/opengl1/renderer.cpp - ) - target_compile_definitions(miniwin PRIVATE USE_OPENGL1) - target_link_libraries(miniwin PRIVATE OpenGL::GL) -else() - message(STATUS "🧩 OpenGL 1.x support not enabled — needs OpenGL") +list(APPEND GRAPHICS_BACKENDS USE_SOFTWARE_RENDER) +list(APPEND GRAPHICS_BACKENDS USE_SDL_GPU) + +if(NOT WINDOWS_STORE) + find_package(OpenGL) + if(OpenGL_FOUND) + message(STATUS "Found OpenGL: enabling OpenGL 1.x renderer") + target_sources(miniwin PRIVATE + src/d3drm/backends/opengl1/actual.cpp + src/d3drm/backends/opengl1/renderer.cpp + ) + list(APPEND GRAPHICS_BACKENDS USE_OPENGL1) + target_link_libraries(miniwin PRIVATE OpenGL::GL) + else() + message(STATUS "🧩 OpenGL 1.x support not enabled — needs OpenGL") + endif() + + find_library(OPENGL_ES2_LIBRARY NAMES GLESv2) + if(EMSCRIPTEN OR OPENGL_ES2_LIBRARY) + message(STATUS "Found OpenGL: enabling OpenGL ES 2.x renderer") + target_sources(miniwin PRIVATE src/d3drm/backends/opengles2/renderer.cpp) + list(APPEND GRAPHICS_BACKENDS USE_OPENGLES2) + if(OPENGL_ES2_LIBRARY) + target_link_libraries(miniwin PRIVATE ${OPENGL_ES2_LIBRARY}) + endif() + else() + message(STATUS "🧩 OpenGL ES 2.x support not enabled") + endif() endif() -find_library(OPENGL_ES2_LIBRARY NAMES GLESv2) -if(EMSCRIPTEN OR OPENGL_ES2_LIBRARY AND NOT WINDOWS_STORE) - message(STATUS "Found OpenGL: enabling OpenGL ES 2.x renderer") - target_sources(miniwin PRIVATE src/d3drm/backends/opengles2/renderer.cpp) - target_compile_definitions(miniwin PRIVATE USE_OPENGLES2) - if(OPENGL_ES2_LIBRARY) - target_link_libraries(miniwin PRIVATE OpenGL::GL) - endif() -else() - message(STATUS "🧩 OpenGL ES 2.x support not enabled") -endif() if(NINTENDO_3DS) if(ISLE_DEBUG) @@ -68,6 +70,7 @@ if(NINTENDO_3DS) ctr_add_shader_library(vshader src/d3drm/backends/citro3d/vshader.v.pica) dkp_add_embedded_binary_library(3ds_shaders vshader) target_link_libraries(miniwin PRIVATE ${CITRO3D_LIBRARY} 3ds_shaders) + list(APPEND GRAPHICS_BACKENDS USE_CITRO3D) else() message(STATUS "🧩 Citro3D support not enabled") endif() @@ -79,12 +82,26 @@ if(WIN32 AND NOT WINDOWS_STORE) src/d3drm/backends/directx9/renderer.cpp ) target_link_libraries(miniwin PRIVATE d3d9) + list(APPEND GRAPHICS_BACKENDS USE_DIRECTX9) endif() if(WINDOWS_STORE) add_compile_definitions(WINDOWS_STORE) endif() +if(USE_SDL_GPU IN_LIST GRAPHICS_BACKENDS) + target_sources(miniwin PRIVATE + src/d3drm/backends/sdl3gpu/renderer.cpp + src/d3drm/backends/sdl3gpu/shaders/generated/ShaderIndex.cpp + ) +endif() + +if(USE_SOFTWARE_RENDER IN_LIST GRAPHICS_BACKENDS) + target_sources(miniwin PRIVATE + src/d3drm/backends/software/renderer.cpp + ) +endif() + target_compile_definitions(miniwin PUBLIC MINIWIN) target_include_directories(miniwin @@ -96,6 +113,9 @@ target_link_libraries(miniwin PUBLIC miniwin-headers) target_link_libraries(miniwin PRIVATE SDL3::SDL3) +target_compile_definitions(miniwin PUBLIC ${GRAPHICS_BACKENDS}) + + # Shader stuff set(shader_src_dir "${CMAKE_CURRENT_SOURCE_DIR}/src/d3drm/backends/sdl3gpu/shaders/src") diff --git a/miniwin/src/d3drm/d3drm.cpp b/miniwin/src/d3drm/d3drm.cpp index 1088a943..7328d21c 100644 --- a/miniwin/src/d3drm/d3drm.cpp +++ b/miniwin/src/d3drm/d3drm.cpp @@ -7,20 +7,6 @@ #include "d3drmmesh_impl.h" #include "d3drmobject_impl.h" #include "d3drmrenderer.h" -#ifdef USE_OPENGL1 -#include "d3drmrenderer_opengl1.h" -#endif -#ifdef USE_OPENGLES2 -#include "d3drmrenderer_opengles2.h" -#endif -#ifdef __3DS__ -#include "d3drmrenderer_citro3d.h" -#endif -#ifdef _WIN32 -#include "d3drmrenderer_directx9.h" -#endif -#include "d3drmrenderer_sdl3gpu.h" -#include "d3drmrenderer_software.h" #include "d3drmtexture_impl.h" #include "d3drmviewport_impl.h" #include "ddraw_impl.h" @@ -146,33 +132,8 @@ HRESULT Direct3DRMImpl::CreateDeviceFromSurface( DDSDesc.dwSize = sizeof(DDSURFACEDESC); surface->GetSurfaceDesc(&DDSDesc); - if (SDL_memcmp(&guid, &SDL3_GPU_GUID, sizeof(GUID)) == 0) { - DDRenderer = Direct3DRMSDL3GPURenderer::Create(DDSDesc.dwWidth, DDSDesc.dwHeight); - } - else if (SDL_memcmp(&guid, &SOFTWARE_GUID, sizeof(GUID)) == 0) { - DDRenderer = new Direct3DRMSoftwareRenderer(DDSDesc.dwWidth, DDSDesc.dwHeight); - } -#ifdef USE_OPENGLES2 - else if (SDL_memcmp(&guid, &OpenGLES2_GUID, sizeof(GUID)) == 0) { - DDRenderer = OpenGLES2Renderer::Create(DDSDesc.dwWidth, DDSDesc.dwHeight); - } -#endif -#ifdef USE_OPENGL1 - else if (SDL_memcmp(&guid, &OpenGL1_GUID, sizeof(GUID)) == 0) { - DDRenderer = OpenGL1Renderer::Create(DDSDesc.dwWidth, DDSDesc.dwHeight); - } -#endif -#ifdef __3DS__ - else if (SDL_memcmp(&guid, &Citro3D_GUID, sizeof(GUID)) == 0) { - DDRenderer = new Citro3DRenderer(DDSDesc.dwWidth, DDSDesc.dwHeight); - } -#endif -#if defined(_WIN32) && !defined(WINDOWS_STORE) - else if (SDL_memcmp(&guid, &DirectX9_GUID, sizeof(GUID)) == 0) { - DDRenderer = DirectX9Renderer::Create(DDSDesc.dwWidth, DDSDesc.dwHeight); - } -#endif - else { + DDRenderer = CreateDirect3DRMRenderer(DDSDesc, guid); + if (!DDRenderer) { SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Device GUID not recognized"); return E_NOINTERFACE; } diff --git a/miniwin/src/d3drm/d3drmrenderer.cpp b/miniwin/src/d3drm/d3drmrenderer.cpp new file mode 100644 index 00000000..ca9b5541 --- /dev/null +++ b/miniwin/src/d3drm/d3drmrenderer.cpp @@ -0,0 +1,76 @@ +#include "d3drmrenderer.h" +#ifdef USE_OPENGL1 +#include "d3drmrenderer_opengl1.h" +#endif +#ifdef USE_OPENGLES2 +#include "d3drmrenderer_opengles2.h" +#endif +#ifdef USE_CITRO3D +#include "d3drmrenderer_citro3d.h" +#endif +#ifdef USE_DIRECTX9 +#include "d3drmrenderer_directx9.h" +#endif +#ifdef USE_SDL_GPU +#include "d3drmrenderer_sdl3gpu.h" +#endif +#ifdef USE_SOFTWARE_RENDER +#include "d3drmrenderer_software.h" +#endif + +Direct3DRMRenderer* CreateDirect3DRMRenderer(const DDSURFACEDESC& DDSDesc, const GUID* guid) +{ +#ifdef USE_SDL_GPU + if (SDL_memcmp(guid, &SDL3_GPU_GUID, sizeof(GUID)) == 0) { + return Direct3DRMSDL3GPURenderer::Create(DDSDesc.dwWidth, DDSDesc.dwHeight); + } +#endif +#ifdef USE_SOFTWARE_RENDER + if (SDL_memcmp(guid, &SOFTWARE_GUID, sizeof(GUID)) == 0) { + return new Direct3DRMSoftwareRenderer(DDSDesc.dwWidth, DDSDesc.dwHeight); + } +#endif +#ifdef USE_OPENGLES2 + if (SDL_memcmp(guid, &OpenGLES2_GUID, sizeof(GUID)) == 0) { + return OpenGLES2Renderer::Create(DDSDesc.dwWidth, DDSDesc.dwHeight); + } +#endif +#ifdef USE_OPENGL1 + if (SDL_memcmp(guid, &OpenGL1_GUID, sizeof(GUID)) == 0) { + return OpenGL1Renderer::Create(DDSDesc.dwWidth, DDSDesc.dwHeight); + } +#endif +#ifdef USE_CITRO3D + if (SDL_memcmp(guid, &Citro3D_GUID, sizeof(GUID)) == 0) { + return new Citro3DRenderer(DDSDesc.dwWidth, DDSDesc.dwHeight); + } +#endif +#ifdef USE_DIRECTX9 + if (SDL_memcmp(guid, &DirectX9_GUID, sizeof(GUID)) == 0) { + return DirectX9Renderer::Create(DDSDesc.dwWidth, DDSDesc.dwHeight); + } +#endif + return nullptr; +} + +void Direct3DRMRenderer_EnumDevices(LPD3DENUMDEVICESCALLBACK cb, void* ctx) +{ +#ifdef USE_SDL_GPU + Direct3DRMSDL3GPU_EnumDevice(cb, ctx); +#endif +#ifdef USE_OPENGLES2 + OpenGLES2Renderer_EnumDevice(cb, ctx); +#endif +#ifdef USE_OPENGL1 + OpenGL1Renderer_EnumDevice(cb, ctx); +#endif +#ifdef USE_CITRO3D + Citro3DRenderer_EnumDevice(cb, ctx); +#endif +#ifdef USE_DIRECTX9 + DirectX9Renderer_EnumDevice(cb, ctx); +#endif +#ifdef USE_SOFTWARE_RENDER + Direct3DRMSoftware_EnumDevice(cb, ctx); +#endif +} diff --git a/miniwin/src/ddraw/ddraw.cpp b/miniwin/src/ddraw/ddraw.cpp index d2ce8743..264ceeb1 100644 --- a/miniwin/src/ddraw/ddraw.cpp +++ b/miniwin/src/ddraw/ddraw.cpp @@ -1,17 +1,5 @@ -#ifdef USE_OPENGL1 -#include "d3drmrenderer_opengl1.h" -#endif -#ifdef USE_OPENGLES2 -#include "d3drmrenderer_opengles2.h" -#endif -#ifdef __3DS__ -#include "d3drmrenderer_citro3d.h" -#endif -#if defined(_WIN32) && !defined(WINDOWS_STORE) -#include "d3drmrenderer_directx9.h" -#endif -#include "d3drmrenderer_sdl3gpu.h" -#include "d3drmrenderer_software.h" + +#include "d3drmrenderer.h" #include "ddpalette_impl.h" #include "ddraw_impl.h" #include "ddsurface_impl.h" @@ -232,21 +220,7 @@ void EnumDevice( HRESULT DirectDrawImpl::EnumDevices(LPD3DENUMDEVICESCALLBACK cb, void* ctx) { - Direct3DRMSDL3GPU_EnumDevice(cb, ctx); -#ifdef USE_OPENGLES2 - OpenGLES2Renderer_EnumDevice(cb, ctx); -#endif -#ifdef USE_OPENGL1 - OpenGL1Renderer_EnumDevice(cb, ctx); -#endif -#ifdef __3DS__ - Citro3DRenderer_EnumDevice(cb, ctx); -#endif -#if defined(_WIN32) && !defined(WINDOWS_STORE) - DirectX9Renderer_EnumDevice(cb, ctx); -#endif - Direct3DRMSoftware_EnumDevice(cb, ctx); - + Direct3DRMRenderer_EnumDevices(cb, ctx); return S_OK; } @@ -343,33 +317,8 @@ HRESULT DirectDrawImpl::CreateDevice( DDSDesc.dwSize = sizeof(DDSURFACEDESC); pBackBuffer->GetSurfaceDesc(&DDSDesc); - if (SDL_memcmp(&guid, &SDL3_GPU_GUID, sizeof(GUID)) == 0) { - DDRenderer = Direct3DRMSDL3GPURenderer::Create(DDSDesc.dwWidth, DDSDesc.dwHeight); - } -#ifdef USE_OPENGLES2 - else if (SDL_memcmp(&guid, &OpenGLES2_GUID, sizeof(GUID)) == 0) { - DDRenderer = OpenGLES2Renderer::Create(DDSDesc.dwWidth, DDSDesc.dwHeight); - } -#endif -#ifdef USE_OPENGL1 - else if (SDL_memcmp(&guid, &OpenGL1_GUID, sizeof(GUID)) == 0) { - DDRenderer = OpenGL1Renderer::Create(DDSDesc.dwWidth, DDSDesc.dwHeight); - } -#endif -#ifdef __3DS__ - else if (SDL_memcmp(&guid, &Citro3D_GUID, sizeof(GUID)) == 0) { - DDRenderer = new Citro3DRenderer(DDSDesc.dwWidth, DDSDesc.dwHeight); - } -#endif -#if defined(_WIN32) && !defined(WINDOWS_STORE) - else if (SDL_memcmp(&guid, &DirectX9_GUID, sizeof(GUID)) == 0) { - DDRenderer = DirectX9Renderer::Create(DDSDesc.dwWidth, DDSDesc.dwHeight); - } -#endif - else if (SDL_memcmp(&guid, &SOFTWARE_GUID, sizeof(GUID)) == 0) { - DDRenderer = new Direct3DRMSoftwareRenderer(DDSDesc.dwWidth, DDSDesc.dwHeight); - } - else { + DDRenderer = CreateDirect3DRMRenderer(DDSDesc, &guid); + if (!DDRenderer) { SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Device GUID not recognized"); return E_NOINTERFACE; } diff --git a/miniwin/src/internal/d3drmrenderer.h b/miniwin/src/internal/d3drmrenderer.h index 7cfe14b2..85b2d1b9 100644 --- a/miniwin/src/internal/d3drmrenderer.h +++ b/miniwin/src/internal/d3drmrenderer.h @@ -60,3 +60,6 @@ class Direct3DRMRenderer : public IDirect3DDevice2 { int m_virtualWidth, m_virtualHeight; ViewportTransform m_viewportTransform; }; + +Direct3DRMRenderer* CreateDirect3DRMRenderer(const DDSURFACEDESC& DDSDesc, const GUID* guid); +void Direct3DRMRenderer_EnumDevices(LPD3DENUMDEVICESCALLBACK cb, void* ctx); From 6497cb42b7b6873584a2a272077b8e1f62889716 Mon Sep 17 00:00:00 2001 From: Fabian Neundorf Date: Fri, 11 Jul 2025 03:58:53 +0200 Subject: [PATCH 116/188] Clear unknowns in `Matrix` (#1615) --- .../legoomni/src/video/legoanimpresenter.cpp | 6 +- LEGO1/realtime/matrix.h | 2 +- LEGO1/realtime/matrix4d.inl.h | 60 +++++++++---------- 3 files changed, 34 insertions(+), 34 deletions(-) diff --git a/LEGO1/lego/legoomni/src/video/legoanimpresenter.cpp b/LEGO1/lego/legoomni/src/video/legoanimpresenter.cpp index 82c76aa6..033525f4 100644 --- a/LEGO1/lego/legoomni/src/video/legoanimpresenter.cpp +++ b/LEGO1/lego/legoomni/src/video/legoanimpresenter.cpp @@ -707,7 +707,7 @@ MxResult LegoAnimPresenter::FUN_1006b140(LegoROI* p_roi) Matrix4* mn = new MxMatrix(); assert(mn); - MxMatrix local58; + MxMatrix inverse; const Matrix4& local2world = p_roi->GetLocal2World(); MxMatrix* local5c; MxU32 i; @@ -718,7 +718,7 @@ MxResult LegoAnimPresenter::FUN_1006b140(LegoROI* p_roi) for (i = 1; i <= m_roiMapSize; i++) { if (m_roiMap[i] == p_roi) { - if (local5c[i].BETA_1005a590(local58) != SUCCESS) { + if (local5c[i].Invert(inverse) != SUCCESS) { goto done; } @@ -727,7 +727,7 @@ MxResult LegoAnimPresenter::FUN_1006b140(LegoROI* p_roi) } { - mn->Product(local58, local2world); + mn->Product(inverse, local2world); SetUnknown0xa0(mn); delete[] local5c; SetUnknown0x0cTo1(); diff --git a/LEGO1/realtime/matrix.h b/LEGO1/realtime/matrix.h index bbadcdf2..c799d88f 100644 --- a/LEGO1/realtime/matrix.h +++ b/LEGO1/realtime/matrix.h @@ -45,7 +45,7 @@ class Matrix4 { inline void RotateX(const float& p_angle); inline void RotateY(const float& p_angle); inline void RotateZ(const float& p_angle); - inline int BETA_1005a590(Matrix4& p_mat); + inline int Invert(Matrix4& p_mat); inline void Swap(int p_d1, int p_d2); // FUNCTION: BETA10 0x1001c670 diff --git a/LEGO1/realtime/matrix4d.inl.h b/LEGO1/realtime/matrix4d.inl.h index b8bba5a3..b15e6135 100644 --- a/LEGO1/realtime/matrix4d.inl.h +++ b/LEGO1/realtime/matrix4d.inl.h @@ -288,62 +288,62 @@ void Matrix4::RotateZ(const float& p_angle) } // FUNCTION: BETA10 0x1005a590 -int Matrix4::BETA_1005a590(Matrix4& p_mat) +int Matrix4::Invert(Matrix4& p_mat) { - float local5c[4][4]; - Matrix4 localc(local5c); - localc = *this; + float copyData[4][4]; + Matrix4 copy(copyData); + copy = *this; p_mat.SetIdentity(); for (int i = 0; i < 4; i++) { - int local1c = i; - int local10; + int pivotColumn = i; + int column; - for (local10 = i + 1; local10 < 4; local10++) { - if (fabs(localc[local1c][i]) < fabs(localc[local10][i])) { - local1c = local10; + for (column = i + 1; column < 4; column++) { + if (fabs(copy[pivotColumn][i]) < fabs(copy[column][i])) { + pivotColumn = column; } } - if (local1c != i) { - localc.Swap(local1c, i); - p_mat.Swap(local1c, i); + if (pivotColumn != i) { + copy.Swap(pivotColumn, i); + p_mat.Swap(pivotColumn, i); } - if (localc[i][i] < 0.001f && localc[i][i] > -0.001f) { + if (copy[i][i] < 0.001f && copy[i][i] > -0.001f) { return -1; } - float local60 = localc[i][i]; - int local18; + float pivotValue = copy[i][i]; + int k; - for (local18 = 0; local18 < 4; local18++) { - p_mat[i][local18] /= local60; + for (k = 0; k < 4; k++) { + p_mat[i][k] /= pivotValue; } - for (local18 = 0; local18 < 4; local18++) { - localc[i][local18] /= local60; + for (k = 0; k < 4; k++) { + copy[i][k] /= pivotValue; } - for (local10 = 0; local10 < 4; local10++) { - if (i != local10) { - float afStack70[4]; + for (column = 0; column < 4; column++) { + if (i != column) { + float tempColumn[4]; - for (local18 = 0; local18 < 4; local18++) { - afStack70[local18] = p_mat[i][local18] * localc[local10][i]; + for (k = 0; k < 4; k++) { + tempColumn[k] = p_mat[i][k] * copy[column][i]; } - for (local18 = 0; local18 < 4; local18++) { - p_mat[local10][local18] -= afStack70[local18]; + for (k = 0; k < 4; k++) { + p_mat[column][k] -= tempColumn[k]; } - for (local18 = 0; local18 < 4; local18++) { - afStack70[local18] = localc[i][local18] * localc[local10][i]; + for (k = 0; k < 4; k++) { + tempColumn[k] = copy[i][k] * copy[column][i]; } - for (local18 = 0; local18 < 4; local18++) { - localc[local10][local18] -= afStack70[local18]; + for (k = 0; k < 4; k++) { + copy[column][k] -= tempColumn[k]; } } } From 0e95e6d521c6d2a024c584245138a039869c3ac1 Mon Sep 17 00:00:00 2001 From: Christian Semmler Date: Fri, 11 Jul 2025 10:45:10 -0700 Subject: [PATCH 117/188] Fix ConvertEventToRenderCoordinates for mouse button events (#582) --- miniwin/src/d3drm/d3drmdevice.cpp | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/miniwin/src/d3drm/d3drmdevice.cpp b/miniwin/src/d3drm/d3drmdevice.cpp index 987efb6e..25703f86 100644 --- a/miniwin/src/d3drm/d3drmdevice.cpp +++ b/miniwin/src/d3drm/d3drmdevice.cpp @@ -172,9 +172,7 @@ bool Direct3DRMDevice2Impl::ConvertEventToRenderCoordinates(SDL_Event* event) Resize(); break; } - case SDL_EVENT_MOUSE_MOTION: - case SDL_EVENT_MOUSE_BUTTON_DOWN: - case SDL_EVENT_MOUSE_BUTTON_UP: { + case SDL_EVENT_MOUSE_MOTION: { int rawX = event->motion.x; int rawY = event->motion.y; float x = (rawX - m_viewportTransform.offsetX) / m_viewportTransform.scale; @@ -183,6 +181,16 @@ bool Direct3DRMDevice2Impl::ConvertEventToRenderCoordinates(SDL_Event* event) event->motion.y = static_cast(y); break; } + case SDL_EVENT_MOUSE_BUTTON_DOWN: + case SDL_EVENT_MOUSE_BUTTON_UP: { + int rawX = event->button.x; + int rawY = event->button.y; + float x = (rawX - m_viewportTransform.offsetX) / m_viewportTransform.scale; + float y = (rawY - m_viewportTransform.offsetY) / m_viewportTransform.scale; + event->button.x = static_cast(x); + event->button.y = static_cast(y); + break; + } case SDL_EVENT_FINGER_MOTION: case SDL_EVENT_FINGER_DOWN: case SDL_EVENT_FINGER_UP: { From a34c293177c3326dc0d3fd22ffab0ac9263ac91d Mon Sep 17 00:00:00 2001 From: Fabian Neundorf Date: Sat, 12 Jul 2025 02:10:36 +0200 Subject: [PATCH 118/188] Name Disable function in `LegoOmni` and `misc` (#1616) --- LEGO1/lego/legoomni/include/legomain.h | 2 +- LEGO1/lego/legoomni/include/misc.h | 2 +- LEGO1/lego/legoomni/src/actors/ambulance.cpp | 2 +- LEGO1/lego/legoomni/src/actors/bike.cpp | 2 +- LEGO1/lego/legoomni/src/actors/dunebuggy.cpp | 2 +- LEGO1/lego/legoomni/src/actors/helicopter.cpp | 2 +- LEGO1/lego/legoomni/src/actors/jetski.cpp | 2 +- LEGO1/lego/legoomni/src/actors/motorcycle.cpp | 2 +- LEGO1/lego/legoomni/src/actors/skateboard.cpp | 2 +- LEGO1/lego/legoomni/src/actors/towtrack.cpp | 2 +- .../lego/legoomni/src/build/legocarbuild.cpp | 2 +- .../legoomni/src/common/legogamestate.cpp | 2 +- LEGO1/lego/legoomni/src/common/misc.cpp | 4 ++-- .../src/entity/legoworldpresenter.cpp | 2 +- LEGO1/lego/legoomni/src/main/legomain.cpp | 2 +- LEGO1/lego/legoomni/src/race/carrace.cpp | 2 +- LEGO1/lego/legoomni/src/race/jetskirace.cpp | 2 +- LEGO1/lego/legoomni/src/worlds/act3.cpp | 8 ++++---- .../legoomni/src/worlds/elevatorbottom.cpp | 2 +- LEGO1/lego/legoomni/src/worlds/gasstation.cpp | 2 +- LEGO1/lego/legoomni/src/worlds/hospital.cpp | 2 +- LEGO1/lego/legoomni/src/worlds/infocenter.cpp | 20 +++++++++---------- .../legoomni/src/worlds/infocenterdoor.cpp | 2 +- LEGO1/lego/legoomni/src/worlds/isle.cpp | 10 +++++----- LEGO1/lego/legoomni/src/worlds/jukebox.cpp | 2 +- LEGO1/lego/legoomni/src/worlds/legoact2.cpp | 6 +++--- LEGO1/lego/legoomni/src/worlds/police.cpp | 2 +- LEGO1/lego/legoomni/src/worlds/score.cpp | 2 +- 28 files changed, 47 insertions(+), 47 deletions(-) diff --git a/LEGO1/lego/legoomni/include/legomain.h b/LEGO1/lego/legoomni/include/legomain.h index 6f2b1e21..423d715c 100644 --- a/LEGO1/lego/legoomni/include/legomain.h +++ b/LEGO1/lego/legoomni/include/legomain.h @@ -129,7 +129,7 @@ class LegoOmni : public MxOmni { LegoROI* FindROI(const char* p_name); void AddWorld(LegoWorld* p_world); void DeleteWorld(LegoWorld* p_world); - void FUN_1005b4f0(MxBool p_disable, MxU16 p_flags); + void Disable(MxBool p_disable, MxU16 p_flags); void CreateBackgroundAudio(); void RemoveWorld(const MxAtomId& p_atom, MxLong p_objectId); MxResult RegisterWorlds(); diff --git a/LEGO1/lego/legoomni/include/misc.h b/LEGO1/lego/legoomni/include/misc.h index 374b83f2..f9a26e8b 100644 --- a/LEGO1/lego/legoomni/include/misc.h +++ b/LEGO1/lego/legoomni/include/misc.h @@ -48,7 +48,7 @@ LegoPlantManager* PlantManager(); LegoBuildingManager* BuildingManager(); LegoTextureContainer* TextureContainer(); ViewLODListManager* GetViewLODListManager(); -void FUN_10015820(MxBool p_disable, MxU16 p_flags); +void Disable(MxBool p_disable, MxU16 p_flags); LegoROI* FindROI(const char* p_name); void SetROIVisible(const char* p_name, MxBool p_visible); void SetUserActor(LegoPathActor* p_userActor); diff --git a/LEGO1/lego/legoomni/src/actors/ambulance.cpp b/LEGO1/lego/legoomni/src/actors/ambulance.cpp index 95ca6975..b439db66 100644 --- a/LEGO1/lego/legoomni/src/actors/ambulance.cpp +++ b/LEGO1/lego/legoomni/src/actors/ambulance.cpp @@ -372,7 +372,7 @@ MxLong Ambulance::HandleClick() return 1; } - FUN_10015820(TRUE, 0); + Disable(TRUE, 0); ((Isle*) CurrentWorld())->SetDestLocation(LegoGameState::e_ambulance); TransitionManager()->StartTransition(MxTransitionManager::e_mosaic, 50, FALSE, FALSE); diff --git a/LEGO1/lego/legoomni/src/actors/bike.cpp b/LEGO1/lego/legoomni/src/actors/bike.cpp index 6c82cb0d..fa9bedcb 100644 --- a/LEGO1/lego/legoomni/src/actors/bike.cpp +++ b/LEGO1/lego/legoomni/src/actors/bike.cpp @@ -54,7 +54,7 @@ MxLong Bike::HandleClick() { if (CanExit()) { Act1State* state = (Act1State*) GameState()->GetState("Act1State"); - FUN_10015820(TRUE, 0); + Disable(TRUE, 0); ((Isle*) CurrentWorld())->SetDestLocation(LegoGameState::Area::e_bike); TransitionManager()->StartTransition(MxTransitionManager::e_mosaic, 50, FALSE, TRUE); diff --git a/LEGO1/lego/legoomni/src/actors/dunebuggy.cpp b/LEGO1/lego/legoomni/src/actors/dunebuggy.cpp index e9ec7817..562edfd9 100644 --- a/LEGO1/lego/legoomni/src/actors/dunebuggy.cpp +++ b/LEGO1/lego/legoomni/src/actors/dunebuggy.cpp @@ -92,7 +92,7 @@ MxLong DuneBuggy::HandleClick() return 1; } - FUN_10015820(TRUE, 0); + Disable(TRUE, 0); ((Isle*) CurrentWorld())->SetDestLocation(LegoGameState::Area::e_dunecar); TransitionManager()->StartTransition(MxTransitionManager::e_mosaic, 50, FALSE, TRUE); diff --git a/LEGO1/lego/legoomni/src/actors/helicopter.cpp b/LEGO1/lego/legoomni/src/actors/helicopter.cpp index c8c465f7..622fe71e 100644 --- a/LEGO1/lego/legoomni/src/actors/helicopter.cpp +++ b/LEGO1/lego/legoomni/src/actors/helicopter.cpp @@ -148,7 +148,7 @@ MxLong Helicopter::HandleClick() IslePathActor::c_spawnBit1 | IslePathActor::c_playMusic | IslePathActor::c_spawnBit3 ); ((Isle*) CurrentWorld())->SetDestLocation(LegoGameState::e_copter); - FUN_10015820(TRUE, 0); + Disable(TRUE, 0); TransitionManager()->StartTransition(MxTransitionManager::e_mosaic, 50, FALSE, TRUE); SetActorState(c_disabled); PlayMusic(JukeboxScript::c_Jail_Music); diff --git a/LEGO1/lego/legoomni/src/actors/jetski.cpp b/LEGO1/lego/legoomni/src/actors/jetski.cpp index 11ff7d18..caf3d1f8 100644 --- a/LEGO1/lego/legoomni/src/actors/jetski.cpp +++ b/LEGO1/lego/legoomni/src/actors/jetski.cpp @@ -85,7 +85,7 @@ MxLong Jetski::HandleClick() return 1; } - FUN_10015820(TRUE, 0); + Disable(TRUE, 0); ((Isle*) CurrentWorld())->SetDestLocation(LegoGameState::Area::e_jetski); TransitionManager()->StartTransition(MxTransitionManager::e_mosaic, 50, FALSE, TRUE); diff --git a/LEGO1/lego/legoomni/src/actors/motorcycle.cpp b/LEGO1/lego/legoomni/src/actors/motorcycle.cpp index 5819598e..99810420 100644 --- a/LEGO1/lego/legoomni/src/actors/motorcycle.cpp +++ b/LEGO1/lego/legoomni/src/actors/motorcycle.cpp @@ -88,7 +88,7 @@ MxLong Motocycle::HandleClick() return 1; } - FUN_10015820(TRUE, 0); + Disable(TRUE, 0); ((Isle*) CurrentWorld())->SetDestLocation(LegoGameState::Area::e_motocycle); TransitionManager()->StartTransition(MxTransitionManager::e_mosaic, 50, FALSE, TRUE); diff --git a/LEGO1/lego/legoomni/src/actors/skateboard.cpp b/LEGO1/lego/legoomni/src/actors/skateboard.cpp index 0998ba58..12524111 100644 --- a/LEGO1/lego/legoomni/src/actors/skateboard.cpp +++ b/LEGO1/lego/legoomni/src/actors/skateboard.cpp @@ -79,7 +79,7 @@ MxLong SkateBoard::HandleClick() return 1; } - FUN_10015820(TRUE, 0); + Disable(TRUE, 0); ((Isle*) CurrentWorld())->SetDestLocation(LegoGameState::Area::e_skateboard); TransitionManager()->StartTransition(MxTransitionManager::e_mosaic, 50, FALSE, TRUE); diff --git a/LEGO1/lego/legoomni/src/actors/towtrack.cpp b/LEGO1/lego/legoomni/src/actors/towtrack.cpp index 7d8a8975..bb117e5f 100644 --- a/LEGO1/lego/legoomni/src/actors/towtrack.cpp +++ b/LEGO1/lego/legoomni/src/actors/towtrack.cpp @@ -411,7 +411,7 @@ MxLong TowTrack::HandleClick() return 1; } - FUN_10015820(TRUE, 0); + Disable(TRUE, 0); ((Isle*) CurrentWorld())->SetDestLocation(LegoGameState::e_towtrack); TransitionManager()->StartTransition(MxTransitionManager::e_mosaic, 50, FALSE, FALSE); diff --git a/LEGO1/lego/legoomni/src/build/legocarbuild.cpp b/LEGO1/lego/legoomni/src/build/legocarbuild.cpp index 5f99393e..8809cd5c 100644 --- a/LEGO1/lego/legoomni/src/build/legocarbuild.cpp +++ b/LEGO1/lego/legoomni/src/build/legocarbuild.cpp @@ -1253,7 +1253,7 @@ void LegoCarBuild::FUN_10024ef0() m_buildState->m_animationState = LegoVehicleBuildState::e_cutscene; FUN_10025720(FUN_10025d70()); m_buildState->m_unk0x4c += 1; - FUN_10015820(FALSE, LegoOmni::c_disableInput | LegoOmni::c_disable3d | LegoOmni::c_clearScreen); + Disable(FALSE, LegoOmni::c_disableInput | LegoOmni::c_disable3d | LegoOmni::c_clearScreen); } // FUNCTION: LEGO1 0x10024f30 diff --git a/LEGO1/lego/legoomni/src/common/legogamestate.cpp b/LEGO1/lego/legoomni/src/common/legogamestate.cpp index 1e53cb4f..3dbe88bc 100644 --- a/LEGO1/lego/legoomni/src/common/legogamestate.cpp +++ b/LEGO1/lego/legoomni/src/common/legogamestate.cpp @@ -851,7 +851,7 @@ void LegoGameState::SwitchArea(Area p_area) m_previousArea = m_currentArea; m_currentArea = p_area; - FUN_10015820(TRUE, LegoOmni::c_disableInput | LegoOmni::c_disable3d); + Disable(TRUE, LegoOmni::c_disableInput | LegoOmni::c_disable3d); BackgroundAudioManager()->Stop(); AnimationManager()->Suspend(); VideoManager()->SetUnk0x554(FALSE); diff --git a/LEGO1/lego/legoomni/src/common/misc.cpp b/LEGO1/lego/legoomni/src/common/misc.cpp index 08072b7c..6b231b56 100644 --- a/LEGO1/lego/legoomni/src/common/misc.cpp +++ b/LEGO1/lego/legoomni/src/common/misc.cpp @@ -140,10 +140,10 @@ ViewLODListManager* GetViewLODListManager() // FUNCTION: LEGO1 0x10015820 // FUNCTION: BETA10 0x100e4c92 -void FUN_10015820(MxBool p_disable, MxU16 p_flags) +void Disable(MxBool p_disable, MxU16 p_flags) { assert(LegoOmni::GetInstance()); - LegoOmni::GetInstance()->FUN_1005b4f0(p_disable, p_flags); + LegoOmni::GetInstance()->Disable(p_disable, p_flags); } // FUNCTION: LEGO1 0x10015840 diff --git a/LEGO1/lego/legoomni/src/entity/legoworldpresenter.cpp b/LEGO1/lego/legoomni/src/entity/legoworldpresenter.cpp index 6bd3ca51..397a4a77 100644 --- a/LEGO1/lego/legoomni/src/entity/legoworldpresenter.cpp +++ b/LEGO1/lego/legoomni/src/entity/legoworldpresenter.cpp @@ -62,7 +62,7 @@ LegoWorldPresenter::~LegoWorldPresenter() } if (result == FALSE) { - FUN_10015820(FALSE, LegoOmni::c_disableInput | LegoOmni::c_disable3d | LegoOmni::c_clearScreen); + Disable(FALSE, LegoOmni::c_disableInput | LegoOmni::c_disable3d | LegoOmni::c_clearScreen); } if (m_entity) { diff --git a/LEGO1/lego/legoomni/src/main/legomain.cpp b/LEGO1/lego/legoomni/src/main/legomain.cpp index 632b2943..548cc427 100644 --- a/LEGO1/lego/legoomni/src/main/legomain.cpp +++ b/LEGO1/lego/legoomni/src/main/legomain.cpp @@ -518,7 +518,7 @@ LegoOmni::World LegoOmni::GetWorldId(const char* p_key) } // FUNCTION: LEGO1 0x1005b4f0 -void LegoOmni::FUN_1005b4f0(MxBool p_disable, MxU16 p_flags) +void LegoOmni::Disable(MxBool p_disable, MxU16 p_flags) { if (p_disable) { if (p_flags & c_disableInput) { diff --git a/LEGO1/lego/legoomni/src/race/carrace.cpp b/LEGO1/lego/legoomni/src/race/carrace.cpp index 0c3c3a51..fb4cc192 100644 --- a/LEGO1/lego/legoomni/src/race/carrace.cpp +++ b/LEGO1/lego/legoomni/src/race/carrace.cpp @@ -124,7 +124,7 @@ void CarRace::ReadyWorld() BackgroundAudioManager()->PlayMusic(action, 5, MxPresenter::e_repeating); AnimationManager()->Resume(); - FUN_10015820(FALSE, LegoOmni::c_disableInput | LegoOmni::c_disable3d | LegoOmni::c_clearScreen); + Disable(FALSE, LegoOmni::c_disableInput | LegoOmni::c_disable3d | LegoOmni::c_clearScreen); m_unk0x144 = g_unk0x100d5d10[rand() & 7]; diff --git a/LEGO1/lego/legoomni/src/race/jetskirace.cpp b/LEGO1/lego/legoomni/src/race/jetskirace.cpp index 1297ed36..eddb7266 100644 --- a/LEGO1/lego/legoomni/src/race/jetskirace.cpp +++ b/LEGO1/lego/legoomni/src/race/jetskirace.cpp @@ -95,7 +95,7 @@ void JetskiRace::ReadyWorld() m_unk0x12c = (MxStillPresenter*) Find("MxPresenter", "JetskiLocator3"); m_unk0x12c->SetPosition(m_unk0x130.GetLeft(), m_unk0x130.GetTop()); - FUN_10015820(FALSE, LegoOmni::c_disableInput | LegoOmni::c_disable3d | LegoOmni::c_clearScreen); + Disable(FALSE, LegoOmni::c_disableInput | LegoOmni::c_disable3d | LegoOmni::c_clearScreen); VariableTable()->SetVariable("DISTANCE", "0.036"); diff --git a/LEGO1/lego/legoomni/src/worlds/act3.cpp b/LEGO1/lego/legoomni/src/worlds/act3.cpp index 10bf69cf..ecef0c51 100644 --- a/LEGO1/lego/legoomni/src/worlds/act3.cpp +++ b/LEGO1/lego/legoomni/src/worlds/act3.cpp @@ -552,7 +552,7 @@ MxLong Act3::Notify(MxParam& p_param) if (param.GetAction() != NULL && param.GetAction()->GetAtomId() == *g_act3Script) { if (param.GetAction()->GetObjectId() == Act3Script::c_HelicopterDashboard) { MxDSAction action; - FUN_10015820(FALSE, LegoOmni::c_disableInput | LegoOmni::c_disable3d | LegoOmni::c_clearScreen); + Disable(FALSE, LegoOmni::c_disableInput | LegoOmni::c_disable3d | LegoOmni::c_clearScreen); SetAppCursor(e_cursorArrow); VideoManager()->Get3DManager()->SetFrustrum(45.0f, 0.1f, 125.0f); @@ -634,7 +634,7 @@ MxLong Act3::Notify(MxParam& p_param) assert(m_copter && m_brickster && m_cop1 && m_cop2); m_unk0x4220.FUN_100720d0(0); m_state->m_unk0x08 = 0; - FUN_10015820(TRUE, LegoOmni::c_disableInput | LegoOmni::c_disable3d | LegoOmni::c_clearScreen); + Disable(TRUE, LegoOmni::c_disableInput | LegoOmni::c_disable3d | LegoOmni::c_clearScreen); m_copter->HandleClick(); m_copter->m_state->m_unk0x08 = 1; m_copter->HandleEndAnim((LegoEndAnimNotificationParam&) param); @@ -685,7 +685,7 @@ MxResult Act3::Tickle() if (m_unk0x426c != (Act3Script::Script) 0) { if (AnimationManager()->FUN_10064ee0(m_unk0x426c)) { - FUN_10015820(FALSE, LegoOmni::c_disableInput | LegoOmni::c_disable3d | LegoOmni::c_clearScreen); + Disable(FALSE, LegoOmni::c_disableInput | LegoOmni::c_disable3d | LegoOmni::c_clearScreen); TickleManager()->UnregisterClient(this); m_unk0x426c = (Act3Script::Script) 0; } @@ -880,7 +880,7 @@ void Act3::Enable(MxBool p_enable) GameState()->StopArea(LegoGameState::e_infomain); } - FUN_10015820(FALSE, LegoOmni::c_disableInput | LegoOmni::c_disable3d | LegoOmni::c_clearScreen); + Disable(FALSE, LegoOmni::c_disableInput | LegoOmni::c_disable3d | LegoOmni::c_clearScreen); PlayMusic(JukeboxScript::c_Act3Music); GameState()->m_isDirty = TRUE; diff --git a/LEGO1/lego/legoomni/src/worlds/elevatorbottom.cpp b/LEGO1/lego/legoomni/src/worlds/elevatorbottom.cpp index 7affe1c8..2a9c0772 100644 --- a/LEGO1/lego/legoomni/src/worlds/elevatorbottom.cpp +++ b/LEGO1/lego/legoomni/src/worlds/elevatorbottom.cpp @@ -79,7 +79,7 @@ void ElevatorBottom::ReadyWorld() { LegoWorld::ReadyWorld(); PlayMusic(JukeboxScript::c_InformationCenter_Music); - FUN_10015820(FALSE, LegoOmni::c_disableInput | LegoOmni::c_disable3d | LegoOmni::c_clearScreen); + Disable(FALSE, LegoOmni::c_disableInput | LegoOmni::c_disable3d | LegoOmni::c_clearScreen); } // FUNCTION: LEGO1 0x100181d0 diff --git a/LEGO1/lego/legoomni/src/worlds/gasstation.cpp b/LEGO1/lego/legoomni/src/worlds/gasstation.cpp index 024683b3..40e757c7 100644 --- a/LEGO1/lego/legoomni/src/worlds/gasstation.cpp +++ b/LEGO1/lego/legoomni/src/worlds/gasstation.cpp @@ -265,7 +265,7 @@ void GasStation::ReadyWorld() break; } - FUN_10015820(FALSE, LegoOmni::c_disableInput | LegoOmni::c_disable3d | LegoOmni::c_clearScreen); + Disable(FALSE, LegoOmni::c_disableInput | LegoOmni::c_disable3d | LegoOmni::c_clearScreen); } // FUNCTION: LEGO1 0x10005590 diff --git a/LEGO1/lego/legoomni/src/worlds/hospital.cpp b/LEGO1/lego/legoomni/src/worlds/hospital.cpp index dbb1c00c..74d15678 100644 --- a/LEGO1/lego/legoomni/src/worlds/hospital.cpp +++ b/LEGO1/lego/legoomni/src/worlds/hospital.cpp @@ -214,7 +214,7 @@ void Hospital::ReadyWorld() m_setWithCurrentAction = 1; } - FUN_10015820(FALSE, LegoOmni::c_disableInput | LegoOmni::c_disable3d | LegoOmni::c_clearScreen); + Disable(FALSE, LegoOmni::c_disableInput | LegoOmni::c_disable3d | LegoOmni::c_clearScreen); } // FUNCTION: LEGO1 0x10074dd0 diff --git a/LEGO1/lego/legoomni/src/worlds/infocenter.cpp b/LEGO1/lego/legoomni/src/worlds/infocenter.cpp index 0a8d5f1c..19957ac8 100644 --- a/LEGO1/lego/legoomni/src/worlds/infocenter.cpp +++ b/LEGO1/lego/legoomni/src/worlds/infocenter.cpp @@ -465,7 +465,7 @@ void Infocenter::ReadyWorld() PlayAction(InfomainScript::c_iicx18in_RunAnim); PlayMusic(JukeboxScript::c_InformationCenter_Music); - FUN_10015820(FALSE, LegoOmni::c_disableInput | LegoOmni::c_disable3d | LegoOmni::c_clearScreen); + Disable(FALSE, LegoOmni::c_disableInput | LegoOmni::c_disable3d | LegoOmni::c_clearScreen); return; case 5: default: { @@ -478,7 +478,7 @@ void Infocenter::ReadyWorld() m_bigInfoBlinkTimer = 1; } - FUN_10015820(FALSE, LegoOmni::c_disableInput | LegoOmni::c_disable3d | LegoOmni::c_clearScreen); + Disable(FALSE, LegoOmni::c_disableInput | LegoOmni::c_disable3d | LegoOmni::c_clearScreen); if (!m_infocenterState->HasRegistered()) { m_bookAnimationTimer = 1; @@ -489,7 +489,7 @@ void Infocenter::ReadyWorld() case 8: PlayMusic(JukeboxScript::c_InformationCenter_Music); PlayAction(InfomainScript::c_iic043in_RunAnim); - FUN_10015820(FALSE, LegoOmni::c_disableInput | LegoOmni::c_disable3d | LegoOmni::c_clearScreen); + Disable(FALSE, LegoOmni::c_disableInput | LegoOmni::c_disable3d | LegoOmni::c_clearScreen); return; case 0xf: m_infocenterState->m_unk0x74 = 2; @@ -499,7 +499,7 @@ void Infocenter::ReadyWorld() PlayAction(InfomainScript::c_iicx17in_RunAnim); PlayMusic(JukeboxScript::c_InformationCenter_Music); - FUN_10015820(FALSE, LegoOmni::c_disableInput | LegoOmni::c_disable3d | LegoOmni::c_clearScreen); + Disable(FALSE, LegoOmni::c_disableInput | LegoOmni::c_disable3d | LegoOmni::c_clearScreen); return; } break; @@ -508,7 +508,7 @@ void Infocenter::ReadyWorld() PlayMusic(JukeboxScript::c_InformationCenter_Music); bgRed->Enable(TRUE); PlayAction(InfomainScript::c_iic043in_RunAnim); - FUN_10015820(FALSE, LegoOmni::c_disableInput | LegoOmni::c_disable3d | LegoOmni::c_clearScreen); + Disable(FALSE, LegoOmni::c_disableInput | LegoOmni::c_disable3d | LegoOmni::c_clearScreen); return; } @@ -538,7 +538,7 @@ void Infocenter::ReadyWorld() PlayAction(script); InputManager()->DisableInputProcessing(); - FUN_10015820(FALSE, LegoOmni::c_disableInput | LegoOmni::c_disable3d | LegoOmni::c_clearScreen); + Disable(FALSE, LegoOmni::c_disableInput | LegoOmni::c_disable3d | LegoOmni::c_clearScreen); return; } @@ -553,7 +553,7 @@ void Infocenter::ReadyWorld() PlayMusic(JukeboxScript::c_InformationCenter_Music); bgRed->Enable(TRUE); PlayAction(InfomainScript::c_iic043in_RunAnim); - FUN_10015820(FALSE, LegoOmni::c_disableInput | LegoOmni::c_disable3d | LegoOmni::c_clearScreen); + Disable(FALSE, LegoOmni::c_disableInput | LegoOmni::c_disable3d | LegoOmni::c_clearScreen); return; } @@ -590,7 +590,7 @@ void Infocenter::ReadyWorld() PlayAction(script); InputManager()->DisableInputProcessing(); - FUN_10015820(FALSE, LegoOmni::c_disableInput | LegoOmni::c_disable3d | LegoOmni::c_clearScreen); + Disable(FALSE, LegoOmni::c_disableInput | LegoOmni::c_disable3d | LegoOmni::c_clearScreen); return; } @@ -603,7 +603,7 @@ void Infocenter::ReadyWorld() } m_infocenterState->m_unk0x74 = 11; - FUN_10015820(FALSE, LegoOmni::c_disableInput | LegoOmni::c_disable3d | LegoOmni::c_clearScreen); + Disable(FALSE, LegoOmni::c_disableInput | LegoOmni::c_disable3d | LegoOmni::c_clearScreen); } // FUNCTION: LEGO1 0x1006f9a0 @@ -1284,7 +1284,7 @@ void Infocenter::StopCutscene() VideoManager()->EnableFullScreenMovie(FALSE); InputManager()->SetUnknown335(FALSE); SetAppCursor(e_cursorArrow); - FUN_10015820(FALSE, LegoOmni::c_disableInput | LegoOmni::c_disable3d | LegoOmni::c_clearScreen); + Disable(FALSE, LegoOmni::c_disableInput | LegoOmni::c_disable3d | LegoOmni::c_clearScreen); } // FUNCTION: LEGO1 0x10070d00 diff --git a/LEGO1/lego/legoomni/src/worlds/infocenterdoor.cpp b/LEGO1/lego/legoomni/src/worlds/infocenterdoor.cpp index c546744a..8f92a323 100644 --- a/LEGO1/lego/legoomni/src/worlds/infocenterdoor.cpp +++ b/LEGO1/lego/legoomni/src/worlds/infocenterdoor.cpp @@ -88,7 +88,7 @@ void InfocenterDoor::ReadyWorld() { LegoWorld::ReadyWorld(); PlayMusic(JukeboxScript::c_InformationCenter_Music); - FUN_10015820(FALSE, LegoOmni::c_disableInput | LegoOmni::c_disable3d | LegoOmni::c_clearScreen); + Disable(FALSE, LegoOmni::c_disableInput | LegoOmni::c_disable3d | LegoOmni::c_clearScreen); } // FUNCTION: LEGO1 0x10037a90 diff --git a/LEGO1/lego/legoomni/src/worlds/isle.cpp b/LEGO1/lego/legoomni/src/worlds/isle.cpp index 7865969b..2b630be4 100644 --- a/LEGO1/lego/legoomni/src/worlds/isle.cpp +++ b/LEGO1/lego/legoomni/src/worlds/isle.cpp @@ -277,7 +277,7 @@ void Isle::ReadyWorld() EnableAnimations(TRUE); FUN_10032620(); m_act1state->PlaceActors(); - FUN_10015820(FALSE, LegoOmni::c_disableInput | LegoOmni::c_disable3d | LegoOmni::c_clearScreen); + Disable(FALSE, LegoOmni::c_disableInput | LegoOmni::c_disable3d | LegoOmni::c_clearScreen); } } @@ -839,7 +839,7 @@ void Isle::Enable(MxBool p_enable) (m_act1state->m_unk0x018 != 0 || GameState()->m_currentArea != LegoGameState::e_jetski) && (m_act1state->m_unk0x018 != 0 || GameState()->m_currentArea != LegoGameState::e_skateboard) && (m_act1state->m_unk0x018 != 0 || GameState()->m_currentArea != LegoGameState::e_jetrace2)) { - FUN_10015820(FALSE, LegoOmni::c_disableInput | LegoOmni::c_disable3d | LegoOmni::c_clearScreen); + Disable(FALSE, LegoOmni::c_disableInput | LegoOmni::c_disable3d | LegoOmni::c_clearScreen); } SetROIVisible("stretch", FALSE); @@ -961,7 +961,7 @@ MxLong Isle::HandleTransitionEnd() m_destLocation = LegoGameState::e_undefined; VariableTable()->SetVariable("VISIBILITY", "Show Gas"); AnimationManager()->Resume(); - FUN_10015820(FALSE, LegoOmni::c_disableInput | LegoOmni::c_disable3d | LegoOmni::c_clearScreen); + Disable(FALSE, LegoOmni::c_disableInput | LegoOmni::c_disable3d | LegoOmni::c_clearScreen); SetAppCursor(e_cursorArrow); SetIsWorldActive(TRUE); break; @@ -971,7 +971,7 @@ MxLong Isle::HandleTransitionEnd() m_destLocation = LegoGameState::e_undefined; VariableTable()->SetVariable("VISIBILITY", "Show Policsta"); AnimationManager()->Resume(); - FUN_10015820(FALSE, LegoOmni::c_disableInput | LegoOmni::c_disable3d | LegoOmni::c_clearScreen); + Disable(FALSE, LegoOmni::c_disableInput | LegoOmni::c_disable3d | LegoOmni::c_clearScreen); SetAppCursor(e_cursorArrow); SetIsWorldActive(TRUE); break; @@ -1082,7 +1082,7 @@ void Isle::FUN_10032d30( VariableTable()->SetVariable(g_varCAMERALOCATION, p_cameraLocation); } - FUN_10015820(FALSE, LegoOmni::c_disableInput | LegoOmni::c_disable3d | LegoOmni::c_clearScreen); + Disable(FALSE, LegoOmni::c_disableInput | LegoOmni::c_disable3d | LegoOmni::c_clearScreen); SetAppCursor(e_cursorArrow); m_destLocation = LegoGameState::e_undefined; m_act1state->m_unk0x01f = FALSE; diff --git a/LEGO1/lego/legoomni/src/worlds/jukebox.cpp b/LEGO1/lego/legoomni/src/worlds/jukebox.cpp index a92f493d..d09b7e62 100644 --- a/LEGO1/lego/legoomni/src/worlds/jukebox.cpp +++ b/LEGO1/lego/legoomni/src/worlds/jukebox.cpp @@ -247,7 +247,7 @@ MxResult JukeBox::Tickle() if (m_unk0x100 == 1) { m_unk0x100 = 0; - FUN_10015820(FALSE, LegoOmni::c_disableInput | LegoOmni::c_disable3d | LegoOmni::c_clearScreen); + Disable(FALSE, LegoOmni::c_disableInput | LegoOmni::c_disable3d | LegoOmni::c_clearScreen); } return SUCCESS; diff --git a/LEGO1/lego/legoomni/src/worlds/legoact2.cpp b/LEGO1/lego/legoomni/src/worlds/legoact2.cpp index ddc97f1c..49936b28 100644 --- a/LEGO1/lego/legoomni/src/worlds/legoact2.cpp +++ b/LEGO1/lego/legoomni/src/worlds/legoact2.cpp @@ -192,7 +192,7 @@ MxResult LegoAct2::Tickle() case 2: if (g_unk0x100f4474) { if (AnimationManager()->FUN_10064ee0(g_unk0x100f4474)) { - FUN_10015820(FALSE, LegoOmni::c_disableInput | LegoOmni::c_disable3d | LegoOmni::c_clearScreen); + Disable(FALSE, LegoOmni::c_disableInput | LegoOmni::c_disable3d | LegoOmni::c_clearScreen); g_unk0x100f4474 = (Act2mainScript::Script) 0; } } @@ -200,7 +200,7 @@ MxResult LegoAct2::Tickle() m_unk0x10d0 += 50; break; case 3: - FUN_10015820(FALSE, LegoOmni::c_disableInput | LegoOmni::c_disable3d | LegoOmni::c_clearScreen); + Disable(FALSE, LegoOmni::c_disableInput | LegoOmni::c_disable3d | LegoOmni::c_clearScreen); m_unk0x10d0 = 0; m_unk0x10c4 = 4; FUN_10052560(Act2mainScript::c_tja009ni_RunAnim, TRUE, TRUE, NULL, NULL, NULL); @@ -540,7 +540,7 @@ void LegoAct2::Enable(MxBool p_enable) GameState()->StopArea(LegoGameState::e_infomain); } - FUN_10015820(FALSE, LegoOmni::c_disableInput | LegoOmni::c_disable3d | LegoOmni::c_clearScreen); + Disable(FALSE, LegoOmni::c_disableInput | LegoOmni::c_disable3d | LegoOmni::c_clearScreen); if (m_unk0x10c4 != 6 && m_unk0x10c4 != 12) { PlayMusic(m_music); diff --git a/LEGO1/lego/legoomni/src/worlds/police.cpp b/LEGO1/lego/legoomni/src/worlds/police.cpp index f1936ead..1c3bccd7 100644 --- a/LEGO1/lego/legoomni/src/worlds/police.cpp +++ b/LEGO1/lego/legoomni/src/worlds/police.cpp @@ -95,7 +95,7 @@ void Police::ReadyWorld() { LegoWorld::ReadyWorld(); PlayMusic(JukeboxScript::c_PoliceStation_Music); - FUN_10015820(FALSE, LegoOmni::c_disableInput | LegoOmni::c_disable3d | LegoOmni::c_clearScreen); + Disable(FALSE, LegoOmni::c_disableInput | LegoOmni::c_disable3d | LegoOmni::c_clearScreen); } // FUNCTION: LEGO1 0x1005e550 diff --git a/LEGO1/lego/legoomni/src/worlds/score.cpp b/LEGO1/lego/legoomni/src/worlds/score.cpp index 76f987e2..edc2d991 100644 --- a/LEGO1/lego/legoomni/src/worlds/score.cpp +++ b/LEGO1/lego/legoomni/src/worlds/score.cpp @@ -159,7 +159,7 @@ void Score::ReadyWorld() PlayMusic(JukeboxScript::c_InformationCenter_Music); } - FUN_10015820(FALSE, LegoOmni::c_disableInput | LegoOmni::c_disable3d | LegoOmni::c_clearScreen); + Disable(FALSE, LegoOmni::c_disableInput | LegoOmni::c_disable3d | LegoOmni::c_clearScreen); } // FUNCTION: LEGO1 0x100016d0 From d9d9880d8b37c0cf10128575e1a9684fdfe1c64a Mon Sep 17 00:00:00 2001 From: Fabian Neundorf Date: Sat, 12 Jul 2025 02:11:12 +0200 Subject: [PATCH 119/188] Clear unknowns in `LegoAnimNodeData` (#1617) --- LEGO1/lego/legoomni/src/video/legohideanimpresenter.cpp | 8 ++++---- LEGO1/lego/sources/anim/legoanim.cpp | 2 +- LEGO1/lego/sources/anim/legoanim.h | 6 +++--- 3 files changed, 8 insertions(+), 8 deletions(-) diff --git a/LEGO1/lego/legoomni/src/video/legohideanimpresenter.cpp b/LEGO1/lego/legoomni/src/video/legohideanimpresenter.cpp index 89768b3e..60f8dce5 100644 --- a/LEGO1/lego/legoomni/src/video/legohideanimpresenter.cpp +++ b/LEGO1/lego/legoomni/src/video/legohideanimpresenter.cpp @@ -114,7 +114,7 @@ void LegoHideAnimPresenter::FUN_1006db60(LegoTreeNode* p_node, LegoTime p_time) } if (m_boundaryMap != NULL) { - LegoPathBoundary* boundary = m_boundaryMap[data->GetUnknown0x22()]; + LegoPathBoundary* boundary = m_boundaryMap[data->GetBoundaryIndex()]; if (boundary != NULL) { newB = data->GetVisibility(p_time); @@ -163,7 +163,7 @@ void LegoHideAnimPresenter::FUN_1006e3f0(LegoHideAnimStructMap& p_map, LegoTreeN FUN_1006e470(p_map, data, name, boundary); } else { - data->SetUnknown0x22(0); + data->SetBoundaryIndex(0); } } @@ -190,7 +190,7 @@ void LegoHideAnimPresenter::FUN_1006e470( animStruct.m_index = p_map.size() + 1; animStruct.m_boundary = p_boundary; - p_data->SetUnknown0x22(animStruct.m_index); + p_data->SetBoundaryIndex(animStruct.m_index); char* name = new char[strlen(p_name) + 1]; strcpy(name, p_name); @@ -198,7 +198,7 @@ void LegoHideAnimPresenter::FUN_1006e470( p_map[name] = animStruct; } else { - p_data->SetUnknown0x22((*it).second.m_index); + p_data->SetBoundaryIndex((*it).second.m_index); } } diff --git a/LEGO1/lego/sources/anim/legoanim.cpp b/LEGO1/lego/sources/anim/legoanim.cpp index 8ec3ee2d..9026c245 100644 --- a/LEGO1/lego/sources/anim/legoanim.cpp +++ b/LEGO1/lego/sources/anim/legoanim.cpp @@ -524,7 +524,7 @@ LegoAnimNodeData::LegoAnimNodeData() m_translationKeys = NULL; m_roiIndex = 0; m_rotationKeys = NULL; - m_unk0x22 = 0; + m_boundaryIndex = 0; m_scaleKeys = NULL; m_morphKeys = NULL; m_translationIndex = 0; diff --git a/LEGO1/lego/sources/anim/legoanim.h b/LEGO1/lego/sources/anim/legoanim.h index 372128b5..7a03d99c 100644 --- a/LEGO1/lego/sources/anim/legoanim.h +++ b/LEGO1/lego/sources/anim/legoanim.h @@ -190,7 +190,7 @@ class LegoAnimNodeData : public LegoTreeNodeData { LegoU16 GetROIIndex() { return m_roiIndex; } // FUNCTION: BETA10 0x1005d5c0 - LegoU16 GetUnknown0x22() { return m_unk0x22; } + LegoU16 GetBoundaryIndex() { return m_boundaryIndex; } // FUNCTION: BETA10 0x10073b80 LegoRotationKey* GetRotationKey(MxS32 index) { return &m_rotationKeys[index]; } @@ -217,7 +217,7 @@ class LegoAnimNodeData : public LegoTreeNodeData { void SetROIIndex(LegoU16 p_roiIndex) { m_roiIndex = p_roiIndex; } // FUNCTION: BETA10 0x1005f2e0 - void SetUnknown0x22(LegoU16 p_unk0x22) { m_unk0x22 = p_unk0x22; } + void SetBoundaryIndex(LegoU16 p_boundaryIndex) { m_boundaryIndex = p_boundaryIndex; } LegoResult CreateLocalTransform(LegoTime p_time, Matrix4& p_matrix) { @@ -280,7 +280,7 @@ class LegoAnimNodeData : public LegoTreeNodeData { LegoScaleKey* m_scaleKeys; // 0x18 LegoMorphKey* m_morphKeys; // 0x1c LegoU16 m_roiIndex; // 0x20 - LegoU16 m_unk0x22; // 0x22 + LegoU16 m_boundaryIndex; // 0x22 LegoU32 m_translationIndex; // 0x24 LegoU32 m_rotationIndex; // 0x28 LegoU32 m_scaleIndex; // 0x2c From 7c91a14875751ca1c3571ae35825040000178129 Mon Sep 17 00:00:00 2001 From: Christian Semmler Date: Fri, 11 Jul 2025 18:05:33 -0700 Subject: [PATCH 120/188] Extended full screen capabilities for Web port (#584) * Extended full screen capabilities for Web port * Add Emscripten patches * Add back newline --- CMakeLists.txt | 1 + ISLE/emscripten/config.cpp | 5 ++ ISLE/emscripten/emscripten.patch | 25 ++++++++ ISLE/emscripten/window.cpp | 98 +++++++++++++++++++++++++++++++ ISLE/emscripten/window.h | 11 ++++ ISLE/isleapp.cpp | 26 +++++--- miniwin/src/d3drm/d3drmdevice.cpp | 22 ++----- 7 files changed, 164 insertions(+), 24 deletions(-) create mode 100644 ISLE/emscripten/window.cpp create mode 100644 ISLE/emscripten/window.h diff --git a/CMakeLists.txt b/CMakeLists.txt index 724ca120..38ce8ef1 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -553,6 +553,7 @@ if (ISLE_BUILD_APP) ISLE/emscripten/events.cpp ISLE/emscripten/filesystem.cpp ISLE/emscripten/messagebox.cpp + ISLE/emscripten/window.cpp ) target_compile_definitions(isle PRIVATE "ISLE_EMSCRIPTEN_HOST=\"${ISLE_EMSCRIPTEN_HOST}\"") set_property(TARGET isle PROPERTY SUFFIX ".html") diff --git a/ISLE/emscripten/config.cpp b/ISLE/emscripten/config.cpp index 7d9cdb31..58a42704 100644 --- a/ISLE/emscripten/config.cpp +++ b/ISLE/emscripten/config.cpp @@ -1,6 +1,7 @@ #include "config.h" #include "filesystem.h" +#include "window.h" #include #include @@ -16,6 +17,10 @@ void Emscripten_SetupDefaultConfigOverrides(dictionary* p_dictionary) iniparser_set(p_dictionary, "isle:Full Screen", "false"); iniparser_set(p_dictionary, "isle:Flip Surfaces", "true"); + // Emscripten-only for now + Emscripten_SetScaleAspect(iniparser_getboolean(p_dictionary, "isle:Original Aspect Ratio", true)); + Emscripten_SetOriginalResolution(iniparser_getboolean(p_dictionary, "isle:Original Resolution", true)); + // clang-format off MAIN_THREAD_EM_ASM({JSEvents.fullscreenEnabled = function() { return false; }}); // clang-format on diff --git a/ISLE/emscripten/emscripten.patch b/ISLE/emscripten/emscripten.patch index 7e9ad611..a24a67bd 100644 --- a/ISLE/emscripten/emscripten.patch +++ b/ISLE/emscripten/emscripten.patch @@ -1,3 +1,28 @@ +diff --git a/src/lib/libhtml5.js b/src/lib/libhtml5.js +index da08765e7..24e5da22e 100644 +--- a/src/lib/libhtml5.js ++++ b/src/lib/libhtml5.js +@@ -1182,6 +1182,7 @@ var LibraryHTML5 = { + + $registerRestoreOldStyle__deps: ['$getCanvasElementSize', '$setCanvasElementSize', '$currentFullscreenStrategy'], + $registerRestoreOldStyle: (canvas) => { ++ return; + var canvasSize = getCanvasElementSize(canvas); + var oldWidth = canvasSize[0]; + var oldHeight = canvasSize[1]; +@@ -1326,9 +1327,9 @@ var LibraryHTML5 = { + var topMargin; + + if (inAspectRatioFixedFullscreenMode) { +- if (w*y < x*h) h = (w * y / x) | 0; +- else if (w*y > x*h) w = (h * x / y) | 0; +- topMargin = ((screenHeight - h) / 2) | 0; ++ if (w*y < x*h) h = Math.round(w * y / x) | 0; ++ else if (w*y > x*h) w = Math.round(h * x / y) | 0; ++ topMargin = Math.round((screenHeight - h) / 2) | 0; + } + + if (inPixelPerfectFullscreenMode) { diff --git a/src/lib/libpthread.js b/src/lib/libpthread.js index 6d979627e..97e3f8684 100644 --- a/src/lib/libpthread.js diff --git a/ISLE/emscripten/window.cpp b/ISLE/emscripten/window.cpp new file mode 100644 index 00000000..6766490f --- /dev/null +++ b/ISLE/emscripten/window.cpp @@ -0,0 +1,98 @@ +#include "window.h" + +#include "mxtypes.h" + +#include +#include +#include + +double g_fullWidth; +double g_fullHeight; +bool g_scaleAspect = true; +bool g_originalResolution = true; + +extern MxS32 g_targetWidth; +extern MxS32 g_targetHeight; + +void Emscripten_SetupWindow(SDL_Window* p_window) +{ + EmscriptenFullscreenStrategy strategy; + strategy.scaleMode = g_scaleAspect ? EMSCRIPTEN_FULLSCREEN_SCALE_ASPECT : EMSCRIPTEN_FULLSCREEN_SCALE_STRETCH; + strategy.canvasResolutionScaleMode = EMSCRIPTEN_FULLSCREEN_CANVAS_SCALE_HIDEF; + strategy.filteringMode = EMSCRIPTEN_FULLSCREEN_FILTERING_DEFAULT; + strategy.canvasResizedCallbackUserData = p_window; + strategy.canvasResizedCallback = [](int eventType, const void* reserved, void* userData) -> bool { + int width, height; + emscripten_get_canvas_element_size("canvas", &width, &height); + emscripten_get_element_css_size("#canvas", &g_fullWidth, &g_fullHeight); + + if (g_originalResolution) { + SDL_SetWindowSize((SDL_Window*) userData, g_targetWidth, g_targetHeight); + } + else { + SDL_SetWindowSize((SDL_Window*) userData, width, height); + } + + SDL_Log( + "Emscripten: window size %dx%d, canvas size %dx%d, scale aspect %s, original resolution %s", + width, + height, + (int) g_fullWidth, + (int) g_fullHeight, + g_scaleAspect ? "TRUE" : "FALSE", + g_originalResolution ? "TRUE" : "FALSE" + ); + return true; + }; + + emscripten_enter_soft_fullscreen("canvas", &strategy); +} + +void Emscripten_SetScaleAspect(bool p_scaleAspect) +{ + g_scaleAspect = p_scaleAspect; +} + +void Emscripten_SetOriginalResolution(bool p_originalResolution) +{ + g_originalResolution = p_originalResolution; +} + +void Emscripten_ConvertEventToRenderCoordinates(SDL_Event* event) +{ + if (!g_scaleAspect) { + return; + } + + switch (event->type) { + case SDL_EVENT_MOUSE_MOTION: { + const float scale = std::min(g_fullWidth / g_targetWidth, g_fullHeight / g_targetHeight); + const float widthRatio = (g_targetWidth * scale) / g_fullWidth; + const float heightRatio = (g_targetHeight * scale) / g_fullHeight; + event->motion.x = (event->motion.x - (g_targetWidth * (1.0f - widthRatio) / 2.0f)) / widthRatio; + event->motion.y = (event->motion.y - (g_targetHeight * (1.0f - heightRatio) / 2.0f)) / heightRatio; + break; + } + case SDL_EVENT_MOUSE_BUTTON_DOWN: + case SDL_EVENT_MOUSE_BUTTON_UP: { + const float scale = std::min(g_fullWidth / g_targetWidth, g_fullHeight / g_targetHeight); + const float widthRatio = (g_targetWidth * scale) / g_fullWidth; + const float heightRatio = (g_targetHeight * scale) / g_fullHeight; + event->button.x = (event->button.x - (g_targetWidth * (1.0f - widthRatio) / 2.0f)) / widthRatio; + event->button.y = (event->button.y - (g_targetHeight * (1.0f - heightRatio) / 2.0f)) / heightRatio; + break; + } + case SDL_EVENT_FINGER_MOTION: + case SDL_EVENT_FINGER_DOWN: + case SDL_EVENT_FINGER_UP: { + const float scale = std::min(g_fullWidth / g_targetWidth, g_fullHeight / g_targetHeight); + const float widthRatio = (g_targetWidth * scale) / g_fullWidth; + const float heightRatio = (g_targetHeight * scale) / g_fullHeight; + event->tfinger.x = (event->tfinger.x * g_targetWidth - (g_targetWidth * (1.0f - widthRatio) / 2.0f)) / + widthRatio / g_targetWidth; + event->tfinger.y = (event->tfinger.y * g_targetHeight - (g_targetHeight * (1.0f - heightRatio) / 2.0f)) / + heightRatio / g_targetHeight; + break; + } + } +} diff --git a/ISLE/emscripten/window.h b/ISLE/emscripten/window.h new file mode 100644 index 00000000..01b13ae4 --- /dev/null +++ b/ISLE/emscripten/window.h @@ -0,0 +1,11 @@ +#ifndef EMSCRIPTEN_WINDOW_H +#define EMSCRIPTEN_WINDOW_H + +#include + +void Emscripten_SetupWindow(SDL_Window* p_window); +void Emscripten_SetScaleAspect(bool p_scaleAspect); +void Emscripten_SetOriginalResolution(bool p_originalResolution); +void Emscripten_ConvertEventToRenderCoordinates(SDL_Event* event); + +#endif // EMSCRIPTEN_WINDOW_H diff --git a/ISLE/isleapp.cpp b/ISLE/isleapp.cpp index dbb3c9ff..d898e48b 100644 --- a/ISLE/isleapp.cpp +++ b/ISLE/isleapp.cpp @@ -53,6 +53,7 @@ #include "emscripten/events.h" #include "emscripten/filesystem.h" #include "emscripten/messagebox.h" +#include "emscripten/window.h" #endif #ifdef __3DS__ @@ -441,6 +442,10 @@ SDL_AppResult SDL_AppEvent(void* appstate, SDL_Event* event) if (device && !device->ConvertEventToRenderCoordinates(event)) { SDL_Log("Failed to convert event coordinates: %s", SDL_GetError()); } + +#ifdef __EMSCRIPTEN__ + Emscripten_ConvertEventToRenderCoordinates(event); +#endif break; } @@ -641,8 +646,8 @@ SDL_AppResult SDL_AppEvent(void* appstate, SDL_Event* event) #endif g_mousemoved = TRUE; - float x = SDL_clamp(event->tfinger.x, 0, 1) * 640; - float y = SDL_clamp(event->tfinger.y, 0, 1) * 480; + float x = SDL_clamp(event->tfinger.x, 0, 1) * g_targetWidth; + float y = SDL_clamp(event->tfinger.y, 0, 1) * g_targetHeight; if (InputManager()) { InputManager()->QueueEvent(c_notificationMouseMove, LegoEventNotificationParam::c_lButtonState, x, y, 0); @@ -682,8 +687,8 @@ SDL_AppResult SDL_AppEvent(void* appstate, SDL_Event* event) #endif g_mousedown = TRUE; - float x = SDL_clamp(event->tfinger.x, 0, 1) * 640; - float y = SDL_clamp(event->tfinger.y, 0, 1) * 480; + float x = SDL_clamp(event->tfinger.x, 0, 1) * g_targetWidth; + float y = SDL_clamp(event->tfinger.y, 0, 1) * g_targetHeight; if (InputManager()) { InputManager()->QueueEvent(c_notificationButtonDown, LegoEventNotificationParam::c_lButtonState, x, y, 0); @@ -729,8 +734,8 @@ SDL_AppResult SDL_AppEvent(void* appstate, SDL_Event* event) #endif g_mousedown = FALSE; - float x = SDL_clamp(event->tfinger.x, 0, 1) * 640; - float y = SDL_clamp(event->tfinger.y, 0, 1) * 480; + float x = SDL_clamp(event->tfinger.x, 0, 1) * g_targetWidth; + float y = SDL_clamp(event->tfinger.y, 0, 1) * g_targetHeight; if (InputManager()) { InputManager()->QueueEvent(c_notificationButtonUp, 0, x, y, 0); @@ -770,6 +775,11 @@ SDL_AppResult SDL_AppEvent(void* appstate, SDL_Event* event) if (!g_isle->GetGameStarted() && action && state == MxPresenter::e_ready && !SDL_strncmp(action->GetObjectName(), "Lego_Smk", 8)) { g_isle->SetGameStarted(TRUE); + +#ifdef __EMSCRIPTEN__ + Emscripten_SetupWindow((SDL_Window*) g_isle->GetWindowHandle()); +#endif + SDL_Log("Game started"); } } @@ -1413,8 +1423,8 @@ void IsleApp::MoveVirtualMouseViaJoystick() if (moveX != 0 || moveY != 0) { g_mousemoved = TRUE; - g_lastMouseX = SDL_clamp(g_lastMouseX + moveX, 0, 640); - g_lastMouseY = SDL_clamp(g_lastMouseY + moveY, 0, 480); + g_lastMouseX = SDL_clamp(g_lastMouseX + moveX, 0, g_targetWidth); + g_lastMouseY = SDL_clamp(g_lastMouseY + moveY, 0, g_targetHeight); if (InputManager()) { InputManager()->QueueEvent( diff --git a/miniwin/src/d3drm/d3drmdevice.cpp b/miniwin/src/d3drm/d3drmdevice.cpp index 25703f86..e3190692 100644 --- a/miniwin/src/d3drm/d3drmdevice.cpp +++ b/miniwin/src/d3drm/d3drmdevice.cpp @@ -173,31 +173,21 @@ bool Direct3DRMDevice2Impl::ConvertEventToRenderCoordinates(SDL_Event* event) break; } case SDL_EVENT_MOUSE_MOTION: { - int rawX = event->motion.x; - int rawY = event->motion.y; - float x = (rawX - m_viewportTransform.offsetX) / m_viewportTransform.scale; - float y = (rawY - m_viewportTransform.offsetY) / m_viewportTransform.scale; - event->motion.x = static_cast(x); - event->motion.y = static_cast(y); + event->motion.x = (event->motion.x - m_viewportTransform.offsetX) / m_viewportTransform.scale; + event->motion.y = (event->motion.y - m_viewportTransform.offsetY) / m_viewportTransform.scale; break; } case SDL_EVENT_MOUSE_BUTTON_DOWN: case SDL_EVENT_MOUSE_BUTTON_UP: { - int rawX = event->button.x; - int rawY = event->button.y; - float x = (rawX - m_viewportTransform.offsetX) / m_viewportTransform.scale; - float y = (rawY - m_viewportTransform.offsetY) / m_viewportTransform.scale; - event->button.x = static_cast(x); - event->button.y = static_cast(y); + event->button.x = (event->button.x - m_viewportTransform.offsetX) / m_viewportTransform.scale; + event->button.y = (event->button.y - m_viewportTransform.offsetY) / m_viewportTransform.scale; break; } case SDL_EVENT_FINGER_MOTION: case SDL_EVENT_FINGER_DOWN: case SDL_EVENT_FINGER_UP: { - int rawX = event->tfinger.x * m_windowWidth; - int rawY = event->tfinger.y * m_windowHeight; - float x = (rawX - m_viewportTransform.offsetX) / m_viewportTransform.scale; - float y = (rawY - m_viewportTransform.offsetY) / m_viewportTransform.scale; + float x = (event->tfinger.x * m_windowWidth - m_viewportTransform.offsetX) / m_viewportTransform.scale; + float y = (event->tfinger.y * m_windowHeight - m_viewportTransform.offsetY) / m_viewportTransform.scale; event->tfinger.x = x / m_virtualWidth; event->tfinger.y = y / m_virtualHeight; break; From 10986376cb543ca1eb76b117b6c220caef78f931 Mon Sep 17 00:00:00 2001 From: MS Date: Fri, 11 Jul 2025 23:26:21 -0400 Subject: [PATCH 121/188] Fix #1575 (#1618) --- CMakeLists.txt | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/CMakeLists.txt b/CMakeLists.txt index 064a207e..c9fe6030 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -592,6 +592,10 @@ if (MSVC_FOR_DECOMP) set_property(TARGET isle ${lego1_targets} PROPERTY MSVC_RUNTIME_LIBRARY "MultiThreaded$<$:Debug>") endif() + if (ISLE_BUILD_CONFIG) + target_link_libraries(config PRIVATE mfc42) + endif() + if (ISLE_INCLUDE_ENTROPY) message(STATUS "Using entropy file: ${ISLE_ENTROPY_FILENAME}") foreach(tgt IN LISTS lego1_targets beta10_targets) From ac4653759958a437c042060632d9ebb08aeffe54 Mon Sep 17 00:00:00 2001 From: jonschz <17198703+jonschz@users.noreply.github.com> Date: Sat, 12 Jul 2025 17:05:29 +0200 Subject: [PATCH 122/188] Interpret Act3List properties, minor fixes (#1619) Co-authored-by: jonschz --- LEGO1/lego/legoomni/include/act3.h | 16 ++-- .../lego/legoomni/include/legoinputmanager.h | 1 + .../lego/legoomni/include/legovideomanager.h | 2 + LEGO1/lego/legoomni/src/main/legomain.cpp | 38 +++++---- LEGO1/lego/legoomni/src/worlds/act3.cpp | 81 ++++++++++--------- LEGO1/omni/include/mxvideomanager.h | 3 + LEGO1/omni/src/video/mxdisplaysurface.cpp | 12 +-- 7 files changed, 89 insertions(+), 64 deletions(-) diff --git a/LEGO1/lego/legoomni/include/act3.h b/LEGO1/lego/legoomni/include/act3.h index 38e2876e..040da125 100644 --- a/LEGO1/lego/legoomni/include/act3.h +++ b/LEGO1/lego/legoomni/include/act3.h @@ -20,12 +20,12 @@ class MxQuaternionTransformer; struct Act3ListElement { MxU32 m_objectId; // 0x00 undefined4 m_unk0x04; // 0x04 - undefined m_unk0x08; // 0x08 + MxBool m_hasStarted; // 0x08 Act3ListElement() {} - Act3ListElement(MxU32 p_objectId, undefined4 p_unk0x04, undefined p_unk0x08) - : m_objectId(p_objectId), m_unk0x04(p_unk0x04), m_unk0x08(p_unk0x08) + Act3ListElement(MxU32 p_objectId, undefined4 p_unk0x04, MxBool p_hasStarted) + : m_objectId(p_objectId), m_unk0x04(p_unk0x04), m_hasStarted(p_hasStarted) { } @@ -36,10 +36,16 @@ struct Act3ListElement { // SIZE 0x10 class Act3List : private list { public: + enum InsertMode { + e_replaceAction = 1, + e_queueAction = 2, + e_onlyIfEmpty = 3 + }; + Act3List() { m_unk0x0c = 0; } - void Insert(MxS32 p_objectId, MxS32 p_option); - void FUN_10071fa0(); + void Insert(MxS32 p_objectId, InsertMode p_option); + void DeleteActionWrapper(); void Clear(); void FUN_100720d0(MxU32 p_objectId); diff --git a/LEGO1/lego/legoomni/include/legoinputmanager.h b/LEGO1/lego/legoomni/include/legoinputmanager.h index 27e53fcb..0013bcde 100644 --- a/LEGO1/lego/legoomni/include/legoinputmanager.h +++ b/LEGO1/lego/legoomni/include/legoinputmanager.h @@ -117,6 +117,7 @@ class LegoInputManager : public MxPresenter { void SetUseJoystick(MxBool p_useJoystick) { m_useJoystick = p_useJoystick; } void SetJoystickIndex(MxS32 p_joystickIndex) { m_joystickIndex = p_joystickIndex; } + // FUNCTION: BETA10 0x1002e290 void DisableInputProcessing() { m_unk0x88 = TRUE; diff --git a/LEGO1/lego/legoomni/include/legovideomanager.h b/LEGO1/lego/legoomni/include/legovideomanager.h index e7848c02..52a760fe 100644 --- a/LEGO1/lego/legoomni/include/legovideomanager.h +++ b/LEGO1/lego/legoomni/include/legovideomanager.h @@ -62,7 +62,9 @@ class LegoVideoManager : public MxVideoManager { MxBool GetRender3D() { return m_render3d; } double GetElapsedSeconds() { return m_elapsedSeconds; } + // FUNCTION: BETA10 0x1002e290 void SetRender3D(MxBool p_render3d) { m_render3d = p_render3d; } + void SetUnk0x554(MxBool p_unk0x554) { m_unk0x554 = p_unk0x554; } private: diff --git a/LEGO1/lego/legoomni/src/main/legomain.cpp b/LEGO1/lego/legoomni/src/main/legomain.cpp index 548cc427..0da66855 100644 --- a/LEGO1/lego/legoomni/src/main/legomain.cpp +++ b/LEGO1/lego/legoomni/src/main/legomain.cpp @@ -518,26 +518,36 @@ LegoOmni::World LegoOmni::GetWorldId(const char* p_key) } // FUNCTION: LEGO1 0x1005b4f0 +// FUNCTION: BETA10 0x1008eeec void LegoOmni::Disable(MxBool p_disable, MxU16 p_flags) { - if (p_disable) { - if (p_flags & c_disableInput) { - m_inputManager->DisableInputProcessing(); - } +#ifdef BETA10 + if (this->m_paused != p_disable) { + // This is probably a different variable, but this code was mostly added for structural matching + m_paused = p_disable; +#endif - if (p_flags & c_disable3d) { - ((LegoVideoManager*) m_videoManager)->SetRender3D(FALSE); - } + if (p_disable) { + if (p_flags & c_disableInput) { + m_inputManager->DisableInputProcessing(); + } - if (p_flags & c_clearScreen) { - m_videoManager->GetDisplaySurface()->ClearScreen(); + if (p_flags & c_disable3d) { + ((LegoVideoManager*) m_videoManager)->SetRender3D(FALSE); + } + + if (p_flags & c_clearScreen) { + m_videoManager->GetDisplaySurface()->ClearScreen(); + } } + else { + m_inputManager->EnableInputProcessing(); + ((LegoVideoManager*) m_videoManager)->SetRender3D(TRUE); + ((LegoVideoManager*) m_videoManager)->UpdateView(0, 0, 0, 0); + } +#ifdef BETA10 } - else { - m_inputManager->EnableInputProcessing(); - ((LegoVideoManager*) m_videoManager)->SetRender3D(TRUE); - ((LegoVideoManager*) m_videoManager)->UpdateView(0, 0, 0, 0); - } +#endif } // FUNCTION: LEGO1 0x1005b560 diff --git a/LEGO1/lego/legoomni/src/worlds/act3.cpp b/LEGO1/lego/legoomni/src/worlds/act3.cpp index ecef0c51..8bfaacd4 100644 --- a/LEGO1/lego/legoomni/src/worlds/act3.cpp +++ b/LEGO1/lego/legoomni/src/worlds/act3.cpp @@ -109,16 +109,16 @@ Act3Script::Script g_unk0x100d95e8[] = {Act3Script::c_tlp053in_RunAnim, Act3Script::c_tlp064la_RunAnim, Act3Script::c_tlp068in_RunAnim}; // FUNCTION: LEGO1 0x10071d40 -void Act3List::Insert(MxS32 p_objectId, MxS32 p_option) +void Act3List::Insert(MxS32 p_objectId, InsertMode p_option) { if (m_unk0x0c) { return; } switch (p_option) { - case 1: + case InsertMode::e_replaceAction: if (!empty()) { - FUN_10071fa0(); + DeleteActionWrapper(); push_back(Act3ListElement(p_objectId, p_option, FALSE)); } else { @@ -126,7 +126,7 @@ void Act3List::Insert(MxS32 p_objectId, MxS32 p_option) push_back(Act3ListElement(p_objectId, p_option, TRUE)); } break; - case 2: + case InsertMode::e_queueAction: if (empty()) { push_back(Act3ListElement(p_objectId, p_option, TRUE)); InvokeAction(Extra::e_start, *g_act3Script, p_objectId, NULL); @@ -135,7 +135,7 @@ void Act3List::Insert(MxS32 p_objectId, MxS32 p_option) push_back(Act3ListElement(p_objectId, p_option, FALSE)); } break; - case 3: + case InsertMode::e_onlyIfEmpty: if (empty()) { push_back(Act3ListElement(p_objectId, p_option, TRUE)); InvokeAction(Extra::e_start, *g_act3Script, p_objectId, NULL); @@ -145,7 +145,7 @@ void Act3List::Insert(MxS32 p_objectId, MxS32 p_option) } // FUNCTION: LEGO1 0x10071fa0 -void Act3List::FUN_10071fa0() +void Act3List::DeleteActionWrapper() { DeleteAction(); } @@ -161,7 +161,7 @@ void Act3List::Clear() } for (Act3List::iterator it = begin(); it != end();) { - if ((*it).m_unk0x08) { + if ((*it).m_hasStarted) { MxDSAction ds; ds.SetAtomId(*g_act3Script); ds.SetObjectId((*it).m_objectId); @@ -175,46 +175,47 @@ void Act3List::Clear() // FUNCTION: LEGO1 0x100720d0 void Act3List::FUN_100720d0(MxU32 p_objectId) { - if (m_unk0x0c == 0) { - MxU32 removed = FALSE; + if (m_unk0x0c) { + return; + } - if (!empty()) { - if (p_objectId != 0) { - for (Act3List::iterator it = begin(); it != end(); it++) { - if ((*it).m_unk0x08 && (*it).m_objectId == p_objectId) { - erase(it); - removed = TRUE; - break; - } + MxU32 removed = FALSE; + + if (!empty()) { + if (p_objectId != 0) { + for (Act3List::iterator it = begin(); it != end(); it++) { + if ((*it).m_hasStarted && (*it).m_objectId == p_objectId) { + erase(it); + removed = TRUE; + break; } } - else { - pop_front(); - removed = TRUE; - } + } + else { + pop_front(); + removed = TRUE; + } - if (removed && size() > 0) { - // TODO: Match - Act3List::iterator it = begin(); - Act3ListElement& item = *(it++); + if (removed && size() > 0) { + Act3List::iterator it = begin(); + Act3ListElement& firstItem = *(it++); - for (; it != end(); it++) { - if ((*it).m_unk0x04 == 1) { - for (Act3List::iterator it2 = begin(); it2 != it;) { - if ((*it2).m_unk0x08) { - FUN_10071fa0(); - return; - } - - it2 = erase(it2); + for (; it != end(); it++) { + if ((*it).m_unk0x04 == 1) { + for (Act3List::iterator it2 = begin(); it2 != it;) { + if ((*it2).m_hasStarted) { + DeleteActionWrapper(); + return; } + + it2 = erase(it2); } } + } - if (!item.m_unk0x08) { - item.m_unk0x08 = TRUE; - InvokeAction(Extra::e_start, *g_act3Script, item.m_objectId, NULL); - } + if (!firstItem.m_hasStarted) { + firstItem.m_hasStarted = TRUE; + InvokeAction(Extra::e_start, *g_act3Script, firstItem.m_objectId, NULL); } } } @@ -454,14 +455,14 @@ void Act3::TriggerHitSound(undefined4 p_param1) m_bricksterDonutSound = 0; } - m_unk0x4220.Insert(g_bricksterDonutSounds[m_bricksterDonutSound++], 1); + m_unk0x4220.Insert(g_bricksterDonutSounds[m_bricksterDonutSound++], Act3List::e_replaceAction); return; } default: return; } - m_unk0x4220.Insert(objectId, 3); + m_unk0x4220.Insert(objectId, Act3List::e_onlyIfEmpty); } // FUNCTION: LEGO1 0x10072c30 diff --git a/LEGO1/omni/include/mxvideomanager.h b/LEGO1/omni/include/mxvideomanager.h index 78411dcb..2ecab25d 100644 --- a/LEGO1/omni/include/mxvideomanager.h +++ b/LEGO1/omni/include/mxvideomanager.h @@ -41,7 +41,10 @@ class MxVideoManager : public MxMediaManager { MxVideoParam& GetVideoParam() { return this->m_videoParam; } LPDIRECTDRAW GetDirectDraw() { return this->m_pDirectDraw; } + + // FUNCTION: BETA10 0x1002e290 MxDisplaySurface* GetDisplaySurface() { return this->m_displaySurface; } + MxRegion* GetRegion() { return this->m_region; } // SYNTHETIC: LEGO1 0x100be280 diff --git a/LEGO1/omni/src/video/mxdisplaysurface.cpp b/LEGO1/omni/src/video/mxdisplaysurface.cpp index bd751a57..ff46b5f4 100644 --- a/LEGO1/omni/src/video/mxdisplaysurface.cpp +++ b/LEGO1/omni/src/video/mxdisplaysurface.cpp @@ -42,20 +42,22 @@ void MxDisplaySurface::Init() } // FUNCTION: LEGO1 0x100ba640 +// FUNCTION: BETA10 0x1013f506 void MxDisplaySurface::ClearScreen() { + MxS32 i; MxS32 backBuffers; DDSURFACEDESC desc; HRESULT hr; - if (!m_videoParam.Flags().GetFlipSurfaces()) { - backBuffers = 1; - } - else { + if (m_videoParam.Flags().GetFlipSurfaces()) { backBuffers = m_videoParam.GetBackBuffers() + 1; } + else { + backBuffers = 1; + } - for (MxS32 i = 0; i < backBuffers; i++) { + for (i = 0; i < backBuffers; i++) { memset(&desc, 0, sizeof(DDSURFACEDESC)); desc.dwSize = sizeof(DDSURFACEDESC); From 00a4861914f409b195623bf7c113882e2da68997 Mon Sep 17 00:00:00 2001 From: MS Date: Sat, 12 Jul 2025 12:39:16 -0400 Subject: [PATCH 123/188] Beta matching `CConfigApp` (#1620) * Beta matching CConfigApp * Docs --- CONFIG/config.cpp | 134 ++++++++++++++++++++++++++++++--------------- CONFIG/config.h | 6 ++ reccmp-project.yml | 5 ++ tools/README.md | 3 + 4 files changed, 105 insertions(+), 43 deletions(-) diff --git a/CONFIG/config.cpp b/CONFIG/config.cpp index 13a80e7f..54b16ee9 100644 --- a/CONFIG/config.cpp +++ b/CONFIG/config.cpp @@ -4,6 +4,7 @@ #include "MainDlg.h" #include "detectdx5.h" +#include #include // _chdir #include #include @@ -19,6 +20,7 @@ ON_COMMAND(ID_HELP, OnHelp) END_MESSAGE_MAP() // FUNCTION: CONFIG 0x00402c40 +// FUNCTION: CONFIGD 0x00406900 CConfigApp::CConfigApp() { } @@ -26,11 +28,13 @@ CConfigApp::CConfigApp() #define MiB (1024 * 1024) // FUNCTION: CONFIG 0x00402dc0 +// FUNCTION: CONFIGD 0x004069dc BOOL CConfigApp::InitInstance() { if (!IsLegoNotRunning()) { return FALSE; } + if (!DetectDirectX5()) { AfxMessageBox( "\"LEGO\xae Island\" is not detecting DirectX 5 or later. Please quit all other applications and try " @@ -38,20 +42,25 @@ BOOL CConfigApp::InitInstance() ); return FALSE; } + #ifdef _AFXDLL Enable3dControls(); #else Enable3dControlsStatic(); #endif + CConfigCommandLineInfo cmdInfo; ParseCommandLine(cmdInfo); if (_stricmp(afxCurrentAppName, "config") == 0) { m_run_config_dialog = TRUE; } + m_device_enumerator = new LegoDeviceEnumerate; if (m_device_enumerator->DoEnumerate()) { + assert("Could not build device list." == NULL); return FALSE; } + m_driver = NULL; m_device = NULL; m_full_screen = TRUE; @@ -76,11 +85,17 @@ BOOL CConfigApp::InitInstance() m_texture_quality = 1; } else { - m_model_quality = 2; m_3d_sound = TRUE; + m_model_quality = 2; m_texture_quality = 1; } - if (!m_run_config_dialog) { + + if (m_run_config_dialog) { + CMainDialog main_dialog(NULL); + m_pMainWnd = &main_dialog; + main_dialog.DoModal(); + } + else { ReadRegisterSettings(); ValidateSettings(); WriteRegisterSettings(); @@ -89,17 +104,17 @@ BOOL CConfigApp::InitInstance() m_driver = NULL; m_device = NULL; char password[256]; - ReadReg("password", password, sizeof(password)); + BOOL read = ReadReg("password", password, sizeof(password)); const char* exe = _stricmp("ogel", password) == 0 ? "isled.exe" : "isle.exe"; char diskpath[1024]; - if (ReadReg("diskpath", diskpath, sizeof(diskpath))) { + read = ReadReg("diskpath", diskpath, sizeof(diskpath)); + if (read) { _chdir(diskpath); } + _spawnl(_P_NOWAIT, exe, exe, "/diskstream", "/script", "\\lego\\scripts\\isle\\isle.si", NULL); - return FALSE; } - CMainDialog main_dialog(NULL); - main_dialog.DoModal(); + return FALSE; } @@ -117,35 +132,40 @@ BOOL CConfigApp::IsLegoNotRunning() } // FUNCTION: CONFIG 0x004031b0 +// FUNCTION: CONFIGD 0x00406dc3 BOOL CConfigApp::WriteReg(const char* p_key, const char* p_value) const { HKEY hKey; DWORD pos; + BOOL success = FALSE; + BOOL created = RegCreateKeyEx( + HKEY_LOCAL_MACHINE, + "SOFTWARE\\Mindscape\\LEGO Island", + 0, + "string", + 0, + KEY_READ | KEY_WRITE, + NULL, + &hKey, + &pos + ); - if (RegCreateKeyEx( - HKEY_LOCAL_MACHINE, - "SOFTWARE\\Mindscape\\LEGO Island", - 0, - "string", - 0, - KEY_READ | KEY_WRITE, - NULL, - &hKey, - &pos - ) == ERROR_SUCCESS) { - if (RegSetValueEx(hKey, p_key, 0, REG_SZ, (LPBYTE) p_value, strlen(p_value)) == ERROR_SUCCESS) { + if (created == ERROR_SUCCESS) { + if (RegSetValueEx(hKey, p_key, 0, REG_SZ, (LPBYTE) p_value, strlen(p_value) + 1) == ERROR_SUCCESS) { if (RegCloseKey(hKey) == ERROR_SUCCESS) { - return TRUE; + success = TRUE; } } else { RegCloseKey(hKey); } } - return FALSE; + + return success; } // FUNCTION: CONFIG 0x00403240 +// FUNCTION: CONFIGD 0x00406e6e BOOL CConfigApp::ReadReg(LPCSTR p_key, LPCSTR p_value, DWORD p_size) const { HKEY hKey; @@ -164,28 +184,30 @@ BOOL CConfigApp::ReadReg(LPCSTR p_key, LPCSTR p_value, DWORD p_size) const } // FUNCTION: CONFIG 0x004032b0 +// FUNCTION: CONFIGD 0x00406ef6 BOOL CConfigApp::ReadRegBool(LPCSTR p_key, BOOL* p_bool) const { char buffer[256]; + BOOL read = TRUE; - BOOL read = ReadReg(p_key, buffer, sizeof(buffer)); + read = ReadReg(p_key, buffer, sizeof(buffer)); if (read) { if (strcmp("YES", buffer) == 0) { *p_bool = TRUE; - return read; } - - if (strcmp("NO", buffer) == 0) { + else if (strcmp("NO", buffer) == 0) { *p_bool = FALSE; - return read; } - - read = FALSE; + else { + read = FALSE; + } } + return read; } // FUNCTION: CONFIG 0x00403380 +// FUNCTION: CONFIGD 0x00406fa1 BOOL CConfigApp::ReadRegInt(LPCSTR p_key, int* p_value) const { char buffer[256]; @@ -199,46 +221,63 @@ BOOL CConfigApp::ReadRegInt(LPCSTR p_key, int* p_value) const } // FUNCTION: CONFIG 0x004033d0 +// FUNCTION: CONFIGD 0x00407080 BOOL CConfigApp::IsDeviceInBasicRGBMode() const { /* * BUG: should be: * return !GetHardwareDeviceColorModel() && (m_device->m_HELDesc.dcmColorModel & D3DCOLOR_RGB); */ + assert(m_device); return !GetHardwareDeviceColorModel() && m_device->m_HELDesc.dcmColorModel == D3DCOLOR_RGB; } // FUNCTION: CONFIG 0x00403400 +// FUNCTION: CONFIGD 0x004070fa D3DCOLORMODEL CConfigApp::GetHardwareDeviceColorModel() const { + assert(m_device); return m_device->m_HWDesc.dcmColorModel; } // FUNCTION: CONFIG 0x00403410 +// FUNCTION: CONFIGD 0x0040714e BOOL CConfigApp::IsPrimaryDriver() const { + assert(m_driver && m_device_enumerator); return m_driver == &m_device_enumerator->GetDriverList().front(); } // FUNCTION: CONFIG 0x00403430 +// FUNCTION: CONFIGD 0x004071d2 BOOL CConfigApp::ReadRegisterSettings() { char buffer[256]; BOOL is_modified = FALSE; - int tmp = -1; - if (ReadReg("3D Device ID", buffer, sizeof(buffer))) { - tmp = m_device_enumerator->ParseDeviceName(buffer); - if (tmp >= 0) { - tmp = m_device_enumerator->GetDevice(tmp, m_driver, m_device); + BOOL read = ReadReg("3D Device ID", buffer, sizeof(buffer)); + int r = -1; + + if (read) { + r = m_device_enumerator->ParseDeviceName(buffer); + if (r >= 0) { + r = m_device_enumerator->GetDevice(r, m_driver, m_device); + if (r) { + r = -1; + } } } - if (tmp != 0) { - is_modified = TRUE; + + if (r < 0) { m_device_enumerator->FUN_1009d210(); - tmp = m_device_enumerator->GetBestDevice(); - m_device_enumerator->GetDevice(tmp, m_driver, m_device); + r = m_device_enumerator->GetBestDevice(); + is_modified = TRUE; + assert(r >= 0); + r = m_device_enumerator->GetDevice(r, m_driver, m_device); } + + assert(r == 0 && m_driver && m_device); + if (!ReadRegInt("Display Bit Depth", &m_display_bit_depth)) { is_modified = TRUE; } @@ -279,6 +318,7 @@ BOOL CConfigApp::ReadRegisterSettings() } // FUNCTION: CONFIG 0x00403630 +// FUNCTION: CONFIGD 0x00407547 BOOL CConfigApp::ValidateSettings() { BOOL is_modified = FALSE; @@ -301,11 +341,7 @@ BOOL CConfigApp::ValidateSettings() is_modified = TRUE; } } - if (!GetHardwareDeviceColorModel()) { - m_draw_cursor = FALSE; - is_modified = TRUE; - } - else { + if (GetHardwareDeviceColorModel()) { if (!m_3d_video_ram) { m_3d_video_ram = TRUE; is_modified = TRUE; @@ -315,6 +351,10 @@ BOOL CConfigApp::ValidateSettings() is_modified = TRUE; } } + else { + m_draw_cursor = FALSE; + is_modified = TRUE; + } if (m_flip_surfaces) { if (!m_3d_video_ram) { m_3d_video_ram = TRUE; @@ -341,8 +381,10 @@ BOOL CConfigApp::ValidateSettings() } // FUNCTION: CONFIG 0x004037a0 +// FUNCTION: CONFIGD 0x00407793 DWORD CConfigApp::GetConditionalDeviceRenderBitDepth() const { + assert(m_device); if (IsDeviceInBasicRGBMode()) { return 0; } @@ -353,8 +395,10 @@ DWORD CConfigApp::GetConditionalDeviceRenderBitDepth() const } // FUNCTION: CONFIG 0x004037e0 +// FUNCTION: CONFIGD 0x00407822 DWORD CConfigApp::GetDeviceRenderBitStatus() const { + assert(m_device); if (GetHardwareDeviceColorModel()) { return m_device->m_HWDesc.dwDeviceRenderBitDepth & DDBD_16; } @@ -364,6 +408,7 @@ DWORD CConfigApp::GetDeviceRenderBitStatus() const } // FUNCTION: CONFIG 0x00403810 +// FUNCTION: CONFIGD 0x004078ac BOOL CConfigApp::AdjustDisplayBitDepthBasedOnRenderStatus() { if (m_display_bit_depth == 8) { @@ -388,7 +433,8 @@ BOOL CConfigApp::AdjustDisplayBitDepthBasedOnRenderStatus() return TRUE; } -// FUNCTION: CONFIG 00403890 +// FUNCTION: CONFIG 0x00403890 +// FUNCTION: CONFIGD 0x00407966 void CConfigApp::WriteRegisterSettings() const { @@ -401,6 +447,7 @@ void CConfigApp::WriteRegisterSettings() const WriteReg(NAME, buffer); \ } while (0) + assert(m_device_enumerator && m_driver && m_device); m_device_enumerator->FormatDeviceName(buffer, m_driver, m_device); WriteReg("3D Device ID", buffer); WriteReg("3D Device Name", m_device->m_deviceName); @@ -422,6 +469,7 @@ void CConfigApp::WriteRegisterSettings() const } // FUNCTION: CONFIG 0x00403a90 +// FUNCTION: CONFIGD 0x00407c44 int CConfigApp::ExitInstance() { if (m_device_enumerator) { diff --git a/CONFIG/config.h b/CONFIG/config.h index c3282769..02366100 100644 --- a/CONFIG/config.h +++ b/CONFIG/config.h @@ -14,6 +14,7 @@ struct MxDriver; #define currentConfigApp ((CConfigApp*) afxCurrentWinApp) // VTABLE: CONFIG 0x00406040 +// VTABLE: CONFIGD 0x0040c0a0 // SIZE 0x108 class CConfigApp : public CWinApp { public: @@ -74,18 +75,23 @@ class CConfigApp : public CWinApp { }; // SYNTHETIC: CONFIG 0x00402cd0 +// SYNTHETIC: CONFIGD 0x00408330 // CConfigApp::`scalar deleting destructor' // FUNCTION: CONFIG 0x402c20 +// FUNCTION: CONFIGD 0x4068d0 // CConfigApp::_GetBaseMessageMap // FUNCTION: CONFIG 0x402c30 +// FUNCTION: CONFIGD 0x4068e5 // CConfigApp::GetMessageMap // GLOBAL: CONFIG 0x406008 +// GLOBAL: CONFIGD 0x40c058 // CConfigApp::messageMap // GLOBAL: CONFIG 0x406010 +// GLOBAL: CONFIGD 0x40c060 // CConfigApp::_messageEntries #endif // !defined(AFX_CONFIG_H) diff --git a/reccmp-project.yml b/reccmp-project.yml index fd9a2ef1..c0a1c907 100644 --- a/reccmp-project.yml +++ b/reccmp-project.yml @@ -43,3 +43,8 @@ targets: source-root: LEGO1 hash: sha256: dc7e5ed8ec9d96851126a40c4d23755f1783a8df61def44c667dfaa992ac509e + CONFIGD: + filename: CONFIGD.EXE + source-root: . + hash: + sha256: 9d2b7de2e53eee99cce24a64777a08a7f919a0401a393d9494bce27ef2e24c39 diff --git a/tools/README.md b/tools/README.md index 0d75e48e..0d443780 100644 --- a/tools/README.md +++ b/tools/README.md @@ -22,6 +22,7 @@ These modules are the most important ones and refer to the English retail versio ## BETA v1.0 * `BETA10` -> `LEGO1D.DLL` +* `CONFIGD` -> `CONFIG.EXE` The Beta 1.0 version contains a debug build of the game. While it does not have debug symbols, it still has a number of benefits: * It is built with less or no optimisation, leading to better decompilations in Ghidra @@ -32,6 +33,8 @@ It is therefore advisable to search for the corresponding function in `BETA10` w Unfortunately, some code has been changed after this beta version was created. Therefore, we are not aiming for a perfect binary match of `BETA10`. In case of discrepancies, `LEGO1` (as defined above) is our "gold standard" for matching. +The beta version of the `CONFIG` application has provided some help with matching [MFC handler functions](https://en.wikipedia.org/wiki/Microsoft_Foundation_Class_Library) that are similar to the final version. + ## Pre-Alpha * `ALPHA` -> `LEGO1D.DLL` From 42bac60ec58163705dfffda876600d739f6488cc Mon Sep 17 00:00:00 2001 From: Christian Semmler Date: Sat, 12 Jul 2025 19:13:37 -0700 Subject: [PATCH 124/188] Add new touch control scheme ("gamepad") (#587) * Add new touch control scheme * Add export * Fix enum naming --- ISLE/isleapp.cpp | 12 +- ISLE/isleapp.h | 3 + .../include/legoeventnotificationparam.h | 1 + .../lego/legoomni/include/legoinputmanager.h | 13 ++ .../src/entity/legocameracontroller.cpp | 4 + .../legoomni/src/input/legoinputmanager.cpp | 115 +++++++++++++----- 6 files changed, 114 insertions(+), 34 deletions(-) diff --git a/ISLE/isleapp.cpp b/ISLE/isleapp.cpp index d898e48b..fb511569 100644 --- a/ISLE/isleapp.cpp +++ b/ISLE/isleapp.cpp @@ -172,6 +172,7 @@ IsleApp::IsleApp() m_maxAllowedExtras = m_islandQuality <= 1 ? 10 : 20; m_transitionType = MxTransitionManager::e_mosaic; m_cursorSensitivity = 4; + m_touchScheme = LegoInputManager::e_gamepad; } // FUNCTION: ISLE 0x4011a0 @@ -650,7 +651,12 @@ SDL_AppResult SDL_AppEvent(void* appstate, SDL_Event* event) float y = SDL_clamp(event->tfinger.y, 0, 1) * g_targetHeight; if (InputManager()) { - InputManager()->QueueEvent(c_notificationMouseMove, LegoEventNotificationParam::c_lButtonState, x, y, 0); + MxU8 modifier = LegoEventNotificationParam::c_lButtonState; + if (InputManager()->HandleTouchEvent(event, g_isle->GetTouchScheme())) { + modifier |= LegoEventNotificationParam::c_motionHandled; + } + + InputManager()->QueueEvent(c_notificationMouseMove, modifier, x, y, 0); } g_lastMouseX = x; @@ -691,6 +697,7 @@ SDL_AppResult SDL_AppEvent(void* appstate, SDL_Event* event) float y = SDL_clamp(event->tfinger.y, 0, 1) * g_targetHeight; if (InputManager()) { + InputManager()->HandleTouchEvent(event, g_isle->GetTouchScheme()); InputManager()->QueueEvent(c_notificationButtonDown, LegoEventNotificationParam::c_lButtonState, x, y, 0); } @@ -738,6 +745,7 @@ SDL_AppResult SDL_AppEvent(void* appstate, SDL_Event* event) float y = SDL_clamp(event->tfinger.y, 0, 1) * g_targetHeight; if (InputManager()) { + InputManager()->HandleTouchEvent(event, g_isle->GetTouchScheme()); InputManager()->QueueEvent(c_notificationButtonUp, 0, x, y, 0); } break; @@ -1033,6 +1041,7 @@ bool IsleApp::LoadConfig() iniparser_set(dict, "isle:Max LOD", buf); iniparser_set(dict, "isle:Max Allowed Extras", SDL_itoa(m_maxAllowedExtras, buf, 10)); iniparser_set(dict, "isle:Transition Type", SDL_itoa(m_transitionType, buf, 10)); + iniparser_set(dict, "isle:Touch Scheme", SDL_itoa(m_touchScheme, buf, 10)); #ifdef EXTENSIONS iniparser_set(dict, "extensions", NULL); @@ -1103,6 +1112,7 @@ bool IsleApp::LoadConfig() m_maxAllowedExtras = iniparser_getint(dict, "isle:Max Allowed Extras", m_maxAllowedExtras); m_transitionType = (MxTransitionManager::TransitionType) iniparser_getint(dict, "isle:Transition Type", m_transitionType); + m_touchScheme = (LegoInputManager::TouchScheme) iniparser_getint(dict, "isle:Touch Scheme", m_touchScheme); const char* deviceId = iniparser_getstring(dict, "isle:3D Device ID", NULL); if (deviceId != NULL) { diff --git a/ISLE/isleapp.h b/ISLE/isleapp.h index 7054f6d1..3322427e 100644 --- a/ISLE/isleapp.h +++ b/ISLE/isleapp.h @@ -3,6 +3,7 @@ #include "cursor.h" #include "lego1_export.h" +#include "legoinputmanager.h" #include "legoutils.h" #include "mxtransitionmanager.h" #include "mxtypes.h" @@ -53,6 +54,7 @@ class IsleApp { MxS32 GetDrawCursor() { return m_drawCursor; } MxS32 GetGameStarted() { return m_gameStarted; } MxFloat GetCursorSensitivity() { return m_cursorSensitivity; } + LegoInputManager::TouchScheme GetTouchScheme() { return m_touchScheme; } void SetWindowActive(MxS32 p_windowActive) { m_windowActive = p_windowActive; } void SetGameStarted(MxS32 p_gameStarted) { m_gameStarted = p_gameStarted; } @@ -102,6 +104,7 @@ class IsleApp { MxFloat m_maxLod; MxU32 m_maxAllowedExtras; MxTransitionManager::TransitionType m_transitionType; + LegoInputManager::TouchScheme m_touchScheme; }; extern IsleApp* g_isle; diff --git a/LEGO1/lego/legoomni/include/legoeventnotificationparam.h b/LEGO1/lego/legoomni/include/legoeventnotificationparam.h index a88c62f0..5569280c 100644 --- a/LEGO1/lego/legoomni/include/legoeventnotificationparam.h +++ b/LEGO1/lego/legoomni/include/legoeventnotificationparam.h @@ -18,6 +18,7 @@ class LegoEventNotificationParam : public MxNotificationParam { c_rButtonState = 2, c_modKey1 = 4, c_modKey2 = 8, + c_motionHandled = 16, }; // FUNCTION: LEGO1 0x10028690 diff --git a/LEGO1/lego/legoomni/include/legoinputmanager.h b/LEGO1/lego/legoomni/include/legoinputmanager.h index 5f3052c1..30170822 100644 --- a/LEGO1/lego/legoomni/include/legoinputmanager.h +++ b/LEGO1/lego/legoomni/include/legoinputmanager.h @@ -18,6 +18,8 @@ #include #endif +#include + class LegoCameraController; class LegoControlManager; class LegoWorld; @@ -89,6 +91,12 @@ class LegoInputManager : public MxPresenter { c_upOrDown = c_up | c_down }; + enum TouchScheme { + e_mouse = 0, + e_arrowKeys, + e_gamepad, + }; + LegoInputManager(); ~LegoInputManager() override; @@ -144,6 +152,7 @@ class LegoInputManager : public MxPresenter { void GetKeyboardState(); MxResult GetNavigationKeyStates(MxU32& p_keyFlags); MxResult GetNavigationTouchStates(MxU32& p_keyFlags); + LEGO1_EXPORT MxBool HandleTouchEvent(SDL_Event* p_event, TouchScheme p_touchScheme); // SYNTHETIC: LEGO1 0x1005b8d0 // LegoInputManager::`scalar deleting destructor' @@ -171,6 +180,10 @@ class LegoInputManager : public MxPresenter { MxBool m_useJoystick; // 0x334 MxBool m_unk0x335; // 0x335 MxBool m_unk0x336; // 0x336 + + std::map m_touchOrigins; + std::map m_touchFlags; + std::map m_touchLastMotion; }; // TEMPLATE: LEGO1 0x10028850 diff --git a/LEGO1/lego/legoomni/src/entity/legocameracontroller.cpp b/LEGO1/lego/legoomni/src/entity/legocameracontroller.cpp index 563ba615..3e977d5b 100644 --- a/LEGO1/lego/legoomni/src/entity/legocameracontroller.cpp +++ b/LEGO1/lego/legoomni/src/entity/legocameracontroller.cpp @@ -41,6 +41,10 @@ MxResult LegoCameraController::Create() // FUNCTION: BETA10 0x10067852 MxLong LegoCameraController::Notify(MxParam& p_param) { + if (((LegoEventNotificationParam&) p_param).GetModifier() & LegoEventNotificationParam::c_motionHandled) { + return SUCCESS; + } + switch (((MxNotificationParam&) p_param).GetNotification()) { case c_notificationDragEnd: { if (((((LegoEventNotificationParam&) p_param).GetModifier()) & LegoEventNotificationParam::c_lButtonState) == diff --git a/LEGO1/lego/legoomni/src/input/legoinputmanager.cpp b/LEGO1/lego/legoomni/src/input/legoinputmanager.cpp index 27b0f814..ddcadaae 100644 --- a/LEGO1/lego/legoomni/src/input/legoinputmanager.cpp +++ b/LEGO1/lego/legoomni/src/input/legoinputmanager.cpp @@ -528,42 +528,91 @@ void LegoInputManager::EnableInputProcessing() MxResult LegoInputManager::GetNavigationTouchStates(MxU32& p_keyStates) { - int count; - SDL_TouchID* touchDevices = SDL_GetTouchDevices(&count); + for (auto& [fingerID, touchFlags] : m_touchFlags) { + p_keyStates |= touchFlags; - if (touchDevices) { - auto applyFingerNavigation = [&p_keyStates](SDL_TouchID p_touchId) { - int count; - SDL_Finger** fingers = SDL_GetTouchFingers(p_touchId, &count); - - if (fingers) { - for (int i = 0; i < count; i++) { - if (fingers[i]->y > 3.0 / 4.0) { - if (fingers[i]->x < 1.0 / 3.0) { - p_keyStates |= c_left; - } - else if (fingers[i]->x > 2.0 / 3.0) { - p_keyStates |= c_right; - } - else { - p_keyStates |= c_down; - } - } - else { - p_keyStates |= c_up; - } - } - - SDL_free(fingers); - } - }; - - for (int i = 0; i < count; i++) { - applyFingerNavigation(touchDevices[i]); + // We need to clear these as they are not meant to be persistent in e_gamepad mode. + if (m_touchOrigins.count(fingerID) && SDL_GetTicks() - m_touchLastMotion[fingerID] > 5) { + touchFlags &= ~c_left; + touchFlags &= ~c_right; } - - SDL_free(touchDevices); } return SUCCESS; } + +MxBool LegoInputManager::HandleTouchEvent(SDL_Event* p_event, TouchScheme p_touchScheme) +{ + const SDL_TouchFingerEvent& event = p_event->tfinger; + + switch (p_touchScheme) { + case e_mouse: + // Handled in LegoCameraController + return FALSE; + case e_arrowKeys: + switch (p_event->type) { + case SDL_EVENT_FINGER_UP: + m_touchFlags.erase(event.fingerID); + break; + case SDL_EVENT_FINGER_DOWN: + case SDL_EVENT_FINGER_MOTION: + m_touchFlags[event.fingerID] = 0; + + if (event.y > 3.0 / 4.0) { + if (event.x < 1.0 / 3.0) { + m_touchFlags[event.fingerID] |= c_left; + } + else if (event.x > 2.0 / 3.0) { + m_touchFlags[event.fingerID] |= c_right; + } + else { + m_touchFlags[event.fingerID] |= c_down; + } + } + else { + m_touchFlags[event.fingerID] |= c_up; + } + break; + } + break; + case e_gamepad: + switch (p_event->type) { + case SDL_EVENT_FINGER_DOWN: + m_touchOrigins[event.fingerID] = {event.x, event.y}; + break; + case SDL_EVENT_FINGER_UP: + m_touchOrigins.erase(event.fingerID); + m_touchFlags.erase(event.fingerID); + break; + case SDL_EVENT_FINGER_MOTION: + if (m_touchOrigins.count(event.fingerID)) { + m_touchFlags[event.fingerID] = 0; + m_touchLastMotion[event.fingerID] = SDL_GetTicks(); + + const float deltaY = event.y - m_touchOrigins[event.fingerID].y; + const float activationThreshold = 0.05f; + + if (SDL_fabsf(deltaY) > activationThreshold) { + if (deltaY > 0) { + m_touchFlags[event.fingerID] |= c_down; + } + else if (deltaY < 0) { + m_touchFlags[event.fingerID] |= c_up; + } + } + + const float deltaX = event.dx; + if (deltaX > 0) { + m_touchFlags[event.fingerID] |= c_right; + } + else if (deltaX < 0) { + m_touchFlags[event.fingerID] |= c_left; + } + } + break; + } + break; + } + + return TRUE; +} From 232ef07b514ec870c0ba8faf4f6842ed7a381173 Mon Sep 17 00:00:00 2001 From: Christian Semmler Date: Sat, 12 Jul 2025 19:30:29 -0700 Subject: [PATCH 125/188] Allow extensions to define options (#590) * Allow extensions to define their own options * Fixes * Remove logging --- ISLE/isleapp.cpp | 18 ++++++++++++------ extensions/include/extensions/extensions.h | 3 ++- extensions/include/extensions/textureloader.h | 12 ++++++++++-- extensions/src/extensions.cpp | 4 +++- extensions/src/textureloader.cpp | 11 +++++++++++ 5 files changed, 38 insertions(+), 10 deletions(-) diff --git a/ISLE/isleapp.cpp b/ISLE/isleapp.cpp index fb511569..a78a9f71 100644 --- a/ISLE/isleapp.cpp +++ b/ISLE/isleapp.cpp @@ -1128,13 +1128,19 @@ bool IsleApp::LoadConfig() strcpy(m_savePath, savePath); #ifdef EXTENSIONS - std::vector keys; - keys.resize(iniparser_getsecnkeys(dict, "extensions")); - iniparser_getseckeys(dict, "extensions", keys.data()); - - for (const char* key : keys) { + for (const char* key : Extensions::availableExtensions) { if (iniparser_getboolean(dict, key, 0)) { - Extensions::Enable(key); + std::vector extensionKeys; + const char* section = SDL_strchr(key, ':') + 1; + extensionKeys.resize(iniparser_getsecnkeys(dict, section)); + iniparser_getseckeys(dict, section, extensionKeys.data()); + + std::map extensionDict; + for (const char* key : extensionKeys) { + extensionDict[key] = iniparser_getstring(dict, key, NULL); + } + + Extensions::Enable(key, std::move(extensionDict)); } } #endif diff --git a/extensions/include/extensions/extensions.h b/extensions/include/extensions/extensions.h index 215bf80a..6c6f4f99 100644 --- a/extensions/include/extensions/extensions.h +++ b/extensions/include/extensions/extensions.h @@ -3,6 +3,7 @@ #include "lego1_export.h" #include +#include #include #include @@ -10,7 +11,7 @@ namespace Extensions { constexpr const char* availableExtensions[] = {"extensions:texture loader"}; -LEGO1_EXPORT void Enable(const char* p_key); +LEGO1_EXPORT void Enable(const char* p_key, std::map p_options); template struct Extension { diff --git a/extensions/include/extensions/textureloader.h b/extensions/include/extensions/textureloader.h index 649c6112..c476c7ff 100644 --- a/extensions/include/extensions/textureloader.h +++ b/extensions/include/extensions/textureloader.h @@ -3,16 +3,24 @@ #include "extensions/extensions.h" #include "legotextureinfo.h" +#include +#include + namespace Extensions { class TextureLoader { public: + static void Initialize(); static bool PatchTexture(LegoTextureInfo* p_textureInfo); + + static std::map options; static bool enabled; -private: - static constexpr const char* texturePath = "/textures/"; + static constexpr std::array, 1> defaults = { + {{"texture loader:texture path", "/textures/"}} + }; +private: static SDL_Surface* FindTexture(const char* p_name); }; diff --git a/extensions/src/extensions.cpp b/extensions/src/extensions.cpp index e1d0c389..779c0547 100644 --- a/extensions/src/extensions.cpp +++ b/extensions/src/extensions.cpp @@ -4,12 +4,14 @@ #include -void Extensions::Enable(const char* p_key) +void Extensions::Enable(const char* p_key, std::map p_options) { for (const char* key : availableExtensions) { if (!SDL_strcasecmp(p_key, key)) { if (!SDL_strcasecmp(p_key, "extensions:texture loader")) { + TextureLoader::options = std::move(p_options); TextureLoader::enabled = true; + TextureLoader::Initialize(); } SDL_Log("Enabled extension: %s", p_key); diff --git a/extensions/src/textureloader.cpp b/extensions/src/textureloader.cpp index dd96105e..07b53253 100644 --- a/extensions/src/textureloader.cpp +++ b/extensions/src/textureloader.cpp @@ -2,8 +2,18 @@ using namespace Extensions; +std::map TextureLoader::options; bool TextureLoader::enabled = false; +void TextureLoader::Initialize() +{ + for (const auto& option : defaults) { + if (!options.count(option.first.data())) { + options[option.first.data()] = option.second; + } + } +} + bool TextureLoader::PatchTexture(LegoTextureInfo* p_textureInfo) { SDL_Surface* surface = FindTexture(p_textureInfo->m_name); @@ -88,6 +98,7 @@ bool TextureLoader::PatchTexture(LegoTextureInfo* p_textureInfo) SDL_Surface* TextureLoader::FindTexture(const char* p_name) { SDL_Surface* surface; + const char* texturePath = options["texture loader:texture path"].c_str(); MxString path = MxString(MxOmni::GetHD()) + texturePath + p_name + ".bmp"; path.MapPathToFilesystem(); From 657720c825490007ede73b7364ae13b17603abcc Mon Sep 17 00:00:00 2001 From: jonschz <17198703+jonschz@users.noreply.github.com> Date: Sun, 13 Jul 2025 16:50:20 +0200 Subject: [PATCH 126/188] Match `Act3List::FUN_100720d0` (#1622) --- LEGO1/lego/legoomni/include/act3.h | 2 +- LEGO1/lego/legoomni/src/worlds/act3.cpp | 75 +++++++++++++++---------- 2 files changed, 45 insertions(+), 32 deletions(-) diff --git a/LEGO1/lego/legoomni/include/act3.h b/LEGO1/lego/legoomni/include/act3.h index 040da125..28129b4b 100644 --- a/LEGO1/lego/legoomni/include/act3.h +++ b/LEGO1/lego/legoomni/include/act3.h @@ -47,7 +47,7 @@ class Act3List : private list { void Insert(MxS32 p_objectId, InsertMode p_option); void DeleteActionWrapper(); void Clear(); - void FUN_100720d0(MxU32 p_objectId); + void RemoveByObjectIdOrFirst(MxU32 p_objectId); private: undefined4 m_unk0x0c; // 0x0c diff --git a/LEGO1/lego/legoomni/src/worlds/act3.cpp b/LEGO1/lego/legoomni/src/worlds/act3.cpp index 8bfaacd4..d48a3980 100644 --- a/LEGO1/lego/legoomni/src/worlds/act3.cpp +++ b/LEGO1/lego/legoomni/src/worlds/act3.cpp @@ -172,51 +172,64 @@ void Act3List::Clear() } } +// Removes the element with the given objectId from the list, or the first if `p_objectId` is zero. // FUNCTION: LEGO1 0x100720d0 -void Act3List::FUN_100720d0(MxU32 p_objectId) +void Act3List::RemoveByObjectIdOrFirst(MxU32 p_objectId) { if (m_unk0x0c) { return; } MxU32 removed = FALSE; + Act3List::iterator it; + // This iterator appears to be unnecessary - maybe left in by accident, or it was used for assertions. + // Removing it decreases the match percentage. + Act3List::iterator unusedIterator; - if (!empty()) { - if (p_objectId != 0) { - for (Act3List::iterator it = begin(); it != end(); it++) { - if ((*it).m_hasStarted && (*it).m_objectId == p_objectId) { - erase(it); - removed = TRUE; - break; - } + if (empty()) { + return; + } + + if (!p_objectId) { + pop_front(); + removed = TRUE; + } + else { + for (it = begin(); it != end(); it++) { + // Removing this variable decreases the match, but replacing `*it` by `unused` below also does. + Act3ListElement& unused = *it; + + if ((*it).m_hasStarted && (*it).m_objectId == p_objectId) { + erase(it); + removed = TRUE; + break; } } - else { - pop_front(); - removed = TRUE; - } + } - if (removed && size() > 0) { - Act3List::iterator it = begin(); - Act3ListElement& firstItem = *(it++); + if (removed && size() > 0) { + it = begin(); + unusedIterator = it; + Act3ListElement& firstItem = front(); + it++; - for (; it != end(); it++) { - if ((*it).m_unk0x04 == 1) { - for (Act3List::iterator it2 = begin(); it2 != it;) { - if ((*it2).m_hasStarted) { - DeleteActionWrapper(); - return; - } - - it2 = erase(it2); + while (it != end()) { + if ((*it).m_unk0x04 == 1) { + for (Act3List::iterator it2 = begin(); it2 != it; erase(it2++)) { + if ((*it2).m_hasStarted) { + DeleteActionWrapper(); + return; } } } - if (!firstItem.m_hasStarted) { - firstItem.m_hasStarted = TRUE; - InvokeAction(Extra::e_start, *g_act3Script, firstItem.m_objectId, NULL); - } + it++; + unusedIterator++; + } + + if (!firstItem.m_hasStarted) { + firstItem.m_hasStarted = TRUE; + InvokeAction(Extra::e_start, *g_act3Script, firstItem.m_objectId, NULL); } } } @@ -613,7 +626,7 @@ MxLong Act3::Notify(MxParam& p_param) } while (length < (MxS32) sizeOfArray(m_helicopterDots)); } else { - m_unk0x4220.FUN_100720d0(param.GetAction()->GetObjectId()); + m_unk0x4220.RemoveByObjectIdOrFirst(param.GetAction()->GetObjectId()); } } break; @@ -633,7 +646,7 @@ MxLong Act3::Notify(MxParam& p_param) case c_notificationEndAnim: if (m_state->m_unk0x08 == 1) { assert(m_copter && m_brickster && m_cop1 && m_cop2); - m_unk0x4220.FUN_100720d0(0); + m_unk0x4220.RemoveByObjectIdOrFirst(0); m_state->m_unk0x08 = 0; Disable(TRUE, LegoOmni::c_disableInput | LegoOmni::c_disable3d | LegoOmni::c_clearScreen); m_copter->HandleClick(); From 40c1a40d2b1580563bff8d575bb1b2c9959775d2 Mon Sep 17 00:00:00 2001 From: jonschz <17198703+jonschz@users.noreply.github.com> Date: Sun, 13 Jul 2025 16:50:51 +0200 Subject: [PATCH 127/188] Match `FUN_1006b140()` and related functions (#1623) * Match on BETA10 * Possible improvement on Matrix4::Swap * Document entropy build result * Comments at at Matrix4::Invert --------- Co-authored-by: jonschz --- LEGO1/lego/legoomni/include/legoanimpresenter.h | 4 ++++ LEGO1/lego/legoomni/src/video/legoanimpresenter.cpp | 3 +++ LEGO1/realtime/matrix4d.inl.h | 12 ++++++++++-- 3 files changed, 17 insertions(+), 2 deletions(-) diff --git a/LEGO1/lego/legoomni/include/legoanimpresenter.h b/LEGO1/lego/legoomni/include/legoanimpresenter.h index a3d6cd60..802e8917 100644 --- a/LEGO1/lego/legoomni/include/legoanimpresenter.h +++ b/LEGO1/lego/legoomni/include/legoanimpresenter.h @@ -92,7 +92,11 @@ class LegoAnimPresenter : public MxVideoPresenter { const char* GetActionObjectName(); void SetCurrentWorld(LegoWorld* p_currentWorld) { m_currentWorld = p_currentWorld; } + + // FUNCTION: BETA10 0x1005aad0 void SetUnknown0x0cTo1() { m_unk0x9c = 1; } + + // FUNCTION: BETA10 0x1005ab00 void SetUnknown0xa0(Matrix4* p_unk0xa0) { m_unk0xa0 = p_unk0xa0; } LegoAnim* GetAnimation() { return m_anim; } diff --git a/LEGO1/lego/legoomni/src/video/legoanimpresenter.cpp b/LEGO1/lego/legoomni/src/video/legoanimpresenter.cpp index 033525f4..f966cf38 100644 --- a/LEGO1/lego/legoomni/src/video/legoanimpresenter.cpp +++ b/LEGO1/lego/legoomni/src/video/legoanimpresenter.cpp @@ -703,6 +703,9 @@ MxResult LegoAnimPresenter::FUN_1006b140(LegoROI* p_roi) if (p_roi == NULL) { return FAILURE; } +#ifdef BETA10 + MxMatrix unused_matrix; +#endif Matrix4* mn = new MxMatrix(); assert(mn); diff --git a/LEGO1/realtime/matrix4d.inl.h b/LEGO1/realtime/matrix4d.inl.h index b15e6135..c1643f76 100644 --- a/LEGO1/realtime/matrix4d.inl.h +++ b/LEGO1/realtime/matrix4d.inl.h @@ -290,6 +290,8 @@ void Matrix4::RotateZ(const float& p_angle) // FUNCTION: BETA10 0x1005a590 int Matrix4::Invert(Matrix4& p_mat) { + // Inlined at LEGO1 0x1006b2d3 + float copyData[4][4]; Matrix4 copy(copyData); copy = *this; @@ -312,6 +314,7 @@ int Matrix4::Invert(Matrix4& p_mat) } if (copy[i][i] < 0.001f && copy[i][i] > -0.001f) { + // FAILURE from mxtypes.h return -1; } @@ -349,14 +352,19 @@ int Matrix4::Invert(Matrix4& p_mat) } } + // SUCCESS from mxtypes.h return 0; } // FUNCTION: LEGO1 0x1006b500 +// FUNCTION: BETA10 0x1005aa20 void Matrix4::Swap(int p_d1, int p_d2) { - for (int i = 0; i < 4; i++) { - float e = m_data[p_d1][i]; + // This function is affected by entropy even in debug builds + int i; + float e; + for (i = 0; i < 4; i++) { + e = m_data[p_d1][i]; m_data[p_d1][i] = m_data[p_d2][i]; m_data[p_d2][i] = e; } From c9930d10f99961683db078fdba67e3e6b95461fa Mon Sep 17 00:00:00 2001 From: Christian Semmler Date: Sun, 13 Jul 2025 11:23:59 -0700 Subject: [PATCH 128/188] Refine gamepad touch controls (#591) --- .../lego/legoomni/include/legoinputmanager.h | 2 +- .../legoomni/src/input/legoinputmanager.cpp | 36 ++++++++++++------- 2 files changed, 24 insertions(+), 14 deletions(-) diff --git a/LEGO1/lego/legoomni/include/legoinputmanager.h b/LEGO1/lego/legoomni/include/legoinputmanager.h index 30170822..3d17df56 100644 --- a/LEGO1/lego/legoomni/include/legoinputmanager.h +++ b/LEGO1/lego/legoomni/include/legoinputmanager.h @@ -183,7 +183,7 @@ class LegoInputManager : public MxPresenter { std::map m_touchOrigins; std::map m_touchFlags; - std::map m_touchLastMotion; + std::map> m_touchLastMotion; }; // TEMPLATE: LEGO1 0x10028850 diff --git a/LEGO1/lego/legoomni/src/input/legoinputmanager.cpp b/LEGO1/lego/legoomni/src/input/legoinputmanager.cpp index ddcadaae..b0eae128 100644 --- a/LEGO1/lego/legoomni/src/input/legoinputmanager.cpp +++ b/LEGO1/lego/legoomni/src/input/legoinputmanager.cpp @@ -532,9 +532,14 @@ MxResult LegoInputManager::GetNavigationTouchStates(MxU32& p_keyStates) p_keyStates |= touchFlags; // We need to clear these as they are not meant to be persistent in e_gamepad mode. - if (m_touchOrigins.count(fingerID) && SDL_GetTicks() - m_touchLastMotion[fingerID] > 5) { - touchFlags &= ~c_left; - touchFlags &= ~c_right; + if (m_touchOrigins.count(fingerID) && m_touchLastMotion.count(fingerID)) { + const MxU32 inactivityThreshold = 3; + + if (m_touchLastMotion[fingerID].first++ > inactivityThreshold) { + touchFlags &= ~c_left; + touchFlags &= ~c_right; + m_touchOrigins[fingerID].x = m_touchLastMotion[fingerID].second.x; + } } } @@ -586,12 +591,11 @@ MxBool LegoInputManager::HandleTouchEvent(SDL_Event* p_event, TouchScheme p_touc break; case SDL_EVENT_FINGER_MOTION: if (m_touchOrigins.count(event.fingerID)) { - m_touchFlags[event.fingerID] = 0; - m_touchLastMotion[event.fingerID] = SDL_GetTicks(); + const float activationThreshold = 0.03f; + m_touchFlags[event.fingerID] &= ~c_down; + m_touchFlags[event.fingerID] &= ~c_up; const float deltaY = event.y - m_touchOrigins[event.fingerID].y; - const float activationThreshold = 0.05f; - if (SDL_fabsf(deltaY) > activationThreshold) { if (deltaY > 0) { m_touchFlags[event.fingerID] |= c_down; @@ -601,12 +605,18 @@ MxBool LegoInputManager::HandleTouchEvent(SDL_Event* p_event, TouchScheme p_touc } } - const float deltaX = event.dx; - if (deltaX > 0) { - m_touchFlags[event.fingerID] |= c_right; - } - else if (deltaX < 0) { - m_touchFlags[event.fingerID] |= c_left; + const float deltaX = event.x - m_touchOrigins[event.fingerID].x; + if (SDL_fabsf(deltaX) > activationThreshold && event.dx) { + if (event.dx > 0) { + m_touchFlags[event.fingerID] |= c_right; + m_touchFlags[event.fingerID] &= ~c_left; + } + else if (event.dx < 0) { + m_touchFlags[event.fingerID] |= c_left; + m_touchFlags[event.fingerID] &= ~c_right; + } + + m_touchLastMotion[event.fingerID] = {0, {event.x, event.y}}; } } break; From 356c64ce0ee510a1580049f83a64ac9e3c26146c Mon Sep 17 00:00:00 2001 From: Fabian Neundorf Date: Sun, 13 Jul 2025 21:01:06 +0200 Subject: [PATCH 129/188] Name state field, getter and setter in Act1State (#1621) --- LEGO1/lego/legoomni/include/isle.h | 21 ++- LEGO1/lego/legoomni/src/actors/ambulance.cpp | 4 +- LEGO1/lego/legoomni/src/actors/buildings.cpp | 18 +-- LEGO1/lego/legoomni/src/actors/bumpbouy.cpp | 2 +- LEGO1/lego/legoomni/src/actors/helicopter.cpp | 6 +- LEGO1/lego/legoomni/src/actors/jetski.cpp | 2 +- LEGO1/lego/legoomni/src/actors/pizza.cpp | 8 +- LEGO1/lego/legoomni/src/actors/skateboard.cpp | 6 +- LEGO1/lego/legoomni/src/actors/towtrack.cpp | 6 +- .../legoomni/src/common/legogamestate.cpp | 2 +- LEGO1/lego/legoomni/src/common/legoutils.cpp | 5 +- LEGO1/lego/legoomni/src/race/carrace.cpp | 8 +- LEGO1/lego/legoomni/src/race/jetskirace.cpp | 6 +- LEGO1/lego/legoomni/src/worlds/gasstation.cpp | 2 +- LEGO1/lego/legoomni/src/worlds/hospital.cpp | 4 +- LEGO1/lego/legoomni/src/worlds/isle.cpp | 126 +++++++++--------- LEGO1/lego/legoomni/src/worlds/jukebox.cpp | 2 +- 17 files changed, 122 insertions(+), 106 deletions(-) diff --git a/LEGO1/lego/legoomni/include/isle.h b/LEGO1/lego/legoomni/include/isle.h index 2c6c8091..53edd931 100644 --- a/LEGO1/lego/legoomni/include/isle.h +++ b/LEGO1/lego/legoomni/include/isle.h @@ -34,6 +34,21 @@ class Act1State : public LegoState { c_floor3 }; + enum { + e_none = 0, + e_initial = 1, + e_elevator = 2, + e_pizza = 3, + e_helicopter = 4, + e_transitionToJetski = 5, + e_transitionToRacecar = 6, + e_transitionToTowtrack = 7, + e_towtrack = 8, + e_transitionToAmbulance = 9, + e_ambulance = 10, + e_jukebox = 11, + }; + Act1State(); // FUNCTION: LEGO1 0x100338a0 @@ -58,11 +73,11 @@ class Act1State : public LegoState { void RemoveActors(); void PlaceActors(); - MxU32 GetUnknown18() { return m_unk0x018; } + MxU32 GetState() { return m_state; } ElevatorFloor GetElevatorFloor() { return (ElevatorFloor) m_elevFloor; } MxU8 GetUnknown21() { return m_unk0x021; } - void SetUnknown18(MxU32 p_unk0x18) { m_unk0x018 = p_unk0x18; } + void SetState(MxU32 p_state) { m_state = p_state; } void SetElevatorFloor(ElevatorFloor p_elevFloor) { m_elevFloor = p_elevFloor; } void SetUnknown21(MxU8 p_unk0x21) { m_unk0x021 = p_unk0x21; } @@ -73,7 +88,7 @@ class Act1State : public LegoState { Playlist m_cptClickDialogue; // 0x008 IsleScript::Script m_currentCptClickDialogue; // 0x014 - MxU32 m_unk0x018; // 0x018 + MxU32 m_state; // 0x018 MxS16 m_elevFloor; // 0x01c MxBool m_unk0x01e; // 0x01e MxBool m_unk0x01f; // 0x01f diff --git a/LEGO1/lego/legoomni/src/actors/ambulance.cpp b/LEGO1/lego/legoomni/src/actors/ambulance.cpp index b439db66..fa98fc65 100644 --- a/LEGO1/lego/legoomni/src/actors/ambulance.cpp +++ b/LEGO1/lego/legoomni/src/actors/ambulance.cpp @@ -364,7 +364,7 @@ MxLong Ambulance::HandlePathStruct(LegoPathStructNotificationParam& p_param) // FUNCTION: BETA10 0x10023506 MxLong Ambulance::HandleClick() { - if (((Act1State*) GameState()->GetState("Act1State"))->m_unk0x018 != 10) { + if (((Act1State*) GameState()->GetState("Act1State"))->m_state != Act1State::e_ambulance) { return 1; } @@ -575,7 +575,7 @@ void Ambulance::Reset() { StopAction(m_lastAction); BackgroundAudioManager()->RaiseVolume(); - ((Act1State*) GameState()->GetState("Act1State"))->m_unk0x018 = 0; + ((Act1State*) GameState()->GetState("Act1State"))->m_state = Act1State::e_none; m_state->m_state = AmbulanceMissionState::e_ready; m_atBeachTask = 0; m_atPoliceTask = 0; diff --git a/LEGO1/lego/legoomni/src/actors/buildings.cpp b/LEGO1/lego/legoomni/src/actors/buildings.cpp index 833287bc..b9e736c4 100644 --- a/LEGO1/lego/legoomni/src/actors/buildings.cpp +++ b/LEGO1/lego/legoomni/src/actors/buildings.cpp @@ -53,7 +53,7 @@ MxLong InfoCenterEntity::HandleClick(LegoEventNotificationParam& p_param) isle->SetDestLocation(LegoGameState::Area::e_infomain); Act1State* act1state = (Act1State*) GameState()->GetState("Act1State"); - act1state->SetUnknown18(0); + act1state->SetState(Act1State::e_none); break; } case LegoGameState::Act::e_act2: { @@ -83,8 +83,8 @@ MxLong GasStationEntity::HandleClick(LegoEventNotificationParam& p_param) if (CanExit()) { Act1State* state = (Act1State*) GameState()->GetState("Act1State"); - if (state->GetUnknown18() != 8) { - state->SetUnknown18(0); + if (state->GetState() != Act1State::e_towtrack) { + state->SetState(Act1State::e_none); if (UserActor()->GetActorId() != GameState()->GetActorId()) { ((IslePathActor*) UserActor())->Exit(); @@ -107,8 +107,8 @@ MxLong HospitalEntity::HandleClick(LegoEventNotificationParam& p_param) if (CanExit()) { Act1State* act1State = (Act1State*) GameState()->GetState("Act1State"); - if (act1State->GetUnknown18() != 10) { - act1State->SetUnknown18(0); + if (act1State->GetState() != Act1State::e_ambulance) { + act1State->SetState(Act1State::e_none); if (UserActor()->GetActorId() != GameState()->GetActorId()) { ((IslePathActor*) UserActor())->Exit(); @@ -131,8 +131,8 @@ MxLong PoliceEntity::HandleClick(LegoEventNotificationParam& p_param) if (CanExit()) { Act1State* state = (Act1State*) GameState()->GetState("Act1State"); - if (state->GetUnknown18() != 10) { - state->SetUnknown18(0); + if (state->GetState() != Act1State::e_ambulance) { + state->SetState(Act1State::e_none); if (UserActor()->GetActorId() != GameState()->GetActorId()) { ((IslePathActor*) UserActor())->Exit(); @@ -154,7 +154,7 @@ MxLong BeachHouseEntity::HandleClick(LegoEventNotificationParam& p_param) { if (CanExit()) { Act1State* state = (Act1State*) GameState()->GetState("Act1State"); - state->SetUnknown18(0); + state->SetState(Act1State::e_none); if (UserActor()->GetActorId() != GameState()->GetActorId()) { ((IslePathActor*) UserActor())->Exit(); @@ -175,7 +175,7 @@ MxLong RaceStandsEntity::HandleClick(LegoEventNotificationParam& p_param) { if (CanExit()) { Act1State* state = (Act1State*) GameState()->GetState("Act1State"); - state->SetUnknown18(0); + state->SetState(Act1State::e_none); if (UserActor()->GetActorId() != GameState()->GetActorId()) { ((IslePathActor*) UserActor())->Exit(); diff --git a/LEGO1/lego/legoomni/src/actors/bumpbouy.cpp b/LEGO1/lego/legoomni/src/actors/bumpbouy.cpp index 371e4257..03c93f7b 100644 --- a/LEGO1/lego/legoomni/src/actors/bumpbouy.cpp +++ b/LEGO1/lego/legoomni/src/actors/bumpbouy.cpp @@ -45,7 +45,7 @@ MxLong BumpBouy::Notify(MxParam& p_param) Act1State* isleState = (Act1State*) GameState()->GetState("Act1State"); assert(isleState); - isleState->m_unk0x018 = 5; + isleState->m_state = Act1State::e_transitionToJetski; Isle* isle = (Isle*) FindWorld(*g_isleScript, IsleScript::c__Isle); assert(isle); diff --git a/LEGO1/lego/legoomni/src/actors/helicopter.cpp b/LEGO1/lego/legoomni/src/actors/helicopter.cpp index 622fe71e..46353585 100644 --- a/LEGO1/lego/legoomni/src/actors/helicopter.cpp +++ b/LEGO1/lego/legoomni/src/actors/helicopter.cpp @@ -214,7 +214,7 @@ MxLong Helicopter::HandleControl(LegoControlManagerNotificationParam& p_param) Act1State* act1State = (Act1State*) GameState()->GetState("Act1State"); assert(act1State); if (m_state->m_unk0x08 == 0) { - act1State->m_unk0x018 = 4; + act1State->m_state = Act1State::e_helicopter; m_state->m_unk0x08 = 1; m_world->RemoveActor(this); InvokeAction(Extra::ActionType::e_start, script, IsleScript::c_HelicopterTakeOff_Anim, NULL); @@ -318,7 +318,7 @@ MxLong Helicopter::HandleEndAnim(LegoEndAnimNotificationParam& p_param) if (GameState()->GetCurrentAct() == LegoGameState::e_act1) { Act1State* act1State = (Act1State*) GameState()->GetState("Act1State"); assert(act1State); - act1State->m_unk0x018 = 4; + act1State->m_state = Act1State::e_helicopter; SpawnPlayer( LegoGameState::e_unk42, TRUE, @@ -359,7 +359,7 @@ MxLong Helicopter::HandleEndAnim(LegoEndAnimNotificationParam& p_param) if (GameState()->GetCurrentAct() == LegoGameState::e_act1) { Act1State* act1State = (Act1State*) GameState()->GetState("Act1State"); assert(act1State); - act1State->m_unk0x018 = 0; + act1State->m_state = Act1State::e_none; SpawnPlayer( LegoGameState::e_unk41, TRUE, diff --git a/LEGO1/lego/legoomni/src/actors/jetski.cpp b/LEGO1/lego/legoomni/src/actors/jetski.cpp index caf3d1f8..2abc3862 100644 --- a/LEGO1/lego/legoomni/src/actors/jetski.cpp +++ b/LEGO1/lego/legoomni/src/actors/jetski.cpp @@ -162,7 +162,7 @@ void Jetski::ActivateSceneActions() PlayMusic(JukeboxScript::c_JetskiRace_Music); Act1State* act1state = (Act1State*) GameState()->GetState("Act1State"); - if (!act1state->m_unk0x018) { + if (!act1state->m_state) { if (act1state->m_unk0x022) { PlayCamAnim(this, FALSE, 68, TRUE); } diff --git a/LEGO1/lego/legoomni/src/actors/pizza.cpp b/LEGO1/lego/legoomni/src/actors/pizza.cpp index 18da7fca..422cf87f 100644 --- a/LEGO1/lego/legoomni/src/actors/pizza.cpp +++ b/LEGO1/lego/legoomni/src/actors/pizza.cpp @@ -182,7 +182,7 @@ void Pizza::FUN_10038220(IsleScript::Script p_objectId) AnimationManager()->FUN_10064740(NULL); m_mission = m_state->GetMission(GameState()->GetActorId()); m_state->m_unk0x0c = 1; - m_act1state->m_unk0x018 = 3; + m_act1state->m_state = Act1State::e_pizza; m_mission->m_startTime = INT_MIN; g_isleFlags &= ~Isle::c_playMusic; AnimationManager()->EnableCamAnims(FALSE); @@ -200,7 +200,7 @@ void Pizza::FUN_100382b0() InvokeAction(Extra::e_stop, *g_isleScript, m_speechAction, NULL); } - m_act1state->m_unk0x018 = 0; + m_act1state->m_state = Act1State::e_none; m_state->m_unk0x0c = 0; UserActor()->SetActorState(LegoPathActor::c_initial); g_isleFlags |= Isle::c_playMusic; @@ -245,7 +245,7 @@ MxLong Pizza::HandleClick() } if (m_state->m_unk0x0c == 2) { - m_act1state->m_unk0x018 = 3; + m_act1state->m_state = Act1State::e_pizza; if (m_skateBoard == NULL) { m_skateBoard = (SkateBoard*) m_world->Find(m_atomId, IsleScript::c_SkateBoard_Actor); @@ -558,7 +558,7 @@ MxLong Pizza::HandleEndAction(MxEndActionNotificationParam& p_param) break; case 8: if (m_state->GetPlayedAction() == objectId) { - m_act1state->m_unk0x018 = 0; + m_act1state->m_state = Act1State::e_none; m_state->m_unk0x0c = 0; GameState()->m_currentArea = LegoGameState::e_isle; TickleManager()->UnregisterClient(this); diff --git a/LEGO1/lego/legoomni/src/actors/skateboard.cpp b/LEGO1/lego/legoomni/src/actors/skateboard.cpp index 12524111..d47b2178 100644 --- a/LEGO1/lego/legoomni/src/actors/skateboard.cpp +++ b/LEGO1/lego/legoomni/src/actors/skateboard.cpp @@ -56,7 +56,7 @@ MxResult SkateBoard::Create(MxDSAction& p_dsAction) // FUNCTION: LEGO1 0x10010050 void SkateBoard::Exit() { - if (m_act1state->m_unk0x018 == 3) { + if (m_act1state->m_state == Act1State::e_pizza) { Pizza* pizza = (Pizza*) CurrentWorld()->Find(*g_isleScript, IsleScript::c_Pizza_Actor); pizza->StopActions(); pizza->FUN_100382b0(); @@ -75,7 +75,7 @@ MxLong SkateBoard::HandleClick() { Act1State* state = (Act1State*) GameState()->GetState("Act1State"); - if (!CanExit() && state->m_unk0x018 != 3) { + if (!CanExit() && state->m_state != Act1State::e_pizza) { return 1; } @@ -148,7 +148,7 @@ MxLong SkateBoard::HandleNotification0() // FUNCTION: LEGO1 0x10010510 void SkateBoard::ActivateSceneActions() { - if (m_act1state->m_unk0x018 != 3) { + if (m_act1state->m_state != Act1State::e_pizza) { PlayMusic(JukeboxScript::c_BeachBlvd_Music); if (!m_act1state->m_unk0x022) { diff --git a/LEGO1/lego/legoomni/src/actors/towtrack.cpp b/LEGO1/lego/legoomni/src/actors/towtrack.cpp index bb117e5f..5d62fedc 100644 --- a/LEGO1/lego/legoomni/src/actors/towtrack.cpp +++ b/LEGO1/lego/legoomni/src/actors/towtrack.cpp @@ -272,7 +272,7 @@ MxLong TowTrack::HandleEndAction(MxEndActionNotificationParam& p_param) m_state->UpdateScore(LegoState::e_yellow, m_actorId); } else if (objectId == IsleScript::c_wgs098nu_RunAnim || objectId == IsleScript::c_wgs099nu_RunAnim || objectId == IsleScript::c_wgs100nu_RunAnim || objectId == IsleScript::c_wgs101nu_RunAnim || objectId == IsleScript::c_wgs102nu_RunAnim || objectId == IsleScript::c_wgs085nu_RunAnim || objectId == IsleScript::c_wgs086nu_RunAnim || objectId == IsleScript::c_wgs087nu_RunAnim || objectId == IsleScript::c_wgs088nu_RunAnim || objectId == IsleScript::c_wgs089nu_RunAnim || objectId == IsleScript::c_wgs091nu_RunAnim || objectId == IsleScript::c_wgs092nu_RunAnim || objectId == IsleScript::c_wgs093nu_RunAnim || objectId == IsleScript::c_wgs094nu_RunAnim || objectId == IsleScript::c_wgs095nu_RunAnim) { - ((Act1State*) GameState()->GetState("Act1State"))->m_unk0x018 = 0; + ((Act1State*) GameState()->GetState("Act1State"))->m_state = Act1State::e_none; AnimationManager()->FUN_1005f6d0(TRUE); g_isleFlags |= Isle::c_playMusic; AnimationManager()->EnableCamAnims(TRUE); @@ -403,7 +403,7 @@ MxLong TowTrack::HandlePathStruct(LegoPathStructNotificationParam& p_param) // FUNCTION: LEGO1 0x1004d690 MxLong TowTrack::HandleClick() { - if (((Act1State*) GameState()->GetState("Act1State"))->m_unk0x018 != 8) { + if (((Act1State*) GameState()->GetState("Act1State"))->m_state != Act1State::e_towtrack) { return 1; } @@ -549,7 +549,7 @@ void TowTrack::FUN_1004dbe0() InvokeAction(Extra::e_stop, *g_isleScript, m_lastAction, NULL); } - ((Act1State*) GameState()->GetState("Act1State"))->m_unk0x018 = 0; + ((Act1State*) GameState()->GetState("Act1State"))->m_state = Act1State::e_none; m_state->m_unk0x08 = 0; g_isleFlags |= Isle::c_playMusic; AnimationManager()->EnableCamAnims(TRUE); diff --git a/LEGO1/lego/legoomni/src/common/legogamestate.cpp b/LEGO1/lego/legoomni/src/common/legogamestate.cpp index 3dbe88bc..eae457ed 100644 --- a/LEGO1/lego/legoomni/src/common/legogamestate.cpp +++ b/LEGO1/lego/legoomni/src/common/legogamestate.cpp @@ -952,7 +952,7 @@ void LegoGameState::SwitchArea(Area p_area) Act1State* state = (Act1State*) GameState()->GetState("Act1State"); LoadIsle(); - if (state->GetUnknown18() == 7) { + if (state->GetState() == Act1State::e_transitionToTowtrack) { VideoManager()->Get3DManager()->SetFrustrum(90, 0.1f, 250.0f); } else { diff --git a/LEGO1/lego/legoomni/src/common/legoutils.cpp b/LEGO1/lego/legoomni/src/common/legoutils.cpp index d5ecb1ad..2f9b1daf 100644 --- a/LEGO1/lego/legoomni/src/common/legoutils.cpp +++ b/LEGO1/lego/legoomni/src/common/legoutils.cpp @@ -583,9 +583,10 @@ MxBool CanExit() GameState()->m_currentArea != LegoGameState::e_polidoor) { if (UserActor() == NULL || !UserActor()->IsA("TowTrack")) { if (UserActor() == NULL || !UserActor()->IsA("Ambulance")) { - MxU32 unk0x18 = act1State->GetUnknown18(); + MxU32 mission = act1State->GetState(); - if (unk0x18 != 10 && unk0x18 != 8 && unk0x18 != 3) { + if (mission != Act1State::e_ambulance && mission != Act1State::e_towtrack && + mission != Act1State::e_pizza) { return TRUE; } } diff --git a/LEGO1/lego/legoomni/src/race/carrace.cpp b/LEGO1/lego/legoomni/src/race/carrace.cpp index fb4cc192..7a9cfef2 100644 --- a/LEGO1/lego/legoomni/src/race/carrace.cpp +++ b/LEGO1/lego/legoomni/src/race/carrace.cpp @@ -95,7 +95,7 @@ MxResult CarRace::Create(MxDSAction& p_dsAction) m_raceState = raceState; - m_act1State->m_unk0x018 = 6; + m_act1State->m_state = Act1State::e_transitionToRacecar; m_unk0x144 = -1; m_unk0x148 = -1; m_unk0x14c = -1; @@ -339,7 +339,7 @@ MxLong CarRace::HandleControl(LegoControlManagerNotificationParam& p_param) switch (p_param.m_clickedObjectId) { case 3: InvokeAction(Extra::e_stop, *g_carraceScript, CarraceScript::c_irtx08ra_PlayWav, NULL); - m_act1State->m_unk0x018 = 0; + m_act1State->m_state = Act1State::e_none; VariableTable()->SetVariable(g_raceState, ""); VariableTable()->SetVariable(g_strHIT_WALL_SOUND, ""); NavController()->SetDeadZone(NavController()->GetDefaultDeadZone()); @@ -351,7 +351,7 @@ MxLong CarRace::HandleControl(LegoControlManagerNotificationParam& p_param) break; case 98: InvokeAction(Extra::e_stop, *g_carraceScript, CarraceScript::c_irtx08ra_PlayWav, NULL); - m_act1State->m_unk0x018 = 0; + m_act1State->m_state = Act1State::e_none; VariableTable()->SetVariable(g_raceState, ""); VariableTable()->SetVariable(g_strHIT_WALL_SOUND, ""); NavController()->SetDeadZone(NavController()->GetDefaultDeadZone()); @@ -413,7 +413,7 @@ MxBool CarRace::Escape() AnimationManager()->FUN_10061010(FALSE); DeleteObjects(&m_atomId, 500, 999); - m_act1State->m_unk0x018 = 0; + m_act1State->m_state = Act1State::e_none; VariableTable()->SetVariable(g_strHIT_WALL_SOUND, ""); VariableTable()->SetVariable(g_raceState, ""); diff --git a/LEGO1/lego/legoomni/src/race/jetskirace.cpp b/LEGO1/lego/legoomni/src/race/jetskirace.cpp index eddb7266..71746cd0 100644 --- a/LEGO1/lego/legoomni/src/race/jetskirace.cpp +++ b/LEGO1/lego/legoomni/src/race/jetskirace.cpp @@ -127,7 +127,7 @@ MxLong JetskiRace::HandleControl(LegoControlManagerNotificationParam& p_param) if (p_param.m_unk0x28 == 1) { switch (p_param.m_clickedObjectId) { case JetraceScript::c_JetskiArms_Ctl: - m_act1State->m_unk0x018 = 0; + m_act1State->m_state = Act1State::e_none; VariableTable()->SetVariable(g_raceState, ""); VariableTable()->SetVariable(g_strHIT_WALL_SOUND, ""); LegoRaceCar::InitYouCantStopSound(); @@ -135,7 +135,7 @@ MxLong JetskiRace::HandleControl(LegoControlManagerNotificationParam& p_param) TransitionManager()->StartTransition(MxTransitionManager::e_mosaic, 50, FALSE, FALSE); break; case JetraceScript::c_JetskiInfo_Ctl: - m_act1State->m_unk0x018 = 0; + m_act1State->m_state = Act1State::e_none; VariableTable()->SetVariable(g_raceState, ""); VariableTable()->SetVariable(g_strHIT_WALL_SOUND, ""); LegoRaceCar::InitYouCantStopSound(); @@ -288,7 +288,7 @@ MxBool JetskiRace::Escape() { AnimationManager()->FUN_10061010(FALSE); DeleteObjects(&m_atomId, 500, 999); - m_act1State->m_unk0x018 = 0; + m_act1State->m_state = Act1State::e_none; VariableTable()->SetVariable(g_raceState, ""); VariableTable()->SetVariable(g_strHIT_WALL_SOUND, ""); m_destLocation = LegoGameState::e_infomain; diff --git a/LEGO1/lego/legoomni/src/worlds/gasstation.cpp b/LEGO1/lego/legoomni/src/worlds/gasstation.cpp index 40e757c7..4a449ba2 100644 --- a/LEGO1/lego/legoomni/src/worlds/gasstation.cpp +++ b/LEGO1/lego/legoomni/src/worlds/gasstation.cpp @@ -328,7 +328,7 @@ MxLong GasStation::HandleEndAction(MxEndActionNotificationParam& p_param) break; case GasStationState::e_afterAcceptingQuest: m_state->m_state = GasStationState::e_beforeExitingForQuest; - ((Act1State*) GameState()->GetState("Act1State"))->m_unk0x018 = 7; + ((Act1State*) GameState()->GetState("Act1State"))->m_state = Act1State::e_transitionToTowtrack; m_destLocation = LegoGameState::e_garageExited; m_radio.Stop(); BackgroundAudioManager()->Stop(); diff --git a/LEGO1/lego/legoomni/src/worlds/hospital.cpp b/LEGO1/lego/legoomni/src/worlds/hospital.cpp index 74d15678..6ab9c218 100644 --- a/LEGO1/lego/legoomni/src/worlds/hospital.cpp +++ b/LEGO1/lego/legoomni/src/worlds/hospital.cpp @@ -365,7 +365,7 @@ MxLong Hospital::HandleEndAction(MxEndActionNotificationParam& p_param) case HospitalState::e_afterAcceptingQuest: m_hospitalState->m_state = HospitalState::e_beforeEnteringAmbulance; act1State = (Act1State*) GameState()->GetState("Act1State"); - act1State->SetUnknown18(9); + act1State->SetState(Act1State::e_transitionToAmbulance); case HospitalState::e_exitToFront: if (m_exited == FALSE) { m_exited = TRUE; @@ -419,7 +419,7 @@ MxLong Hospital::HandleButtonDown(LegoControlManagerNotificationParam& p_param) Act1State* act1State = (Act1State*) GameState()->GetState("Act1State"); assert(act1State); - act1State->m_unk0x018 = 9; + act1State->m_state = Act1State::e_transitionToAmbulance; m_destLocation = LegoGameState::e_hospitalExited; DeleteObjects( diff --git a/LEGO1/lego/legoomni/src/worlds/isle.cpp b/LEGO1/lego/legoomni/src/worlds/isle.cpp index 2b630be4..aeedd601 100644 --- a/LEGO1/lego/legoomni/src/worlds/isle.cpp +++ b/LEGO1/lego/legoomni/src/worlds/isle.cpp @@ -142,11 +142,11 @@ MxLong Isle::Notify(MxParam& p_param) break; case c_notificationButtonUp: case c_notificationButtonDown: - switch (m_act1state->m_unk0x018) { - case 3: + switch (m_act1state->m_state) { + case Act1State::e_pizza: result = m_pizza->Notify(p_param); break; - case 10: + case Act1State::e_ambulance: result = m_ambulance->Notify(p_param); break; } @@ -155,14 +155,14 @@ MxLong Isle::Notify(MxParam& p_param) result = HandleControl((LegoControlManagerNotificationParam&) p_param); break; case c_notificationEndAnim: - switch (m_act1state->m_unk0x018) { - case 4: + switch (m_act1state->m_state) { + case Act1State::e_helicopter: result = UserActor()->Notify(p_param); break; - case 8: + case Act1State::e_towtrack: result = m_towtrack->Notify(p_param); break; - case 10: + case Act1State::e_ambulance: result = m_ambulance->Notify(p_param); break; } @@ -187,18 +187,18 @@ MxLong Isle::HandleEndAction(MxEndActionNotificationParam& p_param) { MxLong result; - switch (m_act1state->m_unk0x018) { - case 2: + switch (m_act1state->m_state) { + case Act1State::e_elevator: HandleElevatorEndAction(); result = 1; break; - case 3: + case Act1State::e_pizza: result = m_pizza->Notify(p_param); break; - case 8: + case Act1State::e_towtrack: result = m_towtrack->Notify(p_param); break; - case 10: + case Act1State::e_ambulance: result = m_ambulance->Notify(p_param); break; default: @@ -241,12 +241,12 @@ void Isle::HandleElevatorEndAction() case Act1State::c_floor1: m_destLocation = LegoGameState::e_infomain; TransitionManager()->StartTransition(MxTransitionManager::e_mosaic, 50, FALSE, FALSE); - m_act1state->m_unk0x018 = 0; + m_act1state->m_state = Act1State::e_none; break; case Act1State::c_floor2: if (m_act1state->m_unk0x01e) { m_act1state->m_unk0x01e = FALSE; - m_act1state->m_unk0x018 = 0; + m_act1state->m_state = Act1State::e_none; InputManager()->EnableInputProcessing(); } else { @@ -258,7 +258,7 @@ void Isle::HandleElevatorEndAction() case Act1State::c_floor3: m_destLocation = LegoGameState::e_elevopen; TransitionManager()->StartTransition(MxTransitionManager::e_mosaic, 50, FALSE, FALSE); - m_act1state->m_unk0x018 = 0; + m_act1state->m_state = Act1State::e_none; break; } } @@ -270,7 +270,7 @@ void Isle::ReadyWorld() if (m_act1state->GetUnknown21()) { GameState()->SwitchArea(LegoGameState::e_infomain); - m_act1state->SetUnknown18(0); + m_act1state->SetState(Act1State::e_none); m_act1state->SetUnknown21(0); } else if (GameState()->GetLoadedAct() != LegoGameState::e_act1) { @@ -289,7 +289,7 @@ MxLong Isle::HandleControl(LegoControlManagerNotificationParam& p_param) switch (p_param.m_clickedObjectId) { case IsleScript::c_ElevRide_Info_Ctl: - m_act1state->m_unk0x018 = 2; + m_act1state->m_state = Act1State::e_elevator; switch (m_act1state->m_elevFloor) { case Act1State::c_floor1: @@ -309,7 +309,7 @@ MxLong Isle::HandleControl(LegoControlManagerNotificationParam& p_param) m_act1state->m_elevFloor = Act1State::c_floor1; break; case IsleScript::c_ElevRide_Two_Ctl: - m_act1state->m_unk0x018 = 2; + m_act1state->m_state = Act1State::e_elevator; switch (m_act1state->m_elevFloor) { case Act1State::c_floor1: @@ -329,7 +329,7 @@ MxLong Isle::HandleControl(LegoControlManagerNotificationParam& p_param) m_act1state->m_elevFloor = Act1State::c_floor2; break; case IsleScript::c_ElevRide_Three_Ctl: - m_act1state->m_unk0x018 = 2; + m_act1state->m_state = Act1State::e_elevator; switch (m_act1state->m_elevFloor) { case Act1State::c_floor1: @@ -478,14 +478,14 @@ MxLong Isle::HandlePathStruct(LegoPathStructNotificationParam& p_param) } } - switch (m_act1state->m_unk0x018) { - case 3: + switch (m_act1state->m_state) { + case Act1State::e_pizza: result = m_pizza->Notify(p_param); break; - case 8: + case Act1State::e_towtrack: result = m_towtrack->Notify(p_param); break; - case 10: + case Act1State::e_ambulance: result = m_ambulance->Notify(p_param); break; } @@ -502,7 +502,7 @@ MxLong Isle::HandlePathStruct(LegoPathStructNotificationParam& p_param) result = 1; break; case 0x131: - if (m_act1state->m_unk0x018 != 10) { + if (m_act1state->m_state != Act1State::e_ambulance) { AnimationManager()->FUN_10064740(NULL); } result = 1; @@ -558,7 +558,7 @@ void Isle::Enable(MxBool p_enable) EnableAnimations(TRUE); - if (m_act1state->m_unk0x018 == 0) { + if (m_act1state->m_state == Act1State::e_none) { MxS32 locations[] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10}; for (MxU32 i = 0; i < 5; i++) { @@ -595,7 +595,7 @@ void Isle::Enable(MxBool p_enable) break; case LegoGameState::e_jetrace2: if (((JetskiRaceState*) GameState()->GetState("JetskiRaceState"))->m_unk0x28 == 2) { - m_act1state->m_unk0x018 = 5; + m_act1state->m_state = Act1State::e_transitionToJetski; } PlaceActor(UserActor()); @@ -704,10 +704,10 @@ void Isle::Enable(MxBool p_enable) break; } - switch (m_act1state->m_unk0x018) { - case 0: - case 1: - m_act1state->m_unk0x018 = 0; + switch (m_act1state->m_state) { + case Act1State::e_none: + case Act1State::e_initial: + m_act1state->m_state = Act1State::e_none; if (GameState()->m_currentArea == LegoGameState::e_pizzeriaExterior) { AnimationManager()->FUN_10064740(NULL); @@ -728,7 +728,7 @@ void Isle::Enable(MxBool p_enable) } } break; - case 5: { + case Act1State::e_transitionToJetski: { ((IslePathActor*) UserActor()) ->SpawnPlayer( LegoGameState::e_jetrace2, @@ -756,12 +756,12 @@ void Isle::Enable(MxBool p_enable) ->FUN_10060dc0(script, NULL, TRUE, LegoAnimationManager::e_unk1, NULL, FALSE, FALSE, TRUE, FALSE); } - m_act1state->m_unk0x018 = 0; + m_act1state->m_state = Act1State::e_none; EnableAnimations(FALSE); AnimationManager()->FUN_10064670(NULL); break; } - case 6: { + case Act1State::e_transitionToRacecar: { GameState()->m_currentArea = LegoGameState::e_carraceExterior; ((IslePathActor*) UserActor()) ->SpawnPlayer( @@ -790,12 +790,12 @@ void Isle::Enable(MxBool p_enable) ->FUN_10060dc0(script, NULL, TRUE, LegoAnimationManager::e_unk1, NULL, FALSE, FALSE, TRUE, FALSE); } - m_act1state->m_unk0x018 = 0; + m_act1state->m_state = Act1State::e_none; EnableAnimations(TRUE); break; } - case 7: - m_act1state->m_unk0x018 = 8; + case Act1State::e_transitionToTowtrack: + m_act1state->m_state = Act1State::e_towtrack; AnimationManager()->FUN_1005f6d0(FALSE); AnimationManager()->EnableCamAnims(FALSE); @@ -803,8 +803,8 @@ void Isle::Enable(MxBool p_enable) g_isleFlags &= ~c_playMusic; m_towtrack->FUN_1004dab0(); break; - case 9: - m_act1state->m_unk0x018 = 10; + case Act1State::e_transitionToAmbulance: + m_act1state->m_state = Act1State::e_ambulance; AnimationManager()->FUN_1005f6d0(FALSE); AnimationManager()->EnableCamAnims(FALSE); @@ -813,7 +813,7 @@ void Isle::Enable(MxBool p_enable) m_ambulance->Init(); break; case 11: - m_act1state->m_unk0x018 = 0; + m_act1state->m_state = Act1State::e_none; ((IslePathActor*) UserActor()) ->SpawnPlayer( LegoGameState::e_jukeboxExterior, @@ -828,17 +828,17 @@ void Isle::Enable(MxBool p_enable) SetAppCursor(e_cursorArrow); - if (m_act1state->m_unk0x018 != 8 && - (m_act1state->m_unk0x018 != 0 || GameState()->m_currentArea != LegoGameState::e_elevride) && - (m_act1state->m_unk0x018 != 0 || GameState()->m_currentArea != LegoGameState::e_polidoor) && - (m_act1state->m_unk0x018 != 0 || GameState()->m_currentArea != LegoGameState::e_garadoor) && - (m_act1state->m_unk0x018 != 0 || GameState()->m_currentArea != LegoGameState::e_bike) && - (m_act1state->m_unk0x018 != 0 || GameState()->m_currentArea != LegoGameState::e_dunecar) && - (m_act1state->m_unk0x018 != 0 || GameState()->m_currentArea != LegoGameState::e_motocycle) && - (m_act1state->m_unk0x018 != 0 || GameState()->m_currentArea != LegoGameState::e_copter) && - (m_act1state->m_unk0x018 != 0 || GameState()->m_currentArea != LegoGameState::e_jetski) && - (m_act1state->m_unk0x018 != 0 || GameState()->m_currentArea != LegoGameState::e_skateboard) && - (m_act1state->m_unk0x018 != 0 || GameState()->m_currentArea != LegoGameState::e_jetrace2)) { + if (m_act1state->m_state != Act1State::e_towtrack && + (m_act1state->m_state != Act1State::e_none || GameState()->m_currentArea != LegoGameState::e_elevride) && + (m_act1state->m_state != Act1State::e_none || GameState()->m_currentArea != LegoGameState::e_polidoor) && + (m_act1state->m_state != Act1State::e_none || GameState()->m_currentArea != LegoGameState::e_garadoor) && + (m_act1state->m_state != Act1State::e_none || GameState()->m_currentArea != LegoGameState::e_bike) && + (m_act1state->m_state != Act1State::e_none || GameState()->m_currentArea != LegoGameState::e_dunecar) && + (m_act1state->m_state != Act1State::e_none || GameState()->m_currentArea != LegoGameState::e_motocycle) && + (m_act1state->m_state != Act1State::e_none || GameState()->m_currentArea != LegoGameState::e_copter) && + (m_act1state->m_state != Act1State::e_none || GameState()->m_currentArea != LegoGameState::e_jetski) && + (m_act1state->m_state != Act1State::e_none || GameState()->m_currentArea != LegoGameState::e_skateboard) && + (m_act1state->m_state != Act1State::e_none || GameState()->m_currentArea != LegoGameState::e_jetrace2)) { Disable(FALSE, LegoOmni::c_disableInput | LegoOmni::c_disable3d | LegoOmni::c_clearScreen); } @@ -897,7 +897,7 @@ MxLong Isle::HandleTransitionEnd() DeleteObjects(&m_atomId, IsleScript::c_Avo900Ps_PlayWav, IsleScript::c_Avo907Ps_PlayWav); if (m_destLocation != LegoGameState::e_skateboard) { - m_act1state->m_unk0x018 = 0; + m_act1state->m_state = Act1State::e_none; } switch (m_destLocation) { @@ -1023,7 +1023,7 @@ MxLong Isle::HandleTransitionEnd() break; case LegoGameState::e_ambulance: m_act1state->m_unk0x01f = TRUE; - m_act1state->m_unk0x018 = 10; + m_act1state->m_state = Act1State::e_ambulance; FUN_10032d30(IsleScript::c_AmbulanceFuelMeter, JukeboxScript::c_MusicTheme1, NULL, TRUE); if (!m_act1state->m_unk0x01f) { @@ -1032,7 +1032,7 @@ MxLong Isle::HandleTransitionEnd() break; case LegoGameState::e_towtrack: m_act1state->m_unk0x01f = TRUE; - m_act1state->m_unk0x018 = 8; + m_act1state->m_state = Act1State::e_towtrack; FUN_10032d30(IsleScript::c_TowFuelMeter, JukeboxScript::c_MusicTheme1, NULL, TRUE); if (!m_act1state->m_unk0x01f) { @@ -1167,7 +1167,7 @@ void Isle::CreateState() m_act1state = (Act1State*) GameState()->GetState("Act1State"); if (!m_act1state) { m_act1state = (Act1State*) GameState()->CreateState("Act1State"); - m_act1state->m_unk0x018 = 0; + m_act1state->m_state = Act1State::e_none; } m_radio.CreateState(); @@ -1193,20 +1193,20 @@ MxBool Isle::Escape() m_radio.Stop(); BackgroundAudioManager()->Stop(); - switch (m_act1state->m_unk0x018) { - case 3: + switch (m_act1state->m_state) { + case Act1State::e_pizza: if (UserActor() != NULL) { m_pizza->StopActions(); m_pizza->FUN_100382b0(); } break; - case 8: + case Act1State::e_towtrack: if (UserActor() != NULL && !UserActor()->IsA("TowTrack")) { m_towtrack->StopActions(); m_towtrack->FUN_1004dbe0(); } break; - case 10: + case Act1State::e_ambulance: if (UserActor() != NULL && !UserActor()->IsA("Ambulance")) { m_ambulance->StopActions(); m_ambulance->Reset(); @@ -1239,7 +1239,7 @@ MxBool Isle::Escape() VariableTable()->SetVariable("VISIBILITY", "Show Gas"); } - m_act1state->m_unk0x018 = 0; + m_act1state->m_state = Act1State::e_none; m_destLocation = LegoGameState::e_infomain; return TRUE; } @@ -1247,21 +1247,21 @@ MxBool Isle::Escape() // FUNCTION: LEGO1 0x10033350 void Isle::FUN_10033350() { - if (m_act1state->m_unk0x018 == 10) { + if (m_act1state->m_state == Act1State::e_ambulance) { if (UserActor() != NULL && !UserActor()->IsA("Ambulance")) { m_ambulance->StopActions(); m_ambulance->Reset(); } } - if (m_act1state->m_unk0x018 == 8) { + if (m_act1state->m_state == Act1State::e_towtrack) { if (UserActor() != NULL && !UserActor()->IsA("TowTrack")) { m_towtrack->StopActions(); m_towtrack->FUN_1004dbe0(); } } - if (m_act1state->m_unk0x018 == 3) { + if (m_act1state->m_state == Act1State::e_pizza) { if (UserActor() != NULL) { m_pizza->StopActions(); m_pizza->FUN_100382b0(); @@ -1293,7 +1293,7 @@ void Isle::FUN_10033350() Act1State::Act1State() { m_elevFloor = Act1State::c_floor1; - m_unk0x018 = 1; + m_state = Act1State::e_initial; m_unk0x01e = FALSE; m_cptClickDialogue = Playlist((MxU32*) g_cptClickDialogue, sizeOfArray(g_cptClickDialogue), Playlist::e_loop); m_unk0x01f = FALSE; diff --git a/LEGO1/lego/legoomni/src/worlds/jukebox.cpp b/LEGO1/lego/legoomni/src/worlds/jukebox.cpp index d09b7e62..db244e00 100644 --- a/LEGO1/lego/legoomni/src/worlds/jukebox.cpp +++ b/LEGO1/lego/legoomni/src/worlds/jukebox.cpp @@ -211,7 +211,7 @@ MxBool JukeBox::HandleControl(LegoControlManagerNotificationParam& p_param) break; case JukeboxwScript::c_Note_Ctl: Act1State* act1State = (Act1State*) GameState()->GetState("Act1State"); - act1State->m_unk0x018 = 11; + act1State->m_state = Act1State::e_jukebox; m_destLocation = LegoGameState::Area::e_jukeboxExterior; TransitionManager()->StartTransition(MxTransitionManager::e_mosaic, 50, 0, FALSE); break; From 42f877e17774d8e20afa4626f5da65d9c2f29600 Mon Sep 17 00:00:00 2001 From: Christian Semmler Date: Sun, 13 Jul 2025 19:16:03 -0700 Subject: [PATCH 130/188] (Web port) Fix virtual cursor transparency (#595) --- LEGO1/omni/src/video/mxdisplaysurface.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/LEGO1/omni/src/video/mxdisplaysurface.cpp b/LEGO1/omni/src/video/mxdisplaysurface.cpp index 2902c4fe..2039a7e2 100644 --- a/LEGO1/omni/src/video/mxdisplaysurface.cpp +++ b/LEGO1/omni/src/video/mxdisplaysurface.cpp @@ -1288,6 +1288,10 @@ LPDIRECTDRAWSURFACE MxDisplaySurface::CreateCursorSurface(const CursorBitmap* p_ break; } default: { + DDCOLORKEY colorkey; + colorkey.dwColorSpaceHighValue = RGB8888_CREATE(0, 0, 0, 0); + colorkey.dwColorSpaceLowValue = RGB8888_CREATE(0, 0, 0, 0); + newSurface->SetColorKey(DDCKEY_SRCBLT, &colorkey); break; } } From 21ce906a32bed3fef4323b0e066242231c0d0ae6 Mon Sep 17 00:00:00 2001 From: Christian Semmler Date: Sun, 13 Jul 2025 20:26:47 -0700 Subject: [PATCH 131/188] Add haptic feedback (rumble) (#596) * Add rumble event for hit actor * Add ini option --- ISLE/isleapp.cpp | 8 ++++++++ ISLE/isleapp.h | 2 ++ LEGO1/lego/legoomni/include/legoinputmanager.h | 1 + LEGO1/lego/legoomni/include/legoutils.h | 1 + LEGO1/lego/legoomni/src/common/legoutils.cpp | 7 +++++++ .../lego/legoomni/src/input/legoinputmanager.cpp | 15 +++++++++++++++ LEGO1/lego/legoomni/src/paths/legoextraactor.cpp | 2 ++ LEGO1/omni/include/mxutilities.h | 1 + LEGO1/omni/src/main/mxomni.cpp | 3 ++- 9 files changed, 39 insertions(+), 1 deletion(-) diff --git a/ISLE/isleapp.cpp b/ISLE/isleapp.cpp index a78a9f71..c3035de6 100644 --- a/ISLE/isleapp.cpp +++ b/ISLE/isleapp.cpp @@ -173,6 +173,7 @@ IsleApp::IsleApp() m_transitionType = MxTransitionManager::e_mosaic; m_cursorSensitivity = 4; m_touchScheme = LegoInputManager::e_gamepad; + m_haptic = TRUE; } // FUNCTION: ISLE 0x4011a0 @@ -791,6 +792,11 @@ SDL_AppResult SDL_AppEvent(void* appstate, SDL_Event* event) SDL_Log("Game started"); } } + else if (event->user.type == g_legoSdlEvents.m_hitActor && g_isle->GetHaptic()) { + if (InputManager()) { + InputManager()->HandleRumbleEvent(); + } + } return SDL_APP_CONTINUE; } @@ -1042,6 +1048,7 @@ bool IsleApp::LoadConfig() iniparser_set(dict, "isle:Max Allowed Extras", SDL_itoa(m_maxAllowedExtras, buf, 10)); iniparser_set(dict, "isle:Transition Type", SDL_itoa(m_transitionType, buf, 10)); iniparser_set(dict, "isle:Touch Scheme", SDL_itoa(m_touchScheme, buf, 10)); + iniparser_set(dict, "isle:Haptic", m_haptic ? "true" : "false"); #ifdef EXTENSIONS iniparser_set(dict, "extensions", NULL); @@ -1113,6 +1120,7 @@ bool IsleApp::LoadConfig() m_transitionType = (MxTransitionManager::TransitionType) iniparser_getint(dict, "isle:Transition Type", m_transitionType); m_touchScheme = (LegoInputManager::TouchScheme) iniparser_getint(dict, "isle:Touch Scheme", m_touchScheme); + m_haptic = iniparser_getboolean(dict, "isle:Haptic", m_haptic); const char* deviceId = iniparser_getstring(dict, "isle:3D Device ID", NULL); if (deviceId != NULL) { diff --git a/ISLE/isleapp.h b/ISLE/isleapp.h index 3322427e..180c2252 100644 --- a/ISLE/isleapp.h +++ b/ISLE/isleapp.h @@ -55,6 +55,7 @@ class IsleApp { MxS32 GetGameStarted() { return m_gameStarted; } MxFloat GetCursorSensitivity() { return m_cursorSensitivity; } LegoInputManager::TouchScheme GetTouchScheme() { return m_touchScheme; } + MxBool GetHaptic() { return m_haptic; } void SetWindowActive(MxS32 p_windowActive) { m_windowActive = p_windowActive; } void SetGameStarted(MxS32 p_gameStarted) { m_gameStarted = p_gameStarted; } @@ -105,6 +106,7 @@ class IsleApp { MxU32 m_maxAllowedExtras; MxTransitionManager::TransitionType m_transitionType; LegoInputManager::TouchScheme m_touchScheme; + MxBool m_haptic; }; extern IsleApp* g_isle; diff --git a/LEGO1/lego/legoomni/include/legoinputmanager.h b/LEGO1/lego/legoomni/include/legoinputmanager.h index 3d17df56..ee87b50a 100644 --- a/LEGO1/lego/legoomni/include/legoinputmanager.h +++ b/LEGO1/lego/legoomni/include/legoinputmanager.h @@ -153,6 +153,7 @@ class LegoInputManager : public MxPresenter { MxResult GetNavigationKeyStates(MxU32& p_keyFlags); MxResult GetNavigationTouchStates(MxU32& p_keyFlags); LEGO1_EXPORT MxBool HandleTouchEvent(SDL_Event* p_event, TouchScheme p_touchScheme); + LEGO1_EXPORT MxBool HandleRumbleEvent(); // SYNTHETIC: LEGO1 0x1005b8d0 // LegoInputManager::`scalar deleting destructor' diff --git a/LEGO1/lego/legoomni/include/legoutils.h b/LEGO1/lego/legoomni/include/legoutils.h index 2df6ad4a..b29a336c 100644 --- a/LEGO1/lego/legoomni/include/legoutils.h +++ b/LEGO1/lego/legoomni/include/legoutils.h @@ -71,6 +71,7 @@ LegoNamedTexture* ReadNamedTexture(LegoStorage* p_storage); void WriteDefaultTexture(LegoStorage* p_storage, const char* p_name); void WriteNamedTexture(LegoStorage* p_storage, LegoNamedTexture* p_namedTexture); void LoadFromNamedTexture(LegoNamedTexture* p_namedTexture); +void HitActorEvent(); // FUNCTION: BETA10 0x100260a0 inline void StartIsleAction(IsleScript::Script p_objectId) diff --git a/LEGO1/lego/legoomni/src/common/legoutils.cpp b/LEGO1/lego/legoomni/src/common/legoutils.cpp index ff45676b..0a07a7c0 100644 --- a/LEGO1/lego/legoomni/src/common/legoutils.cpp +++ b/LEGO1/lego/legoomni/src/common/legoutils.cpp @@ -782,3 +782,10 @@ void LoadFromNamedTexture(LegoNamedTexture* p_namedTexture) textureInfo->LoadBits(p_namedTexture->GetTexture()->GetImage()->GetBits()); } } + +void HitActorEvent() +{ + SDL_Event event; + event.user.type = g_legoSdlEvents.m_hitActor; + SDL_PushEvent(&event); +} diff --git a/LEGO1/lego/legoomni/src/input/legoinputmanager.cpp b/LEGO1/lego/legoomni/src/input/legoinputmanager.cpp index b0eae128..7c80e78e 100644 --- a/LEGO1/lego/legoomni/src/input/legoinputmanager.cpp +++ b/LEGO1/lego/legoomni/src/input/legoinputmanager.cpp @@ -626,3 +626,18 @@ MxBool LegoInputManager::HandleTouchEvent(SDL_Event* p_event, TouchScheme p_touc return TRUE; } + +MxBool LegoInputManager::HandleRumbleEvent() +{ + if (m_joystick != NULL && SDL_GamepadConnected(m_joystick) == TRUE) { + const Uint16 frequency = 65535 / 2; + const Uint32 durationMs = 700; + SDL_RumbleGamepad(m_joystick, frequency, frequency, durationMs); + } + else { + return FALSE; + } + + // Add support for SDL Haptic API + return TRUE; +} diff --git a/LEGO1/lego/legoomni/src/paths/legoextraactor.cpp b/LEGO1/lego/legoomni/src/paths/legoextraactor.cpp index 0a888b46..7f640326 100644 --- a/LEGO1/lego/legoomni/src/paths/legoextraactor.cpp +++ b/LEGO1/lego/legoomni/src/paths/legoextraactor.cpp @@ -235,6 +235,7 @@ MxResult LegoExtraActor::HitActor(LegoPathActor* p_actor, MxBool p_bool) assert(m_roi); assert(SoundManager()->GetCacheSoundManager()); SoundManager()->GetCacheSoundManager()->Play("crash5", m_roi->GetName(), FALSE); + HitActorEvent(); m_scheduledTime = Timer()->GetTime() + m_disAnim->GetDuration(); m_prevWorldSpeed = GetWorldSpeed(); VTable0xc4(); @@ -248,6 +249,7 @@ MxResult LegoExtraActor::HitActor(LegoPathActor* p_actor, MxBool p_bool) LegoROI* roi = GetROI(); assert(roi); SoundManager()->GetCacheSoundManager()->Play("crash5", m_roi->GetName(), FALSE); + HitActorEvent(); VTable0xc4(); SetActorState(c_two | c_noCollide); Mx3DPointFloat dir = p_actor->GetWorldDirection(); diff --git a/LEGO1/omni/include/mxutilities.h b/LEGO1/omni/include/mxutilities.h index c5237bb0..33754f3c 100644 --- a/LEGO1/omni/include/mxutilities.h +++ b/LEGO1/omni/include/mxutilities.h @@ -10,6 +10,7 @@ struct LegoSdlEvents { Uint32 m_windowsMessage; Uint32 m_presenterProgress; + Uint32 m_hitActor; }; LEGO1_EXPORT extern LegoSdlEvents g_legoSdlEvents; diff --git a/LEGO1/omni/src/main/mxomni.cpp b/LEGO1/omni/src/main/mxomni.cpp index d7b76847..b6081b16 100644 --- a/LEGO1/omni/src/main/mxomni.cpp +++ b/LEGO1/omni/src/main/mxomni.cpp @@ -163,9 +163,10 @@ MxResult MxOmni::Create(MxOmniCreateParam& p_param) } { - Uint32 event = SDL_RegisterEvents(2); + Uint32 event = SDL_RegisterEvents(3); g_legoSdlEvents.m_windowsMessage = event + 0; g_legoSdlEvents.m_presenterProgress = event + 1; + g_legoSdlEvents.m_hitActor = event + 2; } result = SUCCESS; From 7e1df12c639c7af3fe6a9f2554706b166d630ac5 Mon Sep 17 00:00:00 2001 From: VoxelTek <53562267+VoxelTek@users.noreply.github.com> Date: Tue, 15 Jul 2025 00:58:29 +1000 Subject: [PATCH 132/188] Add touch control options, add rumble checkbox, clean up layout. (#598) * Reduce maximum sizes, make isle-config smaller * Add extra options, clean up layout * Add line breaks to tooltips * Add Texture Loader extension config, fix var case * Add "categories" in ini file * Fix broken CI building on Linux --- CONFIG/MainDlg.cpp | 80 +++++- CONFIG/MainDlg.h | 6 +- CONFIG/config.cpp | 23 ++ CONFIG/config.h | 4 + CONFIG/res/maindialog.ui | 605 +++++++++++++++++++++++++-------------- 5 files changed, 494 insertions(+), 224 deletions(-) diff --git a/CONFIG/MainDlg.cpp b/CONFIG/MainDlg.cpp index 828395bd..dfba47ea 100644 --- a/CONFIG/MainDlg.cpp +++ b/CONFIG/MainDlg.cpp @@ -57,8 +57,10 @@ CMainDialog::CMainDialog(QWidget* pParent) : QDialog(pParent) connect(m_ui->devicesList, &QListWidget::currentRowChanged, this, &CMainDialog::OnList3DevicesSelectionChanged); connect(m_ui->musicCheckBox, &QCheckBox::toggled, this, &CMainDialog::OnCheckboxMusic); connect(m_ui->sound3DCheckBox, &QCheckBox::toggled, this, &CMainDialog::OnCheckbox3DSound); - connect(m_ui->joystickCheckBox, &QCheckBox::toggled, this, &CMainDialog::OnCheckboxJoystick); connect(m_ui->fullscreenCheckBox, &QCheckBox::toggled, this, &CMainDialog::OnCheckboxFullscreen); + connect(m_ui->rumbleCheckBox, &QCheckBox::toggled, this, &CMainDialog::OnCheckboxRumble); + connect(m_ui->textureCheckBox, &QCheckBox::toggled, this, &CMainDialog::OnCheckboxTexture); + connect(m_ui->touchComboBox, &QComboBox::currentIndexChanged, this, &CMainDialog::TouchControlsChanged); connect(m_ui->transitionTypeComboBox, &QComboBox::currentIndexChanged, this, &CMainDialog::TransitionTypeChanged); connect(m_ui->okButton, &QPushButton::clicked, this, &CMainDialog::accept); connect(m_ui->cancelButton, &QPushButton::clicked, this, &CMainDialog::reject); @@ -70,8 +72,13 @@ CMainDialog::CMainDialog(QWidget* pParent) : QDialog(pParent) connect(m_ui->dataPath, &QLineEdit::editingFinished, this, &CMainDialog::DataPathEdited); connect(m_ui->savePath, &QLineEdit::editingFinished, this, &CMainDialog::SavePathEdited); + connect(m_ui->texturePathOpen, &QPushButton::clicked, this, &CMainDialog::SelectTexturePathDialog); + connect(m_ui->texturePath, &QLineEdit::editingFinished, this, &CMainDialog::TexturePathEdited); + connect(m_ui->maxLoDSlider, &QSlider::valueChanged, this, &CMainDialog::MaxLoDChanged); + connect(m_ui->maxLoDSlider, &QSlider::sliderMoved, this, &CMainDialog::MaxLoDChanged); connect(m_ui->maxActorsSlider, &QSlider::valueChanged, this, &CMainDialog::MaxActorsChanged); + connect(m_ui->maxActorsSlider, &QSlider::sliderMoved, this, &CMainDialog::MaxActorsChanged); layout()->setSizeConstraint(QLayout::SetFixedSize); } @@ -115,7 +122,9 @@ bool CMainDialog::OnInitDialog() m_ui->devicesList->setCurrentRow(selected); m_ui->maxLoDSlider->setValue((int) currentConfigApp->m_max_lod * 10); + m_ui->LoDNum->setNum((int) currentConfigApp->m_max_lod * 10); m_ui->maxActorsSlider->setValue(currentConfigApp->m_max_actors); + m_ui->maxActorsNum->setNum(currentConfigApp->m_max_actors); UpdateInterface(); return true; } @@ -210,12 +219,18 @@ void CMainDialog::UpdateInterface() else { m_ui->textureQualityHighRadioButton->setChecked(true); } - m_ui->joystickCheckBox->setChecked(currentConfigApp->m_use_joystick); m_ui->musicCheckBox->setChecked(currentConfigApp->m_music); m_ui->fullscreenCheckBox->setChecked(currentConfigApp->m_full_screen); + m_ui->rumbleCheckBox->setChecked(currentConfigApp->m_haptic); + m_ui->touchComboBox->setCurrentIndex(currentConfigApp->m_touch_scheme); m_ui->transitionTypeComboBox->setCurrentIndex(currentConfigApp->m_transition_type); m_ui->dataPath->setText(QString::fromStdString(currentConfigApp->m_cd_path)); m_ui->savePath->setText(QString::fromStdString(currentConfigApp->m_save_path)); + m_ui->textureCheckBox->setChecked(currentConfigApp->m_texture_load); + m_ui->texturePath->setText(QString::fromStdString(currentConfigApp->m_texture_path)); + + m_ui->texturePath->setEnabled(currentConfigApp->m_texture_load); + m_ui->texturePathOpen->setEnabled(currentConfigApp->m_texture_load); } // FUNCTION: CONFIG 0x004045e0 @@ -275,14 +290,6 @@ void CMainDialog::OnRadiobuttonTextureHighQuality(bool checked) } } -// FUNCTION: CONFIG 0x00404790 -void CMainDialog::OnCheckboxJoystick(bool checked) -{ - currentConfigApp->m_use_joystick = checked; - m_modified = true; - UpdateInterface(); -} - // FUNCTION: CONFIG 0x004048c0 void CMainDialog::OnCheckboxMusic(bool checked) { @@ -298,6 +305,27 @@ void CMainDialog::OnCheckboxFullscreen(bool checked) UpdateInterface(); } +void CMainDialog::OnCheckboxRumble(bool checked) +{ + currentConfigApp->m_haptic = checked; + m_modified = true; + UpdateInterface(); +} + +void CMainDialog::OnCheckboxTexture(bool checked) +{ + currentConfigApp->m_texture_load = checked; + m_modified = true; + UpdateInterface(); +} + +void CMainDialog::TouchControlsChanged(int index) +{ + currentConfigApp->m_touch_scheme = index; + m_modified = true; + UpdateInterface(); +} + void CMainDialog::TransitionTypeChanged(int index) { currentConfigApp->m_transition_type = index; @@ -376,11 +404,43 @@ void CMainDialog::SavePathEdited() void CMainDialog::MaxLoDChanged(int value) { currentConfigApp->m_max_lod = static_cast(value) / 10.0f; + m_ui->LoDNum->setNum(value); m_modified = true; } void CMainDialog::MaxActorsChanged(int value) { currentConfigApp->m_max_actors = value; + m_ui->maxActorsNum->setNum(value); m_modified = true; } + +void CMainDialog::SelectTexturePathDialog() +{ + QString texture_path = QString::fromStdString(currentConfigApp->m_texture_path); + texture_path = QFileDialog::getExistingDirectory( + this, + tr("Open Directory"), + texture_path, + QFileDialog::ShowDirsOnly | QFileDialog::DontResolveSymlinks + ); + + QDir texture_dir = QDir(texture_path); + + if (texture_dir.exists()) { + currentConfigApp->m_texture_path = texture_dir.absolutePath().toStdString(); + m_modified = true; + } + UpdateInterface(); +} + +void CMainDialog::TexturePathEdited() +{ + QDir texture_dir = QDir(m_ui->texturePath->text()); + + if (texture_dir.exists()) { + currentConfigApp->m_texture_path = texture_dir.absolutePath().toStdString(); + m_modified = true; + } + UpdateInterface(); +} diff --git a/CONFIG/MainDlg.h b/CONFIG/MainDlg.h index 72062486..40156790 100644 --- a/CONFIG/MainDlg.h +++ b/CONFIG/MainDlg.h @@ -40,9 +40,11 @@ private slots: void OnRadiobuttonModelHighQuality(bool checked); void OnRadiobuttonTextureLowQuality(bool checked); void OnRadiobuttonTextureHighQuality(bool checked); - void OnCheckboxJoystick(bool checked); void OnCheckboxMusic(bool checked); void OnCheckboxFullscreen(bool checked); + void OnCheckboxRumble(bool checked); + void OnCheckboxTexture(bool checked); + void TouchControlsChanged(int index); void TransitionTypeChanged(int index); void accept() override; void reject() override; @@ -53,6 +55,8 @@ private slots: void SavePathEdited(); void MaxLoDChanged(int value); void MaxActorsChanged(int value); + void SelectTexturePathDialog(); + void TexturePathEdited(); }; // SYNTHETIC: CONFIG 0x00403de0 diff --git a/CONFIG/config.cpp b/CONFIG/config.cpp index b4435298..c88f779b 100644 --- a/CONFIG/config.cpp +++ b/CONFIG/config.cpp @@ -78,6 +78,10 @@ bool CConfigApp::InitInstance() m_3d_video_ram = FALSE; m_joystick_index = -1; m_display_bit_depth = 16; + m_haptic = TRUE; + m_touch_scheme = 2; + m_texture_load = TRUE; + m_texture_path = "/textures/"; int totalRamMiB = SDL_GetSystemRAM(); if (totalRamMiB < 12) { m_3d_sound = FALSE; @@ -155,6 +159,7 @@ bool CConfigApp::ReadRegisterSettings() m_flip_surfaces = iniparser_getboolean(dict, "isle:Flip Surfaces", m_flip_surfaces); m_full_screen = iniparser_getboolean(dict, "isle:Full Screen", m_full_screen); m_transition_type = iniparser_getint(dict, "isle:Transition Type", m_transition_type); + m_touch_scheme = iniparser_getint(dict, "isle:Touch Scheme", m_touch_scheme); m_3d_video_ram = iniparser_getboolean(dict, "isle:Back Buffers in Video RAM", m_3d_video_ram); m_wide_view_angle = iniparser_getboolean(dict, "isle:Wide View Angle", m_wide_view_angle); m_3d_sound = iniparser_getboolean(dict, "isle:3DSound", m_3d_sound); @@ -162,10 +167,13 @@ bool CConfigApp::ReadRegisterSettings() m_model_quality = iniparser_getint(dict, "isle:Island Quality", m_model_quality); m_texture_quality = iniparser_getint(dict, "isle:Island Texture", m_texture_quality); m_use_joystick = iniparser_getboolean(dict, "isle:UseJoystick", m_use_joystick); + m_haptic = iniparser_getboolean(dict, "isle:Haptic", m_haptic); m_music = iniparser_getboolean(dict, "isle:Music", m_music); m_joystick_index = iniparser_getint(dict, "isle:JoystickIndex", m_joystick_index); m_max_lod = iniparser_getdouble(dict, "isle:Max LOD", m_max_lod); m_max_actors = iniparser_getint(dict, "isle:Max Allowed Extras", m_max_actors); + m_texture_load = iniparser_getboolean(dict, "extensions:texture loader", m_texture_load); + m_texture_path = iniparser_getstring(dict, "texture loader:texture path", m_texture_path.c_str()); iniparser_freedict(dict); return true; } @@ -230,6 +238,14 @@ bool CConfigApp::ValidateSettings() m_max_actors = 20; is_modified = TRUE; } + if (!m_use_joystick) { + m_use_joystick = true; + is_modified = TRUE; + } + if (m_touch_scheme < 0 || m_touch_scheme > 2) { + m_touch_scheme = 2; + is_modified = TRUE; + } return is_modified; } @@ -298,6 +314,8 @@ void CConfigApp::WriteRegisterSettings() const dictionary* dict = dictionary_new(0); iniparser_set(dict, "isle", NULL); + iniparser_set(dict, "extensions", NULL); + iniparser_set(dict, "texture loader", NULL); if (m_device_enumerator->FormatDeviceName(buffer, m_driver, m_device) >= 0) { iniparser_set(dict, "isle:3D Device ID", buffer); } @@ -311,14 +329,19 @@ void CConfigApp::WriteRegisterSettings() const SetIniBool(dict, "isle:Wide View Angle", m_wide_view_angle); SetIniInt(dict, "isle:Transition Type", m_transition_type); + SetIniInt(dict, "isle:Touch Scheme", m_touch_scheme); SetIniBool(dict, "isle:3DSound", m_3d_sound); SetIniBool(dict, "isle:Music", m_music); + SetIniBool(dict, "isle:Haptic", m_haptic); SetIniBool(dict, "isle:UseJoystick", m_use_joystick); SetIniInt(dict, "isle:JoystickIndex", m_joystick_index); SetIniBool(dict, "isle:Draw Cursor", m_draw_cursor); + SetIniBool(dict, "extensions:texture loader", m_texture_load); + iniparser_set(dict, "texture loader:texture path", m_texture_path.c_str()); + SetIniBool(dict, "isle:Back Buffers in Video RAM", m_3d_video_ram); SetIniInt(dict, "isle:Island Quality", m_model_quality); diff --git a/CONFIG/config.h b/CONFIG/config.h index dffc2b6c..796a5659 100644 --- a/CONFIG/config.h +++ b/CONFIG/config.h @@ -71,16 +71,20 @@ class CConfigApp { bool m_3d_sound; bool m_draw_cursor; bool m_use_joystick; + bool m_haptic; int m_joystick_index; int m_model_quality; int m_texture_quality; bool m_music; + bool m_texture_load; + std::string m_texture_path; std::string m_iniPath; std::string m_base_path; std::string m_cd_path; std::string m_save_path; float m_max_lod; int m_max_actors; + int m_touch_scheme; }; extern CConfigApp g_theApp; diff --git a/CONFIG/res/maindialog.ui b/CONFIG/res/maindialog.ui index 66e2b52e..526c31d8 100644 --- a/CONFIG/res/maindialog.ui +++ b/CONFIG/res/maindialog.ui @@ -6,8 +6,8 @@ 0 0 - 575 - 600 + 550 + 700 @@ -23,7 +23,7 @@ :/lego1.png:/lego1.png - + @@ -56,13 +56,16 @@ false - - Qt::AlignmentFlag::AlignCenter - + + + 0 + 0 + + @@ -89,7 +92,7 @@ - 55 + 50 16777215 @@ -108,7 +111,7 @@ - 55 + 50 16777215 @@ -129,17 +132,15 @@ Save Path: - Qt::TextFormat::PlainText - - - Qt::AlignmentFlag::AlignRight|Qt::AlignmentFlag::AlignTrailing|Qt::AlignmentFlag::AlignVCenter + Qt::PlainText - Path to the game data files. Set this to the CD image root. + Path to the game data files. +Set this to the CD image root. @@ -162,10 +163,7 @@ Data Path: - Qt::TextFormat::PlainText - - - Qt::AlignmentFlag::AlignRight|Qt::AlignmentFlag::AlignTrailing|Qt::AlignmentFlag::AlignVCenter + Qt::PlainText @@ -187,13 +185,166 @@ 0 + + + + + 0 + 0 + + + + Maximum number of LEGO actors to exist in the world at a time. +The game will gradually increase the number of actors until this maximum is reached and while performance is acceptable. + + + Maximum Actors (5..40) + + + + + + + 0 + 0 + + + + 5 + + + 40 + + + 5 + + + 20 + + + 20 + + + false + + + Qt::Horizontal + + + QSlider::NoTicks + + + 5 + + + + + + + + 0 + 0 + + + + + 15 + 0 + + + + 20 + + + + + + + + + + + 0 + 0 + + + + + 0 + 70 + + + + Maximum Level of Detail (LOD). +A higher setting will cause higher quality textures to be drawn regardless of distance. + + + Maximum LOD + + + + + + + 0 + 0 + + + + 50 + + + 5 + + + 10 + + + 35 + + + false + + + Qt::Horizontal + + + QSlider::NoTicks + + + 10 + + + + + + + + 0 + 0 + + + + + 15 + 0 + + + + 35 + + + + + + - - - 16777215 - 120 - + + + 0 + 0 + Set 3D model detail level. @@ -204,8 +355,14 @@ + + Broken, not recommended. + + + color: rgb(255, 0, 0); + - Low + Low - BROKEN! @@ -226,63 +383,26 @@ - - - - - 0 - 70 - - - - Maximum Level of Detail (LOD). A higher setting will cause higher quality textures to be drawn regardless of distance. - - - Maximum LOD - - - Qt::AlignmentFlag::AlignCenter - - - - - - 50 - - - 5 - - - 10 - - - 35 - - - false - - - Qt::Orientation::Horizontal - - - QSlider::TickPosition::TicksBothSides - - - 10 - - - - - - + + true + + + + 0 + 0 + + Set texture detail level. Island Texture Quality + + false + 0 @@ -307,58 +427,24 @@ - - - - Maximum number of LEGO actors to exist in the world at a time. The game will gradually increase the number of actors until this maximum is reached and while performance is acceptable. - - - Maximum Actors (5..40) - - - Qt::AlignmentFlag::AlignCenter - - - - - - 5 - - - 40 - - - 5 - - - 20 - - - 20 - - - false - - - Qt::Orientation::Horizontal - - - QSlider::TickPosition::TicksBothSides - - - 5 - - - - - - + + + 0 + 0 + + + + 0 + + + 0 + @@ -379,16 +465,6 @@ - - - - Enable joystick and gamepad support for LEGO Island. - - - Use Joystick - - - @@ -399,80 +475,201 @@ + + + + true + + + Enable controller rumble. + + + Rumble + + + true + + + - - - Transition Type - + + + + 0 + + + 0 + + + 0 + + + + + + 0 + 0 + + + + Sets the transition effect to be used in game. + + + Transition Type + + + + + + Mosaic + + + 3 + + + + Idle - Broken + + + + + No Animation + + + + + Dissolve + + + + + Mosaic + + + + + Wipe Down + + + + + Windows + + + + + Unknown - Broken + + + + + + + + + + + <html><head/><body><p><span style=" font-weight:600;">Virtual Gamepad (Recommended):</span> Slide your finger to move and turn.</p><p><span style=" font-weight:600;">Virtual Arrow Keys:</span> Tap screen areas to move. The top moves forward, the bottom turns or moves back.</p><p><span style=" font-weight:600;">Virtual Mouse:</span> Emulates classic mouse controls with touch.</p></body></html> + + + Touch Control Scheme + + + + + + 2 + + + + Virtual Mouse + + + + + Virtual Arrow Keys + + + + + Virtual Gamepad + + + + + + + + - - - Sets the transition effect to be used in game. + + + Texture Loader Extension - - Idle - Broken - - - - - - - Idle - Broken - - - - - No Animation - - - - - Dissolve - - - - - Mosaic - - - - - Wipe Down - - - - - Windows - - - - - Unknown - Broken - - + + + + + false + + + Path to texture replacements. + + + textures/ + + + + + + + false + + + + 0 + 0 + + + + + 50 + 16777215 + + + + Open + + + + + + + Enabled + + + + - - - 16777215 - 225 - + + + 0 + 0 + 3D graphics device used to render the game. - - - - Qt::AlignmentFlag::AlignCenter + Graphics Devices false @@ -490,28 +687,6 @@ 6 - - - - - 0 - 0 - - - - - 0 - 25 - - - - Direct 3D Devices - - - Qt::AlignmentFlag::AlignCenter - - - @@ -526,17 +701,17 @@ 16777215 - 125 + 100 - QAbstractItemView::EditTrigger::NoEditTriggers + QAbstractItemView::NoEditTriggers true - QAbstractItemView::SelectionBehavior::SelectRows + QAbstractItemView::SelectItems @@ -545,9 +720,15 @@ + + + 0 + 0 + + - 30 + 10 0 @@ -616,8 +797,6 @@ maxActorsSlider sound3DCheckBox musicCheckBox - joystickCheckBox - fullscreenCheckBox devicesList okButton launchButton From 7cf24ae358341ef31a34a93f670479f313961141 Mon Sep 17 00:00:00 2001 From: Helloyunho Date: Tue, 15 Jul 2025 00:10:15 +0900 Subject: [PATCH 133/188] =?UTF-8?q?=F0=9F=A9=B9=20fix:=20no=20black=20bord?= =?UTF-8?q?er=20on=20virtual=20cursor=20(#604)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- LEGO1/omni/src/video/mxdisplaysurface.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/LEGO1/omni/src/video/mxdisplaysurface.cpp b/LEGO1/omni/src/video/mxdisplaysurface.cpp index 2039a7e2..f676dd0a 100644 --- a/LEGO1/omni/src/video/mxdisplaysurface.cpp +++ b/LEGO1/omni/src/video/mxdisplaysurface.cpp @@ -1258,7 +1258,7 @@ LPDIRECTDRAWSURFACE MxDisplaySurface::CreateCursorSurface(const CursorBitmap* p_ MxS32 pixel; if (!isOpaque) { - pixel = RGB8888_CREATE(0, 0, 0, 0); // Transparent pixel + pixel = RGB8888_CREATE(0xff, 0, 0xff, 0); // Transparent pixel } else { pixel = isBlack ? RGB8888_CREATE(0, 0, 0, 0xff) : RGB8888_CREATE(0xff, 0xff, 0xff, 0xff); @@ -1289,8 +1289,8 @@ LPDIRECTDRAWSURFACE MxDisplaySurface::CreateCursorSurface(const CursorBitmap* p_ } default: { DDCOLORKEY colorkey; - colorkey.dwColorSpaceHighValue = RGB8888_CREATE(0, 0, 0, 0); - colorkey.dwColorSpaceLowValue = RGB8888_CREATE(0, 0, 0, 0); + colorkey.dwColorSpaceHighValue = RGB8888_CREATE(0xff, 0, 0xff, 0); + colorkey.dwColorSpaceLowValue = RGB8888_CREATE(0xff, 0, 0xff, 0); newSurface->SetColorKey(DDCKEY_SRCBLT, &colorkey); break; } From e155bea7fd76fa4ea1d339f7c1cd63863c2730c3 Mon Sep 17 00:00:00 2001 From: MS Date: Mon, 14 Jul 2025 11:44:14 -0400 Subject: [PATCH 134/188] Remove GetDriverList getter (#1624) --- CONFIG/MainDlg.cpp | 2 +- CONFIG/config.cpp | 2 +- LEGO1/mxdirectx/legodxinfo.cpp | 24 ++++++++++++------------ LEGO1/mxdirectx/mxdirect3d.cpp | 2 +- LEGO1/mxdirectx/mxdirectxinfo.cpp | 12 ++++++------ LEGO1/mxdirectx/mxdirectxinfo.h | 6 +++--- 6 files changed, 24 insertions(+), 24 deletions(-) diff --git a/CONFIG/MainDlg.cpp b/CONFIG/MainDlg.cpp index c9fad610..cf88727a 100644 --- a/CONFIG/MainDlg.cpp +++ b/CONFIG/MainDlg.cpp @@ -65,7 +65,7 @@ BOOL CMainDialog::OnInitDialog() int device_i = 0; int selected = 0; char device_name[256]; - const list& driver_list = enumerator->GetDriverList(); + const list& driver_list = enumerator->m_ddInfo; for (list::const_iterator it_driver = driver_list.begin(); it_driver != driver_list.end(); it_driver++) { const MxDriver& driver = *it_driver; for (list::const_iterator it_device = driver.m_devices.begin(); diff --git a/CONFIG/config.cpp b/CONFIG/config.cpp index 54b16ee9..c42469ee 100644 --- a/CONFIG/config.cpp +++ b/CONFIG/config.cpp @@ -245,7 +245,7 @@ D3DCOLORMODEL CConfigApp::GetHardwareDeviceColorModel() const BOOL CConfigApp::IsPrimaryDriver() const { assert(m_driver && m_device_enumerator); - return m_driver == &m_device_enumerator->GetDriverList().front(); + return m_driver == &m_device_enumerator->m_ddInfo.front(); } // FUNCTION: CONFIG 0x00403430 diff --git a/LEGO1/mxdirectx/legodxinfo.cpp b/LEGO1/mxdirectx/legodxinfo.cpp index 35db14b7..35c98dec 100644 --- a/LEGO1/mxdirectx/legodxinfo.cpp +++ b/LEGO1/mxdirectx/legodxinfo.cpp @@ -55,7 +55,7 @@ int LegoDeviceEnumerate::ProcessDeviceBytes(int p_deviceNum, GUID& p_guid) GUID4 deviceGuid; memcpy(&deviceGuid, &p_guid, sizeof(GUID4)); - for (list::iterator it = m_list.begin(); it != m_list.end(); it++, i++) { + for (list::iterator it = m_ddInfo.begin(); it != m_ddInfo.end(); it++, i++) { if (p_deviceNum >= 0 && p_deviceNum < i) { return -1; } @@ -90,7 +90,7 @@ int LegoDeviceEnumerate::GetDevice(int p_deviceNum, MxDriver*& p_driver, Direct3 int i = 0; - for (list::iterator it = m_list.begin(); it != m_list.end(); it++) { + for (list::iterator it = m_ddInfo.begin(); it != m_ddInfo.end(); it++) { p_driver = &*it; for (list::iterator it2 = p_driver->m_devices.begin(); it2 != p_driver->m_devices.end(); @@ -114,7 +114,7 @@ int LegoDeviceEnumerate::FormatDeviceName(char* p_buffer, const MxDriver* p_ddIn int number = 0; assert(p_ddInfo && p_d3dInfo); - for (list::const_iterator it = m_list.begin(); it != m_list.end(); it++, number++) { + for (list::const_iterator it = m_ddInfo.begin(); it != m_ddInfo.end(); it++, number++) { if (&(*it) == p_ddInfo) { GUID4 guid; memcpy(&guid, p_d3dInfo->m_guid, sizeof(GUID4)); @@ -137,7 +137,7 @@ int LegoDeviceEnumerate::BETA_1011cc65(int p_idx, char* p_buffer) int i = 0; int j = 0; - for (list::iterator it = m_list.begin(); it != m_list.end(); it++, i++) { + for (list::iterator it = m_ddInfo.begin(); it != m_ddInfo.end(); it++, i++) { MxDriver& driver = *it; for (list::iterator it2 = driver.m_devices.begin(); it2 != driver.m_devices.end(); it2++) { @@ -164,7 +164,7 @@ int LegoDeviceEnumerate::GetBestDevice() return -1; } - if (m_list.size() == 0) { + if (m_ddInfo.size() == 0) { return -1; } @@ -173,7 +173,7 @@ int LegoDeviceEnumerate::GetBestDevice() int k = -1; int cpu_mmx = SupportsMMX(); - for (list::iterator it = m_list.begin(); it != m_list.end(); it++, i++) { + for (list::iterator it = m_ddInfo.begin(); it != m_ddInfo.end(); it++, i++) { MxDriver& driver = *it; for (list::iterator it2 = driver.m_devices.begin(); it2 != driver.m_devices.end(); it2++) { @@ -297,9 +297,9 @@ int LegoDeviceEnumerate::FUN_1009d210() return -1; } - for (list::iterator it = m_list.begin(); it != m_list.end();) { + for (list::iterator it = m_ddInfo.begin(); it != m_ddInfo.end();) { if (!DriverSupportsRequiredDisplayMode(*it)) { - m_list.erase(it++); + m_ddInfo.erase(it++); continue; } @@ -315,14 +315,14 @@ int LegoDeviceEnumerate::FUN_1009d210() } if (!driver.m_devices.size()) { - m_list.erase(it++); + m_ddInfo.erase(it++); } else { it++; } } - if (!m_list.size()) { + if (!m_ddInfo.size()) { return -1; } @@ -351,7 +351,7 @@ unsigned char LegoDeviceEnumerate::DriverSupportsRequiredDisplayMode(MxDriver& p // FUNCTION: BETA10 0x1011d235 unsigned char LegoDeviceEnumerate::FUN_1009d3d0(Direct3DDeviceInfo& p_device) { - if (m_list.size() <= 0) { + if (m_ddInfo.size() <= 0) { return FALSE; } @@ -365,7 +365,7 @@ unsigned char LegoDeviceEnumerate::FUN_1009d3d0(Direct3DDeviceInfo& p_device) } } - MxDriver& front = m_list.front(); + MxDriver& front = m_ddInfo.front(); for (list::iterator it = front.m_devices.begin(); it != front.m_devices.end(); it++) { if ((&*it) == &p_device) { return TRUE; diff --git a/LEGO1/mxdirectx/mxdirect3d.cpp b/LEGO1/mxdirectx/mxdirect3d.cpp index 028b7ced..5a3d9ff6 100644 --- a/LEGO1/mxdirectx/mxdirect3d.cpp +++ b/LEGO1/mxdirectx/mxdirect3d.cpp @@ -262,7 +262,7 @@ BOOL MxDirect3D::SetDevice(MxDeviceEnumerate& p_deviceEnumerate, MxDriver* p_dri assert(d); int i = 0; - for (list::iterator it = p_deviceEnumerate.m_list.begin(); it != p_deviceEnumerate.m_list.end(); + for (list::iterator it = p_deviceEnumerate.m_ddInfo.begin(); it != p_deviceEnumerate.m_ddInfo.end(); it++, i++) { MxDriver& driver = *it; diff --git a/LEGO1/mxdirectx/mxdirectxinfo.cpp b/LEGO1/mxdirectx/mxdirectxinfo.cpp index 11ca9da2..3070eb08 100644 --- a/LEGO1/mxdirectx/mxdirectxinfo.cpp +++ b/LEGO1/mxdirectx/mxdirectxinfo.cpp @@ -197,7 +197,7 @@ MxDeviceEnumerate::~MxDeviceEnumerate() BOOL MxDeviceEnumerate::EnumDirectDrawCallback(LPGUID p_guid, LPSTR p_driverDesc, LPSTR p_driverName) { MxDriver driver(p_guid, p_driverDesc, p_driverName); - m_list.push_back(driver); + m_ddInfo.push_back(driver); // Must be zeroed because held resources are copied by pointer only // and should not be freed at the end of this function @@ -208,7 +208,7 @@ BOOL MxDeviceEnumerate::EnumDirectDrawCallback(LPGUID p_guid, LPSTR p_driverDesc LPDIRECT3D2 lpDirect3d2 = NULL; LPDIRECTDRAW lpDD = NULL; - MxDriver& newDevice = m_list.back(); + MxDriver& newDevice = m_ddInfo.back(); HRESULT result = DirectDrawCreate(newDevice.m_guid, &lpDD, NULL); if (result != DD_OK) { @@ -236,7 +236,7 @@ BOOL MxDeviceEnumerate::EnumDirectDrawCallback(LPGUID p_guid, LPSTR p_driverDesc } else { if (!newDevice.m_devices.size()) { - m_list.pop_back(); + m_ddInfo.pop_back(); } } } @@ -306,12 +306,12 @@ HRESULT CALLBACK MxDeviceEnumerate::DevicesEnumerateCallback( // FUNCTION: BETA10 0x1011e27f HRESULT MxDeviceEnumerate::EnumDisplayModesCallback(LPDDSURFACEDESC p_ddsd) { - assert(m_list.size() > 0); + assert(m_ddInfo.size() > 0); assert(p_ddsd); // TODO: compat_mode? MxDisplayMode displayMode(p_ddsd->dwWidth, p_ddsd->dwHeight, p_ddsd->ddpfPixelFormat.dwRGBBitCount); - m_list.back().m_displayModes.push_back(displayMode); + m_ddInfo.back().m_displayModes.push_back(displayMode); return DDENUMRET_OK; } @@ -327,7 +327,7 @@ HRESULT MxDeviceEnumerate::EnumDevicesCallback( ) { Direct3DDeviceInfo device(p_guid, p_deviceDesc, p_deviceName, p_HWDesc, p_HELDesc); - m_list.back().m_devices.push_back(device); + m_ddInfo.back().m_devices.push_back(device); memset(&device, 0, sizeof(device)); return DDENUMRET_OK; } diff --git a/LEGO1/mxdirectx/mxdirectxinfo.h b/LEGO1/mxdirectx/mxdirectxinfo.h index 3fbecfc8..03370c1e 100644 --- a/LEGO1/mxdirectx/mxdirectxinfo.h +++ b/LEGO1/mxdirectx/mxdirectxinfo.h @@ -218,8 +218,8 @@ class MxDeviceEnumerate { ); friend class MxDirect3D; - - const list& GetDriverList() const { return m_list; } + friend class CConfigApp; + friend class CMainDialog; // SIZE 0x10 struct GUID4 { @@ -240,7 +240,7 @@ class MxDeviceEnumerate { unsigned char IsInitialized() const { return m_initialized; } protected: - list m_list; // 0x04 + list m_ddInfo; // 0x04 unsigned char m_initialized; // 0x10 }; From ee6e230a4d8495c41a4987618e222845e9157703 Mon Sep 17 00:00:00 2001 From: Korbo Date: Mon, 14 Jul 2025 13:37:44 -0500 Subject: [PATCH 135/188] New 3DS banner sound, move 3DS data to packaging (#605) * New 3DS banner sound * move 3DS CIA data to packaging/3ds --- CMakeLists.txt | 12 ++++++------ ISLE/res/3ds/banner.wav | Bin 497206 -> 0 bytes {ISLE/res => packaging}/3ds/banner.png | Bin packaging/3ds/banner.wav | Bin 0 -> 423408 bytes {ISLE/res => packaging}/3ds/icon.png | Bin {ISLE/res => packaging}/3ds/logo.bcma.lz | Bin {ISLE/res => packaging}/3ds/template.rsf | 0 7 files changed, 6 insertions(+), 6 deletions(-) delete mode 100644 ISLE/res/3ds/banner.wav rename {ISLE/res => packaging}/3ds/banner.png (100%) create mode 100644 packaging/3ds/banner.wav rename {ISLE/res => packaging}/3ds/icon.png (100%) rename {ISLE/res => packaging}/3ds/logo.bcma.lz (100%) rename {ISLE/res => packaging}/3ds/template.rsf (100%) diff --git a/CMakeLists.txt b/CMakeLists.txt index 38ce8ef1..b6e1f3aa 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -779,7 +779,7 @@ if(NINTENDO_3DS) DESCRIPTION "LEGO Island for the Nintendo 3DS" AUTHOR "isledecomp/isle-portable" VERSION "${PROJECT_VERSION}" - ICON "ISLE/res/3ds/icon.png" + ICON "${CMAKE_SOURCE_DIR}/packaging/3ds/icon.png" ) ctr_create_3dsx(isle SMDH isle.smdh) @@ -787,10 +787,10 @@ if(NINTENDO_3DS) add_custom_command( OUTPUT "isle.bnr" COMMAND "${BANNERTOOL}" makebanner - -i "${CMAKE_SOURCE_DIR}/ISLE/res/3ds/banner.png" - -a "${CMAKE_SOURCE_DIR}/ISLE/res/3ds/banner.wav" + -i "${CMAKE_SOURCE_DIR}/packaging/3ds/banner.png" + -a "${CMAKE_SOURCE_DIR}/packaging/3ds/banner.wav" -o "isle.bnr" - DEPENDS "${CMAKE_SOURCE_DIR}/ISLE/res/3ds/banner.png" "${CMAKE_SOURCE_DIR}/ISLE/res/3ds/banner.wav" + DEPENDS "${CMAKE_SOURCE_DIR}/packaging/3ds/banner.png" "${CMAKE_SOURCE_DIR}/packaging/3ds/banner.wav" VERBATIM ) @@ -800,14 +800,14 @@ if(NINTENDO_3DS) -f cia -exefslogo -o "isle.cia" - -rsf "${CMAKE_SOURCE_DIR}/ISLE/res/3ds/template.rsf" + -rsf "${CMAKE_SOURCE_DIR}/packaging/3ds/template.rsf" -major "${CMAKE_PROJECT_VERSION_MAJOR}" -minor "${CMAKE_PROJECT_VERSION_MINOR}" -micro 0 -icon "isle.smdh" -banner "isle.bnr" -elf "isle.elf" - DEPENDS "${CMAKE_SOURCE_DIR}/ISLE/res/3ds/template.rsf" "isle.smdh" "isle.bnr" + DEPENDS "${CMAKE_SOURCE_DIR}/packaging/3ds/template.rsf" "isle.smdh" "isle.bnr" COMMENT "Building CIA executable target isle.cia" VERBATIM ) diff --git a/ISLE/res/3ds/banner.wav b/ISLE/res/3ds/banner.wav deleted file mode 100644 index ce7849e72be18f2a1a2ca2ea92c92773ce35701a..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 497206 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�&CNU-(U{g+uzsU&EJLT{20GEC@L@AdxeEy5%=S1?zw2w97zedT=Rz2AEG`VRRPh313` z8YPTm~T5hrdvc z>qns2_yeW!9O%V=Etq@W9vr+^#P23pgdT{ z!ZqATYoz6&XZTk>AY0(0T!614_`@5)Aeu5On#wFo^vWH{D8IpXQ5;{5K6ommh10`R z@O=`zy%Kmr%y-Rqy-j(WQjB#4H~d8EcMZ+^4X}~}=o{^RD|k|@Px0%%w`lH*ay7NhO_$|sawPNf6~-z<_?jK& zI~_zS*+Jg-5$%Y!hrZ?lT=a>-slmVDSM8&RY0KQN1bxqTu-OkgkLATZySPY*?8r_p#eMr~^QaDyR!^VX9AN zQZ==bTAF_59bDTAbZB!dvn{{c#@W(r8Ma2`B|cQ1Dr?~gJPW-HeM7GITYiqf8Lz_- z=*9U}2R%eZ@J!bPMa@Hd;$O4_70c-D!YRdDojniFsjJ7cavMzWYg<^wyquib&4 zy^1MPVdmn|p;MkSp4!< z%Y!8k1hXp!1pl?$6qw9{9+d#0RQ@T3>z4S^LEM*jvt1j>OUVo(b$# zFTAh+>rtO`Pj%3{=s&@Ie2=c+V)%S`8yt(F@RjR?>V|q~-L>8L8s(GnOLgf3n@Y{4 z#$c&KnZLD*Xb>KUGcG;$xrkRf6Zy~%hBo4t+>X+&|bu-HI`|46W=^My(Ce3fNQ*xGuQ@ zc>{M?W0>XkVOG<{(#diXy!&w7{0=9_+7n2>oJydP}CO7n0ue1 z`%s|3R6|33cQ{w#(KD6 zVwO3Vbv1M~G#3tvoi#N$CHUC?#BU}K*9zI=Zs=Ah1V(d=?jS~r;mW*{?wanH9-^xh z+EJbCz!mo8BmjI1W2L&iJ2SEA@(ei#m{dv}0YsrIs!+Dc6&R*okfVU+wE{`c?staF`nNxuBbDL9mcZG~(Dtpk}Lv_#of z#fkvIdJ=s}2UZhHQ*iLyFgiY1UvjR@Qf4W;nXO5(Ax*YUu?kM`>w`_Nq9o%!&CEw&))a|Gbk*^{Lpr!qXzO;t9uDL7op4rv~ z)(e(XmIvgDMw=pFXnZvFH1|MnMgk%8_{r!y(zMT7u5i9^4Exk-ZLRizXD0$J7e;&j zMetW2;HOa!H`2x0Qti5ag_&P>y(B0_F7B%?oE;~)&WY^t-=TmmN6$S59YQ1*&@d~O zHO%j?S#MhVQd5ZYX$#*)ekDH>z=>$4>Y<|;kG^Y&yg^xyr^qS!TeK68$-J2bZ?81C z*Jzkq!cR?j+Y5i&JK8<1EE6B`zKg0wRWDq%zXQ7hyKo;9GlNOIULKT%f`60BOs#>p zf%i5m*`Mqe9IA1_iLl|C1h-P7ln;GFK4M*V4VnL~1N$>QqM z4Lm3`G~`!(>IHbIllZv@;p{u4NG5o@q}F7Q3!J_N(@>$o($KgIJa;QRMsfa50%P4E zt(7*yU2vGJv{^a8@7IR+h5ij)!i#Gv_2_u&*J*fkUJ6|biN2w5uvl<1xdFpC=MD!C z1toMc6TwoOYb~^^^BX8(h-=?u)u=K?MH1eT!NX#wEwW4+31?NII)u#K)pNy6;G{gJnpn6vKUSFLWKH zi>&SdCp77b{%`nDxFH@@eaMB}g6n5pSAEx?S%0D)$jBP&`qkxfWs>{YI(r%Gb@r?5 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 z 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