No description
  • Lua 99.3%
  • C 0.7%
Find a file
Ethan O'Brien a8237f0798 home: kill bright-green debug clear + fix freevert SET_COLORS
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).
2026-05-15 01:09:33 -05:00
objects Initial commit. 2025-12-07 18:38:04 +08:00
pg home: kill bright-green debug clear + fix freevert SET_COLORS 2026-05-15 01:09:33 -05:00
.gitignore Download pipeline + real polling stubs unblock the tutorial path 2026-05-14 10:10:01 -05:00
conf.lua Clean UI: 960x640 native, F1-toggle diagnostic, correct colors 2026-05-13 14:36:28 -05:00
main.lua home: kill bright-green debug clear + fix freevert SET_COLORS 2026-05-15 01:09:33 -05:00
README.md README: full Linux setup + run instructions 2026-05-14 13:56:01 -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 + 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.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).

You will build, from source:

  1. Lua 5.2.4 with LUA_COMPAT_ALL and an optional foreign-size_t patch
  2. LÖVE 12 linked against the Lua 5.2 build
  3. libhonoka (SIF asset decrypter, C library)
  4. pg/native/honoka.so — small C extension that wraps libhonoka for Lua
  5. 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
  • -fPIC is required so we can statically link liblua.a into LÖVE.
  • The Makefile defaults already include -DLUA_COMPAT_ALL. This is load-bearing — it restores lua_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_t field to LoadState.
  • In LoadString, read N bytes per the header-declared width and zero-extend to host size_t.
  • In LoadHeader, accept any size_t width ≤ 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 SIF1 assets/, config/, db/, en/, m_*/, common/, lib/, .lua, .json, .flsh, .texb files 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.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.

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:

  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.

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 each UI_* 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 like import("WUI_List"), import("CardImageForm") — those resolve through lib/strict.lua's define/export registry.

Credits