CI / build (push) Successful in 18s
The client's Settings > T5DE chooses the hub from the next start on (POST /hub/server, kept in server.json); without a choice, -channel (which the client passes) decides: staging for nightly and dev, T5DE's hub for releases. $T5DE_HUB still wins. Each hub's sign-in, product IDs and products are kept in their own folder (T5DE's hub in the data folder as before, others in hubs/<host>), so a hub never gets another's token and product IDs from two hubs never clash; a sign-in saved for another hub is ignored. Signing in always goes to this run's hub. Co-Authored-By: Claude Opus 5.5 <[email protected]>
118 lines
8.4 KiB
Markdown
118 lines
8.4 KiB
Markdown
# t5de-proxy
|
|
|
|
T5DE's local proxy for IMVU Classic. With T5DE's `t5de-proxy` patch, the client starts `t5de-proxy.exe` and
|
|
connects to IMVU through it:
|
|
|
|
- **Every HTTP request**, the embedded browser's included (IMVU's browser hands its requests to the client's
|
|
Python). The proxy makes them with current TLS and root certificates (the client has only IMVU's bundled
|
|
certificates and TLS 1.2), pooled connections and HTTP/2.
|
|
- **HTTPS for IMVU hosts the client reaches over plain HTTP** (its XML-RPC API, images, CSS), so its account
|
|
headers don't cross the network in the clear ([docs/hosts.md](docs/hosts.md)).
|
|
- **IMQ**, IMVU's chat and presence connection: the client speaks plaintext to the proxy, which connects to the
|
|
gateway over TLS.
|
|
- **IMVU's reporting endpoints refused**, as T5DE's patches already do in the client.
|
|
- **Community products** from local packs (t5de-api's pack format), kept away from IMVU (below).
|
|
|
|
If the proxy isn't there or stops, the client connects directly, as before.
|
|
|
|
Unofficial: not made by or affiliated with IMVU.
|
|
|
|
## How it works
|
|
|
|
The proxy listens on a free port of 127.0.0.1 and accepts only requests carrying the token it gave the client
|
|
(`Proxy-Authorization: Basic t5de:<token>`), so other programs and web pages can't use it.
|
|
|
|
- HTTP: requests in absolute form (`GET https://api.imvu.com/... HTTP/1.1`) over plain HTTP on loopback, for
|
|
http and https URLs alike. Responses come back as the server sent them: redirects aren't followed, bodies
|
|
aren't decompressed. When the proxy can't reach a server it answers 502 with `X-T5de-Proxy-Error`, which the
|
|
client raises as the network error it would have had connecting directly.
|
|
- `CONNECT host:port`: the client then speaks plaintext and the proxy connects to `host:port` over TLS. The
|
|
proxy reads IMQ's framing (message type and size, not contents) to log it.
|
|
|
|
The client starts it as `t5de-proxy -parent <pid> -ready <file> -log %TEMP%\t5de-proxy.log`; it writes its
|
|
address and token to the ready file once listening, and exits with the client. Setting `T5DE_PROXY_TRACE=1`
|
|
before starting the client adds full URLs (with queries) and every IMQ frame to the log.
|
|
|
|
## Community products
|
|
|
|
Phase 1 of T5DE's community products (design: t5de-hub's `docs/design.md`): products in local packs, a folder
|
|
each, in `%APPDATA%\T5DE\packs` and the folders in `T5DE_PACKS` (separated by `;`). Each product gets a local ID
|
|
from 2,000,000,000 up, kept in `%APPDATA%\T5DE\proxy\products.json`. For those IDs the proxy answers the client
|
|
itself:
|
|
|
|
- `test.inventoryProductBatch` (product info) and `test.authorizeProducts2` (permission, the CFL's address and
|
|
checksum): for a product deriving from an IMVU product, IMVU is asked about the parent and the proxy adds its own
|
|
entry (the client needs the parent's derivation chain).
|
|
- `productdata/<local ID>/<revision>`: the pack built into a CFL.
|
|
- `api/shop/product.php`: putting a product on asks whether it's a bundle (not), and the shop asks for product
|
|
details (answered from the hub's listing, or the pack; IMVU is asked only about its own products).
|
|
|
|
IMVU never sees community products: they're taken out of outfit saves (`api/base_look.php`) and outfit chat
|
|
commands (`*use`, `*putOn`, `*putOnOutfit`, `*remove`, `*takeOff` on `/chat/` queues; a command left empty is
|
|
answered by the proxy), so IMVU and everyone else see the defaults. The proxy remembers each account's community
|
|
products with the outfit they were saved with, and adds them back when the client loads that same outfit
|
|
(`base_look.php`, and `look` in the login response); an outfit changed elsewhere comes back without them.
|
|
Requests it changes are re-signed (`X-imvu-auth`) with the session key from the login response, which never
|
|
leaves the machine.
|
|
The client sends some product IDs as floats (`2000000008.0`), so every ID is read either way (`parseID`). As a
|
|
last check, `products.Guard` refuses any request to IMVU that still names a local ID given out, and logs it
|
|
(`products: refused a request to ...`): such a request is a path not handled yet.
|
|
|
|
## The hub
|
|
|
|
Which hub: `$T5DE_HUB` if set, else the one chosen in the client's Settings > T5DE (kept in
|
|
`%APPDATA%\T5DE\proxy\server.json`, used from the next start), else the install's channel's (`-channel`, which
|
|
the client passes): T5DE's hub (`https://hub.t5de.dev`) for releases, staging (`https://hub.staging.t5de.dev`) for
|
|
nightlies and builds outside an install (`dev`). What belongs to a hub (its sign-in, product IDs, its products) is
|
|
kept per hub: in `%APPDATA%\T5DE\proxy` for T5DE's hub, in `hubs\<host>` under it for others, so a hub never gets
|
|
another's sign-in, and product IDs from two hubs never clash.
|
|
|
|
The client's Settings > T5DE page signs in and out (the proxy answers it at `http://t5de-proxy.local/hub`,
|
|
`internal/control`). `t5de-proxy signin [-hub URL]` also signs the proxy in to T5DE's hub (git.t5de.dev/t5de/t5de-hub; URL is the hub's
|
|
API): it shows a code to enter on the hub's website (`/device`, OAuth 2.0 device authorization) and saves the
|
|
session token it gets in `hub.json` in the hub's folder, encrypted for the Windows user (DPAPI). From then on the
|
|
running proxy keeps a WebSocket to the hub (`internal/hub`) with the token as a bearer token, reporting which IMVU
|
|
account the client signed in as, the community products it wears, and the IMVU chats it's in. If the hub ends the
|
|
session (signed out everywhere, banned), the proxy stops connecting until `signin` is run again.
|
|
|
|
The room channel (`internal/products/room.go`): the hub tells the proxy which other T5DE users are in the same chat
|
|
and what they wear; the proxy gives the client those of their products it has packs for, as `*use`/`*remove` lines
|
|
from them, and adds them to the whole outfits they send. It also keeps community products out of the outfit the
|
|
client posts to IMVU's chat participant endpoint.
|
|
|
|
The shop (the client's `t5de-shop` patch adds a T5 choice to its rating filter): searches with
|
|
`maturity_rating` `t5` (`api/shop/search_st.php`) are answered from the hub's products (`GET /api/v1/products`,
|
|
filtered by the shop's categories against each product's IMVU parent's category path), fetching listed products
|
|
that aren't here yet (up to 15 seconds) so they can be shown and tried on. Buying T5 products
|
|
(`api/service/shop/svc_catalog_purchase.php`) adds them to the person's hub library; IMVU products in the same
|
|
purchase go on to IMVU without them.
|
|
|
|
T5DE-hosted rooms (`internal/hosted`, t5de-hub's `docs/hosted-rooms.md`): rooms living on the hub, with instance IDs
|
|
`2100000000-<n>` and chat IDs `2100000000+n`. The proxy answers the client's room calls for them (`getRoomCardInfo`,
|
|
`getOrMakeChat`, `getRoomState`, `setRoomState`, `getParticipants`, `terminateChat`), serves their IMQ queues
|
|
(`/chat/<id>`, `inv:/room/room-<id>`, `room_occupancy/<id>`: subscriptions answered here through `proxy.IMQRouter`,
|
|
chat to and from the hub over its WebSocket), adds them to the room list, checks the room's products against its real
|
|
owner (`authorizeProducts2`), and answers any other request to IMVU naming one ("not found") rather than send it.
|
|
|
|
Decorations (`internal/products/decor.go`): community furniture and poses in IMVU rooms. They're taken out of the
|
|
room its owner saves (`test.setRoomState`) and broadcasts (`*imvu:setRoomState`, a zlib-compressed scene) so IMVU
|
|
keeps the base room, and stored on the hub for that room (`PUT /api/v1/decorations/<instance>`; placements name their
|
|
product, stored as the hub's ID). Loading the room (`test.getRoomState`, the hub asked at the same time) and the owner's
|
|
incoming broadcasts get them added back as local IDs; the hub's `decor` notice makes the proxy give the client the room
|
|
again with the new ones.
|
|
|
|
`T5DE_INSTANCE=<name>` (set by the client's `t5de-instances` patch for a second client run beside the first) keeps
|
|
the proxy's data in `%APPDATA%\T5DE-<name>\proxy` and its log in `t5de-proxy-<name>.log`.
|
|
|
|
For finding out what IMVU's APIs send, `T5DE_PROXY_DUMP=<text>` logs the request and response bodies of requests
|
|
whose path contains it (test accounts only: bodies hold personal data). In Git Bash, keep the value from starting
|
|
with `/` (or set `MSYS_NO_PATHCONV=1`), or it's turned into a Windows path.
|
|
|
|
## Building
|
|
|
|
go test ./...
|
|
GOOS=windows GOARCH=amd64 go build -trimpath -ldflags "-H=windowsgui" -o dist/t5de-proxy.exe ./cmd/t5de-proxy
|
|
|
|
CI publishes `t5de-proxy.exe`: pushes to main replace the `nightly` pre-release, `vX.Y.Z` tags make releases.
|
|
To ship a release in T5DE, copy its url and sha256 into t5de's `t5de.toml` (`[[programs]]`, patch `t5de-proxy`).
|