No description
  • Lua 98.7%
  • C 1.3%
Find a file
Ethan O'Brien 1d1ccb894f
All checks were successful
Build Linux release / build (push) Successful in 25s
Build Windows release / build (push) Successful in 30s
fix(render): inline-draw whole-widget Shadow Scale9 behind sibling pill forms
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)
2026-05-22 15:44:20 -05:00
.forgejo/workflows ci(win): ship replayground-debug.exe (lovec) alongside replayground.exe 2026-05-21 23:22:54 -05:00
build ci: drop-in pre-patched lundump.c; love.exe (no console) on Windows 2026-05-21 22:58:10 -05:00
objects Initial commit. 2025-12-07 18:38:04 +08:00
pg fix(render): inline-draw whole-widget Shadow Scale9 behind sibling pill forms 2026-05-22 15:44:20 -05:00
.gitignore Some patches to remove os.execute and using less io.open. 2026-05-17 00:52:41 +08:00
conf.lua Clean UI: 960x640 native, F1-toggle diagnostic, correct colors 2026-05-13 14:36:28 -05:00
main.lua fix(scenario): wrap dbapi.*Unique to return {} instead of nil 2026-05-22 13:41:28 -05:00
README.md ci: build SDL3 from source on Linux; use powershell on Windows 2026-05-21 22:12:34 -05:00
setup.lua Initial commit. 2025-12-07 18:38:04 +08:00

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.zip from 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 via server_info.json inside 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:

  1. Lua 5.2.4 with LUA_COMPAT_ALL and a foreign-size_t patch (so 32-bit ARM SIF bytecode loads on a 64-bit host).
  2. LÖVE 12 linked against the Lua 5.2 build. Optionally use MikuAuahDark/Replayground-love2d for a physfs SQLite VFS that lets read-only DBs open straight out of love.filesystem without copying — useful when the project is fused into a .love. Stock LÖVE 12 also works; pg/lualib/db.lua falls back to opening the underlying OS file via love.filesystem.getRealDirectory.
  3. 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.gzreplayground (fused LÖVE binary + game code), liblove-12.0.so, lib/lua/5.2/lsqlite3.so.
  • replayground-windows-x64.zipreplayground.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
  • -fPIC is required so we can statically link liblua.a into LÖVE.
  • The Makefile defaults already include -DLUA_COMPAT_ALLthis is load-bearing, it restores lua_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 the LoadState struct.
  • In LoadString, dispatch on S->foreign_size_t — when it matches the host width, read size_t as before; otherwise read S->foreign_size_t bytes and zero-extend (little-endian; endianness already verified in LoadHeader).
  • In LoadHeader, after the bytes are read into s[], record S->foreign_size_t = (int)s[8] (offset 8 is the sizeof(size_t) byte). If the value is > 0 and ≤ sizeof(size_t), mask it to h[8] before the memcmp.
  • In luaU_undump, initialize S.foreign_size_t = (int)sizeof(size_t) before calling LoadHeader.

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

(~2040 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:

  1. Around line 168, comment out the if(MSVC OR ANDROID) … message(FATAL_ERROR …) block (or narrow it to if(ANDROID)). LÖVE upstream insists on megasource for MSVC; we're using vcpkg instead.

  2. Around line 218 (the non-MEGA else() branch with find_package(Lua51 REQUIRED)), replace the Lua block with a Windows-aware version that points at our lua-install for 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()
    
  3. Around line 2058 the Windows installer block does install(PROGRAMS $<TARGET_FILE:${MEGA_SDL3}> DESTINATION .) unconditionally; guard the whole if(CMAKE_SYSTEM_NAME STREQUAL "Windows") on MEGA so the empty ${MEGA_SDL3} doesn't blow up CMake's generate step.

  4. In src/modules/video/theora/OggDemuxer.cpp:51, comment out the if (syncBuffer && !streamInited && ogg_stream_check(&stream)) block — vcpkg's libogg 1.3.6 DLL doesn't export ogg_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) with luaL_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 515 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 requiredasset:// 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.

# 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.json inside the asset bundle before zipping it up, or
  • Drop an override at external/config/server_info.json (the project's external/ directory takes precedence over asset/).

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:

  1. Plays the Bushiroad → KLab → joga splash animation.
  2. Shows m_login splash; tap anywhere to advance.
  3. Walks register → tos/tosAgree → changeName → download/update → m_download.
  4. Reboots to m_login.
  5. 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 each UI_* 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 like import("WUI_List"), import("CardImageForm") — those resolve through lib/strict.lua's define/export registry.

Credits