Files
vpinball-wasm/patches/vpinball/0006-static-pinmame-plugin-emscripten.patch
valknarandClaude Sonnet 5 9479cea280 Add real PinMAME (VPinMAME.Controller) integration for ROM-based tables
Statically links the real PinMAME emulation core (libpinmame) instead of
leaving VPinMAME.Controller creation to fail, which was crashing table
scripts on ROM-based tables before they could spawn a ball. PinMAME's own
run_game()->cpu_run() loop is split into a one-shot init, a per-frame step,
and a one-shot teardown (patches/pinmame/0004) so it runs cooperatively on
the same frame callback as vpinball's own loop instead of on a real
std::thread, which hard-aborts under Emscripten's single-threaded runtime -
three smaller wasm32 portability fixes to libpinmame itself round out the
patch set (0001-0003). Adds pinball.loadRom() to supply a ROM zip, written
to the table-relative pinmame/roms/ path vpinball's own plugin already
checks. Confirmed against a real community ROM-based table: Controller
creation and game identification succeed, and a missing ROM now fails
cleanly instead of crashing the page - actual ROM-driven gameplay is still
unconfirmed since no ROM was available (or sought out) to test with.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SoSarxLgY33Kax5UNXcafZ
2026-08-23 12:09:35 +02:00

113 lines
4.7 KiB
Diff

diff --git a/CMakeLists.txt b/CMakeLists.txt
index 745b91b..edb4112 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -810,8 +810,29 @@ elseif(PLATFORM STREQUAL "emscripten")
src/input/OpenPinDevHandler.cpp
)
+ # PinMAME VPX-side plugin glue (registers VPinMAME.Controller for real
+ # ROM-driven scoring/switches/solenoids) - statically linked and
+ # registered from player.cpp instead of desktop VP's dynamic /plugins
+ # folder scan, same as the built-in "vpx" plugin already is. The real
+ # emulation core it drives (libpinmame.a) is staged into
+ # VPINBALL_WASM_DEPS_DIR by vpinball-wasm's own build-deps.sh (see
+ # patches/pinmame/ for the 3 portability fixes that wasm32 build needed).
+ set(VPX_PINMAME_PLUGIN_SOURCES
+ plugins/pinmame/common.cpp
+ plugins/pinmame/PinMAMEPlugin.cpp
+ plugins/pinmame/Controller.cpp
+ plugins/pinmame/ControllerSettings.cpp
+ plugins/pinmame/Game.cpp
+ plugins/pinmame/Games.cpp
+ plugins/pinmame/GameSettings.cpp
+ plugins/pinmame/Rom.cpp
+ plugins/pinmame/Roms.cpp
+ plugins/pinmame/Settings.cpp
+ )
+
add_executable(vpinball
${VPX_STANDALONE_SOURCES}
+ ${VPX_PINMAME_PLUGIN_SOURCES}
${CMAKE_SOURCE_DIR}/src/core/EmscriptenBridge.cpp
)
@@ -824,6 +845,7 @@ elseif(PLATFORM STREQUAL "emscripten")
${VPINBALL_WASM_DEPS_DIR}/include/libwinevbs/wine/include
src
plugins
+ plugins/pinmame
)
target_compile_definitions(vpinball PRIVATE
@@ -863,6 +885,7 @@ elseif(PLATFORM STREQUAL "emscripten")
freetype
freeimage
winevbs
+ pinmame
glad
)
diff --git a/src/core/player.cpp b/src/core/player.cpp
index 60c7d5f..ec9ebdd 100644
--- a/src/core/player.cpp
+++ b/src/core/player.cpp
@@ -79,6 +79,19 @@ using namespace VPX;
// leave as-is as e.g. VPM relies on this
#define WIN32_PLAYER_WND_CLASSNAME _T("VPPlayer")
+#ifdef __EMSCRIPTEN__
+// Statically-linked player plugin entry points (see the __EMSCRIPTEN__
+// branch in Player::Player below) - same declaration shape used for the
+// iOS/Android static plugin table in lib/src/VPinballLib.cpp, just declared
+// at file scope here since MSGPI_EXPORT's __attribute__((visibility(...)))
+// isn't accepted on a block-scope declaration.
+MSGPI_EXPORT void MSGPIAPI PinMAMEPluginLoad(const uint32_t sessionId, const MsgPluginAPI* api);
+MSGPI_EXPORT void MSGPIAPI PinMAMEPluginUnload();
+
+// For PinmameEmscriptenStep(), called once per frame in EmscriptenStepFrame()
+// below - see that function's own comment.
+#include "pinmame/libpinmame.h"
+#endif
Player::Player(PinTable *const table, const PlayMode playMode)
: m_ptable(table)
@@ -128,6 +141,24 @@ Player::Player(PinTable *const table, const PlayMode playMode)
#ifdef __LIBVPINBALL__
VPinballLib::VPinballLib::SetupStaticPlugins(m_pluginManager);
+#elif defined(__EMSCRIPTEN__)
+ // A browser sandbox has no dynamic-library-loading /plugins folder to
+ // scan (see the ScanPluginFolder call in the #else branch below), so
+ // player plugins have to be statically linked into this same wasm binary
+ // and registered by function pointer instead - the same technique
+ // VPinballLib::SetupStaticPlugins already uses for the iOS/Android
+ // library build (lib/src/VPinballLib.cpp), just scoped here to the one
+ // plugin vpinball-wasm actually ships: PinMAME, which is what lets
+ // table scripts get a real (non-Nothing) VPinMAME.Controller for ROM-
+ // driven scoring/switches/solenoids instead of failing on
+ // CreateObject("VPinMAME.Controller") - see patches/pinmame/ for the
+ // wasm32 portability fixes its emulation core (libpinmame) needed.
+ //
+ // Registered only, not Load()-ed here: the per-plugin enable/Load loop
+ // just below (shared with the dynamic-scan #else branch) already does
+ // that for every entry in m_pluginManager.GetPlugins(), so calling Load()
+ // here too would just load it twice.
+ m_pluginManager.RegisterPlugin("PinMAME", "PinMAME", "PinMAME", "", "", "https://github.com/vpinball/pinmame", &PinMAMEPluginLoad, &PinMAMEPluginUnload);
#else
class SDLModuleLoader final : public MsgPI::MsgModuleLoader
{
@@ -2002,6 +2033,12 @@ bool Player::EmscriptenStepFrame()
if (GetCloseState() != CS_PLAYING && GetCloseState() != CS_USER_INPUT)
return false;
+ // PinMAME (VPinMAME.Controller for ROM-driven tables) is likewise
+ // single-threaded here instead of running on its own real thread - see
+ // PinmameEmscriptenStep's own comment in libpinmame.cpp. A no-op when no
+ // ROM-based table has called Controller.Run().
+ PinmameEmscriptenStep();
+
UpdateGameLogic();
PrepareFrame();
UpdateGameLogic();