- Lua 99.3%
- C 0.7%
SIF's start.lua / splash_screen.lua / assets.lua all branch on
ENG_isRelease(): release builds clear-to-black, debug builds light up
(0, 1, 0, 1) bright green to make it obvious you're running an unshipped
build. Replayground's ENG_isRelease() returns false (other code paths
need the debug branches active), so the green branch fired by default and
every menu / home screen drew on top of a fluorescent green background.
gl.lua's GL_ClearColor wrapper clamps any (0,1,0,1) the bytecode-shipped
scripts hand it back to black. (Also patched install/start.lua locally
to call GL_ClearColor(0,0,0,1) unconditionally, but install/ is a
symlink to the asset bundle outside this repo so that part isn't
tracked.)
Pack UI_FREEVERT_SET_COLORS correctly: the engine signature is an array
of 8 numbers laid out as {alpha[i], color[i]} pairs that pack into 4
ARGB values per vertex. Treating the first 4 entries as 4 ARGBs put the
unit's background_color in the alpha channel and 0xFF in the color
channel, which would paint the navi border opaque-green-on-green if the
clear color hadn't already drowned it.
Autoclick gains a close-button step (X on the webview popup → home).
|
||
|---|---|---|
| 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 + libhonoka decrypter + lsqlite3) and run the project on Linux. Verified working on Fedora 43 under WSL2 with WSLg.
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. See MEMORY.md-equivalent notes in the project history for a more granular changelog.
Prerequisites
- Linux (tested on Fedora 43 WSL2). macOS/Windows likely need adaptation.
- 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).
You will build, from source:
- Lua 5.2.4 with
LUA_COMPAT_ALLand an optional foreign-size_tpatch - LÖVE 12 linked against the Lua 5.2 build
- libhonoka (SIF asset decrypter, C library)
pg/native/honoka.so— small C extension that wraps libhonoka for Lua- lsqlite3 — Lua SQLite binding (via luarocks)
The order matters: Lua → LÖVE → libhonoka → honoka.so → lsqlite3. Each step depends on the previous.
Step 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.)
Adapt package names for other distros — the equivalents on Debian/Ubuntu are roughly libsdl3-dev libfreetype-dev libharfbuzz-dev libopenal-dev libmodplug-dev libtheora-dev libvorbis-dev libogg-dev libreadline-dev cmake g++ luarocks unzip.
Step 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. Without it the LÖVE build won't compile against Lua 5.2.
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.
Optional: 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. Replayground currently runs against AppAssets_dec (raw bytecode that came out of the SIF1 APK), so this patch is required if you want to mirror the verified working setup.
Patch /tmp/lua-5.2.4/src/lundump.c:
- Add a
foreign_size_tfield toLoadState. - In
LoadString, read N bytes per the header-declared width and zero-extend to hostsize_t. - In
LoadHeader, accept anysize_twidth ≤ host width (4 on a 32-bit-bytecode-on-64-bit-host run, 8 native).
Rebuild Lua and re-run make install. Then continue to step 3 — LÖVE statically links liblua.a so it'll need a relink after the Lua change.
Step 3 — Build LÖVE 12
mkdir -p /home/$USER/sif
cd /home/$USER/sif
git clone https://github.com/love2d/love.git love2d
Edit love2d/CMakeLists.txt. In the non-MEGA else() branch (currently around line 218), replace the find_package(Lua51 REQUIRED) block with hardcoded paths to our 5.2 install:
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.
Step 4 — Build libhonoka
cd /home/$USER/sif
git clone https://github.com/DarkEnergyProcessor/libhonoka.git
cd libhonoka
# libhonoka 2.1.2 uses K&R-style function definitions in md5.c; modern gcc
# needs both flags.
mkdir -p build && cd build
cmake -DCMAKE_C_FLAGS="-std=gnu89 -fcommon" ..
make -j$(nproc)
You should now have libhonoka/build/libhonoka.so (shared) and libhonoka/build/libhonoka_static.a.
Step 5 — Build the honoka Lua extension
Replayground includes a small C wrapper at pg/native/honoka.c that exposes libhonoka to Lua. Build it once:
cd /home/$USER/sif/Replayground/pg/native
gcc -shared -fPIC honoka.c \
-I/usr/local/include/lua5.2 \
-I/home/$USER/sif/libhonoka \
/home/$USER/sif/libhonoka/build/libhonoka.so \
-Wl,-rpath,/home/$USER/sif/libhonoka/build \
-o honoka.so
The -Wl,-rpath is so the loaded honoka.so can find libhonoka.so at runtime without LD_LIBRARY_PATH.
Gotcha: in the original libhonoka API, ctx->dm (the detected mode id) isn't finalized by honokamiku_decrypt_init_auto. The wrapper must call honokamiku_decrypt_final_init before reading it. The shipped honoka.c already does this — don't "simplify" it.
Step 6 — 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.)
Step 7 — Set up the asset tree
Replayground expects two sibling symlinks inside the project:
asset/→ directory containing the SIF1assets/,config/,db/,en/,m_*/,common/,lib/,.lua,.json,.flsh,.texbfiles at its root. Encrypted bytes; Replayground decrypts at runtime via honoka.so.install/→ same directory in either encrypted (bytecode) or decoded form. This is where SIF reads its Lua scripts from. Using bytecode is simpler; decoded source (run through libhonoka + unluac + cleanup) is easier to debug because tracebacks carry file:line information.
Extract AppAssets.zip and symlink:
cd /home/$USER/sif
# Extract the encrypted asset bundle from a SIF1 APK.
unzip -d AppAssets path/to/AppAssets.zip
# For runtime decryption from encrypted bundle (verified working):
cd Replayground
ln -s /home/$USER/sif/AppAssets asset
# install/ — bytecode mode:
ln -s /home/$USER/sif/AppAssets install
For decoded-source mode (better tracebacks; required when iterating on decompiler defects):
# Decrypt all Lua files in-place, then decompile + clean. See
# https://github.com/DarkEnergyProcessor/libhonoka and `unluac.jar` for the
# pipeline. Final readable source goes in a directory called e.g.
# `decoded_clean/`.
cd Replayground
rm install
ln -s /home/$USER/sif/decoded_clean install
Step 8 — 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.
Step 9 — Run
cd /home/$USER/sif/Replayground
/home/$USER/sif/love2d/build/love .
A 960×640 window opens via WSLg (or your native display). 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.
The window remains a 960×640 fixed-aspect render. 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:207) that drives the game past the early UI without manual interaction. Useful for repeated boot testing:
REPLAYGROUND_AUTOCLICK=1 /home/$USER/sif/love2d/build/love .
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
│ ├── 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/
│ ├── honoka.c # libhonoka Lua binding
│ └── honoka.so # (built artifact)
├── asset/ → AppAssets/ # Encrypted SIF1 assets (symlink)
├── install/ → AppAssets/ # Bytecode or decoded source (symlink)
├── external/ # Player-writable: DBs, save state, dl version
└── 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 and use the binary at love2d/build/love.
[pg.io] honoka.so not loaded; runtime decryption disabled. Either honoka.so wasn't built (step 5) or it can't find libhonoka.so. Test directly:
LD_LIBRARY_PATH=/home/$USER/sif/libhonoka/build ldd /home/$USER/sif/Replayground/pg/native/honoka.so
If libhonoka.so shows as not found, the -Wl,-rpath baked into honoka.so is wrong. Rebuild step 5.
[db] DB_open materialize failed. lsqlite3 isn't installed, or it's installed for the wrong Lua version. Confirm with:
lua -e 'print(require("lsqlite3").version())'
If that errors, re-do step 6 with the explicit --lua-version=5.2 --lua-dir=/usr/local flags.
Asset reset triggers every boot (no Bushiroad splash, jumps straight to m_login). The _tag_assets_reset_v2 marker file needs to live in the real external/ directory. If you're seeing this fresh, pg/lualib/db.lua should have already routed file://external/* to the project's external/. Check ls external/ for the marker after one run.
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 /home/$USER/sif/decoded_clean install
Where things live (for hacking)
- Engine semantics: cross-reference
/home/ethan/sif/Playground/Engine/source/UISystem/CKLBUI*.{h,cpp}(clone https://github.com/MikuAuahDark/Playground). These are the ground truth for what eachUI_*task does. - Format specs:
Playground/repo also has 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, NPPS4 server.
- kotori2 — Playground-SIF fork (additional engine source).
- LÖVE — https://love2d.org