Emscripten port

This commit is contained in:
Christian Semmler 2025-06-03 17:03:42 -07:00
parent e349842ea4
commit a12f2ee179
No known key found for this signature in database
GPG Key ID: 086DAA1360BEEE5C
23 changed files with 458 additions and 41 deletions

View File

@ -2,6 +2,15 @@ cmake_minimum_required(VERSION 3.25...4.0 FATAL_ERROR)
project(isle LANGUAGES CXX C VERSION 0.1)
if (EMSCRIPTEN)
set(CMAKE_EXECUTABLE_SUFFIX ".html")
add_compile_options("$<$<PLATFORM_ID:Emscripten>:-pthread>")
add_link_options("$<$<PLATFORM_ID:Emscripten>:-sALLOW_MEMORY_GROWTH=1>")
add_link_options("$<$<PLATFORM_ID:Emscripten>:-sMAXIMUM_MEMORY=2gb>")
set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -pthread -sUSE_PTHREADS=1 -sPROXY_TO_PTHREAD=1 -sPTHREAD_POOL_SIZE_STRICT=0 -sFORCE_FILESYSTEM=1 -sWASMFS=1 -sEXIT_RUNTIME=1")
set(SDL_PTHREADS ON CACHE BOOL "Enable SDL pthreads" FORCE)
endif()
list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_LIST_DIR}/cmake")
include(CheckCXXSourceCompiles)
@ -34,6 +43,7 @@ option(CMAKE_POSITION_INDEPENDENT_CODE "Build with -fPIC" ON)
option(ENABLE_CLANG_TIDY "Enable clang-tidy")
option(DOWNLOAD_DEPENDENCIES "Download dependencies" ON)
set(CMAKE_RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}" CACHE PATH "Directory where to put executables and dll")
set(ISLE_EMSCRIPTEN_HOST "" CACHE STRING "Host URL for Emscripten streaming (e.g., https://test.com)")
message(STATUS "Isle app: ${ISLE_BUILD_APP}")
message(STATUS "Config app: ${ISLE_BUILD_CONFIG}")
@ -131,9 +141,16 @@ target_link_directories(DirectX5::DirectX5 INTERFACE "${CMAKE_SOURCE_DIR}/3rdpar
add_library(Vec::Vec INTERFACE IMPORTED)
target_include_directories(Vec::Vec INTERFACE "${CMAKE_SOURCE_DIR}/3rdparty/vec")
add_library(lego1 SHARED
LEGO1/main.cpp
)
if (EMSCRIPTEN)
add_library(lego1 STATIC
LEGO1/main.cpp
)
else()
add_library(lego1 SHARED
LEGO1/main.cpp
)
endif()
target_precompile_headers(lego1 PRIVATE "LEGO1/lego1_pch.h")
set_property(TARGET lego1 PROPERTY DEFINE_SYMBOL "LEGO1_DLL")
target_include_directories(lego1 PUBLIC "$<BUILD_INTERFACE:${CMAKE_SOURCE_DIR}/util>")
@ -488,6 +505,14 @@ if (ISLE_BUILD_APP)
target_compile_definitions(isle PRIVATE ISLE_DEBUG)
target_link_libraries(isle PRIVATE imgui)
endif()
if(EMSCRIPTEN)
target_sources(isle PRIVATE
ISLE/emscripten/events.cpp
ISLE/emscripten/filesystem.cpp
ISLE/emscripten/messagebox.cpp
)
target_compile_definitions(isle PRIVATE "ISLE_EMSCRIPTEN_HOST=\"${ISLE_EMSCRIPTEN_HOST}\"")
endif()
endif()
if (ISLE_BUILD_CONFIG)

View File

@ -0,0 +1,38 @@
#include "events.h"
#include "mxdsaction.h"
#include <emscripten.h>
// clang-format off
void Emscripten_SendEvent(const char* p_event, const char* p_json)
{
MAIN_THREAD_EM_ASM({
const eventName = UTF8ToString($0);
let eventDetail = {};
if ($1 && UTF8ToString($1).length > 0) {
eventDetail = JSON.parse(UTF8ToString($1));
}
const targetElement = Module.canvas || window;
const event = new CustomEvent(eventName, {detail : eventDetail});
targetElement.dispatchEvent(event);
}, p_event, p_json);
}
// clang-format on
void Emscripten_SendPresenterProgress(MxPresenter* p_presenter, MxPresenter::TickleState p_tickleState)
{
char buf[128];
SDL_snprintf(
buf,
sizeof(buf),
"{\"objectId\": %d, \"objectName\": \"%s\", \"tickleState\": %d}",
p_presenter->GetAction() ? p_presenter->GetAction()->GetObjectId() : 0,
p_presenter->GetAction() ? p_presenter->GetAction()->GetObjectName() : "",
p_tickleState
);
Emscripten_SendEvent("presenterProgress", buf);
}

8
ISLE/emscripten/events.h Normal file
View File

@ -0,0 +1,8 @@
#ifndef EMSCRIPTEN_EVENTS_H
#define EMSCRIPTEN_EVENTS_H
#include "mxpresenter.h"
void Emscripten_SendPresenterProgress(MxPresenter* p_presenter, MxPresenter::TickleState p_tickleState);
#endif // EMSCRIPTEN_EVENTS_H

View File

@ -0,0 +1,106 @@
#include "filesystem.h"
#include "legogamestate.h"
#include "misc.h"
#include "mxomni.h"
#include <SDL3/SDL_filesystem.h>
#include <SDL3/SDL_log.h>
#include <emscripten/wasmfs.h>
void Emscripten_SetupFilesystem()
{
auto fetchfs =
wasmfs_create_fetch_backend((MxString(Emscripten_streamHost) + MxString("/LEGO")).GetData(), 512 * 1024);
wasmfs_create_directory("/LEGO", 0644, fetchfs);
wasmfs_create_directory("/LEGO/Scripts", 0644, fetchfs);
wasmfs_create_directory("/LEGO/Scripts/Act2", 0644, fetchfs);
wasmfs_create_directory("/LEGO/Scripts/Act3", 0644, fetchfs);
wasmfs_create_directory("/LEGO/Scripts/Build", 0644, fetchfs);
wasmfs_create_directory("/LEGO/Scripts/Garage", 0644, fetchfs);
wasmfs_create_directory("/LEGO/Scripts/Hospital", 0644, fetchfs);
wasmfs_create_directory("/LEGO/Scripts/Infocntr", 0644, fetchfs);
wasmfs_create_directory("/LEGO/Scripts/Isle", 0644, fetchfs);
wasmfs_create_directory("/LEGO/Scripts/Police", 0644, fetchfs);
wasmfs_create_directory("/LEGO/Scripts/Race", 0644, fetchfs);
wasmfs_create_directory("/LEGO/data", 0644, fetchfs);
const auto registerFile = [&fetchfs](const char* p_path) {
MxString path = MxString(Emscripten_bundledPath) + MxString(p_path);
path.MapPathToFilesystem();
if (SDL_GetPathInfo(path.GetData(), NULL)) {
SDL_Log("File %s is bundled and won't be streamed", p_path);
}
else {
wasmfs_create_file(p_path, 0644, fetchfs);
MxOmni::GetCDFiles().emplace_back(p_path);
SDL_Log("File %s set up for streaming", p_path);
}
};
registerFile("/LEGO/Scripts/CREDITS.SI");
registerFile("/LEGO/Scripts/INTRO.SI");
registerFile("/LEGO/Scripts/NOCD.SI");
registerFile("/LEGO/Scripts/SNDANIM.SI");
registerFile("/LEGO/Scripts/Act2/ACT2MAIN.SI");
registerFile("/LEGO/Scripts/Act3/ACT3.SI");
registerFile("/LEGO/Scripts/Build/COPTER.SI");
registerFile("/LEGO/Scripts/Build/DUNECAR.SI");
registerFile("/LEGO/Scripts/Build/JETSKI.SI");
registerFile("/LEGO/Scripts/Build/RACECAR.SI");
registerFile("/LEGO/Scripts/Garage/GARAGE.SI");
registerFile("/LEGO/Scripts/Hospital/HOSPITAL.SI");
registerFile("/LEGO/Scripts/Infocntr/ELEVBOTT.SI");
registerFile("/LEGO/Scripts/Infocntr/HISTBOOK.SI");
registerFile("/LEGO/Scripts/Infocntr/INFODOOR.SI");
registerFile("/LEGO/Scripts/Infocntr/INFOMAIN.SI");
registerFile("/LEGO/Scripts/Infocntr/INFOSCOR.SI");
registerFile("/LEGO/Scripts/Infocntr/REGBOOK.SI");
registerFile("/LEGO/Scripts/Isle/ISLE.SI");
registerFile("/LEGO/Scripts/Isle/JUKEBOX.SI");
registerFile("/LEGO/Scripts/Isle/JUKEBOXW.SI");
registerFile("/LEGO/Scripts/Police/POLICE.SI");
registerFile("/LEGO/Scripts/Race/CARRACE.SI");
registerFile("/LEGO/Scripts/Race/CARRACER.SI");
registerFile("/LEGO/Scripts/Race/JETRACE.SI");
registerFile("/LEGO/Scripts/Race/JETRACER.SI");
registerFile("/LEGO/data/ACT1INF.DTA");
registerFile("/LEGO/data/ACT2INF.DTA");
registerFile("/LEGO/data/ACT3INF.DTA");
registerFile("/LEGO/data/BLDDINF.DTA");
registerFile("/LEGO/data/BLDHINF.DTA");
registerFile("/LEGO/data/BLDJINF.DTA");
registerFile("/LEGO/data/BLDRINF.DTA");
registerFile("/LEGO/data/GMAININF.DTA");
registerFile("/LEGO/data/HOSPINF.DTA");
registerFile("/LEGO/data/ICUBEINF.DTA");
registerFile("/LEGO/data/IELEVINF.DTA");
registerFile("/LEGO/data/IISLEINF.DTA");
registerFile("/LEGO/data/IMAININF.DTA");
registerFile("/LEGO/data/IREGINF.DTA");
registerFile("/LEGO/data/OBSTINF.DTA");
registerFile("/LEGO/data/PMAININF.DTA");
registerFile("/LEGO/data/RACCINF.DTA");
registerFile("/LEGO/data/RACJINF.DTA");
registerFile("/LEGO/data/WORLD.WDB");
registerFile("/LEGO/data/testinf.dta");
if (GameState()->GetSavePath() && *GameState()->GetSavePath()) {
auto opfs = wasmfs_create_opfs_backend();
MxString savePath = GameState()->GetSavePath();
if (savePath.GetData()[savePath.GetLength() - 1] != '/') {
savePath += "/";
}
char* parse = savePath.GetData();
while ((parse = SDL_strchr(++parse, '/'))) {
*parse = '\0';
wasmfs_create_directory(savePath.GetData(), 0644, opfs);
*parse = '/';
}
}
}

View File

@ -0,0 +1,15 @@
#ifndef EMSCRIPTEN_FILESYSTEM_H
#define EMSCRIPTEN_FILESYSTEM_H
#ifndef ISLE_EMSCRIPTEN_HOST
#define ISLE_EMSCRIPTEN_HOST ""
#endif
inline static const char* Emscripten_bundledPath = "/bundled";
inline static const char* Emscripten_savePath = "/save";
inline static const char* Emscripten_streamPath = "/";
inline static const char* Emscripten_streamHost = ISLE_EMSCRIPTEN_HOST;
void Emscripten_SetupFilesystem();
#endif // EMSCRIPTEN_FILESYSTEM_H

View File

@ -0,0 +1,14 @@
#include "messagebox.h"
#include <emscripten.h>
bool Emscripten_ShowSimpleMessageBox(
SDL_MessageBoxFlags flags,
const char* title,
const char* message,
SDL_Window* window
)
{
MAIN_THREAD_EM_ASM({alert(UTF8ToString($0) + "\n\n" + UTF8ToString($1))}, title, message);
return true;
}

View File

@ -0,0 +1,13 @@
#ifndef EMSCRIPTEN_MESSAGE_BOX_H
#define EMSCRIPTEN_MESSAGE_BOX_H
#include <SDL3/SDL_messagebox.h>
bool Emscripten_ShowSimpleMessageBox(
SDL_MessageBoxFlags flags,
const char* title,
const char* message,
SDL_Window* window
);
#endif // EMSCRIPTEN_MESSAGE_BOX_H

View File

@ -39,6 +39,12 @@
#include <stdlib.h>
#include <time.h>
#ifdef __EMSCRIPTEN__
#include "emscripten/events.h"
#include "emscripten/filesystem.h"
#include "emscripten/messagebox.h"
#endif
DECOMP_SIZE_ASSERT(IsleApp, 0x8c)
// GLOBAL: ISLE 0x410030
@ -95,7 +101,7 @@ IsleApp::IsleApp()
m_useJoystick = FALSE;
m_joystickIndex = 0;
m_wideViewAngle = TRUE;
m_islandQuality = 1;
m_islandQuality = 2;
m_islandTexture = 1;
m_gameStarted = FALSE;
m_frameDelta = 10;
@ -239,6 +245,9 @@ SDL_AppResult SDL_AppInit(void** appstate, int argc, char** argv)
{
*appstate = NULL;
SDL_SetHint(SDL_HINT_MOUSE_TOUCH_EVENTS, "0");
SDL_SetHint(SDL_HINT_TOUCH_MOUSE_EVENTS, "0");
if (!SDL_Init(SDL_INIT_VIDEO | SDL_INIT_AUDIO | SDL_INIT_JOYSTICK)) {
char buffer[256];
SDL_snprintf(
@ -247,7 +256,7 @@ SDL_AppResult SDL_AppInit(void** appstate, int argc, char** argv)
"\"LEGO® Island\" failed to start.\nPlease quit all other applications and try again.\nSDL error: %s",
SDL_GetError()
);
SDL_ShowSimpleMessageBox(SDL_MESSAGEBOX_ERROR, "LEGO® Island Error", buffer, NULL);
Any_ShowSimpleMessageBox(SDL_MESSAGEBOX_ERROR, "LEGO® Island Error", buffer, NULL);
return SDL_APP_FAILURE;
}
@ -259,7 +268,7 @@ SDL_AppResult SDL_AppInit(void** appstate, int argc, char** argv)
g_isle = new IsleApp();
if (g_isle->ParseArguments(argc, argv) != SUCCESS) {
SDL_ShowSimpleMessageBox(
Any_ShowSimpleMessageBox(
SDL_MESSAGEBOX_ERROR,
"LEGO® Island Error",
"\"LEGO® Island\" failed to start. Invalid CLI arguments.",
@ -270,7 +279,7 @@ SDL_AppResult SDL_AppInit(void** appstate, int argc, char** argv)
// Create window
if (g_isle->SetupWindow() != SUCCESS) {
SDL_ShowSimpleMessageBox(
Any_ShowSimpleMessageBox(
SDL_MESSAGEBOX_ERROR,
"LEGO® Island Error",
"\"LEGO® Island\" failed to start.\nPlease quit all other applications and try again.",
@ -281,6 +290,20 @@ SDL_AppResult SDL_AppInit(void** appstate, int argc, char** argv)
// Get reference to window
*appstate = g_isle->GetWindowHandle();
#ifdef __EMSCRIPTEN__
SDL_AddEventWatch(
[](void* userdata, SDL_Event* event) -> bool {
if (event->type == SDL_EVENT_TERMINATING && g_isle && g_isle->GetGameStarted()) {
GameState()->Save(0);
return false;
}
return true;
},
NULL
);
#endif
return SDL_APP_CONTINUE;
}
@ -291,7 +314,7 @@ SDL_AppResult SDL_AppIterate(void* appstate)
}
if (!g_isle->Tick()) {
SDL_ShowSimpleMessageBox(
Any_ShowSimpleMessageBox(
SDL_MESSAGEBOX_ERROR,
"LEGO® Island Error",
"\"LEGO® Island\" failed to start.\nPlease quit all other applications and try again."
@ -317,7 +340,7 @@ SDL_AppResult SDL_AppIterate(void* appstate)
if (g_mousedown && g_mousemoved && g_isle) {
if (!g_isle->Tick()) {
SDL_ShowSimpleMessageBox(
Any_ShowSimpleMessageBox(
SDL_MESSAGEBOX_ERROR,
"LEGO® Island Error",
"\"LEGO® Island\" failed to start.\nPlease quit all other applications and try again."
@ -351,6 +374,12 @@ SDL_AppResult SDL_AppEvent(void* appstate, SDL_Event* event)
// Full screen - crashes when minimizing/maximizing, but this will probably be fixed once DirectDraw is replaced
// WM_TIMER - use SDL_Timer functionality instead
#ifdef __EMSCRIPTEN__
// Workaround for the fact we are getting both mouse & touch events on mobile devices running Emscripten.
// On desktops, we are only getting mouse events, but a touch device (pen_input) may also be present...
static bool detectedTouchEvents = false;
#endif
switch (event->type) {
case SDL_EVENT_WINDOW_FOCUS_GAINED:
if (!IsleDebug_Enabled()) {
@ -359,9 +388,12 @@ SDL_AppResult SDL_AppEvent(void* appstate, SDL_Event* event)
}
break;
case SDL_EVENT_WINDOW_FOCUS_LOST:
if (!IsleDebug_Enabled()) {
if (!IsleDebug_Enabled() && g_isle->GetGameStarted()) {
g_isle->SetWindowActive(FALSE);
Lego()->Pause();
#ifdef __EMSCRIPTEN__
GameState()->Save(0);
#endif
}
break;
case SDL_EVENT_WINDOW_CLOSE_REQUESTED:
@ -383,6 +415,11 @@ SDL_AppResult SDL_AppEvent(void* appstate, SDL_Event* event)
break;
}
case SDL_EVENT_MOUSE_MOTION:
#ifdef __EMSCRIPTEN__
if (detectedTouchEvents) {
break;
}
#endif
g_mousemoved = TRUE;
if (InputManager()) {
@ -399,7 +436,30 @@ SDL_AppResult SDL_AppEvent(void* appstate, SDL_Event* event)
VideoManager()->MoveCursor(Min((MxS32) event->motion.x, 639), Min((MxS32) event->motion.y, 479));
}
break;
case SDL_EVENT_FINGER_MOTION: {
#ifdef __EMSCRIPTEN__
detectedTouchEvents = true;
#endif
g_mousemoved = TRUE;
float x = SDL_clamp(event->tfinger.x, 0, 1) * 640;
float y = SDL_clamp(event->tfinger.y, 0, 1) * 480;
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));
}
break;
}
case SDL_EVENT_MOUSE_BUTTON_DOWN:
#ifdef __EMSCRIPTEN__
if (detectedTouchEvents) {
break;
}
#endif
g_mousedown = TRUE;
if (InputManager()) {
@ -412,7 +472,32 @@ SDL_AppResult SDL_AppEvent(void* appstate, SDL_Event* event)
);
}
break;
case SDL_EVENT_FINGER_DOWN: {
#ifdef __EMSCRIPTEN__
detectedTouchEvents = true;
#endif
g_mousedown = TRUE;
float x = SDL_clamp(event->tfinger.x, 0, 1) * 640;
float y = SDL_clamp(event->tfinger.y, 0, 1) * 480;
if (InputManager()) {
InputManager()->QueueEvent(c_notificationButtonDown, LegoEventNotificationParam::c_lButtonState, x, y, 0);
}
break;
}
case SDL_EVENT_MOUSE_BUTTON_UP:
#ifdef __EMSCRIPTEN__
if (detectedTouchEvents) {
// Abusing the fact (bug?) that we are always getting mouse events on Emscripten.
// This functionality should be enabled in a more general way with touch events,
// but SDL touch event's don't have a "double tap" indicator right now.
if (event->button.clicks == 2) {
InputManager()->QueueEvent(c_notificationKeyPress, SDLK_SPACE, 0, 0, SDLK_SPACE);
}
break;
}
#endif
g_mousedown = FALSE;
if (InputManager()) {
@ -425,6 +510,20 @@ SDL_AppResult SDL_AppEvent(void* appstate, SDL_Event* event)
);
}
break;
case SDL_EVENT_FINGER_UP: {
#ifdef __EMSCRIPTEN__
detectedTouchEvents = true;
#endif
g_mousedown = FALSE;
float x = SDL_clamp(event->tfinger.x, 0, 1) * 640;
float y = SDL_clamp(event->tfinger.y, 0, 1) * 480;
if (InputManager()) {
InputManager()->QueueEvent(c_notificationButtonUp, 0, x, y, 0);
}
break;
}
case SDL_EVENT_QUIT:
return SDL_APP_SUCCESS;
break;
@ -445,6 +544,23 @@ SDL_AppResult SDL_AppEvent(void* appstate, SDL_Event* event)
break;
}
}
else if (event->user.type == g_legoSdlEvents.m_presenterProgress) {
MxPresenter* presenter = static_cast<MxPresenter*>(event->user.data1);
MxDSAction* action = presenter->GetAction();
MxPresenter::TickleState state = static_cast<MxPresenter::TickleState>(event->user.code);
#ifdef __EMSCRIPTEN__
if (!g_isle->GetGameStarted()) {
Emscripten_SendPresenterProgress(presenter, state);
}
#endif
if (!g_isle->GetGameStarted() && action && state == MxPresenter::e_ready &&
!SDL_strncmp(action->GetObjectName(), "Lego_Smk", 8)) {
g_isle->SetGameStarted(TRUE);
SDL_Log("Game started");
}
}
return SDL_APP_CONTINUE;
}
@ -546,6 +662,11 @@ MxResult IsleApp::SetupWindow()
}
GameState()->SetSavePath(m_savePath);
#ifdef __EMSCRIPTEN__
Emscripten_SetupFilesystem();
#endif
GameState()->SerializePlayersInfo(LegoStorage::c_read);
GameState()->SerializeScoreHistory(LegoStorage::c_read);
@ -644,7 +765,7 @@ bool IsleApp::LoadConfig()
iniparser_set(dict, "isle:Back Buffers in Video RAM", "-1");
iniparser_set(dict, "isle:Island Quality", "1");
iniparser_set(dict, "isle:Island Quality", "2");
iniparser_set(dict, "isle:Island Texture", "1");
iniparser_dump_ini(dict, iniFP);
@ -652,12 +773,20 @@ bool IsleApp::LoadConfig()
fclose(iniFP);
}
#ifdef __EMSCRIPTEN__
const char* hdPath = Emscripten_bundledPath;
#else
const char* hdPath = iniparser_getstring(dict, "isle:diskpath", SDL_GetBasePath());
#endif
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);
@ -690,7 +819,7 @@ bool IsleApp::LoadConfig()
}
}
m_islandQuality = iniparser_getint(dict, "isle:Island Quality", 1);
m_islandQuality = iniparser_getint(dict, "isle:Island Quality", 2);
m_islandTexture = iniparser_getint(dict, "isle:Island Texture", 1);
const char* deviceId = iniparser_getstring(dict, "isle:3D Device ID", NULL);
@ -702,7 +831,11 @@ 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);
@ -801,7 +934,6 @@ inline bool IsleApp::Tick()
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Failed to open ISLE.si: Failed to start initial action");
return false;
}
m_gameStarted = TRUE;
}
return true;
@ -844,7 +976,6 @@ void IsleApp::SetupCursor(Cursor p_cursor)
MxResult IsleApp::ParseArguments(int argc, char** argv)
{
for (int i = 1, consumed; i < argc; i += consumed) {
consumed = -1;

View File

@ -48,8 +48,10 @@ class IsleApp {
SDL_Cursor* GetCursorBusy() { return m_cursorBusy; }
SDL_Cursor* GetCursorNo() { return m_cursorNo; }
MxS32 GetDrawCursor() { return m_drawCursor; }
MxS32 GetGameStarted() { return m_gameStarted; }
void SetWindowActive(MxS32 p_windowActive) { m_windowActive = p_windowActive; }
void SetGameStarted(MxS32 p_gameStarted) { m_gameStarted = p_gameStarted; }
MxResult ParseArguments(int argc, char** argv);

View File

@ -224,6 +224,8 @@ class LegoGameState {
void FindLoadedAct();
void RegisterState(LegoState* p_state);
const char* GetSavePath() { return m_savePath; }
private:
MxResult WriteVariable(LegoStorage* p_storage, MxVariableTable* p_from, const char* p_variableName);
MxResult WriteEndOfVariables(LegoStorage* p_storage);

View File

@ -143,6 +143,7 @@ class LegoInputManager : public MxPresenter {
MxBool FUN_1005cdf0(LegoEventNotificationParam& p_param);
void GetKeyboardState();
MxResult GetNavigationKeyStates(MxU32& p_keyFlags);
MxResult GetNavigationTouchStates(MxU32& p_keyFlags);
// SYNTHETIC: LEGO1 0x1005b8d0
// LegoInputManager::`scalar deleting destructor'

View File

@ -4,10 +4,8 @@
#include "actionsfwd.h"
#include "decomp.h"
#include "extra.h"
#include "lego1_export.h"
#include "mxtypes.h"
#include <SDL3/SDL_stdinc.h>
#ifdef MINIWIN
#include "miniwin/windows.h"
#else
@ -19,12 +17,6 @@
// name verified by BETA10 0x100d4054
#define DS_NOT_A_STREAM -1
struct LegoSdlEvents {
Uint32 m_windowsMessage;
};
LEGO1_EXPORT extern LegoSdlEvents g_legoSdlEvents;
enum Cursor {
e_cursorArrow = 0,
e_cursorBusy,
@ -70,7 +62,6 @@ void PlayCamAnim(LegoPathActor* p_actor, MxBool p_unused, MxU32 p_location, MxBo
void FUN_1003eda0();
MxBool RemoveFromCurrentWorld(const MxAtomId& p_atomId, MxS32 p_id);
void EnableAnimations(MxBool p_enable);
void InitSdlEvents();
void SetAppCursor(Cursor p_cursor);
MxBool FUN_1003ef60();
MxBool RemoveFromWorld(MxAtomId& p_entityAtom, MxS32 p_entityId, MxAtomId& p_worldAtom, MxS32 p_worldEntityId);

View File

@ -37,8 +37,6 @@
#include <string.h>
#include <vec.h>
LegoSdlEvents g_legoSdlEvents;
// FUNCTION: LEGO1 0x1003dd70
// FUNCTION: BETA10 0x100d3410
LegoROI* PickROI(MxLong p_x, MxLong p_y)
@ -568,17 +566,6 @@ void EnableAnimations(MxBool p_enable)
AnimationManager()->FUN_100604d0(p_enable);
}
void InitSdlEvents()
{
static bool g_initialized = false;
if (!g_initialized) {
g_initialized = true;
Uint32 event = SDL_RegisterEvents(1);
g_legoSdlEvents.m_windowsMessage = event + 0;
}
}
// FUNCTION: LEGO1 0x1003ef40
void SetAppCursor(Cursor p_cursor)
{

View File

@ -136,6 +136,8 @@ MxResult LegoInputManager::GetNavigationKeyStates(MxU32& p_keyFlags)
keyFlags |= c_ctrl;
}
GetNavigationTouchStates(keyFlags);
p_keyFlags = keyFlags;
return SUCCESS;
@ -542,3 +544,45 @@ void LegoInputManager::EnableInputProcessing()
g_unk0x100f31b0 = -1;
g_unk0x100f31b4 = NULL;
}
MxResult LegoInputManager::GetNavigationTouchStates(MxU32& p_keyStates)
{
int count;
SDL_TouchID* touchDevices = SDL_GetTouchDevices(&count);
if (touchDevices) {
auto applyFingerNavigation = [&p_keyStates](SDL_TouchID touchId) {
int count;
SDL_Finger** fingers = SDL_GetTouchFingers(touchId, &count);
if (fingers) {
for (int i = 0; i < count; i++) {
if (fingers[i]->y > 3.0 / 4.0) {
if (fingers[i]->x < 1.0 / 3.0) {
p_keyStates |= c_left;
}
else if (fingers[i]->x > 2.0 / 3.0) {
p_keyStates |= c_right;
}
else {
p_keyStates |= c_down;
}
}
else {
p_keyStates |= c_up;
}
}
SDL_free(fingers);
}
};
for (int i = 0; i < count; i++) {
applyFingerNavigation(touchDevices[i]);
}
SDL_free(touchDevices);
}
return SUCCESS;
}

View File

@ -281,8 +281,6 @@ MxResult LegoOmni::Create(MxOmniCreateParam& p_param)
SetAppCursor(e_cursorBusy);
m_gameState->SetCurrentAct(LegoGameState::e_act1);
InitSdlEvents();
result = SUCCESS;
done:

View File

@ -204,7 +204,7 @@ int LegoDeviceEnumerate::GetBestDevice()
// FUNCTION: BETA10 0x1011cf54
bool LegoDeviceEnumerate::SupportsSIMD()
{
#if defined(__x86_64__) || defined(_M_X64) || defined(__aarch64__) || defined(_M_ARM64)
#if defined(__x86_64__) || defined(_M_X64) || defined(__aarch64__) || defined(_M_ARM64) || defined(__EMSCRIPTEN__)
// All x86_64 and 64-bit ARM CPUs support at least SSE2 or NEON
return true;
#elif defined(__i386__) || defined(_M_IX86)
@ -320,6 +320,10 @@ int LegoDeviceEnumerate::FUN_1009d210()
// FUNCTION: BETA10 0x1011d176
unsigned char LegoDeviceEnumerate::DriverSupportsRequiredDisplayMode(MxDriver& p_driver)
{
#ifdef __EMSCRIPTEN__
return true;
#endif
for (list<MxDisplayMode>::iterator it = p_driver.m_displayModes.begin(); it != p_driver.m_displayModes.end();
it++) {
if ((*it).m_width == 640 && (*it).m_height == 480) {

View File

@ -43,8 +43,8 @@ class MxOmni : public MxCore {
LEGO1_EXPORT static void SetCD(const char* p_cd);
LEGO1_EXPORT static void SetHD(const char* p_hd);
LEGO1_EXPORT static void SetSound3D(MxBool p_use3dSound);
static const vector<MxString>& GetHDFiles() { return g_hdFiles; }
static const vector<MxString>& GetCDFiles() { return g_cdFiles; }
static vector<MxString>& GetHDFiles() { return g_hdFiles; }
static vector<MxString>& GetCDFiles() { return g_cdFiles; }
MxOmni();
~MxOmni() override;

View File

@ -5,6 +5,9 @@
#include "mxcore.h"
#include "mxcriticalsection.h"
#include "mxgeometry.h"
#include "mxutilities.h"
#include <SDL3/SDL_events.h>
class MxCompositePresenter;
class MxDSAction;
@ -62,6 +65,12 @@ class MxPresenter : public MxCore {
{
m_previousTickleStates |= 1 << (MxU8) m_currentTickleState;
m_currentTickleState = p_tickleState;
SDL_Event event;
event.user.type = g_legoSdlEvents.m_presenterProgress;
event.user.code = m_currentTickleState;
event.user.data1 = (void*) this;
SDL_PushEvent(&event);
}
public:

View File

@ -1,10 +1,19 @@
#ifndef MXUTILITIES_H
#define MXUTILITIES_H
#include "lego1_export.h"
#include "mxtypes.h"
#include <SDL3/SDL_stdinc.h>
#include <string.h>
struct LegoSdlEvents {
Uint32 m_windowsMessage;
Uint32 m_presenterProgress;
};
LEGO1_EXPORT extern LegoSdlEvents g_legoSdlEvents;
class MxDSFile;
class MxDSObject;
class MxDSAction;

View File

@ -12,6 +12,8 @@
#include <SDL3/SDL_stdinc.h>
#include <assert.h>
LegoSdlEvents g_legoSdlEvents;
// GLOBAL: LEGO1 0x101020e8
void (*g_omniUserMessage)(const char*, MxS32) = NULL;

View File

@ -162,6 +162,12 @@ MxResult MxOmni::Create(MxOmniCreateParam& p_param)
}
}
{
Uint32 event = SDL_RegisterEvents(2);
g_legoSdlEvents.m_windowsMessage = event + 0;
g_legoSdlEvents.m_presenterProgress = event + 1;
}
result = SUCCESS;
done:

View File

@ -280,6 +280,12 @@ HRESULT DirectDrawImpl::RestoreDisplayMode()
HRESULT DirectDrawImpl::SetCooperativeLevel(HWND hWnd, DDSCLFlags dwFlags)
{
SDL_Window* sdlWindow = reinterpret_cast<SDL_Window*>(hWnd);
#ifdef __EMSCRIPTEN__
DDWindow = sdlWindow;
return DD_OK;
#endif
if (sdlWindow) {
bool fullscreen;
if ((dwFlags & DDSCL_NORMAL) == DDSCL_NORMAL) {

View File

@ -17,6 +17,12 @@
#define DDBitDepths DWORD
#endif
#ifdef __EMSCRIPTEN__
#define Any_ShowSimpleMessageBox Emscripten_ShowSimpleMessageBox
#else
#define Any_ShowSimpleMessageBox SDL_ShowSimpleMessageBox
#endif
// Disable "identifier was truncated to '255' characters" warning.
// Impossible to avoid this if using STL map or set.
// This removes most (but not all) occurrences of the warning.