From d59cd0d370de07adaa6ec8d2bd5677ed76feab1c Mon Sep 17 00:00:00 2001 From: ODevStudio Date: Tue, 6 Oct 2026 11:01:35 +0200 Subject: [PATCH] Reduce PR to PATCHGO package rejection Keep the maintainer's 1.04 build support, calibration and documentation from main. Retain the missing PATCHGO flag check and a regression fixture that is larger than the base package. --- README-PC-VR.md | 164 +++++------------- README.md | 3 +- pc-vr/launch.ps1 | 47 ++--- shadps4-arm64-main/src/core/known_title.cpp | 100 ++++++----- shadps4-arm64-main/src/core/known_title.h | 4 + .../src/core/known_title_builds.h | 23 +-- .../src/core/known_title_memory.h | 68 -------- .../libraries/vr_tracker/calibration_state.h | 23 --- .../core/libraries/vr_tracker/vr_tracker.cpp | 117 ++++++------- tools/tests/known_title_builds_test.cpp | 16 +- tools/tests/launcher-test.ps1 | 71 +------- tools/tests/vr_tracker_calibration_test.cpp | 34 ---- 12 files changed, 190 insertions(+), 480 deletions(-) delete mode 100644 shadps4-arm64-main/src/core/known_title_memory.h delete mode 100644 shadps4-arm64-main/src/core/libraries/vr_tracker/calibration_state.h delete mode 100644 tools/tests/vr_tracker_calibration_test.cpp diff --git a/README-PC-VR.md b/README-PC-VR.md index 47a572a..8021c81 100644 --- a/README-PC-VR.md +++ b/README-PC-VR.md @@ -57,109 +57,6 @@ multisampling, and draws far larger than the console ever did: 2880x3072 an eye four times the pixels of its largest size (1440x1536, what a PlayStation 4 Pro uses), chosen in a small window at every start. -## Game versions - -The PC emulator supports the verified European **CUSA12392 1.00 and 1.04 executable -layouts** in one build. There is no version selector: choose your game's `eboot.bin` or -complete package. The emulator checks the executable's signatures, offsets and original -values before applying resolution, allocation or timing patches. A package filename, -`APP_VER` or `VERSION` does not determine compatibility. - -### A complete 1.04 game in one package - -Put your dumped, unencrypted full-game `.pkg` in `games`, or select it when the launcher -asks where the game is. Let the launcher unpack it, then press Play. A complete 1.04 -package already includes the base assets; you do not need to install 1.00 first. - -For an already unpacked complete 1.04 game, select its `eboot.bin` or put its folder in -`games`. Keep `sce_sys\param.sfo`, `sce_module` and the other game assets with it. Do not -copy just the updated executable into an otherwise incomplete folder. - -We tested a base game with a separate update, not a merged 1.04 package. A complete -package with the same verified executable layout uses the same profile; modified or -unknown layouts do not receive title patches. - -### A base game plus a separate 1.04 update - -An update-only package lacks the base assets. The launcher's base-game installer rejects -packages marked as updates, so they cannot overwrite your base installation. Use shadPS4's -native update overlay instead: - -1. Unpack the base game into `games\CUSA12392` with the launcher. -2. Extract your dumped update into a staging folder with PkgTool (included in the PC - distribution) or another compatible extractor. For example, from the AstroQuest folder - in PowerShell: - - ```powershell - $tool = ".\pc-vr\pkgtool\PkgTool.exe" - $update = "C:\Games\astrobot-update-1.04.pkg" - & $tool pkg_extract --passcode ("0" * 32) $update ".\update-unpacked" - ``` - -3. PkgTool puts the game files under `update-unpacked\uroot`. Rename that folder to - `CUSA12392-UPDATE` and place it beside the base folder in `games`. If your extractor - writes the files directly into its output folder, use that folder instead. -4. Check that the update has `sce_sys\param.sfo`. PkgTool stores this metadata outside - `uroot`, so extract it separately. Run `pkg_listentries` and use the **Index** shown on - the `PARAM_SFO` row; the example below uses `10`, which may differ in your package: - - ```powershell - & $tool pkg_listentries $update - New-Item -ItemType Directory -Force ".\games\CUSA12392-UPDATE\sce_sys" | Out-Null - & $tool pkg_extractentry --passcode ("0" * 32) $update 10 ".\games\CUSA12392-UPDATE\sce_sys\param.sfo" - ``` - -The resulting layout is: - -```text -games\CUSA12392\eboot.bin -games\CUSA12392\sce_sys\param.sfo -games\CUSA12392-UPDATE\eboot.bin -games\CUSA12392-UPDATE\sce_sys\param.sfo -``` - -Leave the base assets and all the extracted update files in those folders. Launch the -**base** `games\CUSA12392\eboot.bin`; shadPS4 loads the update executable and overlays -the update's files where present. The launcher reports the selected path and package -metadata, then prints the emulator's verified executable profile: - -- `Verified title profile 1.00, as on the disc`: the supported 1.00 layout. -- `Verified title profile 1.04, the last update`: the supported 1.04 layout. -- `Unrecognized or modified CUSA12392 layout`: no title resolution or time-step patches - were applied. Check your complete dump; do not rename a package or edit its metadata - to force a profile. - -### Saves and rollback - -For an independent 1.04 test, use a second AstroQuest folder with its own `pc-vr\user` -directory, fresh saves and caches, and copies of the required configuration and system -modules. Keep your working 1.00 installation unchanged. Both versions otherwise use the -same title ID and save location; back up `pc-vr\user` before switching game files in one -installation. Do not assume a save written by 1.04 can be loaded by 1.00. - -With a base-plus-update installation, move the `CUSA12392-UPDATE` folder outside `games` -to return to the base version, then restore the matching save backup. Keep the folder -for later; do not merge update files into the base folder. - -Before the merge with main, ODevStudio built and linked PR #15 both on its own and with -the Index and spectator changes from PRs #6 and #11. On a Valve Index through SteamVR/OpenXR -with a PC-connected DualSense and fresh 1.04 saves, the tester completed controller -calibration and played the first level using the combined build. Combined-eye spectator -crop also passed. Tests checked both actual executable layouts, 2880x3072 output and -timing at 60 and 40 FPS, and timing at the original 1440x1536 resolution. - -Upstream also tested both versions on the PC and, without wearing it, on the Quest 3 -(title, controller screen, world map and entry into a level). Those runs used upstream's -calibration implementation. The merged pose-based calibration still needs headset -regression tests. Other regions and additional executable variants remain untested. - -The 1.04 resolution offsets build on -[Clodo76's investigation in issue #1](https://github.com/bigmak94/AstroQuest/issues/1). -Reports in issues [#3](https://github.com/bigmak94/AstroQuest/issues/3), -[#5](https://github.com/bigmak94/AstroQuest/issues/5) and -[#7](https://github.com/bigmak94/AstroQuest/issues/7) describe related 1.04 symptoms; -they are not separate tested configurations. - ## Playing 1. **The controller goes to the PC**, not to the headset: a USB-C cable, or Bluetooth (hold @@ -291,6 +188,50 @@ controller into the outline; gadgets shoot where it points. `controller_hand=lef settings makes it the left one. The game's rumble goes to both controllers. A gamepad that is connected to the PC takes over at once, and gives the controllers back when it goes. +## The game's versions + +The emulator knows two builds of the game's executable from inside: the one on the disc +(1.00) and the last update (1.04). It tells them apart by what the executable holds when it +is loaded, not by what a package's name or `param.sfo` says, and the console window names +the one it found (`CUSA12392 in a build known from inside: ...`). Any other build is left to +itself: it plays at the console's sizes, and in slow motion where frames take long. + +- **A copy of the game that has the update in it** (one package, or one folder): put it in + `games` as it is. Keep the whole folder together: an updated `eboot.bin` alone in an + otherwise incomplete folder is not the game. +- **The game and its update as two packages**: the launcher unpacks the game's own package + and leaves the update alone (an update holds only the files it changed, and is turned down + if it is offered as the game). The game then plays as on its disc. To play it updated, the + update gets a folder of its own next to the game's, named `CUSA12392-UPDATE`, which the + emulator lays over the game's files as shadPS4 does; the game's own folder is not changed. + With PkgTool, which is in the `pc-vr\pkgtool` folder, from the AstroQuest folder in + PowerShell: + + ```powershell + $tool = ".\pc-vr\pkgtool\PkgTool.exe" + $update = "C:\Games\astrobot-update-1.04.pkg" + & $tool pkg_extract --passcode ("0" * 32) $update ".\update-unpacked" + Move-Item ".\update-unpacked\uroot" ".\games\CUSA12392-UPDATE" + # The update's description of itself is kept apart in the package: its number is on the + # PARAM_SFO line of the list (10 in the 1.04 update tried here). + & $tool pkg_listentries $update + New-Item -ItemType Directory -Force ".\games\CUSA12392-UPDATE\sce_sys" | Out-Null + & $tool pkg_extractentry --passcode ("0" * 32) $update 10 ".\games\CUSA12392-UPDATE\sce_sys\param.sfo" + ``` + + Start the game as always: the launcher still names `games\CUSA12392\eboot.bin`, and the + emulator runs the update's. +- **Saves** are in the same place for both versions (`pc-vr\user\home\1000\savedata\CUSA12392`). + A save made by 1.00 loads in 1.04; whether one written by 1.04 loads in 1.00 has not been + tried, so copy that folder before changing versions. To go back to 1.00, move + `CUSA12392-UPDATE` out of `games`. + +Tried here: both versions on the PC and, without wearing it, on the Quest 3 (title, the +controller's screen, world map, into a level; 1.04 at 2880x3072 an eye on the PC). +ODevStudio played 1.04 through its first level on a Valve Index with a fix of their own for +the same thing (pull request #15), whose write-up of the folders this section follows. 1.00 +is the version played the most. + ## Desktop Spectator View The launcher offers **Stereo**, **Single eye** and **Combined eyes**. Single eye shows the @@ -502,7 +443,7 @@ Things that had to be right, for whoever works on this again: executable is also laid out differently, so the emulator did not know it and gave it neither the larger pictures nor the game's own speed ("resolution stuck at the lowest"): both builds are known now, 1.00 and 1.04, told apart by what the executable holds when it - is loaded (the console window says "Verified title profile ..."). The + is loaded (the console window says "CUSA12392 in a build known from inside: ..."). The addresses for 1.04 are Clodo76's. Any other version still plays as before: at the console's sizes, and in slow motion where frames take long. - **A package that is only the game's update** was unpacked as if it were the game. The @@ -641,7 +582,7 @@ it; the system's runtime stays Virtual Desktop's. | `tools/xrsim-keys.ps1 : ...` | works the simulator's window: `B` is A and X, `N` is B and Y, `Y G H J` the sticks, `I` the sticks pressed in, `Comma` the menu button (hold keys for a second: short presses are not always seen); `Look:,` turns the simulated head, `Click:,` clicks | | `tools/pc-rate-compare.sh ""` | the same scripted level walk at 60 frames a second and with the given settings, a picture every two seconds from each | | `tools/xr-probe-win` (`build/xr-probe-win/xr_probe_win.exe`) | what a runtime offers: extensions, system, Vulkan requirements | -| `tools/tests/launcher-test.ps1` | checks game discovery, update-only installer rejection, cancellation and profile log messages. Supply `-PkgTool ` for package-header tests; `-BasePackage ` and `-UpdatePackage ` also check your local dumps without extracting them | +| `tools/tests/launcher-test.ps1` | tries the launcher's search for the game (unpacked games and packages, names with brackets, leftovers of an unpacking) on made-up folders | | `tools/ui-drive.ps1 -Steps "text\|picture.png\|button\|seconds", ...` | works the launcher's own windows and message boxes from outside: waits for one that shows a text, saves a picture of it, presses a button. With a release unzipped somewhere and a package put in its `games` folder, that is the whole first start, from the question about unpacking to Play | Settings for tests: `SHADPS4_XR_HEAD=0` (the head is a script's to move, the host only @@ -687,16 +628,3 @@ bash tools/make-pc-vr.sh # cop The OpenXR loader (Khronos OpenXR-SDK 1.1.63, `externals/openxr-sdk`) is built with the emulator; `ENABLE_OPENXR` (on for Windows) switches the whole of it. - -The compatibility checks need a C++23 compiler and no headset or game files: - -```sh -clang++ -std=c++23 -Ishadps4-arm64-main/src tools/tests/known_title_builds_test.cpp -o build/profile-test.exe -clang++ -std=c++23 -Ishadps4-arm64-main/src tools/tests/vr_tracker_calibration_test.cpp -o build/calibration-test.exe -build/profile-test.exe -build/calibration-test.exe -``` - -The profile test also accepts local plain ELF executables to check real layouts. Use -`tools/ps4elf.mjs unwrap` to convert a SELF executable first, and keep extracted game files -outside Git. diff --git a/README.md b/README.md index 8b6a5e9..450a6e5 100644 --- a/README.md +++ b/README.md @@ -53,8 +53,7 @@ over Virtual Desktop and fixed since. Expect rough edges, and please report what that is only the game's update is not the game: unpacked into a folder named `CUSA12392-UPDATE` next to the game's `CUSA12392`, it is played over it.) Other regions and the versions in between are untested, and the fixes for the game's timing and resolution - only apply to these two. See [Game versions](README-PC-VR.md#game-versions) for complete - packages, separate updates, saves and rollback. + only apply to these two. - To install on the headset: a computer with [adb](https://developer.android.com/tools/releases/platform-tools) (or [SideQuest](https://sidequestvr.com/)) and the headset in diff --git a/pc-vr/launch.ps1 b/pc-vr/launch.ps1 index 7ae2131..f12e92d 100644 --- a/pc-vr/launch.ps1 +++ b/pc-vr/launch.ps1 @@ -192,7 +192,7 @@ function Get-GameInfo([string]$eboot) { function Find-Game([string]$top) { $first = $null foreach ($folder in (Get-Folders $top)) { - if ([System.IO.Path]::GetFileName($folder) -match '-(UPDATE|patch|mods)$') { continue } + if ($folder -match '-(UPDATE|patch|mods)$') { continue } $eboot = [System.IO.Path]::Combine($folder, "eboot.bin") if (-not [System.IO.File]::Exists($eboot)) { continue } if ((Get-GameInfo $eboot)["TITLE_ID"] -eq $madeFor) { return $eboot } @@ -252,23 +252,6 @@ function Read-PackageId([string]$path) { } catch { return $null } } -function Read-PackageHeader([string]$path, [string]$tool) { - $library = Join-Path ([System.IO.Path]::GetDirectoryName($tool)) "LibOrbisPkg.dll" - [void][System.Reflection.Assembly]::LoadFrom($library) - $stream = [System.IO.File]::OpenRead($path) - try { - if ($stream.Length -lt 0x490) { throw "Truncated package header" } - return ([LibOrbisPkg.PKG.PkgReader]::new($stream)).ReadHeader() - } finally { $stream.Dispose() } -} - -function Test-UpdateHeader($header) { - $patchFlags = [LibOrbisPkg.PKG.ContentFlags]::FIRST_PATCH -bor - [LibOrbisPkg.PKG.ContentFlags]::PATCHGO -bor - [LibOrbisPkg.PKG.ContentFlags]::SUBSEQUENT_PATCH - return ($header.content_flags -band $patchFlags) -ne 0 -} - # The window shown while a package is unpacked. True when the unpacking ran to its end, false # when it was cancelled (and stopped). function Show-Unpacking($process, [string]$drive, [double]$freeBefore) { @@ -355,16 +338,6 @@ function Expand-Package($package) { [void](Show-Box ("The game is here as a package:`n" + $package.FullName + "`n`nbut PkgTool, which unpacks packages, is missing from`n" + (Join-Path $here "pkgtool") + "`n`nUnzip the whole AstroQuest package again.") "OK" "Warning") return $null } - try { - $header = Read-PackageHeader $package.FullName $tool - } catch { - [void](Show-Box ("The package header could not be read. Nothing was unpacked.`n`n" + $package.FullName + "`n`n" + $_.Exception.Message) "OK" "Warning") - return $null - } - if (Test-UpdateHeader $header) { - [void](Show-Box ("This package is an update, not a complete game. It needs the base game's files and must not replace them.`n`nExtract it into a separate CUSA12392-UPDATE folder beside CUSA12392, then launch the base game's eboot.bin. See README-PC-VR.md, Game versions.`n`n" + $package.FullName) "OK" "Warning") - return $null - } $serial = "game" if ($contentId -match "[A-Z]{4}[0-9]{5}") { $serial = $Matches[0] } $target = Join-Path $gamesFolder $serial @@ -794,13 +767,13 @@ Say ("The game: " + $game) $info = Get-GameInfo $game if ($info.Count -eq 0) { Say "sce_sys\param.sfo is missing next to it: this is not a complete copy of the game, and the emulator may not know it." "Yellow" -} else { - Say ("Package metadata: " + $info["TITLE"] + ", " + $info["TITLE_ID"] + ", APP_VER=" + $info["APP_VER"] + ", VERSION=" + $info["VERSION"]) - if ($info["TITLE_ID"] -ne $madeFor) { - Say ("AstroQuest's title patches support the European release " + $madeFor + ". This title may not run.") "Yellow" - } +} elseif ($info["TITLE_ID"] -ne $madeFor) { + Say ("This is " + $info["TITLE"] + ", " + $info["TITLE_ID"] + ". AstroQuest is made for ASTRO BOT Rescue Mission in its European release, " + $madeFor + ": its fixes for the game's speed and picture do not apply to another, and it may not run.") "Yellow" +} elseif (@("01.00", "01.04") -notcontains $info["APP_VER"]) { + # (What decides is the executable itself, which the emulator looks at as it loads it and + # names below; what the game says its version is can be wrong.) + Say ("This copy of the game says it is version " + $info["APP_VER"] + ". AstroQuest knows versions 01.00 and 01.04 from inside: another plays in slow motion where frames take long, and at the console's resolution.") "Yellow" } -Say "The emulator recognizes the 1.00 and 1.04 executable layouts by their contents, not package metadata. Its verified profile or rejection appears below." $gameFolder = [System.IO.Path]::GetDirectoryName($game) if ($info["TITLE_ID"] -eq $madeFor -and -not [System.IO.File]::Exists([System.IO.Path]::Combine($gameFolder, "sce_module", "libc.prx"))) { [void](Show-Box ("This is not a complete copy of the game:`n" + $gameFolder + "`n`nParts that every copy has are missing (sce_module\libc.prx for one). A folder with only an update of the game in it looks like this: an update holds the files it changed and no more. Put the update's files over a copy of the whole game, or use the game without its update.") "OK" "Warning") @@ -966,8 +939,10 @@ function Show-Log { Say (" " + $Matches[1]) } elseif ($line -match '^\[Input\] \([^)]*\) \S+ (?:\w+: )?(Controller .*)$') { Say (" " + $Matches[1]) "Yellow" - } elseif ($line -match '^\[Core\] <(Info|Warning)> \([^)]*\) \S+ (?:\w+: )?(Verified title profile .*|Unrecognized or modified CUSA12392 layout:.*|Title patch rejected at .*|The title draws at up to .*|The scene is drawn at .*|Frames are given .*)$') { - if ($Matches[1] -eq "Warning") { Say (" " + $Matches[2]) "Yellow" } else { Say (" " + $Matches[2]) } + } elseif ($line -match '^\[Core\] \([^)]*\) \S+ (?:\w+: )?(CUSA12392 in a build .*|The title draws at up to .*|The scene is drawn at .*|Frames are given .*)$') { + Say (" " + $Matches[1]) + } elseif ($line -match '^\[Core\] \([^)]*\) \S+ (?:\w+: )?(This build of CUSA12392 .*)$') { + Say (" " + $Matches[1]) "Yellow" } elseif ($line -match '^\[Lib\.AudioIn\] <(Info|Warning)> \([^)]*\) \S+ (?:\w+: )?(Microphone: .*)$') { if ($Matches[1] -eq "Warning") { Say (" " + $Matches[2]) "Yellow" } else { Say (" " + $Matches[2]) } } elseif ($line -match '.*?: (.*)$') { diff --git a/shadps4-arm64-main/src/core/known_title.cpp b/shadps4-arm64-main/src/core/known_title.cpp index c305340..dbf72eb 100644 --- a/shadps4-arm64-main/src/core/known_title.cpp +++ b/shadps4-arm64-main/src/core/known_title.cpp @@ -19,7 +19,6 @@ #include "core/emulator_settings.h" #include "core/known_title.h" #include "core/known_title_builds.h" -#include "core/known_title_memory.h" #include "core/vr/vr_runtime.h" #include "video_core/renderer_vulkan/vk_scheduler.h" @@ -29,21 +28,36 @@ namespace { using Clock = std::chrono::steady_clock; +// Astro Bot Rescue Mission, CUSA12392. Where things are in each build of it that is known from +// inside, and how a build is told from the others, is in known_title_builds.h; what follows is +// the same in all of them. using Builds::Build; using Builds::ConsoleSizes; using Builds::FirstHeadsetLevel; using Builds::LastHeadsetLevel; +// The tracking manager, a singleton: the point it counts positions from, and for the headset +// the position counted from it. constexpr u64 ManagerOrigin = 0x6210; constexpr u64 ManagerHeadState = 0x6848; constexpr u64 StatePosition = 0x10; constexpr u64 StatePositionValid = 0x54; -constexpr u64 ResolutionBase = 0x0; -constexpr u64 ResolutionLevel = 0x4; -constexpr u64 ResolutionOffset = 0x20; -constexpr u64 ResolutionHighest = 0x28; -constexpr u64 ResolutionLowest = 0x2c; +// What sets the size the scene is drawn at, a singleton made when first asked for: the size is +// one of a list (ConsoleSizes, the first three are for a television), a base (4 on the +// console, 6 on its Pro model) plus an offset that the game moves between a lowest and a +// highest one by how long it finds the GPU to take over its drawing. It reads that off +// timestamps which, emulated, tell how long the emulator took to pass the drawing on, not how +// long the GPU takes over it: left to itself, the game draws large where the GPU is busiest. +constexpr u64 ResolutionBase = 0x0; // s32 +constexpr u64 ResolutionLevel = 0x4; // s32 +constexpr u64 ResolutionOffset = 0x20; // s32 +constexpr u64 ResolutionHighest = 0x28; // s32, an offset +constexpr u64 ResolutionLowest = 0x2c; // s32, an offset + +/// The build of the title that runs, and where its image starts, from the moment its image is +/// loaded and found to be one of those known (OnGameLoaded); nullptr for every other title and +/// every other build. std::atomic known_build{nullptr}; VAddr known_base{}; @@ -235,8 +249,8 @@ public: s32 fastest_now = refresh > SlowDisplay ? 1 : fastest_pace; if (fps_cap > 0.0) { // The fewest refreshes that keep frames to the cap. - fastest_now = - std::max(1, static_cast(std::ceil(1.0 / (refresh * fps_cap) - 0.01))); + fastest_now = std::max( + 1, static_cast(std::ceil(1.0 / (refresh * fps_cap) - 0.01))); } if (fixed_pace == 0 && (fastest_now != fastest || pace < fastest_now)) { // The display turned out to be another kind than was thought (its rate is only @@ -308,8 +322,7 @@ private: const auto since_change = now - changed; const auto cost_at = [&](s32 other) { return gpu * cost[other] / cost[level]; }; // The slowest pace frames are ever held to, and the smallest size wanted at a pace. - const s32 slowest = - std::max(std::max(2, fastest), static_cast(SlowestWanted / refresh)); + const s32 slowest = std::max(std::max(2, fastest), static_cast(SlowestWanted / refresh)); const auto smallest_at = [&](s32 refreshes_given) { return !paced || refreshes_given >= slowest ? FirstHeadsetLevel : UsualLevel; }; @@ -404,8 +417,8 @@ private: // (A frame for every refresh is not tried again for long where the title's own work was // seen not to fit one, as on a display that refreshes faster than the title can draw // whatever its size: every try is a few seconds of frames that come unevenly.) - const bool own_fits = pace != 2 || own_time == 0.0 || own_time * (1.0 + Over) <= refresh || - now - last_pace_regret > Forget; + const bool own_fits = pace != 2 || own_time == 0.0 || + own_time * (1.0 + Over) <= refresh || now - last_pace_regret > Forget; if (paced && pace > fastest && now > faster_allowed && own_fits) { const s32 smallest = sized ? smallest_at(pace - 1) : level; if (cost_at(smallest) * (1.0 + PaceMargin) <= (pace - 1) * refresh) { @@ -527,12 +540,13 @@ private: /// it has not got that far. Holds it to `wanted` on the way unless that is 0. s32 TendResolution(VAddr base, const Build& build, s32 wanted) { const u64 control = Read(base + build.resolution_pointer); - if (!Accessible(control, ResolutionLowest + sizeof(s32), true)) { + if (control == 0) { return -1; } const s32 level = Read(control + ResolutionLevel); const s32 base_level = Read(control + ResolutionBase); if (level < 0 || level > LastHeadsetLevel || base_level < 0 || base_level > LastHeadsetLevel) { + // Not what is known of it: looked at, never written to. return -1; } if (wanted != 0 && level >= FirstHeadsetLevel) { @@ -665,7 +679,8 @@ void OnFrameSubmitted() { "left to itself); it draws the scene at {} an eye and is given {} refreshes a " "frame ({:.1f} ms), the GPU busy {:.0f}% of the time", report_real / report_frames * 1e3, step * 1e3, 100.0 * stepped / report_real, - report_real, 100.0 * Nominal * report_frames / report_real, SizeName(resolution), + report_real, 100.0 * Nominal * report_frames / report_real, + SizeName(resolution), summary.pace, summary.slot * 1e3, summary.load * 100.0); report_time = now; report_real = 0.0; @@ -693,37 +708,45 @@ void Prepare() { } void OnGameLoaded(VAddr base, u64 size) { - known_build.store(nullptr, std::memory_order_release); - const auto image = std::span{reinterpret_cast(base), static_cast(size)}; - const Build* const build = RecognizeBuild(base, size); - if (build == nullptr) { - if (Common::ElfInfo::Instance().GameSerial() == "CUSA12392") { - LOG_WARNING(Core, "Unrecognized or modified CUSA12392 layout: title resolution and " - "time-step patches disabled (no game memory changed)"); - } + if (Common::ElfInfo::Instance().GameSerial() != "CUSA12392") { return; } + // Asked here and nowhere else: this is the one moment at which the image is as its build + // has it. Its sizes may be other ones from here on, and its clock is once it runs. + const std::span image{reinterpret_cast(base), static_cast(size)}; + const Build* const build = Builds::Recognise(image); + if (build == nullptr) { + LOG_WARNING(Core, + "This build of CUSA12392 is none of those known from inside (its versions " + "1.00 and 1.04 are): it is left to itself. It then counts time in frames, " + "which is slow motion wherever a frame takes longer than a sixtieth of a " + "second, and draws at the console's sizes"); + return; + } + LOG_INFO(Core, "CUSA12392 in a build known from inside: {}", build->name); + const Larger& larger = GetLarger(); if (larger.factor != 1.0) { + // Every place is checked for what the console's build has there before anything is + // written: a title that turns out to be other than thought is left as it is. const auto changes = Builds::SizeChanges(*build, larger); - if (const auto* unexpected = Builds::Apply(image, changes); unexpected != nullptr) { - LOG_WARNING(Core, "Title patch rejected at {:#x}: no title patches enabled", - unexpected->at); - return; + if (const Builds::Change* unexpected = Builds::Apply(image, changes); + unexpected != nullptr) { + LOG_WARNING(Core, + "The title does not have {:#x} at {:#x} as expected: it draws at the " + "console's sizes", + unexpected->was, unexpected->at); + } else { + LOG_INFO(Core, + "The title draws at up to {} an eye instead of 1440x1536 ({:.2f} times as " + "wide), the smallest {}; its render targets have {} MB, its graphics memory " + "{} MB", + SizeName(LastHeadsetLevel), larger.factor, SizeName(FirstHeadsetLevel), + larger.target_pool >> 20, larger.graphics_heap >> 20); } } known_base = base; known_build.store(build, std::memory_order_release); - LOG_INFO(Core, "Verified title profile {}: resolution and time-step support enabled", - build->name); - if (larger.factor == 1.0) { - return; - } - LOG_INFO(Core, - "The title draws at up to {} an eye instead of 1440x1536 ({:.2f} times as wide), " - "the smallest {}; its render targets have {} MB, its graphics memory {} MB", - SizeName(LastHeadsetLevel), larger.factor, SizeName(FirstHeadsetLevel), - larger.target_pool >> 20, larger.graphics_heap >> 20); } void NoteView(const Vr::Vec3& tracker_head) { @@ -744,13 +767,12 @@ void NoteView(const Vr::Vec3& tracker_head) { return; } const u64 manager = Read(base + build->manager_pointer); - if (!Accessible(manager, ManagerHeadState + sizeof(u64))) { + if (manager == 0) { return; } const auto origin = Read(manager + ManagerOrigin); const u64 state = Read(manager + ManagerHeadState); - if (!Accessible(state, StatePositionValid + sizeof(u8)) || - Read(state + StatePositionValid) == 0) { + if (state == 0 || Read(state + StatePositionValid) == 0) { return; } const auto head = Read(state + StatePosition); diff --git a/shadps4-arm64-main/src/core/known_title.h b/shadps4-arm64-main/src/core/known_title.h index 17c9070..e4a248f 100644 --- a/shadps4-arm64-main/src/core/known_title.h +++ b/shadps4-arm64-main/src/core/known_title.h @@ -9,6 +9,10 @@ namespace Core::Vr { struct Vec3; } +/// What is known about the inner workings of particular titles, and what the emulator does with +/// it. Everything here applies to exact builds of one title, told apart by what their images +/// hold when loaded (known_title_builds.h), and does nothing for any other title or build. +/// /// Astro Bot Rescue Mission (CUSA12392): /// - It advances its world by one sixtieth of a second for every frame it draws, however long /// the frame took: where frames take longer than that, on anything slower than the console diff --git a/shadps4-arm64-main/src/core/known_title_builds.h b/shadps4-arm64-main/src/core/known_title_builds.h index 6ad4a4d..1cf9680 100644 --- a/shadps4-arm64-main/src/core/known_title_builds.h +++ b/shadps4-arm64-main/src/core/known_title_builds.h @@ -71,18 +71,6 @@ struct Build { u64 graphics_heap; // u64 }; -struct CodeCheck { - u64 at; - std::array bytes; - u32 size; -}; - -inline constexpr std::array AlternateCode{{ - {0x40d7, {0x48, 0x8d, 0x1d, 0x82, 0xbd, 0xec, 0x02}, 7}, - {0xcc613b, {0x4c, 0x8d, 0x3d, 0xa6, 0xc9, 0x20, 0x02}, 7}, - {0xecc560, {0x48, 0x8d, 0x05, 0x61, 0x43, 0x85, 0x00, 0x48, 0x8b, 0x00, 0xc3}, 11}, -}}; - inline constexpr std::array Known{{ { .name = "1.00, as on the disc", @@ -213,20 +201,11 @@ inline bool Is(const Build& build, std::span image) { return false; } for (u32 level = 0; level < ConsoleSizes.size(); ++level) { - const u64 pixels = u64{ConsoleSizes[level][0]} * ConsoleSizes[level][1]; if (!holds(build.size_widths + 4 * level, &ConsoleSizes[level][0], sizeof(u32)) || - !holds(build.size_heights + 4 * level, &ConsoleSizes[level][1], sizeof(u32)) || - !holds(build.size_pixels + 32 * level, &pixels, sizeof(pixels))) { + !holds(build.size_heights + 4 * level, &ConsoleSizes[level][1], sizeof(u32))) { return false; } } - if (build.set_recentre == Known[1].set_recentre) { - for (const auto& check : AlternateCode) { - if (!holds(check.at, check.bytes.data(), check.size)) { - return false; - } - } - } // Every place a larger picture is written to, as the console has it. return FirstUnexpected(image, SizeChanges(build, Sizes{})) == nullptr; } diff --git a/shadps4-arm64-main/src/core/known_title_memory.h b/shadps4-arm64-main/src/core/known_title_memory.h deleted file mode 100644 index 8629082..0000000 --- a/shadps4-arm64-main/src/core/known_title_memory.h +++ /dev/null @@ -1,68 +0,0 @@ -#pragma once - -#include -#include - -#include "common/elf_info.h" -#include "core/known_title_builds.h" -#include "core/memory.h" - -namespace Core::KnownTitle { - -inline bool Accessible(VAddr address, u64 bytes, bool write = false) { - if (address == 0) { - return false; - } - void* end{}; - u32 protection{}; - if (Memory::Instance()->QueryProtection(address, nullptr, &end, &protection) != 0) { - return false; - } - const auto limit = reinterpret_cast(end); - const u32 required = write ? 3 : 1; - return limit >= address && bytes <= limit - address && (protection & required) == required; -} - -inline const Builds::Build* RecognizeBuild(VAddr base, u64 size) { - if (Common::ElfInfo::Instance().GameSerial() != "CUSA12392" || base == 0) { - return nullptr; - } - const auto image = std::span{reinterpret_cast(base), static_cast(size)}; - const auto mapped = [&](u64 at, u64 bytes) { - return at <= image.size() && bytes <= image.size() - at && at <= UINT64_MAX - base && - Accessible(base + at, bytes); - }; - const Builds::Build* selected = nullptr; - for (const auto& build : Builds::Known) { - if (!mapped(build.set_recentre, Builds::SetRecentreCode.size()) || - !mapped(build.manager_pointer, sizeof(u64)) || - !mapped(build.resolution_pointer, sizeof(u64)) || - !mapped(build.frame_rate, sizeof(double)) || - !mapped(build.frame_seconds, sizeof(float)) || - !mapped(build.frame_microseconds, sizeof(u64)) || - !mapped(build.size_widths, sizeof(u32) * Builds::ConsoleSizes.size()) || - !mapped(build.size_heights, sizeof(u32) * Builds::ConsoleSizes.size()) || - !mapped(build.size_pixels, 32 * 6 + sizeof(u64))) { - continue; - } - const auto changes = Builds::SizeChanges(build, Builds::Sizes{}); - if (!std::ranges::all_of( - changes, [&](const auto& change) { return mapped(change.at, change.bytes); })) { - continue; - } - if (&build == &Builds::Known[1] && - !std::ranges::all_of(Builds::AlternateCode, - [&](const auto& check) { return mapped(check.at, check.size); })) { - continue; - } - if (Builds::Is(build, image)) { - if (selected != nullptr) { - return nullptr; - } - selected = &build; - } - } - return selected; -} - -} diff --git a/shadps4-arm64-main/src/core/libraries/vr_tracker/calibration_state.h b/shadps4-arm64-main/src/core/libraries/vr_tracker/calibration_state.h deleted file mode 100644 index 117569e..0000000 --- a/shadps4-arm64-main/src/core/libraries/vr_tracker/calibration_state.h +++ /dev/null @@ -1,23 +0,0 @@ -// SPDX-FileCopyrightText: Copyright 2026 shadPS4 Emulator Project -// SPDX-License-Identifier: GPL-2.0-or-later - -#pragma once - -#include "common/types.h" - -namespace Libraries::VrTracker { - -struct CalibrationState { - u64 sequence; - bool active; - bool reported; -}; - -inline CalibrationState AdvanceCalibration(const CalibrationState& current, u64 sequence, - bool tracked) { - return {current.sequence, - current.active && (!current.reported || !tracked || sequence <= current.sequence), - true}; -} - -} // namespace Libraries::VrTracker diff --git a/shadps4-arm64-main/src/core/libraries/vr_tracker/vr_tracker.cpp b/shadps4-arm64-main/src/core/libraries/vr_tracker/vr_tracker.cpp index ad8d9ab..0e772a2 100644 --- a/shadps4-arm64-main/src/core/libraries/vr_tracker/vr_tracker.cpp +++ b/shadps4-arm64-main/src/core/libraries/vr_tracker/vr_tracker.cpp @@ -2,19 +2,18 @@ // SPDX-License-Identifier: GPL-2.0-or-later #include +#include #include -#include #include #include #include #include "common/logging/log.h" -#include "core/known_title.h" #include "core/libraries/error_codes.h" #include "core/libraries/kernel/time.h" #include "core/libraries/libs.h" -#include "core/libraries/vr_tracker/calibration_state.h" #include "core/libraries/vr_tracker/vr_tracker.h" +#include "core/known_title.h" #include "core/libraries/vr_tracker/vr_tracker_error.h" #include "core/memory.h" #include "core/vr/vr_runtime.h" @@ -45,32 +44,6 @@ static std::mutex g_pads_mutex; static s32 g_move_handle = -1; static s32 g_gun_handle = -1; static s32 g_hmd_handle = -1; -static std::map g_calibrations; -static std::mutex g_calibrations_mutex; - -static void BeginCalibration(s32 handle, u64 sequence) { - std::scoped_lock lock{g_calibrations_mutex}; - g_calibrations.insert_or_assign(handle, CalibrationState{sequence, true, false}); - LOG_INFO(Lib_VrTracker, "Calibration requested for handle {:#x}, pose sequence {}", handle, - sequence); -} - -static bool IsCalibrating(s32 handle, const Core::Vr::DeviceState& state) { - std::scoped_lock lock{g_calibrations_mutex}; - const auto found = g_calibrations.find(handle); - if (found == g_calibrations.end()) { - return false; - } - const auto next = AdvanceCalibration(found->second, state.sequence, state.tracked); - if (next.active) { - found->second = next; - return true; - } - LOG_INFO(Lib_VrTracker, "Calibration finished for handle {:#x} with tracked pose sequence {}", - handle, state.sequence); - g_calibrations.erase(found); - return false; -} /// The registration of a DualShock 4 handle, if it is one. static std::optional FindPad(s32 handle) { @@ -93,6 +66,40 @@ static bool HeadsetConnected() { return Core::Vr::Runtime::Instance().IsHeadsetConnected(); } +// Asked to find a kind of device anew (sceVrTrackerRecalibrate), a console's tracker says for +// a moment that it is calibrating it, and then that it tracks it again. Titles wait for exactly +// that: ASTRO BOT Rescue Mission from its version 1.01 on asks for its controller to be found +// anew on the screen that has the player sit inside a silhouette, and stays there until it has +// seen the controller's status go from calibrating to anything else. +struct Recalibration { + /// Process time, in microseconds, up to which the devices count as being calibrated. + u64 until{}; + /// Whether a result has said so yet: the title must get to see it, however late it asks. + bool reported{true}; +}; +static constexpr u64 RecalibrationTime = 200'000; +static std::array g_recalibrations; +static std::mutex g_recalibrations_mutex; + +static void BeginRecalibration(OrbisVrTrackerDeviceType device_type) { + std::scoped_lock lock{g_recalibrations_mutex}; + g_recalibrations[device_type] = { + .until = Libraries::Kernel::sceKernelGetProcessTime() + RecalibrationTime, + .reported = false, + }; +} + +/// Whether a result for a device of this kind is to say that it is being calibrated. +static bool IsRecalibrating(OrbisVrTrackerDeviceType device_type, u64 now) { + std::scoped_lock lock{g_recalibrations_mutex}; + Recalibration& recalibration = g_recalibrations[device_type]; + if (recalibration.reported && now >= recalibration.until) { + return false; + } + recalibration.reported = true; + return true; +} + static void WritePose(OrbisVrTrackerPoseData& out, const Core::Vr::Pose& pose) { out.position_x = pose.position.x; out.position_y = pose.position.y; @@ -365,13 +372,12 @@ s32 PS4_SYSV_ABI sceVrTrackerGetResult(const OrbisVrTrackerGetResultParam* param static std::atomic result_calls{}; if (const u32 call = result_calls.fetch_add(1); call < 4 || call % 1200 == 0) { - LOG_DEBUG( - Lib_VrTracker, - "call {}: handle = {:#x}, result_type = {}, prediction_time = {}, orientation_type " - "= {}, usage_type = {}, position = ({:.3f}, {:.3f}, {:.3f})", - call, handle, static_cast(param->result_type), param->prediction_time, - static_cast(param->orientation_type), static_cast(param->usage_type), - state.pose.position.x, state.pose.position.y, state.pose.position.z); + LOG_DEBUG(Lib_VrTracker, + "call {}: handle = {:#x}, result_type = {}, prediction_time = {}, orientation_type " + "= {}, usage_type = {}, position = ({:.3f}, {:.3f}, {:.3f})", + call, handle, static_cast(param->result_type), param->prediction_time, + static_cast(param->orientation_type), static_cast(param->usage_type), + state.pose.position.x, state.pose.position.y, state.pose.position.z); } // The host already predicts poses for the moment its display lights up, which replaces the @@ -384,11 +390,14 @@ s32 PS4_SYSV_ABI sceVrTrackerGetResult(const OrbisVrTrackerGetResultParam* param OrbisVrTrackerRecalibrateNecessityType::ORBIS_VR_TRACKER_RECALIBRATE_NECESSITY_NOTHING; result->playarea_brightness_risk = OrbisVrTrackerPlayareaBrightnessRiskType::ORBIS_VR_TRACKER_PLAYAREA_BRIGHTNESS_RISK_LOW; - result->led_color = - is_pad ? pad_registration->color : OrbisVrTrackerLedColor::ORBIS_VR_TRACKER_LED_COLOR_BLUE; - result->status = IsCalibrating(handle, state) - ? OrbisVrTrackerStatus::ORBIS_VR_TRACKER_STATUS_CALIBRATING - : OrbisVrTrackerStatus::ORBIS_VR_TRACKER_STATUS_TRACKING; + result->led_color = is_pad ? pad_registration->color + : OrbisVrTrackerLedColor::ORBIS_VR_TRACKER_LED_COLOR_BLUE; + result->status = + IsRecalibrating(is_hmd ? OrbisVrTrackerDeviceType::ORBIS_VR_TRACKER_DEVICE_HMD + : OrbisVrTrackerDeviceType::ORBIS_VR_TRACKER_DEVICE_DUALSHOCK4, + now) + ? OrbisVrTrackerStatus::ORBIS_VR_TRACKER_STATUS_CALIBRATING + : OrbisVrTrackerStatus::ORBIS_VR_TRACKER_STATUS_TRACKING; result->position_quality = OrbisVrTrackerQuality::ORBIS_VR_TRACKER_QUALITY_FULL; result->orientation_quality = OrbisVrTrackerQuality::ORBIS_VR_TRACKER_QUALITY_FULL; result->velocity_x = state.linear_velocity.x; @@ -494,11 +503,7 @@ s32 PS4_SYSV_ABI sceVrTrackerRecalibrate(const OrbisVrTrackerRecalibrateParam* p return ORBIS_VR_TRACKER_ERROR_NOT_INIT; } if (param == nullptr || param->size != sizeof(OrbisVrTrackerRecalibrateParam) || - param->device_type > OrbisVrTrackerDeviceType::ORBIS_VR_TRACKER_DEVICE_GUN || - (param->calibration_type != - OrbisVrTrackerCalibrationType::ORBIS_VR_TRACKER_CALIBRATION_POSITION && - param->calibration_type != - OrbisVrTrackerCalibrationType::ORBIS_VR_TRACKER_CALIBRATION_ALL)) { + param->device_type > OrbisVrTrackerDeviceType::ORBIS_VR_TRACKER_DEVICE_GUN) { return ORBIS_VR_TRACKER_ERROR_ARGUMENT_INVALID; } @@ -509,7 +514,6 @@ s32 PS4_SYSV_ABI sceVrTrackerRecalibrate(const OrbisVrTrackerRecalibrateParam* p if (!HeadsetConnected() || g_hmd_handle == -1) { return ORBIS_VR_TRACKER_ERROR_DEVICE_NOT_REGISTERED; } - BeginCalibration(g_hmd_handle, Core::Vr::Runtime::Instance().GetHead().sequence); break; } case OrbisVrTrackerDeviceType::ORBIS_VR_TRACKER_DEVICE_DUALSHOCK4: { @@ -517,10 +521,6 @@ s32 PS4_SYSV_ABI sceVrTrackerRecalibrate(const OrbisVrTrackerRecalibrateParam* p if (g_pads.empty()) { return ORBIS_VR_TRACKER_ERROR_DEVICE_NOT_REGISTERED; } - const u64 sequence = Core::Vr::Runtime::Instance().GetPad().sequence; - for (const auto& pad : g_pads) { - BeginCalibration(pad.handle, sequence); - } break; } case OrbisVrTrackerDeviceType::ORBIS_VR_TRACKER_DEVICE_MOVE: { @@ -541,6 +541,11 @@ s32 PS4_SYSV_ABI sceVrTrackerRecalibrate(const OrbisVrTrackerRecalibrateParam* p } } + // The host tracks the devices and has nothing to find anew; what the title gets is the + // status a console's tracker shows while it does. + LOG_INFO(Lib_VrTracker, "called, device_type = {}, calibration_type = {}", + static_cast(device_type), static_cast(param->calibration_type)); + BeginRecalibration(device_type); return ORBIS_OK; } @@ -698,10 +703,6 @@ s32 PS4_SYSV_ABI sceVrTrackerUnregisterDevice(const s32 handle) { return ORBIS_VR_TRACKER_ERROR_ARGUMENT_INVALID; } - { - std::scoped_lock lock{g_calibrations_mutex}; - g_calibrations.erase(handle); - } return ORBIS_OK; } @@ -711,10 +712,6 @@ s32 PS4_SYSV_ABI sceVrTrackerTerm() { return ORBIS_VR_TRACKER_ERROR_NOT_INIT; } g_library_initialized = false; - { - std::scoped_lock lock{g_calibrations_mutex}; - g_calibrations.clear(); - } return ORBIS_OK; } @@ -785,4 +782,4 @@ void RegisterLib(Core::Loader::SymbolsResolver* sym) { sceVrTrackerStopLiveCapture); }; -} // namespace Libraries::VrTracker +} // namespace Libraries::VrTracker \ No newline at end of file diff --git a/tools/tests/known_title_builds_test.cpp b/tools/tests/known_title_builds_test.cpp index 886dbfc..ede3d90 100644 --- a/tools/tests/known_title_builds_test.cpp +++ b/tools/tests/known_title_builds_test.cpp @@ -34,19 +34,12 @@ static void Put(std::vector& image, u64 at, T value) { static std::vector MadeUp(const Build& build) { std::vector image(0x3200000); std::memcpy(image.data() + build.set_recentre, SetRecentreCode.data(), SetRecentreCode.size()); - if (build.set_recentre == Known[1].set_recentre) { - for (const auto& check : AlternateCode) { - std::memcpy(image.data() + check.at, check.bytes.data(), check.size); - } - } Put(image, build.frame_rate, ConsoleFrameRate); Put(image, build.frame_seconds, ConsoleFrameSeconds); Put(image, build.frame_microseconds, ConsoleFrameMicroseconds); for (u32 level = 0; level < ConsoleSizes.size(); ++level) { Put(image, build.size_widths + 4 * level, ConsoleSizes[level][0]); Put(image, build.size_heights + 4 * level, ConsoleSizes[level][1]); - Put(image, build.size_pixels + 32 * level, - u64{ConsoleSizes[level][0]} * ConsoleSizes[level][1]); } for (const Change& change : SizeChanges(build, Sizes{})) { std::memcpy(image.data() + change.at, &change.was, change.bytes); @@ -119,12 +112,7 @@ int main(int argc, char** argv) { // One byte off anywhere the emulator looks, and it is no build that is known. std::vector places{build.set_recentre + 3, build.frame_rate, build.frame_seconds, build.frame_microseconds, build.size_widths + 4 * 6, - build.size_heights + 4 * 2, build.size_pixels}; - if (build.set_recentre == Known[1].set_recentre) { - for (const auto& check : AlternateCode) { - places.push_back(check.at); - } - } + build.size_heights + 4 * 2}; for (const Change& change : SizeChanges(build, Sizes{})) { places.push_back(change.at); } @@ -167,8 +155,6 @@ int main(int argc, char** argv) { // A place beyond the image. const std::vector beyond{{image.size() - 2, 0, 1, 4}}; Check(Apply(image, beyond) == &beyond[0], name + ": nothing written beyond the image"); - const std::vector overflow{{UINT64_MAX, 0, 1, 8}}; - Check(Apply(image, overflow) == &overflow[0], name + ": overflowing destination refused"); } // An image that would be both builds at once is neither. diff --git a/tools/tests/launcher-test.ps1 b/tools/tests/launcher-test.ps1 index f0fb2e1..6030079 100644 --- a/tools/tests/launcher-test.ps1 +++ b/tools/tests/launcher-test.ps1 @@ -3,23 +3,18 @@ # powershell -ExecutionPolicy Bypass -File tools/tests/launcher-test.ps1 # With the game in games/CUSA12392 its param.sfo is used for the layouts; without it, the # checks that need one are left out. -param([string]$GameSfo = "", [string]$PkgTool = "", [string]$BasePackage = "", [string]$UpdatePackage = "") - $top = Split-Path -Parent (Split-Path -Parent $PSScriptRoot) $launcher = Join-Path $top "pc-vr\launch.ps1" Add-Type -AssemblyName System.Windows.Forms -$parseErrors = $null -$ast = [System.Management.Automation.Language.Parser]::ParseFile($launcher, [ref]$null, [ref]$parseErrors) -if ($parseErrors.Count -gt 0) { throw ($parseErrors | Out-String) } +$ast = [System.Management.Automation.Language.Parser]::ParseFile($launcher, [ref]$null, [ref]$null) foreach ($function in $ast.FindAll({ $args[0] -is [System.Management.Automation.Language.FunctionDefinitionAst] }, $false)) { . ([scriptblock]::Create($function.Extent.Text)) } $unpackFolder = ".unpacking" $madeFor = "CUSA12392" -$realSfo = if ($GameSfo) { $GameSfo } else { Join-Path $top "games\CUSA12392\sce_sys\param.sfo" } +$realSfo = Join-Path $top "games\CUSA12392\sce_sys\param.sfo" $haveSfo = [System.IO.File]::Exists($realSfo) -$base = [System.IO.Path]::GetFullPath((Join-Path $top "build\launcher-test")) -if (-not $base.StartsWith($top + "\build\", [System.StringComparison]::OrdinalIgnoreCase)) { throw "Unsafe test folder" } +$base = Join-Path $top "build\launcher-test" if ([System.IO.Directory]::Exists($base)) { [System.IO.Directory]::Delete($base, $true) } $failed = 0 function Check([string]$what, $got, $want) { @@ -33,14 +28,14 @@ function Make-Game([string]$folder, [bool]$withSfo) { [System.IO.File]::Copy($realSfo, (Join-Path $folder "sce_sys\param.sfo")) } } +# (`$flags`: what a package's header says it is, as its four bytes at 0x78: a game's own has +# 0x0A000000 there, an update of it 0x62300000.) function Make-Package([string]$path, [string]$contentId, [int]$size, [uint32]$flags = 0x0A000000) { [void][System.IO.Directory]::CreateDirectory([System.IO.Path]::GetDirectoryName($path)) $bytes = New-Object byte[] $size $bytes[0] = 0x7F; $bytes[1] = 0x43; $bytes[2] = 0x4E; $bytes[3] = 0x54 [System.Text.Encoding]::ASCII.GetBytes($contentId).CopyTo($bytes, 0x40) - $flagBytes = [System.BitConverter]::GetBytes($flags) - [Array]::Reverse($flagBytes) - $flagBytes.CopyTo($bytes, 0x78) + $bytes[0x78] = [byte](($flags -shr 24) -band 0xFF); $bytes[0x79] = [byte](($flags -shr 16) -band 0xFF) [System.IO.File]::WriteAllBytes($path, $bytes) } @@ -94,8 +89,10 @@ $g = "$base\e2\games" Make-Package "$g\game.pkg" "EP9000-CUSA12392_00-PLATFORMERVR00EU" 2048 Make-Package "$g\A-Update-v1.04.pkg" "EP9000-CUSA12392_00-PLATFORMERVR00EU" 8192 0x62300000 Make-Package "$g\first-patch.pkg" "EP9000-CUSA12392_00-PLATFORMERVR00EU" 4096 0x00100000 +Make-Package "$g\patchgo.pkg" "EP9000-CUSA12392_00-PLATFORMERVR00EU" 16384 0x00200000 Check "e2 an update says so" (Test-UpdatePackage "$g\A-Update-v1.04.pkg") $true Check "e2 a first patch says so" (Test-UpdatePackage "$g\first-patch.pkg") $true +Check "e2 a PATCHGO package is an update" (Test-UpdatePackage "$g\patchgo.pkg") $true Check "e2 the game's package before a larger update" (Find-Package $g).FullName "$g\game.pkg" # e3: nothing but updates: the largest of them, which is then turned down for what it is $g = "$base\e3\games" @@ -115,58 +112,6 @@ Check "g a folder" (Use-Path "$base\a") "$g\CUSA12392\eboot.bin" Check "g an eboot.bin" (Use-Path "$g\CUSA12392\eboot.bin") "$g\CUSA12392\eboot.bin" Check "g nothing there" (Use-Path "$base\a\nothing.bin") "" -$g = "$base\h\games" -foreach ($suffix in @("-UPDATE", "-patch", "-mods")) { Make-Game "$g\CUSA12392$suffix" $true } -Check "h overlays alone are not a base game" (Find-Game $g) "" -Make-Game "$g\CUSA12392" $true -Check "h selects the base beside its overlays" (Find-Game $g) "$g\CUSA12392\eboot.bin" - -if (-not $PkgTool) { $PkgTool = Join-Path $top "tools\pkgtool\PkgTool.exe" } -if ([System.IO.File]::Exists($PkgTool)) { - $full = "$base\full-1.04.pkg" - Make-Package $full "EP9000-CUSA12392_00-PLATFORMERVR00EU" 8192 - Check "i a full package needs no version choice" (Test-UpdateHeader (Read-PackageHeader $full $PkgTool)) $false - foreach ($flags in @(0x00100000, 0x00200000, 0x40000000, 0x41000000, 0x60000000)) { - $patch = "$base\patch.pkg" - Make-Package $patch "EP9000-CUSA12392_00-PLATFORMERVR00EU" 8192 $flags - Check "i patch header flags $flags" (Test-UpdateHeader (Read-PackageHeader $patch $PkgTool)) $true - Check "i patch discovery flags $flags" (Test-UpdatePackage $patch) $true - } - if ($BasePackage) { Check "i actual base package" (Test-UpdateHeader (Read-PackageHeader $BasePackage $PkgTool)) $false } - if ($UpdatePackage) { Check "i actual update package" (Test-UpdateHeader (Read-PackageHeader $UpdatePackage $PkgTool)) $true } - - function Show-Box([string]$text) { $script:boxText = $text; return "No" } - $here = Split-Path -Parent (Split-Path -Parent $PkgTool) - $root = $base - $gamesFolder = "$base\h\games" - $longestInside = 126 - Check "i update rejected before unpacking" (Expand-Package ([System.IO.FileInfo]$patch)) "" - Check "i update-only explanation" $boxText.StartsWith("This package is an update") $true - Check "i base executable remains untouched" ([System.IO.File]::ReadAllText("$gamesFolder\CUSA12392\eboot.bin")) "x" - Check "i no staging folder created" ([System.IO.Directory]::Exists("$gamesFolder\CUSA12392\.unpacking")) $false - Check "i full-package cancellation" (Expand-Package ([System.IO.FileInfo]$full)) "" - Check "i full package reaches unpack confirmation" $boxText.Contains("Unpack it now?") $true - Check "i cancellation creates no staging folder" ([System.IO.Directory]::Exists("$gamesFolder\CUSA12392\.unpacking")) $false - $truncated = "$base\truncated.pkg" - Make-Package $truncated "EP9000-CUSA12392_00-PLATFORMERVR00EU" 128 - Check "i malformed header rejected" (Expand-Package ([System.IO.FileInfo]$truncated)) "" - Check "i malformed-header explanation" $boxText.StartsWith("The package header could not be read") $true -} else { "SKIP package-header checks: supply -PkgTool" } - -function Say([string]$text, [string]$color = "Gray") { $script:messages += "$color|$text" } -$messages = @() -$log = "$base\profile-log.txt" -$position = 0 -$shown = @{} -[System.IO.File]::WriteAllLines($log, @( - "[Core] (Loader) known_title.cpp:785 OnGameLoaded: Verified title profile 1.04, the last update: resolution and time-step support enabled", - "[Core] (Loader) known_title.cpp:766 OnGameLoaded: Unrecognized or modified CUSA12392 layout: title resolution and time-step patches disabled (no game memory changed)", - "[Core] (Loader) known_title.cpp:778 OnGameLoaded: Title patch rejected at 0x123: no title patches enabled" -)) -Show-Log -Check "j prints verified executable profile" ($messages[0] -like "Gray|*Verified title profile 1.04, the last update:*") $true -Check "j prints unknown-layout warning" ($messages[1] -like "Yellow|*Unrecognized or modified CUSA12392 layout:*") $true -Check "j prints patch rejection" ($messages[2] -like "Yellow|*Title patch rejected at 0x123:*") $true $steam = (Get-VrInstructions 'F:\steam\steamapps\common\SteamVR\steamxr_win64.json') -join "`n" Check "SteamVR instructions name the Index" ($steam -match 'Index') $true Check "SteamVR instructions require no Virtual Desktop" ($steam -match 'Virtual Desktop is not needed') $true diff --git a/tools/tests/vr_tracker_calibration_test.cpp b/tools/tests/vr_tracker_calibration_test.cpp deleted file mode 100644 index 5bce47b..0000000 --- a/tools/tests/vr_tracker_calibration_test.cpp +++ /dev/null @@ -1,34 +0,0 @@ -#include -#include - -#include "core/libraries/vr_tracker/calibration_state.h" - -using namespace Libraries::VrTracker; - -void Check(bool condition, const char *what) { - if (!condition) { - std::cerr << "FAIL: " << what << '\n'; - std::exit(1); - } -} - -int main() { - const CalibrationState requested{42, true, false}; - const auto first = AdvanceCalibration(requested, 43, true); - Check(first.active && first.reported, - "pending is reported even if a new pose already arrived"); - const auto stale = AdvanceCalibration(first, 42, true); - Check(stale.active, "the pre-request sample cannot complete calibration"); - Check(AdvanceCalibration(first, 41, true).active, - "an older sample cannot complete calibration"); - const auto lost = AdvanceCalibration(first, 44, false); - Check(lost.active, "untracked poses cannot complete calibration"); - const auto complete = AdvanceCalibration(lost, 45, true); - Check(!complete.active, "a new tracked pose completes calibration"); - Check(!AdvanceCalibration(complete, 45, true).active, - "completed calibration stays completed"); - const CalibrationState again{45, true, false}; - Check(AdvanceCalibration(again, 46, true).active, - "a repeated request reports pending again"); - std::cout << "PASS: calibration lifecycle, stale samples and tracking loss\n"; -}