mirror of
https://github.com/mitch030504/AstroQuest-Frame.git
synced 2026-10-29 04:02:13 +01:00
ASTRO BOT 1.04 runs: its controller is found, and it draws and keeps time as 1.00 does
From its first update on, the game asks the tracker to find the controller anew on the
screen that has the player sit inside a silhouette, and waits there until it has seen the
controller's status go from calibrating to anything else. sceVrTrackerRecalibrate was a
stub: the status never changed, and the updated game stayed on that screen for good ("the
controller with the red light bar should also be in view"). The tracker now says what a
console's does while it finds a device anew.
The executable of 1.04 is laid out differently from the disc's, each part moved by an
amount of its own. Where its time step, its sizes and the memory for them are is in
known_title_builds.h, as found and tried by Clodo76 (issue #1) and checked here against
both executables. A build is told from the others by what its image holds when it is
loaded, before anything is written to it; tools/tests/known_title_builds_test.cpp tries
that on images made up for it and on real executables.
Issues #1, #3, #5, #7, #10.
Co-authored-by: Clodo76 <1511345+Clodo76@users.noreply.github.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
bf7f9acfe3
commit
5d736504a0
6 files changed
+545
-142
No files matched your search
@@ -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
|
||||
|
||||
@@ -9,18 +9,16 @@
|
||||
#include <cstdlib>
|
||||
#include <cstring>
|
||||
#include <mutex>
|
||||
#include <span>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
#include <vector>
|
||||
#include <fmt/format.h>
|
||||
|
||||
#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<std::array<u32, 2>, 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<std::array<u64, 2>, 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<u64, 4> 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<u64, 2> TargetPool{0xef8bf7, 0xef8c4d};
|
||||
constexpr std::array<u64, 3> 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<const Build*> 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=<pixels> is the width of the largest, 1440 on the console.
|
||||
struct Larger {
|
||||
struct Larger : Builds::Sizes {
|
||||
double factor{1.0};
|
||||
std::array<std::array<u32, 2>, 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<u64>(static_cast<double>(bytes) * pixels * 1.1) + MB - 1) / MB * MB;
|
||||
};
|
||||
result.target_pool = static_cast<u32>(megabytes(ConsoleTargetPool));
|
||||
result.small_pool = static_cast<u32>(megabytes(ConsoleSmallPool));
|
||||
const u64 growth = (result.target_pool - ConsoleTargetPool) +
|
||||
(result.small_pool - ConsoleSmallPool) +
|
||||
result.target_pool = static_cast<u32>(megabytes(Builds::ConsoleTargetPool));
|
||||
result.small_pool = static_cast<u32>(megabytes(Builds::ConsoleSmallPool));
|
||||
const u64 growth = (result.target_pool - Builds::ConsoleTargetPool) +
|
||||
(result.small_pool - Builds::ConsoleSmallPool) +
|
||||
static_cast<u64>(200.0 * MB * (pixels - 1.0));
|
||||
result.graphics_heap = ConsoleGraphicsHeap + growth;
|
||||
result.graphics_heap = Builds::ConsoleGraphicsHeap + growth;
|
||||
result.extra_memory_mb = static_cast<s32>((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<Linker>::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<const void*>(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 <typename T>
|
||||
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<u64>(base + ResolutionPointer);
|
||||
s32 TendResolution(VAddr base, const Build& build, s32 wanted) {
|
||||
const u64 control = Read<u64>(base + build.resolution_pointer);
|
||||
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) {
|
||||
const s32 offset = wanted - Read<s32>(control + ResolutionBase);
|
||||
const s32 offset = wanted - base_level;
|
||||
Write<s32>(control + ResolutionOffset, offset);
|
||||
Write<s32>(control + ResolutionHighest, offset);
|
||||
Write<s32>(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<double>(base + EngineFrameRate, 1.0 / step);
|
||||
Write<float>(base + EngineFrameSeconds, static_cast<float>(step));
|
||||
Write<u64>(base + EngineFrameMicroseconds, static_cast<u64>(step * 1e6));
|
||||
Write<double>(base + build->frame_rate, 1.0 / step);
|
||||
Write<float>(base + build->frame_seconds, static_cast<float>(step));
|
||||
Write<u64>(base + build->frame_microseconds, static_cast<u64>(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<const void*>(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<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;
|
||||
}
|
||||
// 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<Change> 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<const void*>(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<void*>(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<u64>(base + ManagerPointer);
|
||||
const u64 manager = Read<u64>(base + build->manager_pointer);
|
||||
if (manager == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -0,0 +1,242 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2026 shadPS4 Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <array>
|
||||
#include <cstring>
|
||||
#include <span>
|
||||
#include <vector>
|
||||
|
||||
#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<std::array<u32, 2>, 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<u8, 9> 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<std::array<u64, 2>, 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<u64, 4> eye_sizes;
|
||||
/// The sizes of the two pools (in two places and in three) and of the heap.
|
||||
std::array<u64, 2> target_pool;
|
||||
std::array<u64, 3> small_pool;
|
||||
u64 graphics_heap; // u64
|
||||
};
|
||||
|
||||
inline constexpr std::array<Build, 2> 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<std::array<u32, 2>, 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<Change> SizeChanges(const Build& build, const Sizes& sizes) {
|
||||
std::vector<Change> 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<const u8> image, std::span<const Change> 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<const u8> 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<const u8> 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<u8> image, std::span<const Change> 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
|
||||
@@ -2,6 +2,7 @@
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#include <algorithm>
|
||||
#include <array>
|
||||
#include <atomic>
|
||||
#include <mutex>
|
||||
#include <optional>
|
||||
@@ -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<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;
|
||||
@@ -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<u32>(device_type), static_cast<u32>(param->calibration_type));
|
||||
BeginRecalibration(device_type);
|
||||
return ORBIS_OK;
|
||||
}
|
||||
|
||||
|
||||
@@ -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 <eboot.bin> <out.elf>`), it also says which build each is.
|
||||
#include <cstdio>
|
||||
#include <cstring>
|
||||
#include <fstream>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#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 <typename T>
|
||||
static void Put(std::vector<u8>& 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<u8> MadeUp(const Build& build) {
|
||||
std::vector<u8> 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<u8> Load(const char* path) {
|
||||
std::ifstream file{path, std::ios::binary};
|
||||
std::vector<u8> elf{std::istreambuf_iterator<char>{file}, std::istreambuf_iterator<char>{}};
|
||||
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<u8> 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<u8> image = MadeUp(build);
|
||||
Check(Recognise(image) == &build, name + ": recognised");
|
||||
Check(Recognise(std::span<const u8>{}) == 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<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};
|
||||
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<u8> 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<u8> odd = image;
|
||||
odd[changes.back().at] ^= 1;
|
||||
const std::vector<u8> 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<Change> 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<u8> both = MadeUp(Known[0]);
|
||||
const std::vector<u8> 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<u8> 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<u8> 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;
|
||||
}
|
||||
Reference in new issue
Block a user