Repository navigation
Trackmania (PPSA02195) #13709
simnJS
started this conversation in
Game bring-up
Replies: 0 comments
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
Setup
PPSA02195, contentVersion01.026.000, masterVersion01.00mainat813496001e, WinLibs GCC 15.2.0 r7, ReleaseThe app ships one executable and five ELF modules:
libaegir.prx(48 MB, the engine) at the app root,anzu/anzu.prx, andsce_module/{libc,libScePfs,libSceNpCppWebApi}.prx.fakelib/holds SELF stubs forlibSceAgc,libSceAgcDriver,libSceAmpr,libScePlayGoandlibScePsml; they were left out of the module directories, since AnyPS5 provides those libraries itself. All six were unwrapped withtools/unwrap_self.py, then converted with:The executable needs 48 system libraries, all of them declared under
core/libs/prx.libaegir.prxadditionally needslibSceRazorCpu.prxandGbxGame, which is the executable's own module name.Status
Conversion fails on
main. With the two changes below it converts fully (5 modules, 2558 external prx references), and the runtime then stops in the module loader. No frame has been rendered yet.1. First stop: a segment prefix with no memory operand
64 22 d0isfs and dl, al: ModRMd0is register-direct, so the FS prefix has no memory operand and selects nothing.WindowsTlsBuilderclassifies it anyway and refuses the image.This refusal is deliberate, and I first read it as an oversight.
UNSUPPORTED_TLSintest_sce_code_extent.pyis64 2d 65 6c 66 2e c3, anfs sub eax, imm32whose immediate spellself., and several fixtures there require that the conversion fail on it.segment-prefix-at-eofintest_tls_function_coverage.pyuses64 90. So an inert segment prefix is the project's canary for code taken from data: the refusal is what surfaces a misclassification instead of silently miscompiling. Skipping it would hide the cause rather than fix it, and section 2 is that cause.Those bytes are not code. They sit in an OpenSSL CRYPTOGAMS AES constant block at va
0x1850680-0x1850710, which a function at va0x1850620addresses withlea r10, [rip+0x79]and reads with sevenmovdqa. None of these modules carry symbols for this code, so the attribution to CRYPTOGAMS rests on the instruction shape and on the copyright string in section 2, not on a symbol name.2. The cause: embedded constants followed as code in an image without
PT_SCE_DYNLIBDATAAfter the first stop, the next one is:
64 20 73 71is not an instruction either. It is the ASCIId sqofwhich the eboot stores at va
0x18706fe, right after the 24 Keccak iota round constants at va0x1870640. Twolea r15, [rip+...]at va0x187040dand0x1870495take that constant block's address.The code analysis has a code/data test for exactly this, but it never ran:
sceFunctionsis filled only in theif (sceBase)branch ofCodeInstructionCollector::Collect, and no module of this title has aPT_SCE_DYNLIBDATAsegment, while all six do havePT_GNU_EH_FRAME. With the list empty,addPointerRoottakes thesceFunctions.empty()short circuit and makes every RIP-relativeleatarget in an executable segment a code root, andBuildControlFlowGraphis told to follow code addresses.dataTargetsstays empty, so nothing is reported.The eboot does carry what the test needs:
PT_GNU_EH_FRAMEwith 80625 FDEs covering 31.4 MB of the 32.1 MB executable segment, 98.0 %. Both failing addresses fall in inter-function gaps, 0.65 MB in total across 76503 gaps.Reading the unwind table in that branch too makes the conversion complete and report
Took 55 pointer target(s) outside the unwind functions as data, the first at 0x1850770.3. Runtime stops on the executable's own module name
With the conversion through, the title gets as far as the module loader:
GbxGameis the eboot's module name, the title's equivalent ofMainProg.libaegir.prximportsGvGnMOCTRXAfrom it, and the eboot exports exactly that symbol (value=0xc27f20,size=8,GLOBAL,FUNC). The convertedlibaegir.prx.guest.prxinstead declaresGbxGameamong its host dependencies, and the executable's own.needed.jsonlists it as well, so the loader looks for alibs/GbxGamethat cannot exist. The relinker prints no warning;tools/import_audit.pydoes flag it.I tested #11432 on top of the section 2 change, merged cleanly at
77cb5f907c. It gives the executable what it was missing:app.exenow carries an export directory andobjdump -pshows[ 0] +base[ 1] 0000 GvGnMOCTRXA. The title still stops at the same place:The entry stub loads every needed library from
libs/by name before it resolves any import, so the executable's new export table is never reached. The chain is:libaegir.prxhasDT_NEEDED GbxGame. The executable's ownDT_NEEDEDhas 48 entries andGbxGameis not among them..needed.jsoncompanion listsGbxGameall the same, since it aggregates the host dependencies of the bundled modules, asUSAGE.mddescribes.WindowsEntryStubBuilderturns that list into a load oflibs/<name>for each entry. A missing library is tolerated only when nothing is imported from it, andGvGnMOCTRXAis imported, so this one is fatal.So #11432 is necessary here but not sufficient: something also has to recognise that a needed name is the title's own executable. One rule that would not need the module name to be recoverable from the image, which it does not look to be here (no
DT_SONAME, no module info the unwrapped ELF kept): a needed library with no file inlibs/or the module directories, whose imported symbols are all exported by the converted executable, resolves to the executable. With #11432 those exports exist, so the check is possible.I tried that rule locally, on top of #11432: the entry stub takes the handle of the process image with
GetModuleHandleW(NULL)for a needed library that has no module suffix and whose every import the converted executable exports, and stores it in the same handle slot, so the existingGetProcAddressresolution is unchanged.libs/GbxGameis no longer looked for and the title starts.4. Where it stops now
With sections 2 and 3 in place the title reaches its own engine and runs until the audio setup:
So it mounts every data pack, checks their hashes, links its shader cache from
GpuCache_Ps5.zipand starts, then stops on one NGS2 voice parameter.0x30000002is not among the identifierslibSceNgs2.nativehandles. No frame has been presented yet, and nothing beyond this point has been exercised.Library status
tools/import_audit.pyover the executable and the five converted modules, against amainbuild oflibs:The only
absentimport isGvGnMOCTRXA [GbxGame]from section 3. The two stubs that would throw when called arelibSceHttp2.prx:sceHttp2WaitAsyncandsceHttp2ReadDataAsync. Of the 25 partials, 18 are alsolibSceHttp2, so HTTP/2 is where this title will land next once it boots.Patches
One change, on section 2, with a fixture that fails without it: a non-SCE image whose function takes the address of an
fsinstruction past the end of its unwind function. It clears both stops, since the constants are no longer followed as code, and it leaves the canary of section 1 in place: #13716.All reactions