add Skate 3 demo path

This commit is contained in:
mchughalex 2026-06-14 12:02:09 +01:00
parent 1caef13677
commit c790817d56
6 changed files with 283 additions and 1 deletions

View file

@ -183,6 +183,7 @@ include(cmake/CodegenTargets.cmake)
set(SKATE3_COMMON_SOURCES set(SKATE3_COMMON_SOURCES
src/exception_compat.cpp src/exception_compat.cpp
src/skate3_app_common.cpp src/skate3_app_common.cpp
src/skate3_demo_path.cpp
src/skate3_fov.cpp src/skate3_fov.cpp
src/skate3_iso_installer.cpp src/skate3_iso_installer.cpp
src/skate3_title_update_installer.cpp src/skate3_title_update_installer.cpp

View file

@ -86,3 +86,45 @@ endforeach()
if(NOT _fov_patched) if(NOT _fov_patched)
message(FATAL_ERROR "Failed to apply Skate 3 generated projection FOV patch; projection FOV anchor not found") message(FATAL_ERROR "Failed to apply Skate 3 generated projection FOV patch; projection FOV anchor not found")
endif() endif()
set(_demo_path_movie_patched FALSE)
foreach(_file IN LISTS _skate3_recomp_files)
file(READ "${_file}" _contents)
if(_contents MATCHES "ShouldForceIntroMovieComplete")
set(_demo_path_movie_patched TRUE)
break()
endif()
set(_demo_path_movie_site
" // bl 0x825d60c8
ctx.lr = 0x825E05A0;
sub_825D60C8(ctx, base);")
if(NOT _contents MATCHES "ctx\\.lr = 0x825E05A0;")
continue()
endif()
if(NOT _contents MATCHES "DEFINE_REX_FUNC\\(sub_825E0510\\)")
continue()
endif()
string(FIND "${_contents}" "${_demo_path_movie_site}" _demo_path_movie_anchor)
if(_demo_path_movie_anchor EQUAL -1)
continue()
endif()
set(_demo_path_movie_patch
" if (skate3::demo_path::ShouldForceIntroMovieComplete()) {
ctx.r3.u64 = 0;
} else {
// bl 0x825d60c8
ctx.lr = 0x825E05A0;
sub_825D60C8(ctx, base);
}")
string(REPLACE "${_demo_path_movie_site}" "${_demo_path_movie_patch}" _contents "${_contents}")
_skate3_add_include(_contents "skate3_demo_path.h")
file(WRITE "${_file}" "${_contents}")
set(_demo_path_movie_patched TRUE)
message(STATUS "Applied Skate 3 demo path intro movie patch in ${_file}")
break()
endforeach()
if(NOT _demo_path_movie_patched)
message(FATAL_ERROR "Failed to apply Skate 3 demo path intro movie patch; FEMoviePlayer::Update anchor not found")
endif()

View file

@ -1,5 +1,6 @@
#include "skate3_app_common.h" #include "skate3_app_common.h"
#include "skate3_demo_path.h"
#include "skate3_fov.h" #include "skate3_fov.h"
#include "skate3_iso_installer.h" #include "skate3_iso_installer.h"
#include "skate3_title_update_installer.h" #include "skate3_title_update_installer.h"
@ -121,6 +122,16 @@ REXCVAR_DEFINE_DOUBLE(skate3_ultrawide_target_aspect, 0.0, "Skate 3",
namespace { namespace {
void ApplyDemoPathProfileOverride() {
if (!rex::cvar::Query<bool>("skate3_demo_path") &&
!rex::cvar::Query<bool>("skate3_demo_path_probe")) {
return;
}
rex::cvar::SetFlagByName("user_profile_signed_in", "false");
rex::cvar::SetFlagByName("user_live_signed_in", "false");
}
#if defined(__linux__) || defined(__APPLE__) #if defined(__linux__) || defined(__APPLE__)
std::vector<std::string> CurrentProcessArgumentsForRestart( std::vector<std::string> CurrentProcessArgumentsForRestart(
const std::filesystem::path& executable_path) { const std::filesystem::path& executable_path) {
@ -599,6 +610,7 @@ std::optional<rex::PathConfig> Skate3BaseApp::OnFinalizePaths(
skate3::EnsureUsableProfileStore(profiles, "Player"); skate3::EnsureUsableProfileStore(profiles, "Player");
if (auto* profile = skate3::FindSelectedProfile(profiles)) { if (auto* profile = skate3::FindSelectedProfile(profiles)) {
skate3::ApplyProfileCvars(*profile); skate3::ApplyProfileCvars(*profile);
ApplyDemoPathProfileOverride();
} }
const bool has_config_file = std::filesystem::exists(defaults.config_path); const bool has_config_file = std::filesystem::exists(defaults.config_path);
@ -727,6 +739,7 @@ void Skate3BaseApp::OnPostSetup() {
#endif #endif
auto* dispatcher = runtime()->function_dispatcher(); auto* dispatcher = runtime()->function_dispatcher();
skate3::demo_path::InstallHooks(dispatcher);
if (dispatcher->InitializeFunctionTable(eawebkit_PPCImageConfig.code_base, if (dispatcher->InitializeFunctionTable(eawebkit_PPCImageConfig.code_base,
eawebkit_PPCImageConfig.code_size, eawebkit_PPCImageConfig.code_size,
eawebkit_PPCImageConfig.image_base, eawebkit_PPCImageConfig.image_base,
@ -797,6 +810,7 @@ void Skate3BaseApp::ToggleSimpleSettings() {
store.selected_profile = profile.id; store.selected_profile = profile.id;
skate3::SaveProfiles(profiles_path_, store); skate3::SaveProfiles(profiles_path_, store);
skate3::ApplyProfileCvars(profile); skate3::ApplyProfileCvars(profile);
ApplyDemoPathProfileOverride();
ApplySelectedProfileToRuntime(); ApplySelectedProfileToRuntime();
}; };
auto close_settings = [this]() { ApplyGameplayCursorMode(); }; auto close_settings = [this]() { ApplyGameplayCursorMode(); };
@ -984,6 +998,7 @@ void Skate3BaseApp::ApplySelectedProfileToRuntime() {
auto profiles = skate3::LoadProfiles(profiles_path_); auto profiles = skate3::LoadProfiles(profiles_path_);
if (auto* profile = skate3::FindSelectedProfile(profiles)) { if (auto* profile = skate3::FindSelectedProfile(profiles)) {
skate3::ApplyProfileCvars(*profile); skate3::ApplyProfileCvars(*profile);
ApplyDemoPathProfileOverride();
runtime()->kernel_state()->user_profile()->SetIdentity(profile->xuid, profile->gamertag); runtime()->kernel_state()->user_profile()->SetIdentity(profile->xuid, profile->gamertag);
} }
} }

212
src/skate3_demo_path.cpp Normal file
View file

@ -0,0 +1,212 @@
#include "skate3_demo_path.h"
#include "generated/skate3_init.h"
#include <atomic>
#include <cstdint>
#include <limits>
#include <rex/cvar.h>
#include <rex/input/input.h>
#include <rex/kernel/xam/input_injection.h>
#include <rex/logging.h>
#include <rex/ppc/context.h>
#include <rex/system/function_dispatcher.h>
#if defined(_WIN32)
#include <Windows.h>
#endif
REXCVAR_DEFINE_BOOL(skate3_demo_path, false, "Skate 3",
"Probe and automate the boot path to gameplay");
REXCVAR_DEFINE_BOOL(skate3_demo_path_probe, false, "Skate 3",
"Log Skate 3 boot/frontend states used by the demo path");
namespace skate3::demo_path {
namespace {
constexpr uint32_t kFrontEndStatePressStart = 24;
constexpr uint32_t kFrontEndStateLanguageSelect = 47;
std::atomic<uint32_t> g_last_requested_state{0};
std::atomic<bool> g_seen_language_update{false};
std::atomic<uint32_t> g_last_language_select_event{std::numeric_limits<uint32_t>::max()};
std::atomic<uint32_t> g_last_press_start_event{std::numeric_limits<uint32_t>::max()};
std::atomic<uint32_t> g_automation_stage{0};
std::atomic<bool> g_skip_intro_movie{false};
std::atomic<bool> g_logged_intro_movie_skip{false};
std::atomic<bool> g_f10_poll_was_down{false};
bool ProbeEnabled() {
return rex::cvar::Query<bool>("skate3_demo_path") ||
rex::cvar::Query<bool>("skate3_demo_path_probe");
}
bool AutomationEnabled() {
return rex::cvar::Query<bool>("skate3_demo_path");
}
const char* KnownFrontEndStateName(uint32_t state_id) {
switch (state_id) {
case kFrontEndStatePressStart:
return "press-start";
case kFrontEndStateLanguageSelect:
return "language-select";
default:
return "unknown";
}
}
void LogBootFlowEventChange(std::atomic<uint32_t>& last_event, const char* name, uint32_t event,
uint32_t state_this) {
if (!ProbeEnabled()) {
return;
}
uint32_t previous = last_event.load(std::memory_order_relaxed);
while (previous != event) {
if (last_event.compare_exchange_weak(previous, event, std::memory_order_relaxed)) {
REXLOG_INFO("Skate 3 demo path: {} event={} this=0x{:08X}", name, event, state_this);
return;
}
}
}
void QueueLanguageAcceptIfNeeded() {
if (!AutomationEnabled()) {
return;
}
uint32_t expected = 0;
if (!g_automation_stage.compare_exchange_strong(expected, 1, std::memory_order_relaxed)) {
return;
}
rex::kernel::xam::QueueSyntheticInput(rex::input::X_INPUT_GAMEPAD_A, 8);
REXLOG_INFO("Skate 3 demo path: queued language-select A pulse");
}
void EnableTitleStartAutoTapIfNeeded(const char* reason) {
if (!AutomationEnabled()) {
return;
}
uint32_t stage = g_automation_stage.load(std::memory_order_relaxed);
while (stage < 2) {
if (g_automation_stage.compare_exchange_weak(stage, 2, std::memory_order_relaxed)) {
rex::kernel::xam::SetSyntheticAutoTap(rex::input::X_INPUT_GAMEPAD_START, true);
REXLOG_INFO("Skate 3 demo path: enabled press-start auto-tap ({})", reason);
return;
}
}
}
void EnableIntroMovieSkipIfNeeded() {
if (!AutomationEnabled()) {
return;
}
bool expected = false;
if (g_skip_intro_movie.compare_exchange_strong(expected, true, std::memory_order_relaxed)) {
REXLOG_INFO("Skate 3 demo path: enabled intro movie completion override");
}
}
void PollF10Marker() {
#if defined(_WIN32)
if (!ProbeEnabled()) {
return;
}
const bool f10_down = (GetAsyncKeyState(VK_F10) & 0x8000) != 0;
const bool was_down = g_f10_poll_was_down.exchange(f10_down, std::memory_order_relaxed);
if (f10_down && !was_down) {
REXLOG_WARN("Skate 3 demo path: polled F10 milestone marker");
}
#endif
}
extern "C" REX_FUNC(Skate3DemoPath_SetFrontEndStateHook) {
const uint32_t manager = ctx.r3.u32;
const uint32_t state_id = ctx.r4.u32;
const uint32_t mode = ctx.r5.u32;
const uint32_t caller_lr = ctx.lr;
if (ProbeEnabled()) {
g_last_requested_state.store(state_id, std::memory_order_relaxed);
REXLOG_INFO(
"Skate 3 demo path: FE SetState state={} ({}) mode={} manager=0x{:08X} lr=0x{:08X}",
state_id, KnownFrontEndStateName(state_id), mode, manager, caller_lr);
}
sub_82D0AFA0(ctx, base);
}
extern "C" REX_FUNC(Skate3DemoPath_LanguageSelectStateHook) {
PollF10Marker();
LogBootFlowEventChange(g_last_language_select_event, "BootFlow LanguageSelectState",
ctx.r4.u32, ctx.r3.u32);
if (ctx.r4.u32 == 4) {
EnableIntroMovieSkipIfNeeded();
}
sub_826FDD70(ctx, base);
}
extern "C" REX_FUNC(Skate3DemoPath_ShowPressStartModeHook) {
PollF10Marker();
LogBootFlowEventChange(g_last_press_start_event, "BootFlow ShowPressStartMode", ctx.r4.u32,
ctx.r3.u32);
if (ctx.r4.u32 == 1) {
g_skip_intro_movie.store(false, std::memory_order_relaxed);
EnableTitleStartAutoTapIfNeeded("press-start state");
}
sub_826FE1D8(ctx, base);
}
extern "C" REX_FUNC(Skate3DemoPath_LanguageSelectUpdateHook) {
PollF10Marker();
if (ProbeEnabled()) {
bool expected = false;
if (g_seen_language_update.compare_exchange_strong(expected, true,
std::memory_order_relaxed)) {
REXLOG_INFO(
"Skate 3 demo path: FrontEndState_LanguageSelect::OnUpdate first seen "
"dt={} this=0x{:08X}",
ctx.r4.u32, ctx.r3.u32);
QueueLanguageAcceptIfNeeded();
}
}
sub_82639400(ctx, base);
}
} // namespace
void InstallHooks(rex::runtime::FunctionDispatcher* dispatcher) {
if (!dispatcher || !ProbeEnabled()) {
return;
}
dispatcher->SetFunction(0x82D0AFA0, &Skate3DemoPath_SetFrontEndStateHook);
dispatcher->SetFunction(0x826FDD70, &Skate3DemoPath_LanguageSelectStateHook);
dispatcher->SetFunction(0x826FE1D8, &Skate3DemoPath_ShowPressStartModeHook);
dispatcher->SetFunction(0x82639400, &Skate3DemoPath_LanguageSelectUpdateHook);
REXLOG_INFO("Skate 3 demo path: frontend probe hooks installed");
}
bool ShouldForceIntroMovieComplete() {
if (!AutomationEnabled() || !g_skip_intro_movie.load(std::memory_order_relaxed)) {
return false;
}
bool expected = false;
if (g_logged_intro_movie_skip.compare_exchange_strong(expected, true,
std::memory_order_relaxed)) {
REXLOG_INFO("Skate 3 demo path: forcing frontend intro movie complete");
}
return true;
}
} // namespace skate3::demo_path

12
src/skate3_demo_path.h Normal file
View file

@ -0,0 +1,12 @@
#pragma once
namespace rex::runtime {
class FunctionDispatcher;
}
namespace skate3::demo_path {
void InstallHooks(rex::runtime::FunctionDispatcher* dispatcher);
bool ShouldForceIntroMovieComplete();
} // namespace skate3::demo_path

@ -1 +1 @@
Subproject commit 4e468082dce6f5cf32cad0ce2622abd6468e1219 Subproject commit 8ce31c280a92a041474e95db1a6958c5b1158537