From c7fda26cf4676cbafee9986f39ef3ddbee41bed0 Mon Sep 17 00:00:00 2001 From: Fabian Neundorf Date: Tue, 8 Jul 2025 02:35:21 +0200 Subject: [PATCH 01/20] 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 5d3b6884e0c412f7e4033e25c430604f7d3ae2dc Mon Sep 17 00:00:00 2001 From: Fabian Neundorf Date: Tue, 8 Jul 2025 16:26:32 +0200 Subject: [PATCH 02/20] 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 03/20] 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 04/20] 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 05/20] 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 06/20] 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 07/20] 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 08/20] 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 09/20] 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 10/20] 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 11/20] 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 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 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 12/20] 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 13/20] 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 14/20] 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 15/20] 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 16/20] =?UTF-8?q?=F0=9F=A9=B9=20fix:=20touch=20screens=20g?= =?UTF-8?q?o=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 17/20] 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 18/20] 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 0e95e6d521c6d2a024c584245138a039869c3ac1 Mon Sep 17 00:00:00 2001 From: Christian Semmler Date: Fri, 11 Jul 2025 10:45:10 -0700 Subject: [PATCH 19/20] 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 7c91a14875751ca1c3571ae35825040000178129 Mon Sep 17 00:00:00 2001 From: Christian Semmler Date: Fri, 11 Jul 2025 18:05:33 -0700 Subject: [PATCH 20/20] 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;