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.
This commit is contained in:
ODevStudio committed 2026-10-06 11:01:35 +02:00
1 parent 664710034a
commit d59cd0d370
12 files changed
+190 -480

No files matched your search

+46 -118
View File
@@ -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 <key>:<ms> ...` | 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:<dx>,<dy>` turns the simulated head, `Click:<x>,<y>` clicks |
| `tools/pc-rate-compare.sh <name> "<NAME=value ...>"` | 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 <path>` for package-header tests; `-BasePackage <path>` and `-UpdatePackage <path>` 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.
+1 -2
View File
@@ -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
+11 -36
View File
@@ -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\] <Warning> \([^)]*\) \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\] <Info> \([^)]*\) \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\] <Warning> \([^)]*\) \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 '<Critical>.*?: (.*)$') {
+61 -39
View File
@@ -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<const Build*> 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<s32>(1, static_cast<s32>(std::ceil(1.0 / (refresh * fps_cap) - 0.01)));
fastest_now = std::max<s32>(
1, static_cast<s32>(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<s32>(SlowestWanted / refresh));
const s32 slowest = std::max(std::max(2, fastest), static_cast<s32>(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<u64>(base + build.resolution_pointer);
if (!Accessible(control, ResolutionLowest + sizeof(s32), true)) {
if (control == 0) {
return -1;
}
const s32 level = Read<s32>(control + ResolutionLevel);
const s32 base_level = Read<s32>(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<u8*>(base), static_cast<size_t>(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<u8> image{reinterpret_cast<u8*>(base), static_cast<size_t>(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<u64>(base + build->manager_pointer);
if (!Accessible(manager, ManagerHeadState + sizeof(u64))) {
if (manager == 0) {
return;
}
const auto origin = Read<Vr::Vec3>(manager + ManagerOrigin);
const u64 state = Read<u64>(manager + ManagerHeadState);
if (!Accessible(state, StatePositionValid + sizeof(u8)) ||
Read<u8>(state + StatePositionValid) == 0) {
if (state == 0 || Read<u8>(state + StatePositionValid) == 0) {
return;
}
const auto head = Read<Vr::Vec3>(state + StatePosition);
@@ -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
@@ -71,18 +71,6 @@ struct Build {
u64 graphics_heap; // u64
};
struct CodeCheck {
u64 at;
std::array<u8, 11> bytes;
u32 size;
};
inline constexpr std::array<CodeCheck, 3> 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<Build, 2> Known{{
{
.name = "1.00, as on the disc",
@@ -213,20 +201,11 @@ inline bool Is(const Build& build, std::span<const u8> 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;
}
@@ -1,68 +0,0 @@
#pragma once
#include <algorithm>
#include <span>
#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<VAddr>(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<const u8*>(base), static_cast<size_t>(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;
}
}
@@ -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
@@ -2,19 +2,18 @@
// SPDX-License-Identifier: GPL-2.0-or-later
#include <algorithm>
#include <array>
#include <atomic>
#include <map>
#include <mutex>
#include <optional>
#include <vector>
#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<s32, CalibrationState> 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<PadRegistration> 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<Recalibration, 4> 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<u32> 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<u32>(param->result_type), param->prediction_time,
static_cast<u32>(param->orientation_type), static_cast<u32>(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<u32>(param->result_type), param->prediction_time,
static_cast<u32>(param->orientation_type), static_cast<u32>(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<u32>(device_type), static_cast<u32>(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
+1 -15
View File
@@ -34,19 +34,12 @@ static void Put(std::vector<u8>& image, u64 at, T value) {
static std::vector<u8> MadeUp(const Build& build) {
std::vector<u8> 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<u64> 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<Change> beyond{{image.size() - 2, 0, 1, 4}};
Check(Apply(image, beyond) == &beyond[0], name + ": nothing written beyond the image");
const std::vector<Change> 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.
+8 -63
View File
@@ -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] <Info> (Loader) known_title.cpp:785 OnGameLoaded: Verified title profile 1.04, the last update: resolution and time-step support enabled",
"[Core] <Warning> (Loader) known_title.cpp:766 OnGameLoaded: Unrecognized or modified CUSA12392 layout: title resolution and time-step patches disabled (no game memory changed)",
"[Core] <Warning> (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
@@ -1,34 +0,0 @@
#include <cstdlib>
#include <iostream>
#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";
}