- Lua 98.7%
- C 1.3%
common/guideline/switch_button.lua's L38_2 creates a standalone Common_SwitchButton-01_Shadow-01 UI_Scale9 at order = parent.order + 1 to serve as the toggle's drop shadow. Atlas center pixel is α=0.53 black. The engine renders each task at its own m_uiOrder (CKLBSpriteScale9 via allocateCommandSprite), so SHADOW at formOrder+1 sits UNDER the pill form's asset_base (effective z = formOrder + JSON.priority(2) = formOrder+2). The orange button covers SHADOW's center, leaving only the soft drop-shadow edges visible. Our flat z-sort treated the entire pill form as drawing at formOrder, which made SHADOW draw on top and the SHADOW's opaque middle patch darkened the orange toggle to (255*0.467, 153*0.467, 33*0.467). Tag standalone UI_Scale9 children of form-stubs whose order is exactly 1 above the stub's effective order as `_inline_scale9_bg`, then in draw_node draw them BEFORE the matching node's content so sibling inline pill forms cover the dark middle. BORDER per-pill (parent=pill form, not stub) and LINE (delta=10, intended foreground) keep their existing standalone-z draw. Resolves: #173 (toggle dark mystery) |
||
|---|---|---|
| .forgejo/workflows | ||
| build | ||
| objects | ||
| pg | ||
| .gitignore | ||
| conf.lua | ||
| main.lua | ||
| README.md | ||
| setup.lua | ||
Replayground
A reimplementation of KLab's Playground engine in Lua + LÖVE, targeting the SIF1 (Love Live! School Idol Festival) client. Loads the original SIF Lua scripts and assets, talks to a private server, and runs the game through to the tutorial flow.
This README documents how to set up the toolchain (custom LÖVE 12 + Lua 5.2 + lsqlite3) and run the project on Linux (Fedora 43 under WSL2 is the verified target) and Windows (Windows 11 with MSVC + vcpkg).
URL schemes: file://X and install://X resolve through love.filesystem. asset://X checks external/X first (DLAPI overlay), then falls back to install/X. external://X is an explicit overlay-only shortcut. Encrypted bundles are decrypted on-the-fly: the /download/ extractor (pg/lualib/task.lua) decrypts as it unzips into external/, and pg/io.read auto-detects + decrypts any libhonoka-encrypted bytes it pulls from install/ so APK-extracted trees that weren't pre-decrypted still work.
Status
The project boots, decrypts assets at runtime, hits a real private server, and reaches the partner-pick screen of the in-game tutorial. The boot animation, register flow, download flow, post-download reboot, and partner.lua rendering (cards + idol icons + arrow buttons + group-switch toggle) all work.
Asset decryption (libhonoka) and login crypto (RSA-1024 PKCS#1 v1.5 + AES-128-CBC + HMAC-SHA1) are pure-Lua — no native build, no openssl shell-out, no /dev/urandom. The login flow has been verified end-to-end against an NPPS4 instance from both Linux and Windows.
Prerequisites
- SIF1 client assets:
AppAssets.zipfrom a SIF1 community APK or the original retail installation. Required because Replayground loads SIF's own Lua scripts + asset files; nothing useful happens without them. - A SIF1 private server to talk to. Default points at
sif.ethanthesleepy.one; configurable viaserver_info.jsoninside the assets bundle (see NPPS4 for setup). - One of:
- Linux — any modern distro with SDL3 / FreeType / HarfBuzz / OpenAL / ModPlug / Theora / Vorbis / Ogg dev packages.
- Windows — Windows 10 or 11 with Visual Studio 2022 Build Tools, CMake, and vcpkg.
You will build, from source:
- Lua 5.2.4 with
LUA_COMPAT_ALLand a foreign-size_tpatch (so 32-bit ARM SIF bytecode loads on a 64-bit host). - LÖVE 12 linked against the Lua 5.2 build. Optionally use MikuAuahDark/Replayground-love2d for a
physfsSQLite VFS that lets read-only DBs open straight out oflove.filesystemwithout copying — useful when the project is fused into a.love. Stock LÖVE 12 also works;pg/lualib/db.luafalls back to opening the underlying OS file vialove.filesystem.getRealDirectory. - lsqlite3 — Lua SQLite binding.
libhonoka is no longer a build dependency — the project vendors a pure-Lua port at pg/honokamiku/. Likewise the old pg/native/honoka.so C extension is unused. Encrypted SIF assets are decrypted at unzip time (for downloaded updates) or on first read (for legacy install/ trees that ship encrypted).
Quick start: prebuilt release
If you just want to run Replayground, grab the latest fused-binary release from the Releases page. Every tag push triggers .forgejo/workflows/build-linux.yml and .forgejo/workflows/build-windows.yml, which build the patched Lua 5.2 + LÖVE 12 + lsqlite3 toolchain and produce:
replayground-linux-x86_64.tar.gz—replayground(fused LÖVE binary + game code),liblove-12.0.so,lib/lua/5.2/lsqlite3.so.replayground-windows-x64.zip—replayground.exe(fused), the SDL3/freetype/etc. vcpkg DLLs,lua52.dll,sqlite3.dll,lua/5.2/lsqlite3.dll.
The .love is concatenated onto the LÖVE binary so a single ./replayground (or replayground.exe) is all you launch — no separate .love file needed.
You still need to drop a SIF1 asset tree at install/ next to the binary; the build only ships the engine + game code. See Asset tree setup below for that.
If you're on a platform without a prebuilt release (macOS, ARM Linux, etc.) or you want to hack on the engine, follow the from-source instructions below.
Linux setup
Tested on Fedora 43 WSL2 with WSLg.
1 — System packages
sudo dnf install -y \
SDL3-devel freetype-devel harfbuzz-devel openal-soft-devel \
libmodplug-devel libtheora-devel libvorbis-devel libogg-devel \
readline-devel cmake gcc-c++ luarocks unzip
(zlib-ng-compat-devel is typically pre-installed.)
Debian/Ubuntu equivalent: libsdl3-dev libfreetype-dev libharfbuzz-dev libopenal-dev libmodplug-dev libtheora-dev libvorbis-dev libogg-dev libreadline-dev cmake g++ luarocks unzip.
2 — Build Lua 5.2.4
cd /tmp
wget https://www.lua.org/ftp/lua-5.2.4.tar.gz
tar xf lua-5.2.4.tar.gz
cd lua-5.2.4
make linux MYCFLAGS="-fPIC"
sudo make install INSTALL_TOP=/usr/local INSTALL_INC=/usr/local/include/lua5.2
-fPICis required so we can statically linkliblua.ainto LÖVE.- The Makefile defaults already include
-DLUA_COMPAT_ALL— this is load-bearing, it restoreslua_objlen,setfenv/getfenv,luaL_openlib, etc. that LÖVE's C++ source uses.
The resulting /usr/local/bin/lua (5.2.4) will shadow Fedora's system /usr/bin/lua (5.4) on PATH. Not yet observed to break anything.
Foreign-size_t patch for SIF bytecode
SIF1's shipped Lua files are 32-bit ARM bytecode. Loading them on x86_64 needs lundump.c to accept a foreign size_t width. The patch teaches lundump.c to load chunks whose declared sizeof(size_t) is smaller than the host's:
- Add an
int foreign_size_t;field to theLoadStatestruct. - In
LoadString, dispatch onS->foreign_size_t— when it matches the host width, readsize_tas before; otherwise readS->foreign_size_tbytes and zero-extend (little-endian; endianness already verified inLoadHeader). - In
LoadHeader, after the bytes are read intos[], recordS->foreign_size_t = (int)s[8](offset 8 is thesizeof(size_t)byte). If the value is> 0and≤ sizeof(size_t), mask it toh[8]before thememcmp. - In
luaU_undump, initializeS.foreign_size_t = (int)sizeof(size_t)before callingLoadHeader.
The changes are gcc- and MSVC-clean. Rebuild Lua and re-run make install. LÖVE statically links liblua.a, so it needs a relink after the Lua change.
3 — Build LÖVE 12
mkdir -p ~/sif
cd ~/sif
git clone https://github.com/love2d/love.git love2d
Edit love2d/CMakeLists.txt. In the non-MEGA else() branch (around line 218), the project ships a patch that hardcodes the Lua 5.2 paths:
set(LUA_INCLUDE_DIR /usr/local/include/lua5.2)
set(LUA_LIBRARY /usr/local/lib/liblua.a)
target_include_directories(lovedep::Lua INTERFACE ${LUA_INCLUDE_DIR})
target_link_libraries(lovedep::Lua INTERFACE ${LUA_LIBRARY} m dl)
m dl is required when linking against the static Lua archive — LÖVE's liblove-12.0.so references pow, dlopen, etc. through them.
Configure and build:
mkdir -p love2d/build
cd love2d/build
cmake -DLOVE_JIT=OFF -DCMAKE_BUILD_TYPE=Release ..
make -j$(nproc)
This produces love2d/build/love — the binary you'll run Replayground with. Don't use Fedora's dnf install love — that ships LÖVE 11.5 with LuaJIT, not 5.2.
4 — Install lsqlite3
luarocks defaults to whichever Lua it can find on PATH. We need to target our /usr/local/ Lua 5.2.
sudo luarocks --lua-version=5.2 --lua-dir=/usr/local install lsqlite3
# When luarocks runs as root it installs to /root/.luarocks; copy to a
# directory LÖVE's package.cpath will find.
sudo mkdir -p /usr/local/lib/lua/5.2
sudo cp /root/.luarocks/lib64/lua/5.2/lsqlite3.so /usr/local/lib/lua/5.2/
(On some distros the source path may be /root/.luarocks/lib/lua/5.2/ without the 64 suffix.)
5 — Run
cd ~/sif/Replayground
~/sif/love2d/build/love .
Skip to Asset tree setup and Configure the server endpoint.
Windows setup
Tested on Windows 11 24H2 with Visual Studio 2022 Build Tools, CMake 4.x, Python 3.13, and vcpkg.
1 — Toolchain
Install via winget (all silent, no prompts):
winget install --id Microsoft.VisualStudio.2022.BuildTools --silent --accept-package-agreements --accept-source-agreements --override "--quiet --wait --add Microsoft.VisualStudio.Workload.VCTools --add Microsoft.VisualStudio.Component.Windows11SDK.22621 --add Microsoft.VisualStudio.Component.VC.CMake.Project --includeRecommended"
winget install --id Kitware.CMake --silent --accept-package-agreements --accept-source-agreements
winget install --id Python.Python.3.13 --silent --accept-package-agreements --accept-source-agreements
winget install --id Ninja-build.Ninja --silent --accept-package-agreements --accept-source-agreements
winget install --id 7zip.7zip --silent --accept-package-agreements --accept-source-agreements
Bootstrap vcpkg at C:\vcpkg (short path matters — some packages hit Windows path length limits otherwise):
git clone --depth 1 https://github.com/microsoft/vcpkg.git C:\vcpkg
C:\vcpkg\bootstrap-vcpkg.bat -disableMetrics
Install LÖVE's dependencies + sqlite3:
C:\vcpkg\vcpkg.exe install sdl3 freetype harfbuzz openal-soft libmodplug libtheora libvorbis libogg sqlite3 zlib --triplet x64-windows
(~20–40 min on first run; subsequent runs are cached.)
2 — Build Lua 5.2.4 (Windows)
Download the source:
mkdir C:\Users\$env:USERNAME\Documents\sif\build
cd C:\Users\$env:USERNAME\Documents\sif\build
Invoke-WebRequest -Uri "https://www.lua.org/ftp/lua-5.2.4.tar.gz" -OutFile "lua-5.2.4.tar.gz" -UseBasicParsing
tar -xzf lua-5.2.4.tar.gz
Apply the foreign-size_t patch to lua-5.2.4/src/lundump.c (same patch as Linux — the C source diff is identical).
Drop a CMakeLists.txt next to src/ (the upstream tarball has no CMake config) — build both a shared lua52.dll (so lsqlite3.dll can link against it) and a static lua52_static.lib (for the luac build). The exact CMakeLists used by this project is reproducible from the steps in pg/honokamiku/ history but a minimal version is:
cmake_minimum_required(VERSION 3.20)
project(lua52 LANGUAGES C)
file(GLOB LUA_SRC src/*.c)
list(REMOVE_ITEM LUA_SRC "${CMAKE_SOURCE_DIR}/src/lua.c" "${CMAKE_SOURCE_DIR}/src/luac.c")
add_library(lua52 SHARED ${LUA_SRC})
target_include_directories(lua52 PUBLIC src)
target_compile_definitions(lua52 PRIVATE LUA_BUILD_AS_DLL LUA_COMPAT_ALL _CRT_SECURE_NO_WARNINGS)
add_library(lua52_static STATIC ${LUA_SRC})
target_include_directories(lua52_static PUBLIC src)
target_compile_definitions(lua52_static PRIVATE LUA_COMPAT_ALL _CRT_SECURE_NO_WARNINGS)
add_executable(lua src/lua.c)
target_link_libraries(lua PRIVATE lua52)
add_executable(luac src/luac.c)
target_link_libraries(luac PRIVATE lua52_static)
install(TARGETS lua52 lua52_static lua luac RUNTIME DESTINATION bin ARCHIVE DESTINATION lib LIBRARY DESTINATION bin)
install(FILES src/lua.h src/lualib.h src/lauxlib.h src/luaconf.h src/lua.hpp DESTINATION include)
Configure, build, install:
cd C:\Users\$env:USERNAME\Documents\sif\build\lua-5.2.4
cmake -G "Visual Studio 17 2022" -A x64 -B build -DCMAKE_INSTALL_PREFIX="C:/Users/$env:USERNAME/Documents/sif/build/lua-install"
cmake --build build --config Release --parallel
cmake --install build --config Release
You should end up with:
C:\Users\<you>\Documents\sif\build\lua-install\
├── bin\ lua52.dll, lua.exe, luac.exe
├── lib\ lua52.lib, lua52_static.lib
└── include\ lua.h, lualib.h, lauxlib.h, luaconf.h, lua.hpp
3 — Build LÖVE 12 (Windows)
git clone https://github.com/love2d/love.git C:\Users\$env:USERNAME\Documents\sif\love2d
Patch love2d/CMakeLists.txt:
-
Around line 168, comment out the
if(MSVC OR ANDROID) … message(FATAL_ERROR …)block (or narrow it toif(ANDROID)). LÖVE upstream insists on megasource for MSVC; we're using vcpkg instead. -
Around line 218 (the non-MEGA
else()branch withfind_package(Lua51 REQUIRED)), replace the Lua block with a Windows-aware version that points at ourlua-installfor MSVC + keeps the Linux path:if(WIN32) if(NOT DEFINED LUA_INCLUDE_DIR) set(LUA_INCLUDE_DIR "C:/Users/<you>/Documents/sif/build/lua-install/include") endif() if(NOT DEFINED LUA_LIBRARY) set(LUA_LIBRARY "C:/Users/<you>/Documents/sif/build/lua-install/lib/lua52.lib") endif() target_include_directories(lovedep::Lua INTERFACE ${LUA_INCLUDE_DIR}) target_link_libraries(lovedep::Lua INTERFACE ${LUA_LIBRARY}) else() # ... existing Linux Lua 5.2 block ... endif() -
Around line 2058 the Windows installer block does
install(PROGRAMS $<TARGET_FILE:${MEGA_SDL3}> DESTINATION .)unconditionally; guard the wholeif(CMAKE_SYSTEM_NAME STREQUAL "Windows")onMEGAso the empty${MEGA_SDL3}doesn't blow up CMake's generate step. -
In
src/modules/video/theora/OggDemuxer.cpp:51, comment out theif (syncBuffer && !streamInited && ogg_stream_check(&stream))block — vcpkg's libogg 1.3.6 DLL doesn't exportogg_stream_check, and it's an optional corruption guard.
Configure + build:
cd C:\Users\$env:USERNAME\Documents\sif\love2d
mkdir build; cd build
cmake -G "Visual Studio 17 2022" -A x64 -DCMAKE_TOOLCHAIN_FILE="C:/vcpkg/scripts/buildsystems/vcpkg.cmake" -DVCPKG_TARGET_TRIPLET=x64-windows -DLOVE_JIT=OFF -DCMAKE_BUILD_TYPE=Release ..
cmake --build . --config Release --parallel
Copy lua52.dll next to love.exe (vcpkg's app-local deployment doesn't know about our custom Lua):
copy C:\Users\$env:USERNAME\Documents\sif\build\lua-install\bin\lua52.dll C:\Users\$env:USERNAME\Documents\sif\love2d\build\Release\
Smoke test:
C:\Users\$env:USERNAME\Documents\sif\love2d\build\Release\lovec.exe --version
# → LOVE 12.0 (Bestest Friend)
4 — Build lsqlite3 (Windows)
cd C:\Users\$env:USERNAME\Documents\sif\build
git clone --depth 1 https://github.com/LuaDist/lsqlite3.git
LuaDist's source predates Lua 5.2's API renames. Apply two fixes inside lsqlite3/lsqlite3.c:
- Add a shim near the top for the removed
luaL_typerror:#define luaL_typerror(L, narg, tname) luaL_argerror((L), (narg), tname " expected") - Replace every
luaL_reg(lowercase r) withluaL_Reg(Lua 5.1+ spelling). There are 4 typedef sites.
Drop a CMakeLists.txt (replaces LuaDist's):
cmake_minimum_required(VERSION 3.20)
project(lsqlite3 LANGUAGES C)
set(LUA_INCLUDE_DIR "C:/Users/<you>/Documents/sif/build/lua-install/include")
set(LUA_LIBRARY "C:/Users/<you>/Documents/sif/build/lua-install/lib/lua52.lib")
find_package(unofficial-sqlite3 CONFIG REQUIRED)
add_library(lsqlite3 SHARED lsqlite3.c)
set_target_properties(lsqlite3 PROPERTIES PREFIX "" SUFFIX ".dll")
target_include_directories(lsqlite3 PRIVATE ${LUA_INCLUDE_DIR})
target_link_libraries(lsqlite3 PRIVATE ${LUA_LIBRARY} unofficial::sqlite3::sqlite3)
target_compile_definitions(lsqlite3 PRIVATE LUA_BUILD_AS_DLL LUA_LIB LUA_COMPAT_ALL _CRT_SECURE_NO_WARNINGS)
Build + install next to love.exe:
cd C:\Users\$env:USERNAME\Documents\sif\build\lsqlite3
cmake -G "Visual Studio 17 2022" -A x64 -DCMAKE_TOOLCHAIN_FILE="C:/vcpkg/scripts/buildsystems/vcpkg.cmake" -DVCPKG_TARGET_TRIPLET=x64-windows -B build
cmake --build build --config Release
copy build\Release\lsqlite3.dll C:\Users\$env:USERNAME\Documents\sif\love2d\build\Release\
copy C:\vcpkg\installed\x64-windows\bin\sqlite3.dll C:\Users\$env:USERNAME\Documents\sif\love2d\build\Release\
5 — (Optional) Mesa software OpenGL for headless VMs
If your Windows host has a real GPU + recent drivers, skip this section — LÖVE will find OpenGL 3.3+ via the system opengl32.dll.
For a Windows VM without GPU passthrough (Microsoft Basic Display Adapter → OpenGL 1.1 only), download Mesa3D's MSVC build from pal1000/mesa-dist-win (~65 MB), extract with 7-Zip, and copy three DLLs next to love.exe:
copy mesa3d\x64\opengl32.dll C:\Users\$env:USERNAME\Documents\sif\love2d\build\Release\
copy mesa3d\x64\libgallium_wgl.dll C:\Users\$env:USERNAME\Documents\sif\love2d\build\Release\
copy mesa3d\x64\dxil.dll C:\Users\$env:USERNAME\Documents\sif\love2d\build\Release\
Then set GALLIUM_DRIVER=llvmpipe before launching. Expect 5–15 FPS for SIF scenes.
6 — Run (Windows)
cd C:\Users\$env:USERNAME\Documents\sif\Replayground
$env:GALLIUM_DRIVER = "llvmpipe" # only if you installed Mesa
C:\Users\$env:USERNAME\Documents\sif\love2d\build\Release\lovec.exe .
lovec.exe is the console build (stderr/stdout reach the terminal). love.exe is the silent GUI build for end users.
Asset tree setup
Replayground needs an install/ directory at the project root (the SIF1 game tree). A single tree is all that's required — asset:// URLs resolve to external/<X> first (the DLAPI / downloaded-update overlay in the save dir) and fall back to install/<X> for everything that didn't ship via an update.
If install/ files are libhonoka-encrypted (the format an APK-extracted tree carries), pg/io.read auto-decrypts on every read. If they're already plaintext (decoded with libhonoka ahead of time, or sourced from a decoded mirror), the auto-decrypt fast-paths through on the magic-byte check. Both work. Pre-decoded is faster.
A legacy asset/ symlink is no longer needed — the URL router doesn't look at it anymore.
Step 1: Get AppAssets.zip
It ships INSIDE the SIF1 APK at assets/AppAssets.zip. You need both layers — extract the APK to get the inner zip, then extract THAT to get the actual game tree.
# Either:
# - Download a SIF1 community APK (e.g. lovelive-community.apk), or
# - Pull AppAssets.zip from the /assets/ directory of a SIF1 install
# you already extracted.
# Extract the APK to a temp folder (it's just a zip):
unzip -d /tmp/apk lovelive-community.apk
# Now extract the inner AppAssets.zip to its final home:
unzip -d ~/sif/AppAssets /tmp/apk/assets/AppAssets.zip
After step 1, ~/sif/AppAssets/ should contain (at its root, NOT in any subdirectory):
assets/ common/ config/ db/ en/ formula/ lib/ m_*/ start.lua Maintenance.lua ...
If your tree has assets/AppAssets.zip at the root, you stopped one extract short — go down another level.
Step 2: Link install/ into the project
# Linux:
cd ~/sif/Replayground
ln -s ~/sif/AppAssets install
# Windows (no admin required for mklink /J junctions):
cd C:\Users\$env:USERNAME\Documents\sif\Replayground
cmd /c mklink /J install C:\Users\$env:USERNAME\Documents\sif\AppAssets
Step 3 (optional): Decoded-source mode for debugging
The bundled bytecode is debug-stripped — tracebacks read ?:0. If you have a decoded-source mirror (e.g. via libhonoka + unluac.jar), point install/ at it instead.
cd Replayground
rm install
ln -s ~/sif/decoded_clean install
Verification
A successful boot prints [lifecycle] load_script install/start.lua followed by m_boot/start.lua, m_login/start.lua, etc. If the screen stays white with Error: ... 'install' is missing or install/start.lua is missing, you skipped step 1 or step 2.
Configure the server endpoint
Replayground reads the target server from asset/config/server_info.json (decrypted at runtime). Default is http://sif.ethanthesleepy.one. To point at your own NPPS4 instance, either:
- Edit
server_info.jsoninside the asset bundle before zipping it up, or - Drop an override at
external/config/server_info.json(the project'sexternal/directory takes precedence overasset/).
NPPS4 server: https://github.com/DarkEnergyProcessor/NPPS4. Replayground requires the xmc_verify=CROSS HMAC keys for /lbonus/execute and /live/play — these are hardcoded in pg/tasks/netapi.lua to the NPPS4 sample base_xorpad. If your deployment uses a different base xorpad, override there.
What the boot looks like
A 960×640 window opens. On first boot Replayground:
- Plays the Bushiroad → KLab → joga splash animation.
- Shows m_login splash; tap anywhere to advance.
- Walks register → tos/tosAgree → changeName → download/update → m_download.
- Reboots to m_login.
- Logs in (the first run already wrote the keystore), then loads
partner.lua.
Boot output is logged to stderr with timestamps and tags ([render], [netapi], [autoclick], etc.).
Autoclick mode
Setting REPLAYGROUND_AUTOCLICK=1 enables a hardcoded click script (defined in main.lua around line 245) that drives the game past the early UI without manual interaction. Useful for repeated boot testing:
# Linux
REPLAYGROUND_AUTOCLICK=1 ~/sif/love2d/build/love .
# Windows
$env:REPLAYGROUND_AUTOCLICK = "1"
C:\Users\$env:USERNAME\Documents\sif\love2d\build\Release\lovec.exe .
Setting REPLAYGROUND_SCREENSHOT_AT=N (autoclick required) drops a PNG into the LÖVE save dir at second N — on Linux that's ~/.local/share/love/lovelive/screenshot.png, on Windows %APPDATA%\LOVE\lovelive\screenshot.png.
Project layout
Replayground/
├── main.lua # Lifecycle driver + input handling
├── conf.lua # LÖVE config
├── setup.lua # package.path / cpath setup
├── pg/ # Engine reimplementation
│ ├── luaenv.lua # Per-script sandbox (SIF runs inside this)
│ ├── render.lua # sysCommand-driven UI renderer + hit-test
│ ├── flsh.lua # FLSH (KLab's Flash variant) binary parser
│ ├── texb.lua # TEXB atlas decoder
│ ├── imag.lua # .imag sprite-name → atlas resolver
│ ├── uibjson.lua # Binary JSON decoder for UI .json files
│ ├── io.lua # Asset URL → bytes with auto-decrypt
│ ├── scheduler.lua # setTimeout / setInterval / waitFrames
│ ├── fonts.lua # Per-size font cache
│ ├── platform.lua # Cross-platform tempdir / mkdir / random_bytes
│ ├── crypto.lua # SHA1, HMAC-SHA1, base64, xor
│ ├── crypto_native.lua # RSA + AES public API (wraps pg/crypto/*)
│ ├── crypto/ # Pure-Lua heavy crypto
│ │ ├── aes.lua # AES-128 + CBC + PKCS#7
│ │ ├── bigint.lua # Base-2^24 bigint with modexp
│ │ ├── pem.lua # PEM + ASN.1 DER (SubjectPublicKeyInfo)
│ │ └── rsa.lua # RSA-1024 PKCS#1 v1.5 encrypt
│ ├── honokamiku/ # Vendored libhonoka-lua (pure-Lua libhonoka
│ │ # port from ethanaobrien/libhonoka-lua)
│ ├── lualib/ # Engine globals (one file per subsystem)
│ │ ├── app.lua # APP_*
│ │ ├── asset.lua # ASSET_*, Asset_*
│ │ ├── base.lua # sysCommand/sysReboot/sysLoad/CONV_*/etc.
│ │ ├── data.lua # DATA_*
│ │ ├── db.lua # DB_open/close/query (lsqlite3-backed)
│ │ ├── eng.lua # ENG_*
│ │ ├── font.lua # FONT_*
│ │ ├── gl.lua # GL_*, SG_*
│ │ ├── http.lua # HTTP_*
│ │ ├── snd.lua # SND_*
│ │ ├── task.lua # TASK_*, UTIL_*
│ │ └── ui_const.lua # UI_* command IDs
│ ├── tasks/ # One file per UI_* task class
│ │ ├── uistubs.lua # UI_Form
│ │ ├── uilist.lua, uigroup.lua, uipolygon.lua, uiswfplayer.lua,
│ │ ├── uivirtualdoc.lua, uicontrol.lua, uitouchpad.lua,
│ │ ├── uibutton.lua, uilabel.lua, uiscale9.lua, uisimpleitem.lua,
│ │ ├── uivariableitem.lua, uifreevertitem.lua, uicover.lua,
│ │ ├── uiprogressbar.lua, uipiechart.lua, uidragicon.lua,
│ │ ├── uimovieplayer.lua, uiscore.lua, uiscrollbar.lua,
│ │ ├── uirubberband.lua, uicanvas.lua, uicellanim.lua,
│ │ ├── uiactivityindicator.lua, uimultiimageitem.lua,
│ │ ├── uidbglabel.lua, uishader.lua, uitextinput.lua,
│ │ ├── uiwebview.lua, uiclippedmap.lua
│ │ └── netapi.lua # UI_NetAPI (HTTP / login)
│ └── native/ # (legacy) C extension — no longer used; the
│ # pure-Lua pg/honokamiku/ replaced honoka.so
├── install/ → AppAssets/ # SIF1 game tree (symlink/junction).
│ # Encrypted or decoded — pg/io.read auto-
│ # detects + decrypts at read time.
├── external/ # DLAPI overlay + player-writable state (DBs,
│ # save state, downloaded /update/ packages).
│ # Lives in the LÖVE save dir, NOT under
│ # the project root, so a clean checkout
│ # doesn't ship stale player state.
└── README.md
Troubleshooting
bad argument #1 to 'pairs' (table expected, got nil) at pg/luaenv.lua:10. You're running LÖVE 11.x (which uses LuaJIT and has no bit32 module), not the LÖVE 12 / Lua 5.2 build. Check which love (Linux) / Get-Command love (Windows) and use the binary at love2d/build/love[.exe].
[pg.io] pg.honokamiku not loadable. The vendored pure-Lua decrypter at pg/honokamiku/ is missing or wasn't checked out. Confirm pg/honokamiku/init.lua exists.
module 'lsqlite3' not found. lsqlite3 isn't on LÖVE's package.cpath. On Linux: re-do the install step. On Windows: confirm lsqlite3.dll and sqlite3.dll sit next to love.exe.
incompatible precompiled chunk while loading install/lib/include.lua or similar. Your Lua build is missing the foreign-size_t patch — install/ contains 32-bit ARM bytecode and the unpatched lundump.c rejects it. Re-apply the patch, rebuild Lua, restage lua52.dll/liblua.a, and rebuild LÖVE.
Unable to create renderer / "OpenGL 1.1 - GDI Generic" on Windows. Your VM has no GPU passthrough. See Mesa software OpenGL for headless VMs.
Asset reset triggers every boot (no Bushiroad splash, jumps straight to m_login). The _tag_assets_reset_v2 marker file lives in the LÖVE save dir's external/. Check ls "$(love --print-save-dir 2>/dev/null || echo ~/.local/share/love/lovelive)/external/" for the marker after one run.
no such vfs: physfs in DB error logs. You're running stock LÖVE 12; the patched-LÖVE PhysFS SQLite VFS isn't registered. pg/lualib/db.lua detects this on first DB open and falls back to opening the underlying OS file via love.filesystem.getRealDirectory — the fallback only works for unfused setups, but those are the documented dev path. If you want the VFS, build LÖVE from MikuAuahDark/Replayground-love2d instead of upstream.
Stale .png.imag errors like not a LINK .imag (got: ...) after extracting an AppAssets.zip directly. Your install/ files are libhonoka-encrypted; pg/io.read's auto-decrypt handles them on the fly. If you see this error, either (a) the file in external/ is also encrypted but pg/io.read resolved external/ first and tried to parse the raw bytes — wipe external/ (rm -rf external && mkdir external) and re-run so a fresh /download/ writes plaintext via the unzip-time decryption, or (b) decode install/ ahead of time via libhonoka to skip the per-read overhead entirely.
Failed to create Vulkan descriptor pool: out of host memory. A regression — some draw path is allocating love.graphics resources per frame. Look at recent changes to pg/render.lua draw branches; every newQuad / newMesh / newTransform should be cached.
Tracebacks read ?:0 with no useful info. You're running install/ → AppAssets/ (bytecode mode, debug-stripped). Swap to decoded source if you have it:
cd Replayground && rm install && ln -s ~/sif/decoded_clean install
Where things live (for hacking)
- Engine semantics: cross-reference
Engine/source/UISystem/CKLBUI*.{h,cpp}from MikuAuahDark/Playground or kotori2/Playground-SIF. These C++ implementations are the ground truth for what eachUI_*task does. - Format specs: those repos also have the canonical TEXB, BJSON, FLSH parsers as C++.
- SIF Lua source (decoded): the active client scripts live wherever
install/points. Engine-side imports use names likeimport("WUI_List"),import("CardImageForm")— those resolve throughlib/strict.lua'sdefine/exportregistry.
Credits
- MikuAuahDark — original Replayground implementation and Playground engine reference fork.
- KLab Inc. — original SIF engine (PlaygroundOSS).
- DarkEnergyProcessor — libhonoka decrypter (the C original), NPPS4 server.
- ethanaobrien — libhonoka-lua, the pure-Lua libhonoka port vendored at
pg/honokamiku/. - kotori2 — Playground-SIF fork (additional engine source).
- LÖVE — https://love2d.org