mirror of
https://github.com/mitch030504/Wiicompiled_VR_Frame.git
synced 2026-10-06 09:00:28 +02:00
Added First Person VR item management
- Implemented DVDReadVrAsset function to read mapped disc paths for VR assets. - Created HeldItem structure and ReadHeldItem function for managing held items in the game. - Developed unit tests for ReadHeldItem to ensure correct functionality and edge case handling. - Added cockpit item data tests to validate model indexing and parsing of archives.
This commit is contained in:
1 parent
75f075b234
commit
db30945f4c
26 files changed
+1598
-19
No files matched your search
@@ -456,6 +456,11 @@ target_include_directories(mkw_vr_player_tests PRIVATE "${CMAKE_CURRENT_LIST_DIR
|
||||
target_compile_features(mkw_vr_player_tests PRIVATE cxx_std_17)
|
||||
add_test(NAME mkw_vr_player_tests COMMAND mkw_vr_player_tests)
|
||||
|
||||
add_executable(mkw_vr_item_tests "${CMAKE_CURRENT_LIST_DIR}/tests/vr_item_tests.cpp")
|
||||
target_include_directories(mkw_vr_item_tests PRIVATE "${CMAKE_CURRENT_LIST_DIR}/include")
|
||||
target_compile_features(mkw_vr_item_tests PRIVATE cxx_std_17)
|
||||
add_test(NAME mkw_vr_item_tests COMMAND mkw_vr_item_tests)
|
||||
|
||||
add_executable(mkw_vr_policy_tests tests/vr_policy_tests.cpp src/vr/mkw_vr_policy.cpp)
|
||||
target_include_directories(mkw_vr_policy_tests PRIVATE "${CMAKE_CURRENT_LIST_DIR}/include")
|
||||
target_compile_features(mkw_vr_policy_tests PRIVATE cxx_std_17)
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
#pragma once
|
||||
|
||||
#include <cstdint>
|
||||
#include <vector>
|
||||
|
||||
// Reads the same mapped disc path that the guest sees, including active file
|
||||
// replacements. Called on the guest thread after DVD initialization.
|
||||
std::vector<uint8_t> DVDReadVrAsset(const char* dvdPath);
|
||||
@@ -78,6 +78,7 @@ struct RuntimeUserConfig {
|
||||
std::optional<bool> vrNativeSteeringWheel;
|
||||
std::optional<bool> vrObjectCulling;
|
||||
std::optional<bool> vrHandSteering;
|
||||
std::optional<std::string> vrCockpitItemHand;
|
||||
std::optional<bool> vrHandTracking;
|
||||
std::optional<float> vrWheelKartDegrees;
|
||||
std::optional<float> vrWheelBikeDegrees;
|
||||
@@ -237,6 +238,7 @@ inline constexpr float kVrCockpitUnitsPerMeterMax = 400.0f;
|
||||
inline constexpr bool kVrSteeringWheelDefault = true;
|
||||
inline constexpr bool kVrNativeSteeringWheelDefault = true;
|
||||
inline constexpr bool kVrHandSteeringDefault = true;
|
||||
inline constexpr const char* kVrCockpitItemHandDefault = "left";
|
||||
// The game hides karts and objects its own camera cannot see, which a head
|
||||
// turn in VR reveals. object_culling false draws them anyway (see
|
||||
// vr/mkw_vr_culling.h); it only takes effect while VR is enabled. The PC
|
||||
@@ -605,6 +607,8 @@ inline void EnsureConfigFile() {
|
||||
"# (seconds) a hand that loses tracking keeps hold, and a short\n"
|
||||
"# pulse on grab and release. All changeable live from the F10 menu.\n"
|
||||
"hand_steering = true\n"
|
||||
"# Show the settled inventory item in one cockpit hand: left, right, or off.\n"
|
||||
"cockpit_item_hand = \"left\"\n"
|
||||
"wheel_kart_degrees = 90.0\n"
|
||||
"wheel_bike_degrees = 45.0\n"
|
||||
"wheel_grab_distance = 0.35\n"
|
||||
@@ -880,6 +884,7 @@ inline RuntimeUserConfig ParseConfigDocument(const toml::value& document) {
|
||||
config.vrNativeSteeringWheel = FindConfigValue<bool>(document, "vr", "native_steering_wheel");
|
||||
config.vrObjectCulling = FindConfigValue<bool>(document, "vr", "object_culling");
|
||||
config.vrHandSteering = FindConfigValue<bool>(document, "vr", "hand_steering");
|
||||
config.vrCockpitItemHand = FindConfigValue<std::string>(document, "vr", "cockpit_item_hand");
|
||||
config.vrHandTracking = FindConfigValue<bool>(document, "vr", "hand_tracking");
|
||||
config.vrWheelKartDegrees = readRangedFloat("wheel_kart_degrees", kVrWheelDegreesMin, kVrWheelDegreesMax);
|
||||
config.vrWheelBikeDegrees = readRangedFloat("wheel_bike_degrees", kVrWheelDegreesMin, kVrWheelDegreesMax);
|
||||
@@ -1282,6 +1287,12 @@ inline bool SetVrHandSteering(bool value) {
|
||||
return WriteSetting("vr", "hand_steering", value ? "true" : "false");
|
||||
}
|
||||
|
||||
inline bool SetVrCockpitItemHand(const std::string& value) {
|
||||
if (value != "left" && value != "right" && value != "off") return false;
|
||||
Mutable().vrCockpitItemHand = value;
|
||||
return WriteSetting("vr", "cockpit_item_hand", "\"" + value + "\"");
|
||||
}
|
||||
|
||||
inline bool SetVrHandTracking(bool value) {
|
||||
Mutable().vrHandTracking = value;
|
||||
return WriteSetting("vr", "hand_tracking", value ? "true" : "false");
|
||||
@@ -1773,6 +1784,11 @@ inline bool VrHandSteering(bool fallback = kVrHandSteeringDefault) {
|
||||
return Get().vrHandSteering.value_or(fallback);
|
||||
}
|
||||
|
||||
inline std::string VrCockpitItemHand() {
|
||||
const std::string value = Get().vrCockpitItemHand.value_or(kVrCockpitItemHandDefault);
|
||||
return value == "left" || value == "right" || value == "off" ? value : kVrCockpitItemHandDefault;
|
||||
}
|
||||
|
||||
inline bool VrHandTracking(bool fallback = kVrHandTrackingDefault) {
|
||||
return Get().vrHandTracking.value_or(fallback);
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
#pragma once
|
||||
|
||||
#include "vr/steering_wheel.h"
|
||||
#include "vr/mkw_vr_item.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <array>
|
||||
@@ -572,4 +573,8 @@ void MkwVRFirstPersonRecenter() noexcept;
|
||||
// anchor has been missing long enough to give up holding the last one.
|
||||
FirstPersonAnchor MkwVRFirstPersonGetAnchor() noexcept;
|
||||
|
||||
// Guest-frame inventory snapshot, sampled at the race draw boundary. A
|
||||
// generation change invalidates any item retained by a prior race.
|
||||
HeldItem MkwVRFirstPersonGetHeldItem() noexcept;
|
||||
|
||||
} // namespace mkw::vr
|
||||
@@ -0,0 +1,47 @@
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
#pragma once
|
||||
|
||||
#include <cstdint>
|
||||
|
||||
namespace mkw::vr {
|
||||
|
||||
struct HeldItem {
|
||||
uint8_t id = 0x14;
|
||||
uint8_t count = 0;
|
||||
bool valid = false;
|
||||
uint64_t race_generation = 0;
|
||||
};
|
||||
|
||||
namespace detail {
|
||||
|
||||
// PAL RMCP01 Item::Manager and Item::Player. Item::PlayerInventory is at
|
||||
// Player+0x88. Read the inventory, never PlayerRoulette::nextItemId: roulette
|
||||
// teardown clears the latter, and an interrupted roulette may predict an item
|
||||
// the player never receives.
|
||||
template <typename GuestMemory>
|
||||
HeldItem ReadHeldItem(uint32_t local_racer, uint64_t race_generation) noexcept {
|
||||
HeldItem result{};
|
||||
result.race_generation = race_generation;
|
||||
if (local_racer >= 12) return result;
|
||||
uint32_t manager = 0, players = 0;
|
||||
if (!GuestMemory::TryRead32(0x809C3618u, manager) || !manager ||
|
||||
!GuestMemory::TryRead32(manager + 0x14u, players) || !players) return result;
|
||||
const uint32_t player = players + local_racer * 0x248u;
|
||||
if (player < players || !GuestMemory::Contains(player, 0x94u)) return result;
|
||||
try {
|
||||
if (GuestMemory::Read8(player + 0x18u) != local_racer ||
|
||||
GuestMemory::Read32(player + 0x58u) != 0) return result;
|
||||
const uint32_t id = GuestMemory::Read32(player + 0x8Cu);
|
||||
const uint32_t count = GuestMemory::Read32(player + 0x90u);
|
||||
if (id > 0x12u || count == 0 || count > 3) return result;
|
||||
result.id = static_cast<uint8_t>(id);
|
||||
result.count = static_cast<uint8_t>(count);
|
||||
result.valid = true;
|
||||
} catch (const typename GuestMemory::AccessViolation&) {
|
||||
return result;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
} // namespace detail
|
||||
} // namespace mkw::vr
|
||||
@@ -1,6 +1,7 @@
|
||||
#include "hle_stubs.h"
|
||||
#include "isa/big_endian.h"
|
||||
#include "hle/dvd_contract.h"
|
||||
#include "hle/dvd_vr_asset.h"
|
||||
#include "hle/runtime_parse_helpers.h"
|
||||
#include "memory.h"
|
||||
|
||||
@@ -743,6 +744,20 @@ extern "C" const char* DVDResolveHostPathForTest(const char* dvdPath)
|
||||
return resolved.c_str();
|
||||
}
|
||||
|
||||
std::vector<uint8_t> DVDReadVrAsset(const char* dvdPath) {
|
||||
if (dvdPath == nullptr || dvdPath[0] == '\0') return {};
|
||||
DVDInit_8015EA1C();
|
||||
const auto it = g_pathToEntry.find(NormalizePath(dvdPath));
|
||||
if (it == g_pathToEntry.end() || it->second < 0 ||
|
||||
it->second >= static_cast<int32_t>(g_fileEntries.size())) return {};
|
||||
const DVDFileEntry& entry = g_fileEntries[it->second];
|
||||
if (entry.isDirectory || entry.size == 0 || entry.size > 32u * 1024u * 1024u) return {};
|
||||
std::vector<uint8_t> bytes(entry.size);
|
||||
std::ifstream file(entry.hostPath, std::ios::binary);
|
||||
if (!file.read(reinterpret_cast<char*>(bytes.data()), bytes.size())) return {};
|
||||
return bytes;
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// High-Level DVD API
|
||||
// ============================================================================
|
||||
|
||||
@@ -12,6 +12,7 @@
|
||||
#include "fiber_manager.h"
|
||||
#include "platform/host_platform.h"
|
||||
#include "runtime_log.h"
|
||||
#include "runtime_config.h"
|
||||
#include "vr/mkw_vr_first_person.h"
|
||||
#include "vr/mkw_vr_policy.h"
|
||||
#include "vr/openxr_integration.h"
|
||||
@@ -592,6 +593,7 @@ struct GxPresentRecord {
|
||||
std::array<float, 12> anchor{};
|
||||
bool anchorValid = false;
|
||||
float anchorUnitsPerMeter = 0.0f;
|
||||
AuroraCockpitItem cockpitItem{};
|
||||
bool reportPaced = false;
|
||||
bool paced = false;
|
||||
uint32_t localPlayerCount = 1;
|
||||
@@ -609,6 +611,7 @@ void GxPresent_gx(GxPresentRecord record) {
|
||||
} else {
|
||||
aurora_set_stereo_scene_anchor(record.anchorValid ? record.anchor.data() : nullptr);
|
||||
}
|
||||
aurora_set_stereo_cockpit_item(&record.cockpitItem);
|
||||
aurora_set_stereo_local_player_count(record.localPlayerCount);
|
||||
aurora_end_frame_ex(record.contentTag, record.imguiFrame);
|
||||
g_auroraFrameActive.store(false, std::memory_order_release);
|
||||
@@ -693,6 +696,13 @@ void VI_HLE_PresentFrame(bool presentedXfb, bool paceToRetrace) {
|
||||
record.anchor = anchor.anchor;
|
||||
record.anchorValid = anchor.valid;
|
||||
record.anchorUnitsPerMeter = anchor.unitsPerMeter;
|
||||
if (anchor.valid) {
|
||||
const auto item = mkw::vr::MkwVRFirstPersonGetHeldItem();
|
||||
const auto hand = RuntimeConfigFile::VrCockpitItemHand();
|
||||
record.cockpitItem = {item.race_generation, item.id, item.count,
|
||||
static_cast<uint8_t>(hand == "right" ? 1 : hand == "off" ? 2 : 0),
|
||||
item.valid && hand != "off"};
|
||||
}
|
||||
// Latch the current policy safety state into this exact Aurora job. The
|
||||
// asynchronous worker may ask for an XR packet after the guest has already
|
||||
// begun the next frame, so immersive replay is accepted only when both
|
||||
|
||||
@@ -170,6 +170,9 @@ bool g_vrSteeringWheel = RuntimeConfigFile::VrSteeringWheel();
|
||||
bool g_vrNativeSteeringWheel = RuntimeConfigFile::VrNativeSteeringWheel();
|
||||
bool g_vrObjectCulling = RuntimeConfigFile::VrObjectCulling();
|
||||
bool g_vrHandSteering = RuntimeConfigFile::VrHandSteering();
|
||||
constexpr std::array<const char*, 3> kVrCockpitItemHands{"Left", "Right", "Off"};
|
||||
int g_vrCockpitItemHand = RuntimeConfigFile::VrCockpitItemHand() == "right" ? 1 :
|
||||
RuntimeConfigFile::VrCockpitItemHand() == "off" ? 2 : 0;
|
||||
mkw::vr::WheelTuning g_vrWheelTuning = RuntimeConfigFile::VrWheelTuning();
|
||||
float g_vrFirstPersonHeadUp = RuntimeConfigFile::VrFirstPersonHeadUpMeters();
|
||||
float g_vrFirstPersonHeadForward = RuntimeConfigFile::VrFirstPersonHeadForwardMeters();
|
||||
@@ -1221,11 +1224,19 @@ void DrawVrSteeringWheelSettings() {
|
||||
if (ImGui::IsItemHovered()) {
|
||||
ImGui::SetTooltip("Squeeze a grip near the wheel or handlebar to take hold of it, and turn "
|
||||
"it to steer, with one hand or both. Releasing both grips gives steering "
|
||||
"back to the stick, which still aims items. The runtime's hand mesh is "
|
||||
"used when hand steering was on at launch.");
|
||||
"back to the stick, which still aims items.");
|
||||
}
|
||||
if (ImGui::Combo("Item in cockpit hand", &g_vrCockpitItemHand,
|
||||
kVrCockpitItemHands.data(), static_cast<int>(kVrCockpitItemHands.size()))) {
|
||||
RuntimeConfigFile::SetVrCockpitItemHand(
|
||||
g_vrCockpitItemHand == 1 ? "right" : g_vrCockpitItemHand == 2 ? "off" : "left");
|
||||
}
|
||||
if (ImGui::IsItemHovered()) {
|
||||
ImGui::SetTooltip("Show Player 1's settled inventory item above this palm. "
|
||||
"Triple items show their remaining count. Using or losing the item hides it.");
|
||||
}
|
||||
#if defined(__ANDROID__)
|
||||
ImGui::BeginDisabled(!g_vrHandSteering);
|
||||
ImGui::BeginDisabled(!g_vrHandSteering && g_vrCockpitItemHand == 2);
|
||||
if (ImGui::Checkbox("Tracked hands", &g_vrHandTracking)) {
|
||||
RuntimeConfigFile::SetVrHandTracking(g_vrHandTracking);
|
||||
}
|
||||
@@ -1747,11 +1758,13 @@ void DrawVrCameraSettings() {
|
||||
g_vrSteeringWheel = RuntimeConfigFile::kVrSteeringWheelDefault;
|
||||
g_vrNativeSteeringWheel = RuntimeConfigFile::kVrNativeSteeringWheelDefault;
|
||||
g_vrHandSteering = RuntimeConfigFile::kVrHandSteeringDefault;
|
||||
g_vrCockpitItemHand = 0;
|
||||
RuntimeConfigFile::SetVrFirstPersonSeat(RuntimeConfigFile::kVrFirstPersonSeatDefault);
|
||||
RuntimeConfigFile::SetVrCockpitUnitsPerMeter(g_vrCockpitUnitsPerMeter);
|
||||
RuntimeConfigFile::SetVrSteeringWheel(g_vrSteeringWheel);
|
||||
RuntimeConfigFile::SetVrNativeSteeringWheel(g_vrNativeSteeringWheel);
|
||||
RuntimeConfigFile::SetVrHandSteering(g_vrHandSteering);
|
||||
RuntimeConfigFile::SetVrCockpitItemHand(RuntimeConfigFile::kVrCockpitItemHandDefault);
|
||||
#if defined(__ANDROID__)
|
||||
g_vrHandTracking = RuntimeConfigFile::kVrHandTrackingDefault;
|
||||
RuntimeConfigFile::SetVrHandTracking(g_vrHandTracking);
|
||||
|
||||
@@ -2,7 +2,10 @@
|
||||
|
||||
#include "vr/mkw_vr_first_person.h"
|
||||
|
||||
#include "aurora/aurora.h"
|
||||
|
||||
#include "gx_native_wheel.h"
|
||||
#include "hle/dvd_vr_asset.h"
|
||||
#include "gx_model_visibility.h"
|
||||
#include "memory.h"
|
||||
#include "runtime_config.h"
|
||||
@@ -317,6 +320,8 @@ struct FirstPersonState {
|
||||
|
||||
uint32_t camera_address = 0;
|
||||
detail::LocalPlayerKartRead player_kart{};
|
||||
HeldItem held_item{};
|
||||
uint64_t race_generation = 0;
|
||||
// Armed by the draw boundary, consumed by the frame seal.
|
||||
bool armed = false;
|
||||
uint64_t armed_frame = 0;
|
||||
@@ -1386,6 +1391,9 @@ void MkwVRFirstPersonReset() noexcept {
|
||||
g_state.armed_view_valid = false;
|
||||
g_state.camera_address = 0;
|
||||
g_state.player_kart = {};
|
||||
g_state.held_item = {};
|
||||
++g_state.race_generation;
|
||||
g_state.held_item.race_generation = g_state.race_generation;
|
||||
g_state.anchor = {};
|
||||
g_state.hold_frames = 0;
|
||||
g_state.ever_valid_this_race = false;
|
||||
@@ -1412,6 +1420,8 @@ void MkwVRFirstPersonRecenter() noexcept {
|
||||
|
||||
void MkwVRFirstPersonUpdate(uint64_t guest_frame_index, uint32_t race_camera_address) noexcept {
|
||||
std::lock_guard lock(g_mutex);
|
||||
g_state.held_item = {};
|
||||
g_state.held_item.race_generation = g_state.race_generation;
|
||||
DropHiddenModelsLocked();
|
||||
if (g_recenter_requested.exchange(false, std::memory_order_acq_rel)) {
|
||||
g_state.seated_eye.Recalibrate();
|
||||
@@ -1435,6 +1445,15 @@ void MkwVRFirstPersonUpdate(uint64_t guest_frame_index, uint32_t race_camera_add
|
||||
g_state.hold_frames = 0;
|
||||
}
|
||||
g_state.player_kart = player;
|
||||
if (player.failed_step == nullptr && g_state.seat == FirstPersonSeat::Cockpit) {
|
||||
static bool item_archive_attempted = false;
|
||||
if (!item_archive_attempted && RuntimeConfigFile::VrCockpitItemHand() != "off") {
|
||||
item_archive_attempted = true;
|
||||
const auto archive = DVDReadVrAsset("/Race/Common.szs");
|
||||
if (!archive.empty()) aurora_set_cockpit_item_archive(archive.data(), static_cast<uint32_t>(archive.size()));
|
||||
}
|
||||
g_state.held_item = detail::ReadHeldItem<Memory>(player.player_index, g_state.race_generation);
|
||||
}
|
||||
g_state.armed = true;
|
||||
g_state.armed_frame = guest_frame_index;
|
||||
g_state.armed_view_valid = ReadSceneViewMatrix(g_state.armed_view);
|
||||
@@ -1538,4 +1557,9 @@ FirstPersonAnchor MkwVRFirstPersonGetAnchor() noexcept {
|
||||
return g_state.anchor;
|
||||
}
|
||||
|
||||
HeldItem MkwVRFirstPersonGetHeldItem() noexcept {
|
||||
std::lock_guard lock(g_mutex);
|
||||
return g_state.held_item;
|
||||
}
|
||||
|
||||
} // namespace mkw::vr
|
||||
@@ -656,12 +656,13 @@ void OpenXRInput::UpdateSimultaneousHandsAndControllers(bool wanted) {
|
||||
"side to the cameras");
|
||||
}
|
||||
|
||||
// Trackers only exist while tracked hands and hand steering are both on: the
|
||||
// hands are only drawn while they can steer. They live as long as the session
|
||||
// Tracked hands can carry a cockpit item even while stick steering is used.
|
||||
// They live as long as the session
|
||||
// otherwise (Idle and the cockpit's reset keep them); a runtime that refuses
|
||||
// them is not asked again until the option is turned off and on.
|
||||
void OpenXRInput::UpdateHandTrackers() {
|
||||
const bool wanted = RuntimeConfigFile::VrHandTracking() && RuntimeConfigFile::VrHandSteering();
|
||||
const bool wanted = RuntimeConfigFile::VrHandTracking() &&
|
||||
(RuntimeConfigFile::VrHandSteering() || RuntimeConfigFile::VrCockpitItemHand() != "off");
|
||||
UpdateSimultaneousHandsAndControllers(wanted);
|
||||
if (!wanted) {
|
||||
DestroyHandTrackers();
|
||||
@@ -1417,8 +1418,10 @@ void OpenXRInput::UpdateDriving(XrTime display_time, const driving::SeatFrame& s
|
||||
tracked = false;
|
||||
}
|
||||
DrivingHand& out = snapshot.hands[hand];
|
||||
// Hands are shown only while they can steer.
|
||||
out.tracked = hand_steering && (tracked || joints);
|
||||
// The selected item hand is visible with stick steering too.
|
||||
const auto item_hand = RuntimeConfigFile::VrCockpitItemHand();
|
||||
const bool displays_item = item_hand == (hand == 0 ? "left" : "right");
|
||||
out.tracked = (hand_steering || displays_item) && (tracked || joints);
|
||||
out.held = false;
|
||||
out.squeeze = squeeze;
|
||||
out.seat_from_grip = seat_from_grip;
|
||||
|
||||
@@ -1386,7 +1386,8 @@ private:
|
||||
float lean_back_radians, AuroraCockpit& cockpit) noexcept {
|
||||
cockpit.unitsPerMeter = units_per_meter;
|
||||
const DrivingSnapshot driving = input_ != nullptr ? input_->Driving() : DrivingSnapshot{};
|
||||
if (driving.hand_steering && !hand_meshes_loaded_ && runtime_ != nullptr) {
|
||||
if ((driving.hand_steering || RuntimeConfigFile::VrCockpitItemHand() != "off") &&
|
||||
!hand_meshes_loaded_ && runtime_ != nullptr) {
|
||||
hand_meshes_loaded_ = true;
|
||||
// Tracked hands' trackers, when they exist, serve the mesh too.
|
||||
const XrHandTrackerEXT trackers[2]{input_ != nullptr ? input_->HandTracker(0) : XR_NULL_HANDLE,
|
||||
@@ -1397,7 +1398,8 @@ private:
|
||||
<< std::endl;
|
||||
}
|
||||
cockpit.active = driving.cockpit_active && position_valid && base_position_valid_ &&
|
||||
(driving.synthetic_control || driving.hand_steering);
|
||||
(driving.synthetic_control || driving.hand_steering ||
|
||||
RuntimeConfigFile::VrCockpitItemHand() != "off");
|
||||
if (!cockpit.active) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -15,6 +15,13 @@ static RuntimeUserConfig Parse(const std::string& text) {
|
||||
}
|
||||
|
||||
int main() {
|
||||
for (std::string_view hand : {"left", "right", "off"}) {
|
||||
Require(Parse("[vr]\ncockpit_item_hand = \"" + std::string(hand) + "\"\n")
|
||||
.vrCockpitItemHand == std::string(hand));
|
||||
}
|
||||
Require(std::string_view(RuntimeConfigFile::kVrCockpitItemHandDefault) == "left");
|
||||
Require(!Parse("[vr]\n").vrCockpitItemHand.has_value());
|
||||
Require(!Parse("[vr]\ncockpit_item_hand = 1\n").vrCockpitItemHand.has_value());
|
||||
// [vr] foveation: the Quest's foveated rendering level, index-matched to
|
||||
// aurora_set_stereo_foveation.
|
||||
for (std::string_view level : RuntimeConfigFile::kVrFoveationLevels) {
|
||||
|
||||
@@ -0,0 +1,84 @@
|
||||
#include "vr/mkw_vr_item.h"
|
||||
|
||||
#include <cstdint>
|
||||
#include <iostream>
|
||||
#include <stdexcept>
|
||||
#include <unordered_map>
|
||||
|
||||
namespace {
|
||||
struct GuestMemory {
|
||||
using AccessViolation = std::out_of_range;
|
||||
inline static std::unordered_map<uint32_t, uint8_t> bytes;
|
||||
inline static uint32_t fault = 0;
|
||||
static bool Contains(uint32_t address, uint32_t length) {
|
||||
for (uint32_t i = 0; i < length; ++i) if (!bytes.count(address + i)) return false;
|
||||
return true;
|
||||
}
|
||||
static uint8_t Read8(uint32_t address) {
|
||||
if (address == fault) throw AccessViolation("fault");
|
||||
return bytes.at(address);
|
||||
}
|
||||
static uint32_t Read32(uint32_t address) {
|
||||
uint32_t value = 0;
|
||||
for (uint32_t i = 0; i < 4; ++i) value = (value << 8) | Read8(address + i);
|
||||
return value;
|
||||
}
|
||||
static bool TryRead32(uint32_t address, uint32_t& out) {
|
||||
try { out = Read32(address); return true; } catch (const AccessViolation&) { return false; }
|
||||
}
|
||||
static void Write32(uint32_t address, uint32_t value) {
|
||||
for (uint32_t i = 0; i < 4; ++i) bytes[address + i] = uint8_t(value >> (24 - i * 8));
|
||||
}
|
||||
};
|
||||
|
||||
constexpr uint32_t manager = 0x81000000u, players = 0x82000000u;
|
||||
constexpr uint32_t player = players + 7 * 0x248u;
|
||||
int failures = 0;
|
||||
void Check(bool value, const char* name) {
|
||||
if (!value) { ++failures; std::cerr << "FAILED: " << name << '\n'; }
|
||||
}
|
||||
void Init() {
|
||||
GuestMemory::bytes.clear(); GuestMemory::fault = 0;
|
||||
GuestMemory::Write32(0x809C3618u, manager);
|
||||
GuestMemory::Write32(manager + 0x14u, players);
|
||||
for (uint32_t i = 0; i < 0x94u; ++i) GuestMemory::bytes[player + i] = 0;
|
||||
GuestMemory::bytes[player + 0x18u] = 7;
|
||||
GuestMemory::Write32(player + 0x90u, 1);
|
||||
}
|
||||
}
|
||||
|
||||
int main() {
|
||||
using mkw::vr::detail::ReadHeldItem;
|
||||
for (uint32_t id = 0; id <= 0x12u; ++id) {
|
||||
Init(); GuestMemory::Write32(player + 0x8cu, id);
|
||||
const auto item = ReadHeldItem<GuestMemory>(7, 19);
|
||||
Check(item.valid && item.id == id && item.count == 1 && item.race_generation == 19,
|
||||
"all 19 IDs, nonzero racer, generation");
|
||||
}
|
||||
Init(); GuestMemory::Write32(player + 0x8cu, 0x10); GuestMemory::Write32(player + 0x90u, 3);
|
||||
Check(ReadHeldItem<GuestMemory>(7, 1).count == 3, "triple full");
|
||||
GuestMemory::Write32(player + 0x90u, 2);
|
||||
Check(ReadHeldItem<GuestMemory>(7, 1).count == 2, "triple remaining");
|
||||
GuestMemory::Write32(player + 0x90u, 0);
|
||||
Check(!ReadHeldItem<GuestMemory>(7, 1).valid, "inventory removed on use or damage");
|
||||
GuestMemory::Write32(player + 0x90u, 1);
|
||||
GuestMemory::Write32(player + 0x58u, 1);
|
||||
Check(!ReadHeldItem<GuestMemory>(7, 1).valid, "roulette spinning");
|
||||
GuestMemory::Write32(player + 0x58u, 0);
|
||||
for (uint32_t id : {0x13u, 0x14u, 0xffu}) {
|
||||
GuestMemory::Write32(player + 0x8cu, id);
|
||||
Check(!ReadHeldItem<GuestMemory>(7, 1).valid, "empty or unsupported");
|
||||
}
|
||||
Init(); GuestMemory::Write32(player + 0x8cu, 0x0au);
|
||||
Check(ReadHeldItem<GuestMemory>(7, 1).valid, "golden mushroom held");
|
||||
GuestMemory::Write32(player + 0x90u, 0);
|
||||
Check(!ReadHeldItem<GuestMemory>(7, 1).valid, "golden mushroom expiry");
|
||||
Init(); GuestMemory::fault = player + 0x8cu;
|
||||
const auto faulted = ReadHeldItem<GuestMemory>(7, 2);
|
||||
Check(!faulted.valid && faulted.race_generation == 2, "read fault preserves generation");
|
||||
Init(); GuestMemory::Write32(0x809C3618u, 0);
|
||||
Check(!ReadHeldItem<GuestMemory>(7, 1).valid, "invalid manager");
|
||||
Init(); GuestMemory::bytes[player + 0x18u] = 0;
|
||||
Check(!ReadHeldItem<GuestMemory>(7, 1).valid, "racer identity mismatch");
|
||||
return failures ? 1 : 0;
|
||||
}
|
||||
Reference in new issue
Block a user