diff --git a/shadps4-arm64-main/CMakeLists.txt b/shadps4-arm64-main/CMakeLists.txt index f8915af..534e450 100644 --- a/shadps4-arm64-main/CMakeLists.txt +++ b/shadps4-arm64-main/CMakeLists.txt @@ -1017,6 +1017,7 @@ set(CORE src/core/aerolib/stubs.cpp src/core/guest_cpu/guest_watchdog.h src/core/known_title.cpp src/core/known_title.h + src/core/known_title_builds.h src/core/linker.cpp src/core/linker.h src/core/memory.cpp diff --git a/shadps4-arm64-main/src/core/known_title.cpp b/shadps4-arm64-main/src/core/known_title.cpp index fe8ae79..dbf72eb 100644 --- a/shadps4-arm64-main/src/core/known_title.cpp +++ b/shadps4-arm64-main/src/core/known_title.cpp @@ -9,18 +9,16 @@ #include #include #include +#include #include #include -#include #include #include "common/elf_info.h" #include "common/logging/log.h" -#include "common/singleton.h" #include "core/emulator_settings.h" #include "core/known_title.h" -#include "core/linker.h" -#include "core/module.h" +#include "core/known_title_builds.h" #include "core/vr/vr_runtime.h" #include "video_core/renderer_vulkan/vk_scheduler.h" @@ -30,74 +28,44 @@ namespace { using Clock = std::chrono::steady_clock; -// Astro Bot Rescue Mission, CUSA12392, in the build whose code at SetRecentre reads as below -// (the function of its tracking manager that asks for the head's position to be taken anew). -constexpr u64 SetRecentre = 0xc48320; -constexpr u8 SetRecentreCode[] = {0x40, 0x0f, 0xb6, 0xc6, 0xff, 0xc0, 0x89, 0x07, 0xc3}; +// 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 ManagerPointer = 0x2e025a8; constexpr u64 ManagerOrigin = 0x6210; constexpr u64 ManagerHeadState = 0x6848; constexpr u64 StatePosition = 0x10; constexpr u64 StatePositionValid = 0x54; -// The engine's frame rate and what it derives from it when the rate is set (its function at -// 0xe48fd0): the time one frame stands for, in seconds and in microseconds. Everything in the -// game that moves reads one of the latter two. -constexpr u64 EngineFrameRate = 0x16688a8; // double -constexpr u64 EngineFrameSeconds = 0x16688b0; // float -constexpr u64 EngineFrameMicroseconds = 0x16688b8; // u64 -constexpr u64 ImageEnd = EngineFrameMicroseconds + sizeof(u64); - // 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 ResolutionPointer = 0x2dff9e0; 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 -constexpr s32 FirstHeadsetLevel = 3; -constexpr s32 LastHeadsetLevel = 6; -// The sizes of the list, an eye, as the console has them. -constexpr std::array, 7> ConsoleSizes{{ - {640, 360}, {1280, 720}, {1920, 1080}, {816, 870}, {960, 1080}, {1200, 1280}, {1440, 1536}}}; -// Where the sizes of the headset's list are written down (see Larger): a table of widths, one of -// heights, the same once more in the switch of the function that makes the scene's targets -// (heights, then widths), and a table of pixel counts the title's own choice goes by. -constexpr u64 SizeWidths = 0x12da900; // u32 [7] -constexpr u64 SizeHeights = 0x12da920; // u32 [7] -constexpr std::array, 4> SizeSwitch{{ - {0xf2242f, 0xf22435}, {0xf2240d, 0xf22413}, {0xf22551, 0xf22557}, {0xf2255f, 0xf22565}}}; -constexpr u64 SizePixels = 0x1645048; // u64, 32 bytes apart -// The pictures handed to the headset, made three pairs at a time: their width and height. -constexpr std::array EyeSizes{0xc3fad0, 0xc3fad5, 0xc3fb5c, 0xc3fb61}; -// What the targets are taken from: a pool of 200 MB (its size in two places), a smaller one of -// 10 MB for what goes with them (in three), and the heap of graphics memory both come from, -// 872 MB, which the title takes from the console's memory at its start. -constexpr std::array TargetPool{0xef8bf7, 0xef8c4d}; -constexpr std::array SmallPool{0xf22194, 0xf221be, 0xf221dd}; -constexpr u64 GraphicsHeap = 0x1269708; // u64 -constexpr u32 ConsoleTargetPool = 0xc800000; -constexpr u32 ConsoleSmallPool = 0xa00000; -constexpr u64 ConsoleGraphicsHeap = 0x36800000; +/// The build of the title that runs, and where its image starts, from the moment its image is +/// loaded and found to be one of those known (OnGameLoaded); nullptr for every other title and +/// every other build. +std::atomic known_build{nullptr}; +VAddr known_base{}; /// The title drawing larger than it does on the console: every size of the headset's list (and /// the pictures handed to the headset) grown by the same factor, and the memory that takes. /// SHADPS4_TITLE_EYE_WIDTH= is the width of the largest, 1440 on the console. -struct Larger { +struct Larger : Builds::Sizes { double factor{1.0}; - std::array, 7> sizes{ConsoleSizes}; - u32 target_pool{ConsoleTargetPool}; - u32 small_pool{ConsoleSmallPool}; - u64 graphics_heap{ConsoleGraphicsHeap}; /// What the console's memory has to grow by for it, in MB. s32 extra_memory_mb{}; }; @@ -124,12 +92,12 @@ const Larger& GetLarger() { const auto megabytes = [&](u64 bytes) { return (static_cast(static_cast(bytes) * pixels * 1.1) + MB - 1) / MB * MB; }; - result.target_pool = static_cast(megabytes(ConsoleTargetPool)); - result.small_pool = static_cast(megabytes(ConsoleSmallPool)); - const u64 growth = (result.target_pool - ConsoleTargetPool) + - (result.small_pool - ConsoleSmallPool) + + result.target_pool = static_cast(megabytes(Builds::ConsoleTargetPool)); + result.small_pool = static_cast(megabytes(Builds::ConsoleSmallPool)); + const u64 growth = (result.target_pool - Builds::ConsoleTargetPool) + + (result.small_pool - Builds::ConsoleSmallPool) + static_cast(200.0 * MB * (pixels - 1.0)); - result.graphics_heap = ConsoleGraphicsHeap + growth; + result.graphics_heap = Builds::ConsoleGraphicsHeap + growth; result.extra_memory_mb = static_cast((growth + 256 * MB) / (256 * MB) * 256); return result; }(); @@ -145,30 +113,6 @@ std::string SizeName(s32 level) { return fmt::format("{}x{}", size[0], size[1]); } -/// Where the title's image starts if it is the build described above, 0 otherwise. -VAddr KnownBase() { - static const VAddr base = []() -> VAddr { - if (Common::ElfInfo::Instance().GameSerial() != "CUSA12392") { - return 0; - } - const Module* eboot = Common::Singleton::Instance()->GetModule(0); - if (eboot == nullptr || !eboot->IsValid() || - eboot->aligned_base_size < std::max(ManagerPointer + sizeof(u64), ImageEnd)) { - return 0; - } - const VAddr image = eboot->GetBaseAddress(); - if (std::memcmp(reinterpret_cast(image + SetRecentre), SetRecentreCode, - sizeof(SetRecentreCode)) != 0) { - LOG_INFO(Core, "This is another build of CUSA12392 than the one known from inside: " - "it is left to itself"); - return 0; - } - LOG_INFO(Core, "CUSA12392 in the build known from inside"); - return image; - }(); - return base; -} - template T Read(VAddr address) { T value; @@ -594,14 +538,19 @@ private: // The size the title draws its scene at right now, as an index into ConsoleSizes; -1 when /// it has not got that far. Holds it to `wanted` on the way unless that is 0. -s32 TendResolution(VAddr base, s32 wanted) { - const u64 control = Read(base + ResolutionPointer); +s32 TendResolution(VAddr base, const Build& build, s32 wanted) { + const u64 control = Read(base + build.resolution_pointer); if (control == 0) { return -1; } const s32 level = Read(control + ResolutionLevel); + const s32 base_level = Read(control + ResolutionBase); + if (level < 0 || level > LastHeadsetLevel || base_level < 0 || base_level > LastHeadsetLevel) { + // Not what is known of it: looked at, never written to. + return -1; + } if (wanted != 0 && level >= FirstHeadsetLevel) { - const s32 offset = wanted - Read(control + ResolutionBase); + const s32 offset = wanted - base_level; Write(control + ResolutionOffset, offset); Write(control + ResolutionHighest, offset); Write(control + ResolutionLowest, offset); @@ -655,10 +604,11 @@ private: } // namespace void OnFrameSubmitted() { - const VAddr base = KnownBase(); - if (base == 0) { + const Build* const build = known_build.load(std::memory_order_acquire); + if (build == nullptr) { return; } + const VAddr base = known_base; const Settings& settings = GetSettings(); static bool running = false; @@ -702,7 +652,7 @@ void OnFrameSubmitted() { wanted = governor.Level(frame, now, 0); break; } - const s32 resolution = TendResolution(base, wanted); + const s32 resolution = TendResolution(base, *build, wanted); frame_pace.store(governor.Pace(), std::memory_order_relaxed); if (settings.time_step) { @@ -710,9 +660,9 @@ void OnFrameSubmitted() { // was made for: a frame for every refresh of a display faster than 60 Hz. const double shortest = governor.Pace() == 1 ? 1.0 / 250.0 : Nominal; step = time_step.Next(frame, settings.longest_step, shortest); - Write(base + EngineFrameRate, 1.0 / step); - Write(base + EngineFrameSeconds, static_cast(step)); - Write(base + EngineFrameMicroseconds, static_cast(step * 1e6)); + Write(base + build->frame_rate, 1.0 / step); + Write(base + build->frame_seconds, static_cast(step)); + Write(base + build->frame_microseconds, static_cast(step * 1e6)); } if (frame <= Stall) { @@ -758,74 +708,55 @@ void Prepare() { } void OnGameLoaded(VAddr base, u64 size) { - if (Common::ElfInfo::Instance().GameSerial() != "CUSA12392" || size < ImageEnd || - std::memcmp(reinterpret_cast(base + SetRecentre), SetRecentreCode, - sizeof(SetRecentreCode)) != 0) { + if (Common::ElfInfo::Instance().GameSerial() != "CUSA12392") { return; } - const Larger& larger = GetLarger(); - if (larger.factor == 1.0) { + // Asked here and nowhere else: this is the one moment at which the image is as its build + // has it. Its sizes may be other ones from here on, and its clock is once it runs. + const std::span image{reinterpret_cast(base), static_cast(size)}; + const Build* const build = Builds::Recognise(image); + if (build == nullptr) { + LOG_WARNING(Core, + "This build of CUSA12392 is none of those known from inside (its versions " + "1.00 and 1.04 are): it is left to itself. It then counts time in frames, " + "which is slow motion wherever a frame takes longer than a sixtieth of a " + "second, and draws at the console's sizes"); return; } - // 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. - struct Change { - u64 at; - u64 was; - u64 now; - u32 bytes; - }; - std::vector changes; - for (s32 level = FirstHeadsetLevel; level <= LastHeadsetLevel; ++level) { - const auto& was = ConsoleSizes[level]; - const auto& now = larger.sizes[level]; - changes.push_back({SizeWidths + 4 * level, was[0], now[0], 4}); - changes.push_back({SizeHeights + 4 * level, was[1], now[1], 4}); - changes.push_back({SizeSwitch[level - FirstHeadsetLevel][0], was[1], now[1], 4}); - changes.push_back({SizeSwitch[level - FirstHeadsetLevel][1], was[0], now[0], 4}); - changes.push_back({SizePixels + 32 * level, u64{was[0]} * was[1], u64{now[0]} * now[1], 8}); - } - const auto& eye_was = ConsoleSizes[LastHeadsetLevel]; - const auto& eye_now = larger.sizes[LastHeadsetLevel]; - for (u32 i = 0; i < EyeSizes.size(); ++i) { - changes.push_back({EyeSizes[i], eye_was[i % 2], eye_now[i % 2], 4}); - } - for (const u64 at : TargetPool) { - changes.push_back({at, ConsoleTargetPool, larger.target_pool, 4}); - } - for (const u64 at : SmallPool) { - changes.push_back({at, ConsoleSmallPool, larger.small_pool, 4}); - } - changes.push_back({GraphicsHeap, ConsoleGraphicsHeap, larger.graphics_heap, 8}); + LOG_INFO(Core, "CUSA12392 in a build known from inside: {}", build->name); - for (const Change& change : changes) { - u64 found = 0; - std::memcpy(&found, reinterpret_cast(base + change.at), change.bytes); - if (found != change.was) { + 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 Builds::Change* unexpected = Builds::Apply(image, changes); + unexpected != nullptr) { LOG_WARNING(Core, - "The title has {:#x} at {:#x} where {:#x} was expected: it draws at the " + "The title does not have {:#x} at {:#x} as expected: it draws at the " "console's sizes", - found, change.at, change.was); - return; + 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); } } - for (const Change& change : changes) { - std::memcpy(reinterpret_cast(base + change.at), &change.now, change.bytes); - } - 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); } void NoteView(const Vr::Vec3& tracker_head) { static constexpr auto Interval = std::chrono::seconds{10}; - const VAddr base = KnownBase(); - if (base == 0) { + const Build* const build = known_build.load(std::memory_order_acquire); + if (build == nullptr) { return; } + const VAddr base = known_base; static std::mutex mutex; static Vr::Vec3 last_origin; static Clock::time_point last_report; @@ -835,7 +766,7 @@ void NoteView(const Vr::Vec3& tracker_head) { if (!lock.owns_lock()) { return; } - const u64 manager = Read(base + ManagerPointer); + const u64 manager = Read(base + build->manager_pointer); if (manager == 0) { return; } diff --git a/shadps4-arm64-main/src/core/known_title.h b/shadps4-arm64-main/src/core/known_title.h index e66263d..e4a248f 100644 --- a/shadps4-arm64-main/src/core/known_title.h +++ b/shadps4-arm64-main/src/core/known_title.h @@ -10,7 +10,8 @@ struct Vec3; } /// What is known about the inner workings of particular titles, and what the emulator does with -/// it. Everything here applies to one exact build of one title and does nothing for any other. +/// 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 diff --git a/shadps4-arm64-main/src/core/known_title_builds.h b/shadps4-arm64-main/src/core/known_title_builds.h new file mode 100644 index 0000000..1cf9680 --- /dev/null +++ b/shadps4-arm64-main/src/core/known_title_builds.h @@ -0,0 +1,242 @@ +// SPDX-FileCopyrightText: Copyright 2026 shadPS4 Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include +#include +#include +#include + +#include "common/types.h" + +/// Where things are in the builds of ASTRO BOT Rescue Mission (CUSA12392) that are known from +/// inside, and how a build is told from every other. Kept apart from what is done with it +/// (known_title.cpp) so that it can be tried without an emulator around it +/// (tools/tests/known_title_builds_test.cpp). +/// +/// The game's updates moved its code and data about, each part by an amount of its own: every +/// address below was found in the build it is given for, none is worked out from another's. +/// (Those of the update 1.04 were found and tried by Clodo76, issue #1.) +namespace Core::KnownTitle::Builds { + +constexpr s32 FirstHeadsetLevel = 3; +constexpr s32 LastHeadsetLevel = 6; +// The sizes the title draws its scene at, an eye, as the console has them: the first three are +// for a television, the others for the headset. +constexpr std::array, 7> ConsoleSizes{{ + {640, 360}, {1280, 720}, {1920, 1080}, {816, 870}, {960, 1080}, {1200, 1280}, {1440, 1536}}}; +// What the scene's targets are taken from: a pool of 200 MB, a smaller one of 10 MB for what +// goes with them, and the heap of graphics memory both come from, 872 MB, which the title takes +// from the console's memory at its start. +constexpr u32 ConsoleTargetPool = 0xc800000; +constexpr u32 ConsoleSmallPool = 0xa00000; +constexpr u64 ConsoleGraphicsHeap = 0x36800000; +// The time one frame stands for, as the title's engine has it before the title runs. +constexpr double ConsoleFrameRate = 60.0; +constexpr float ConsoleFrameSeconds = 1.0f / 60.0f; +constexpr u64 ConsoleFrameMicroseconds = 16666; + +// The function of the title's tracking manager that asks for the head's position to be taken +// anew: it reads the same in every build, and is somewhere else in each. +constexpr std::array SetRecentreCode{0x40, 0x0f, 0xb6, 0xc6, 0xff, 0xc0, 0x89, 0x07, 0xc3}; + +struct Build { + /// What the log calls it. + const char* name; + u64 set_recentre; + /// The tracking manager, a singleton: where the pointer to it is kept. + u64 manager_pointer; + /// The engine's frame rate and what it derives from it when the rate is set: the time one + /// frame stands for, in seconds and in microseconds. Everything in the game that moves + /// reads one of the latter two. + u64 frame_rate; // double + u64 frame_seconds; // float + u64 frame_microseconds; // u64 + /// What sets the size the scene is drawn at, a singleton made when first asked for: where + /// the pointer to it is kept. + u64 resolution_pointer; + /// Where the sizes are written down: a table of widths, one of heights, the headset's once + /// more in the switch of the function that makes the scene's targets (heights, then + /// widths), and a table of pixel counts the title's own choice goes by. + u64 size_widths; // u32 [7] + u64 size_heights; // u32 [7] + std::array, 4> size_switch; + u64 size_pixels; // u64, 32 bytes apart + /// The pictures handed to the headset, made three pairs at a time: their width and height. + std::array eye_sizes; + /// The sizes of the two pools (in two places and in three) and of the heap. + std::array target_pool; + std::array small_pool; + u64 graphics_heap; // u64 +}; + +inline constexpr std::array Known{{ + { + .name = "1.00, as on the disc", + .set_recentre = 0xc48320, + .manager_pointer = 0x2e025a8, + .frame_rate = 0x16688a8, + .frame_seconds = 0x16688b0, + .frame_microseconds = 0x16688b8, + .resolution_pointer = 0x2dff9e0, + .size_widths = 0x12da900, + .size_heights = 0x12da920, + .size_switch = {{{0xf2242f, 0xf22435}, + {0xf2240d, 0xf22413}, + {0xf22551, 0xf22557}, + {0xf2255f, 0xf22565}}}, + .size_pixels = 0x1645048, + .eye_sizes = {0xc3fad0, 0xc3fad5, 0xc3fb5c, 0xc3fb61}, + .target_pool = {0xef8bf7, 0xef8c4d}, + .small_pool = {0xf22194, 0xf221be, 0xf221dd}, + .graphics_heap = 0x1269708, + }, + { + .name = "1.04, the last update", + .set_recentre = 0xccc0b0, + .manager_pointer = 0x2ed2ae8, + .frame_rate = 0x17208b8, + .frame_seconds = 0x17208c0, + .frame_microseconds = 0x17208c8, + .resolution_pointer = 0x2ecfe60, + .size_widths = 0x1371120, + .size_heights = 0x1371140, + .size_switch = {{{0xfa5a1f, 0xfa5a25}, + {0xfa59fd, 0xfa5a03}, + {0xfa5b41, 0xfa5b47}, + {0xfa5b4f, 0xfa5b55}}}, + .size_pixels = 0x16fb558, + .eye_sizes = {0xcc3580, 0xcc3585, 0xcc360c, 0xcc3611}, + .target_pool = {0xf7c117, 0xf7c16d}, + .small_pool = {0xfa5784, 0xfa57ae, 0xfa57cd}, + .graphics_heap = 0x12ffb78, + }, +}}; + +/// One place of the title's image that is to hold something else: what the console's build has +/// there and what it is to have, in so many bytes. +struct Change { + u64 at; + u64 was; + u64 now; + u32 bytes; +}; + +/// The sizes of a title that draws larger than on the console, and the memory that takes. +struct Sizes { + std::array, 7> sizes{ConsoleSizes}; + u32 target_pool{ConsoleTargetPool}; + u32 small_pool{ConsoleSmallPool}; + u64 graphics_heap{ConsoleGraphicsHeap}; +}; + +/// Every place that sets the sizes of a build, with what the console has there and what +/// `sizes` wants there. +inline std::vector SizeChanges(const Build& build, const Sizes& sizes) { + std::vector changes; + for (s32 level = FirstHeadsetLevel; level <= LastHeadsetLevel; ++level) { + const auto& was = ConsoleSizes[level]; + const auto& now = sizes.sizes[level]; + changes.push_back({build.size_widths + 4 * level, was[0], now[0], 4}); + changes.push_back({build.size_heights + 4 * level, was[1], now[1], 4}); + changes.push_back({build.size_switch[level - FirstHeadsetLevel][0], was[1], now[1], 4}); + changes.push_back({build.size_switch[level - FirstHeadsetLevel][1], was[0], now[0], 4}); + changes.push_back( + {build.size_pixels + 32 * level, u64{was[0]} * was[1], u64{now[0]} * now[1], 8}); + } + const auto& eye_was = ConsoleSizes[LastHeadsetLevel]; + const auto& eye_now = sizes.sizes[LastHeadsetLevel]; + for (u32 i = 0; i < build.eye_sizes.size(); ++i) { + changes.push_back({build.eye_sizes[i], eye_was[i % 2], eye_now[i % 2], 4}); + } + for (const u64 at : build.target_pool) { + changes.push_back({at, ConsoleTargetPool, sizes.target_pool, 4}); + } + for (const u64 at : build.small_pool) { + changes.push_back({at, ConsoleSmallPool, sizes.small_pool, 4}); + } + changes.push_back({build.graphics_heap, ConsoleGraphicsHeap, sizes.graphics_heap, 8}); + return changes; +} + +/// The first of some places that does not hold what the console's build has there (or lies +/// outside the image), nullptr when all do. +inline const Change* FirstUnexpected(std::span image, std::span changes) { + for (const Change& change : changes) { + if (change.bytes > sizeof(u64) || change.at > image.size() || + image.size() - change.at < change.bytes) { + return &change; + } + u64 found = 0; + std::memcpy(&found, image.data() + change.at, change.bytes); + if (found != change.was) { + return &change; + } + } + return nullptr; +} + +/// Whether an image, as loaded and before any of it has run, is this build: the function that +/// tells builds apart is where it is in this one, the engine's time step is where this one +/// keeps it, and so is every size. (After the title has run, or once its sizes have been +/// changed, this is no longer true of the very same image: ask once.) +inline bool Is(const Build& build, std::span image) { + const auto holds = [&](u64 at, const void* bytes, size_t size) { + return at <= image.size() && image.size() - at >= size && + std::memcmp(image.data() + at, bytes, size) == 0; + }; + // (The two singletons are made while the title runs: their pointers only have to be + // inside the image.) + for (const u64 pointer : {build.manager_pointer, build.resolution_pointer}) { + if (pointer > image.size() || image.size() - pointer < sizeof(u64)) { + return false; + } + } + if (!holds(build.set_recentre, SetRecentreCode.data(), SetRecentreCode.size()) || + !holds(build.frame_rate, &ConsoleFrameRate, sizeof(ConsoleFrameRate)) || + !holds(build.frame_seconds, &ConsoleFrameSeconds, sizeof(ConsoleFrameSeconds)) || + !holds(build.frame_microseconds, &ConsoleFrameMicroseconds, + sizeof(ConsoleFrameMicroseconds))) { + return false; + } + for (u32 level = 0; level < ConsoleSizes.size(); ++level) { + if (!holds(build.size_widths + 4 * level, &ConsoleSizes[level][0], sizeof(u32)) || + !holds(build.size_heights + 4 * level, &ConsoleSizes[level][1], sizeof(u32))) { + return false; + } + } + // Every place a larger picture is written to, as the console has it. + return FirstUnexpected(image, SizeChanges(build, Sizes{})) == nullptr; +} + +/// The build an image is, as loaded and before any of it has run; nullptr for one that is none +/// of those known (or would be more than one of them, which no build of the title is). +inline const Build* Recognise(std::span image) { + const Build* found = nullptr; + for (const Build& build : Known) { + if (!Is(build, image)) { + continue; + } + if (found != nullptr) { + return nullptr; + } + found = &build; + } + return found; +} + +/// Writes changes into an image, all of them or none: nothing is written unless every place +/// holds what the console's build has there. Answers with the place that did not, nullptr +/// when all was written. +inline const Change* Apply(std::span image, std::span changes) { + if (const Change* unexpected = FirstUnexpected(image, changes); unexpected != nullptr) { + return unexpected; + } + for (const Change& change : changes) { + std::memcpy(image.data() + change.at, &change.now, change.bytes); + } + return nullptr; +} + +} // namespace Core::KnownTitle::Builds diff --git a/shadps4-arm64-main/src/core/libraries/vr_tracker/vr_tracker.cpp b/shadps4-arm64-main/src/core/libraries/vr_tracker/vr_tracker.cpp index 955033b..0e772a2 100644 --- a/shadps4-arm64-main/src/core/libraries/vr_tracker/vr_tracker.cpp +++ b/shadps4-arm64-main/src/core/libraries/vr_tracker/vr_tracker.cpp @@ -2,6 +2,7 @@ // SPDX-License-Identifier: GPL-2.0-or-later #include +#include #include #include #include @@ -65,6 +66,40 @@ static bool HeadsetConnected() { return Core::Vr::Runtime::Instance().IsHeadsetConnected(); } +// Asked to find a kind of device anew (sceVrTrackerRecalibrate), a console's tracker says for +// a moment that it is calibrating it, and then that it tracks it again. Titles wait for exactly +// that: ASTRO BOT Rescue Mission from its version 1.01 on asks for its controller to be found +// anew on the screen that has the player sit inside a silhouette, and stays there until it has +// seen the controller's status go from calibrating to anything else. +struct Recalibration { + /// Process time, in microseconds, up to which the devices count as being calibrated. + u64 until{}; + /// Whether a result has said so yet: the title must get to see it, however late it asks. + bool reported{true}; +}; +static constexpr u64 RecalibrationTime = 200'000; +static std::array g_recalibrations; +static std::mutex g_recalibrations_mutex; + +static void BeginRecalibration(OrbisVrTrackerDeviceType device_type) { + std::scoped_lock lock{g_recalibrations_mutex}; + g_recalibrations[device_type] = { + .until = Libraries::Kernel::sceKernelGetProcessTime() + RecalibrationTime, + .reported = false, + }; +} + +/// Whether a result for a device of this kind is to say that it is being calibrated. +static bool IsRecalibrating(OrbisVrTrackerDeviceType device_type, u64 now) { + std::scoped_lock lock{g_recalibrations_mutex}; + Recalibration& recalibration = g_recalibrations[device_type]; + if (recalibration.reported && now >= recalibration.until) { + return false; + } + recalibration.reported = true; + return true; +} + static void WritePose(OrbisVrTrackerPoseData& out, const Core::Vr::Pose& pose) { out.position_x = pose.position.x; out.position_y = pose.position.y; @@ -357,7 +392,12 @@ s32 PS4_SYSV_ABI sceVrTrackerGetResult(const OrbisVrTrackerGetResultParam* param 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 = OrbisVrTrackerStatus::ORBIS_VR_TRACKER_STATUS_TRACKING; + 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; @@ -458,7 +498,7 @@ sceVrTrackerNotifyEndOfCpuProcess(const OrbisVrTrackerNotifyEndOfCpuProcessParam } s32 PS4_SYSV_ABI sceVrTrackerRecalibrate(const OrbisVrTrackerRecalibrateParam* param) { - LOG_ERROR(Lib_VrTracker, "(STUBBED) called"); + LOG_TRACE(Lib_VrTracker, "called"); if (!g_library_initialized) { return ORBIS_VR_TRACKER_ERROR_NOT_INIT; } @@ -501,7 +541,11 @@ s32 PS4_SYSV_ABI sceVrTrackerRecalibrate(const OrbisVrTrackerRecalibrateParam* p } } - // TODO: handle internal recalibration behaviors. + // The host tracks the devices and has nothing to find anew; what the title gets is the + // status a console's tracker shows while it does. + LOG_INFO(Lib_VrTracker, "called, device_type = {}, calibration_type = {}", + static_cast(device_type), static_cast(param->calibration_type)); + BeginRecalibration(device_type); return ORBIS_OK; } diff --git a/tools/tests/known_title_builds_test.cpp b/tools/tests/known_title_builds_test.cpp new file mode 100644 index 0000000..ede3d90 --- /dev/null +++ b/tools/tests/known_title_builds_test.cpp @@ -0,0 +1,184 @@ +// Checks how the emulator tells the builds of ASTRO BOT Rescue Mission apart, and that it only +// ever writes into one it has recognised, all of what it means to write or nothing. +// +// clang-cl /std:c++latest /EHsc -fuse-ld=lld /I shadps4-arm64-main/src +// tools/tests/known_title_builds_test.cpp /Fe:build/tests/known_title_builds_test.exe +// build/tests/known_title_builds_test.exe [plain ELF of an eboot.bin ...] +// +// Without arguments it works on images made up here, which hold what a build has at the places +// the emulator looks at and nothing else. Given executables (made plain with +// `node tools/ps4elf.mjs unwrap `), it also says which build each is. +#include +#include +#include +#include +#include + +#include "core/known_title_builds.h" + +using namespace Core::KnownTitle::Builds; + +static int failures = 0; + +static void Check(bool ok, const std::string& what) { + std::printf("%s %s\n", ok ? "ok " : "FAIL", what.c_str()); + failures += ok ? 0 : 1; +} + +template +static void Put(std::vector& image, u64 at, T value) { + std::memcpy(image.data() + at, &value, sizeof(T)); +} + +/// An image that holds what `build` has, as loaded, in every place the emulator looks at. +static std::vector MadeUp(const Build& build) { + std::vector image(0x3200000); + std::memcpy(image.data() + build.set_recentre, SetRecentreCode.data(), SetRecentreCode.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]); + } + for (const Change& change : SizeChanges(build, Sizes{})) { + std::memcpy(image.data() + change.at, &change.was, change.bytes); + } + return image; +} + +/// Twice the console's sizes, as the emulator works them out for a width of 2880. +static Sizes Doubled() { + Sizes sizes; + for (s32 level = FirstHeadsetLevel; level <= LastHeadsetLevel; ++level) { + sizes.sizes[level] = {ConsoleSizes[level][0] * 2, ConsoleSizes[level][1] * 2}; + } + sizes.target_pool = 880u << 20; + sizes.small_pool = 44u << 20; + sizes.graphics_heap = 2186ull << 20; + return sizes; +} + +/// The image an executable makes in memory: its loaded parts at their addresses. +static std::vector Load(const char* path) { + std::ifstream file{path, std::ios::binary}; + std::vector elf{std::istreambuf_iterator{file}, std::istreambuf_iterator{}}; + if (elf.size() < 0x40 || std::memcmp(elf.data(), "\x7f" "ELF", 4) != 0) { + return {}; + } + const auto read = [&](u64 at, auto& value) { + if (at + sizeof(value) > elf.size()) { + return false; + } + std::memcpy(&value, elf.data() + at, sizeof(value)); + return true; + }; + u64 headers = 0; + u16 header_size = 0; + u16 header_count = 0; + read(0x20, headers); + read(0x36, header_size); + read(0x38, header_count); + std::vector image; + for (u32 i = 0; i < header_count; ++i) { + const u64 at = headers + u64{i} * header_size; + u32 type = 0; + u64 offset = 0, address = 0, in_file = 0, in_memory = 0; + if (!read(at, type) || !read(at + 8, offset) || !read(at + 16, address) || + !read(at + 32, in_file) || !read(at + 40, in_memory)) { + return {}; + } + // PT_LOAD and PT_SCE_RELRO. + if ((type != 1 && type != 0x61000010) || offset + in_file > elf.size()) { + continue; + } + if (image.size() < address + in_memory) { + image.resize(address + in_memory); + } + std::memcpy(image.data() + address, elf.data() + offset, in_file); + } + return image; +} + +int main(int argc, char** argv) { + for (const Build& build : Known) { + const std::string name = build.name; + std::vector image = MadeUp(build); + Check(Recognise(image) == &build, name + ": recognised"); + Check(Recognise(std::span{}) == nullptr, name + ": not an empty image"); + Check(Recognise(std::span{image}.first(build.set_recentre + 4)) == nullptr, + name + ": not an image cut short"); + + // One byte off anywhere the emulator looks, and it is no build that is known. + std::vector places{build.set_recentre + 3, build.frame_rate, build.frame_seconds, + build.frame_microseconds, build.size_widths + 4 * 6, + build.size_heights + 4 * 2}; + for (const Change& change : SizeChanges(build, Sizes{})) { + places.push_back(change.at); + } + bool all_refused = true; + for (const u64 place : places) { + image[place] ^= 0x40; + all_refused &= Recognise(image) == nullptr; + image[place] ^= 0x40; + } + Check(all_refused, name + ": refused with any of " + std::to_string(places.size()) + + " places changed"); + Check(Recognise(image) == &build, name + ": recognised again once they are restored"); + + // Larger sizes: all of them written, the television's left alone. + const Sizes doubled = Doubled(); + const auto changes = SizeChanges(build, doubled); + Check(changes.size() == 30, name + ": 30 places set the sizes"); + std::vector larger = image; + Check(Apply(larger, changes) == nullptr, name + ": larger sizes written"); + bool written = true; + for (const Change& change : changes) { + u64 found = 0; + std::memcpy(&found, larger.data() + change.at, change.bytes); + written &= found == change.now; + } + Check(written, name + ": every place holds the larger size"); + u32 television = 0; + std::memcpy(&television, larger.data() + build.size_widths + 4 * 2, sizeof(u32)); + Check(television == 1920, name + ": the television's sizes are left alone"); + Check(Apply(larger, changes) != nullptr, name + ": not written a second time"); + Check(Recognise(larger) == nullptr, name + ": an image with larger sizes is not asked " + "what it is again"); + + // One place that is not as expected, the last one: nothing at all is written. + std::vector odd = image; + odd[changes.back().at] ^= 1; + const std::vector before = odd; + Check(Apply(odd, changes) == &changes.back() && odd == before, + name + ": nothing written when the last place is not as expected"); + // A place beyond the image. + const std::vector beyond{{image.size() - 2, 0, 1, 4}}; + Check(Apply(image, beyond) == &beyond[0], name + ": nothing written beyond the image"); + } + + // An image that would be both builds at once is neither. + std::vector both = MadeUp(Known[0]); + const std::vector other = MadeUp(Known[1]); + for (size_t i = 0; i < both.size(); ++i) { + both[i] |= other[i]; + } + Check(Is(Known[0], both) && Is(Known[1], both) && Recognise(both) == nullptr, + "an image that is both builds at once is neither"); + + for (int i = 1; i < argc; ++i) { + const std::vector image = Load(argv[i]); + const Build* build = image.empty() ? nullptr : Recognise(image); + std::printf("%s: %s\n", argv[i], + image.empty() ? "not a plain ELF" + : build != nullptr ? build->name : "no build that is known"); + if (build != nullptr) { + std::vector larger = image; + Check(Apply(larger, SizeChanges(*build, Doubled())) == nullptr, + std::string{"larger sizes written into "} + argv[i]); + } + } + + std::printf("failed: %d\n", failures); + return failures == 0 ? 0 : 1; +}