Patch 0026 (t5de-shop): the shop's rating filter gets T5 (GA, AP, AP+, T5, then all), answered by T5DE's proxy from the hub; T5 products show the T5 badge, are free, and buying saves to the library. Patch 0027 (t5de-inventory): the clothing inventory's filter gets T5 the same way, drawn as T5DE's own sprite, and T5 products get the T5 badge. Co-Authored-By: Claude Opus 5.5 <[email protected]>
21 KiB
21 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.tools/drive.pydrives the built client without a person (start, screenshot, click, type,imvu:commands such asshowMode?mode=shopsent as WM_COPYDATA, stop); the test account's saved login signs in by itself. - Patches carry metadata as commit trailers:
T5de-Id(lowercase-words),T5de-Group(the installer's section: general, shop, rooms, avatars, chat, renderer for optional patches; core and privacy, which needT5de-Required: true, are listed together as "Always Included"), optionalT5de-Depends: id, id, optionalT5de-Name(a few words in Title Case, shown in the installer; the user wants short UI text, grouped by area, not one long list). 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. Programs built by T5DE's other repositories are pinned int5de.toml([[programs]]: a release's download folder and each file's sha256);t5de bundledownloads them (cached in.cache/programs/<sha256>/) into their patch'sfiles. The renderer (t5de-rendererpatch) comes this way: to update it, tag avX.Y.Zrelease int5de-renderer(its CI publishes it) and copy the url and sha256s from the release notes. - 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. - The proxy (
t5de-proxypatch; program from git.t5de.dev/t5de/t5de-proxy, checked out beside this one): every HTTP request the client makes goes throughNetwork.urlopen, the embedded browser's too (IMVU'sPythonProtocolHandlerin imvugecko.dll replaces Gecko's http/https and callsNetwork.performGeckoRequest). The patch startst5de-proxy.exeon first use (main/client.pyt5deProxy; ready file%TEMP%\t5de-proxy-<pid>.json, log%TEMP%\t5de-proxy.log,T5DE_PROXY_TRACE=1for full URLs and IMQ frames), adds a urllib2 handler sending absolute-form requests with its token, and puts a CONNECT strategy (plaintext to the proxy, TLS from it) first for IMQ. A refused connection to the proxy, or no exe, means direct connections as before. Installs wait fort5de-proxy.exeandt5de-renderer.exeto exit too. The proxy also serves community products from local packs (%APPDATA%\T5DE\packs,T5DE_PACKS; t5de-proxy's README, t5de-api'sdocs/pack.md); the client needs no patch for it. To test one:drive.py url "imvu:PutOn?productId=<local ID>"(local IDs from 2,000,000,000, in%APPDATA%\T5DE\proxy\products.json), and Dress Up for outfit saves. The proxy also gets T5 products from the hub's catalogs into the person's library and the client's inventory, and answershttp://t5de-proxy.local/(its own control address, needing its token): thet5de-hubpatch's Settings > T5DE > T5DE Hub section signs in and out through it (SettingsGeckoListener.handle_t5deHub). Thet5de-shoppatch adds a T5 choice to the shop's rating filter (for everyone; T5 filter images inshop/img/filter*t5_*.png, the T5 badge inwidgets/product/img/t5.png), answered by the proxy from the hub; T5 products are local IDs from 2,000,000,000 (Product.isT5), free, and buying saves to the library. Thet5de-inventorypatch does the same in the clothing inventory (tool/inventory); the T5 choice isn't saved: IMVU'sinventoryMaturitypreference only takes ga/all/ap/applus, and any other value raises an error that ends the client.t5de buildinstalls the pinned proxy; to test an unreleased one, copy t5de-proxy'sdist/t5de-proxy.exeintobuild/IMVUClient/after building.
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.
- The 3D scene is locked to 30 FPS:
SceneWindow.dll's main loop (runApp) waits outzero.dll'smillisecondsPerFrame(33), thenstepFrameadvances the scene by the 33 ms ticks since the last frame (FrameIntervalMaker::getFrameInterval, at most 5; the tick is also a float 33.0 inzero.dll) and draws once. Animations are 30-per-second frame sequences advanced one whole frame per tick (ActionInstance::stepcallsincrementActionFrameper tick); camera smoothing (FixedRateInterpolatedValue) and particles step per tick too. So shorter ticks speed animations up (tried and dropped: a patch shrinking the tick made them run 2x at 60 FPS), and drawing more often without shorter ticks shows the same scene twice. T5DE's renderer does high frame rates (the Frame Rate setting) by drawing between ticks, with animations, crossfades, the camera and particles at the time between them, through checked hooks in its adapter (t5de-renderer'sdocs/architecture.md; no added delay, nothing predicted). The user wanted no added delay, so blending finished frames was ruled out. Native changes are made in memory from Python (ctypes, checking the instructions first) or as our own source code, never as opaque binary edits: the user wants them readable. - Renderers (Settings > Advanced): "Direct3D" is Direct3D 9 inside
SceneWindow.dll(RenderEngineDx); "OpenGL" is the pluginglRenderEngine.dll(exports onlycreateRenderEngine,isRenderEngineSupported; interfacePiRenderEngine, with readable C++ symbol names inSceneWindow.dll); "Standard" is the Direct3D 9 engine on SwiftShader (swiftshader.dll, CPU).D3D11andOpenGLSM2exist only as Python enum values. The user wants a modern renderer (the next big phase; likely its own repo, C++), taking the plugin's place or as another choice, with higher frame rates (interpolated animation) and newer texture and lighting features like the newer WASM client's (written by us, not copied; texture features depend on what product data the servers send Classic). The renderer is its own repository,t5de-renderer(git.t5de.dev/t5de/t5de-renderer, checked out beside this one; itsdocs/interface.mdhas the findings). Ghidra 12.2 (needs Java 25) is at%LOCALAPPDATA%\Programs\ghidra_12.2_PUBLIC; its projects live in%LOCALAPPDATA%\t5de-dev\ghidra(Ghidra rejects paths with a dot-folder like.cache, and they hold IMVU's code, so never in a repository).
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 sends through the install'st5de-setup.exe track|report(headless,helper.go, which adds the install's IMVU version and patches), since its Python 2.7 can't do modern HTTPS: thet5de-diagnosticspatch sendslaunch(wrappingclientapp.runMain) and reports crashes, unrecoverable errors and startup failures (wrappingclientapp.reportCrash,reportUnrecoverableErrorandclient._main): type, message and stack only, Windows user name scrubbed. Its helpers are inmain/client.py(no module-level imports, so they work when startup fails). Outside an install (e.g.build/IMVUClient) there's not5de-setup.exe, so nothing is sent. Thet5de-settingspatch adds a T5DE page to the client's Settings (imvuContent/settings/t5de.js, handlershandle_t5deGetDiagnostic/handle_t5deSetDiagnosticonSettingsGeckoListener, via_winreg). The page is grouped into sections like IMVU's pages (asubsectionwith an<h1>each, Title Case): Diagnostics; thet5de-rendererpatch adds Graphics above it (Frame Rate,t5de-graphics.js,HKCU\Software\T5DE\Graphics\FrameRate: 30, the default, 60, 120, 240, 360, or 0 for uncapped; read live by the renderer's adapter). IMVU'sPersistentPrefStore.deleteAllSettingsdeletes every subkey ofSoftware\T5DE, includingDiagnostics, which then means no (fail-safe). Still to do: native crashes (minidumps,imvu/bugreport/windows.py). - Self-updates (
internal/update, thet5de-updatespatch): an install follows its channel (the user's choice): release builds are offered the latest release (/releases/latest), nightlies thenightlyrelease (its version is in the name, "Nightly "), dev builds nothing.runClientApp(after the splash: anything imported before it tripsimvu.expectModules, which kills startup) startst5de-setup.exe check-update -out %TEMP%\t5de-update.jsonwithout waiting; IMVU'supdateClientIfNeeded(called before and after login) then offers it once per run in IMVU's own update dialog (titled "T5DE Update Available"). "Update Now" runst5de-setup.exe download-update -dir <install>and exits: it downloadsT5DE-Setup.exe(sha256 from the release notes) to a self-deleting temp name and starts it asupdate -dir <install>, a wizard mode that keeps the install's patches, shortcuts and diagnostics choice, waits for the client to close (install.waitForClient: the exe can't be opened for writing while running), and goes straight to installing unless something needs asking. Tested end to end with a fake release server. Windows won't remove a folder that's any process's working folder: programs started from an install (the update's setup, relaunched copies) getcmd.Dir = os.TempDir(), and replacing an install empties its folder rather than removing it, deleting the record last (apply.prepareOut). A first real update failed this way: every file deleted, the folder kept, then refused as "not a client t5de patched". - 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).