CI / test (push) Successful in 1m41s
Co-Authored-By: Claude Opus 5.5 <[email protected]>
13 KiB
13 KiB
t5de
Patch workflow for the IMVU Classic client (Windows, py2exe, Python 2.7). Rewrite of the original t5de project, now hosted at git.t5de.dev. Later goals: a proxy backend for IMVU's servers, and possibly a client rewrite on a modern stack.
Rules
- Never commit IMVU's code: no decompiled source, extracted UI files, or client binaries.
work/and.cache/are gitignored for this reason. Patches inpatches/are diffs and are fine to commit. - Releases are bring-your-own-client: users run the patcher on their own copy; never redistribute the full client.
- Never commit secrets (VPN configs, keys, tokens).
- Test with a throwaway IMVU account, never a real one.
How it works
t5de setupdownloads the installer pinned int5de.toml(sha256-checked), extracts it with 7-Zip, decompiles the first-party modules inlibrary.zip(imvu,main,im,Ext) with uncompyle6, and extractsui/chrome/imvuContent.jar. The result becomes thebasecommit of the git repowork/(library/,imvuContent/), andpatches/*.patchare applied on top withgit am --3way, in filename order, with a fixed committer (so it works without a git identity, and applied commits hash identically everywhere).t5de saveexports every commit afterbasewithgit format-patch, using flags that make the output deterministic, so unchanged commits re-export byte-identically.- The base must be identical on every machine: uncompyle6/xdis are pinned, output is normalized (
base.py), and the base commit has a fixed author and date.t5de.toml'sbasepins the expected commit (554.8:5b8137f), and setup fails if the decompiled base differs; CI then prints the base's per-file blob hashes to compare. Normalizing drops uncompyle6's# Compiled at:header, which is in the decompiling machine's local time zone (bumpNORMALIZE_VERSIONwhen normalization changes). Windows paths over 260 characters break setup (deep jar paths), so keep the checkout in a short path. - Tests:
pytest(unit tests;tests/py27/scripts run under Python 2.7 for splicing and for checks on a real build).T5DE_REQUIRE_ALL=1turns skips for missing Python 2.7 or build into failures. CI (.gitea/workflows/ci.yml, Gitea 1.25) builds Python 2.7 from source (cached), runs setup, build and all tests; never upload the build (it's IMVU's client).imvu-update.ymlrunst5de check-updatedaily and opens an issue. - Patches carry metadata as commit trailers:
T5de-Id(lowercase-words),T5de-Group(core, privacy, features, annoyances; core and privacy needT5de-Required: true), optionalT5de-Depends: id, id, optionalT5de-Name(a few words, shown in the installer; the user wants short UI text). Subject = title, body = description (shown on hover).bundle.pyvalidates them. t5de bundlewritesbuild/bundle/manifest.json(format 1): per patch, its metadata, its compiled overrides per library module, and its UI changes (byte-offset edits with the exact old bytes, or added/removed files);coreoperations applied with every selection (removedevicefingerprint.exeandnpfpbase_vc10.dll, createui/profile(empty in a real install, so 7-Zip skips it; the client crashes on startup without it), and rewrite the QualityAgent's crash report host to127.0.0.1); and sha256s of the client files modified. It holds no IMVU files. Each patch is compiled on its own (splice27.py, Python 2.7, comparing the module before and after the patch's commit), so users can choose patches: a patch that changes a definition or UI file an earlier patch changed, or uses a definition one added (same module), must depend on it.splice27.pycompiles only changed/added functions and methods (no-O: the decompiler shows explicitraise AssertionErrorasassert); methods are compiled in a same-named class body (private names mangle right) and copied onto the original class. Module- and class-level changes, removals, and decorators that use class-body names are errors; import-time references to changed functions (aliases,from M import f, star imports) are warnings. Replaced definitions stay reachable as_t5de_original_<name>(class attribute for methods, module global for functions), always IMVU's original (later patches don't overwrite it), so patches can wrap them. It warns when a replaced definition's decompiled base doesn't recompile to IMVU's bytecode (e.g.Network.urlopen), unless the patch calls its original. Overrides are marshalled with version 0 (no interning).patcher/(Go;t5de-patcher apply|list) applies a bundle to a client folder: verifies it (every file against IMVU'schecksum.txt, and the bundle's sha256s), copies it, splices library.zip (each patched module's code: a small code object, written by the patcher in marshal version 0, that execs the original module code, embedded verbatim and first in the stream so its interned-string refs stay valid, then each selected patch's overrides in order), applies UI changes to the jar, appliescore, recomputeschecksum.txt, and writest5de-install.json(it only replaces folders with that record). Archives keep entry order and metadata; both are uncompressed.t5de build=t5de bundle, then the patcher (go run, or$T5DE_PATCHER) applying every patch to the cached install intobuild/IMVUClient/. Builds are byte-reproducible.- Patches can add t5de's own files under
client/in work/ (never IMVU's); the bundle carries them per patch (files) and the patcher writes them into the install. Two patches writing the same file need a dependency. - Window icons: Python dialogs load
imvu.icofrom the program folder (win32/win32window.py, which isn't first-party, so not in work/); the native main window (SceneWindow.dll) loadsimvu.icofrom the working directory.imvu.icoisn't inchecksum.txt(the installer creates it). Thet5de-brandingpatch writes T5DE's icon asclient/imvu.ico, sets the main window's icon explicitly, retitles windows "T5DE", and adds a login badge.TopLevelWindow.titleis a property bound at class-creation time, so it's changed by wrappingsetActiveMode, notset_title. Windows started from a shortcut get the shortcut's icon on the taskbar, andIMVUClient.exe's icon can't be extracted (blank), so shortcuts and the uninstall entry useimvu.ico. - Reporting endpoints share hosts with the API (
client-dynamic,secure,www.imvu.com), so they're blocked by URL inNetwork.urlopen/post_multipart(patch 16), which logs each refusal ast5de: blocked request to. Onlybugs.imvu.comis reporting-only, so OS-level blocking (hosts/firewall) can't do much more.
Verified facts (client 554.8)
- The NSIS installer extracts with 7-Zip byte-identically to a real install (all files in
checksum.txtmatch), so CI can run on Linux without running the installer. checksum.txtholds decimal CRC32s and paths; the old patcher removed thelibrary,imvuContentanddevicefingerprintlines. The build should recompute it.- Bytecode is Python 2.7 (magic 62211); 357 first-party modules.
imvu/tool/RadioTool.pyfails to decompile and is stubbed. - The decompiler is deterministic but not faithful: recompiling decompiled code gives identical bytecode for only
~60% of modules, and 6 don't compile at all. So never ship whole recompiled files. The 6 (mostly mangled string
escapes):
imvu/bugreport/__init__.py,imvu/geography.py,imvu/translation.py,imvu/task/util.py,imvu/platform/windows.py(null bytes),main/login.py. Patches avoid them. IMVUQualityAgent.exelaunches and restarts the client, and on a crash it sendsGET /catalog/imvu_monitor_submit.php?cid=towww.imvu.comitself (raw WSOCK32), out of reach of Python patches.npfpbase_vc10.dllis a BlueCava device-fingerprinting browser plugin that the embedded Gecko can load.- Patch 1 also filters Creator Mode facts; patch 14 drops all facts. The filter is kept so patch 1 stands alone.
imvu.expectModules(called from the exe's boot script) rejects unexpected modules loaded at startup;--no-expected-module-checkbypasses it.- The UI is XULRunner/Gecko; IMVU's own JS in the jar isn't minified.
Next
- The build runs on a real client (launched directly, without
IMVUQualityAgent.exe), sends no reporting requests, and the user has checked the patches' features. - End-user release:
T5DE-Setup.exe, the Go patcher with a Wails wizard and the bundle embedded (no IMVU files; bring-your-own-client), per-user (no admin). Wizard: welcome; find IMVU Classic (HKCU\Software\IMVUdefault value = install folder) and verify it; choose patches (core/privacy shown locked); options (install folder, default%LOCALAPPDATA%\T5DEClient, shortcuts); install (apply, shortcuts, "Apps & features" uninstall entry under HKCU); finish. Re-running offers modify/repair/update/uninstall viat5de-install.json. Shortcuts launchIMVUClient.exedirectly (the agent is untested). Done:t5de-patcher install|uninstall(internal/install; Windows parts behind aSysteminterface, faked in tests, real one tested on Windows only). Install keepst5de-setup.exeandt5de-bundle.jsonin the install, records shortcuts int5de-install.json, registersHKCU\...\Uninstall\T5DE(NoModify/NoRepair; the setup does those). Uninstall run from inside the install relaunches from a%TEMP%\t5de-uninstall-<pid>.execopy, which deletes itself afterwards;-remove-dataalso removesHKCU\Software\T5DEand%APPDATA%\T5DE. Reinstalling replaces the folder, including the browser profile (cookies). Verified end to end on a real install. Done:cmd/t5de-setup, the wizard (Wails v2 with a plain HTML/CSS/JSfrontend/, no npm; build tagsdesktop, production,-H=windowsgui; Linux CI vets and cross-compiles it for Windows only). Modes: install; manage (when an install exists, or run from the copy in one: change patches, repair/update, uninstall); uninstall (what "Apps & features" runs). Run from inside an install, it relaunches from a temporary copy. Bundle: embedded fromcmd/t5de-setup/bundle/manifest.json(gitignored; copied in by release builds), else-bundle, else the install'st5de-bundle.json.T5DE_SETUP_SELFTEST=<file>makes the page report what it rendered and close. Look: IMVU Classic's dialog palette (imvuContent/dialogs/css/imvu_jquery_dialog.css: dark gray#383838, gold#ffe795->#ffd200buttons with black text, gray#616161->#3e3e3esecondary), always dark. Copy is terse (the user dislikes wordy titles and descriptions). Wails loads the window icon from icon resource #3, so the setup's resources come fromwinres/winres.json(go-winres make), notsimply(which uses #1). Logo and icon:patcher/assets(the user's); both exes linkrsrc_windows_amd64.syso(icon, version info, manifest), generated by go-winres v0.3.3 viago generate ./cmd/...(reproducible). Done: release CI. ci.yml buildsdist/T5DE-Setup.exe(bundle embedded, version via-X .../internal/buildinfo. Versionand go-winres--product-version/--file-version), uploads it as a run artifact, and.gitea/scripts/release.pypublishes it with the job token:vX.Y.Ztags make release X.Y.Z; pushes to main replace thenightlypre-release (versionX.Y.Z-nightly.N+commit, N commits after the last release tag). The version is shown in the setup and "Apps & features", and recorded int5de-install.json(version). Code signing: postponed by the user. - Diagnostics (
internal/diagnostics): anonymous usage statistics to the user's self-hosted Aptabase (its/api/v0/eventAPI) and crash reports to their Bugsink (Sentry envelopes, hand-built), only from builds with the CI secretsAPTABASE_HOST,APTABASE_APP_KEY,BUGSINK_DSN(embedded via-X .../buildinfo.*), and only with consent:HKCU\Software\T5DE\Diagnostics(UsageStatistics,CrashReportsDWORDs; unset is no). The user wants both on by default: the setup's "Help improve T5DE" page (install and change flows) starts ticked unless a choice is recorded, and records the choice. The setup sendssetup_<action>,setup_failed(plus a report) andsetup_uninstallevents withpatch.<id>properties. The client is to send through the install'st5de-setup.exe track|report(headless,helper.go), since its Python 2.7 can't do modern HTTPS. Still to do: client launch events, client errors to Bugsink, and settings in the client. - Self-updates (planned): take over the client's own updater (bypassed by the no-update-check patch) to offer T5DE releases instead of IMVU's.
- Forks of the old GitHub repo (surveyed 2026-10-27): only one has new patches, apparently AI-generated and
untested. Ideas worth redoing from scratch: a shop "Try on" button (
imvu.call('tryOn', pid)inshop/ShopMode.js) and a creator product search (itsapi.imvu.comendpoints are unverified). - Contributor setup: revisit a single bundled
t5deexecutable once the Go patcher exists. Findings so far:msiexec /aextracts python.org's 2.7.18 MSI unelevated into a working portable Python 2.7 (tests pass with it), and contributors who have the pinned client installed could skip the installer download and 7-Zip. - Installs keep the exe named
IMVUClient.exe(imvu/program/windows.pyfinds the agent by replacing that name). The patched client's user data is%APPDATA%\T5DE, settingsHKCU\Software\T5DE(patch 15).