PSU lives on the Clementine server. Support the people who run it. Support Clementine on Patreon (opens in a new tab)

← Dev blog

Phase 0: scaffolding

Phase 0 is done on the code side. There is a Rust workspace in client/ with three crates. Nothing plays, and I have not yet run any of it against the real Clementine client. This post covers what exists and what I do next.

The three crates

psu-inspect is a command line tool that reads PSUC.exe and PSUEx.dll and writes a report. It prints the SHA-256 of each file and a summary of the executable (image base, entry point, sections). It counts imported functions per DLL, checks whether the file is packed with UPX, and looks for hints of thread-local storage. It writes scope-report.md and imports.json. It only reads files and never runs the game.

The packing check matters because a packed executable only shows the packer's own imports. If PSUC.exe is packed, I unpack a copy with upx -d and run the tool again. The thread-local storage answer is marked inconclusive until then.

To test without any game data, the tool can build a tiny synthetic install, a hand-made PE file with no game content in it. I use that in the tests.

psu-runtime is the start of the runtime library. It does three things so far.

One thing surprised me. Apple Silicon uses 16 KiB memory pages, and PE sections are protected in 4 KiB units. I can't enforce the original per-section permissions, so the image is mapped read-write for now.

psu-launcher is a placeholder. It prints the planned flow and does no networking. The real version will replace the .NET online.exe: patch, check the hash, lift again if the executable changed, then launch.

Tests

cargo test runs 16 tests, and all of them pass. They cover the PE parsing, the synthetic installs (plain and UPX-style), the guest memory window and the import registry.

What's next

The next step is to run psu-inspect on the real client. I will register an account, download the client and unzip it. Then I copy it, so the original is never touched, and point the tool at the copy.

The report tells me how many imports I have to implement, whether the executable is packed, and whether TLS is a problem. After that I can start the lifter, which turns the x86 code into C, and let imports.json decide what the runtime builds first.

I'll write up the results here, including whatever goes wrong.