mirror of
https://github.com/mitch030504/AstroQuest-Frame.git
synced 2026-10-17 16:01:58 +02:00
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:
1 parent
664710034a
commit
d59cd0d370
12 files changed
+190
-480
No files matched your search
+46
-118
@@ -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.
|
||||
@@ -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
@@ -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>.*?: (.*)$') {
|
||||
|
||||
@@ -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
|
||||
@@ -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.
|
||||
|
||||
@@ -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";
|
||||
}
|
||||
Reference in new issue
Block a user