mirror of
https://github.com/Dingo-Shenanigans/ReSkate.git
synced 2026-10-25 02:36:23 +01:00
Resolved where main's trainer meets the plugin branch: - Board Break, the drone, Road Rash, Hall of Meat and Grind Exits are plugins here, so their built-in code stays deleted: the trainer no longer hands Board Break's wear values back (the plugin does not tune physics), its QUICK SETTINGS list no longer names Grind Exits or Board Break, and the drone's debug action and menu are not brought back. - The trainer's pump power, gravity, revert boost and transition help run beside the plugins' pushes and velocities on each physics step; both sides' new declarations are kept. - The game's own pad reads go through game_pad_read (the gamepad filter, Dark Pop's D-pad hiding, the grind sticks) and then the plugin branch's menu and hidden-button handling. - The trainer's forced grind settings are noted as the stance like any other value.
283 lines
15 KiB
C++
283 lines
15 KiB
C++
#pragma once
|
|
#include "Engine/Core/Platform/memory.h"
|
|
#include "Engine/Game/Build/supported_build.h"
|
|
#include "Engine/Game/Build/addresses.h"
|
|
#include "Engine/Game/Build/20260929/client_source_spawn.h"
|
|
#include "Engine/Game/Skater/first_person_spring.h"
|
|
#include "Extension/UI/Overlay/overlay.h"
|
|
#include <Windows.h>
|
|
#include <algorithm>
|
|
#include <array>
|
|
#include <atomic>
|
|
#include <cstddef>
|
|
#include <cstdint>
|
|
#include <cstring>
|
|
#include <mutex>
|
|
#include <optional>
|
|
#include <string>
|
|
#include <vector>
|
|
|
|
// Shared by the debug camera, noclip, first-person and party spectate controls:
|
|
// guarded native readers, engine-thread state and the native camera path.
|
|
namespace dingosdk::client_source::detail {
|
|
namespace spawn = addr::client_source_spawn;
|
|
constexpr std::uintptr_t source_highest = memory::highest_user_address;
|
|
using SourceCameraMode = void (*)(std::uintptr_t, std::uint32_t);
|
|
using SourceCameraTransform = void (*)(std::uintptr_t, const std::array<float, 16>*);
|
|
using SourcePhysicsUpdate = void (*)(std::uintptr_t);
|
|
using SourceSkaterMotion = void (*)(std::uintptr_t, std::uintptr_t, const std::array<float,16>*, std::uint8_t);
|
|
struct SourceGuard { const char* message; };
|
|
inline void source_require(bool value, const char* message) { if (!value) throw SourceGuard{message}; }
|
|
inline bool source_range(std::uintptr_t value, std::size_t size) {
|
|
return value >= 0x10000 && size && value <= source_highest - size;
|
|
}
|
|
inline bool source_object(std::uintptr_t value) { return source_range(value, 8) && value % 8 == 0; }
|
|
struct SourceLastError {
|
|
DWORD value{GetLastError()};
|
|
~SourceLastError() { SetLastError(value); }
|
|
};
|
|
// Releases SourceState::busy after a successful test_and_set.
|
|
struct SourceBusyScope {
|
|
std::atomic_flag& busy;
|
|
~SourceBusyScope() { busy.clear(std::memory_order_release); }
|
|
};
|
|
struct SourceWatch {
|
|
std::uintptr_t address{};
|
|
std::size_t size{};
|
|
std::array<unsigned char, 16> bytes{};
|
|
};
|
|
class SourceReader {
|
|
public:
|
|
// A guarded same-process copy. The camera, skater and physics chains are
|
|
// read (and verified) every frame, often several times: a system call per
|
|
// field added up to thousands per frame.
|
|
bool raw(std::uintptr_t address, void* result, std::size_t size) const {
|
|
if (!source_range(address, size) || size > 4096) return false;
|
|
return memory::peek_bytes(address, result, size);
|
|
}
|
|
template<class T> T value(std::uintptr_t address, std::uintptr_t offset = 0) {
|
|
static_assert(sizeof(T) <= 16);
|
|
source_require(source_range(address, 1) && offset <= source_highest - address,
|
|
"Source metadata pointer rejected.");
|
|
address += offset;
|
|
T result{};
|
|
source_require(raw(address, &result, sizeof(result)), "Source metadata read unavailable.");
|
|
source_require(watches_.size() < 24000, "Source read bound exceeded.");
|
|
// One allocation for a whole chain (a camera snapshot watches ~60 fields).
|
|
if (watches_.empty()) watches_.reserve(128);
|
|
SourceWatch watch{address, sizeof(result)};
|
|
std::memcpy(watch.bytes.data(), &result, sizeof(result));
|
|
watches_.push_back(watch);
|
|
return result;
|
|
}
|
|
std::uintptr_t pointer(std::uintptr_t address, std::uintptr_t offset = 0) {
|
|
return value<std::uintptr_t>(address, offset);
|
|
}
|
|
std::size_t count(std::uintptr_t begin, std::uintptr_t end, std::size_t stride) const {
|
|
source_require(begin <= end && end <= source_highest && begin % 8 == 0 && end % 8 == 0 &&
|
|
(!end || begin >= 0x10000) && (end - begin) % stride == 0 && (end - begin) / stride <= 64,
|
|
"Source collection bound rejected.");
|
|
return static_cast<std::size_t>((end - begin) / stride);
|
|
}
|
|
void verify() const {
|
|
for (const auto& watch : watches_) {
|
|
std::array<unsigned char, 16> after{};
|
|
source_require(raw(watch.address, after.data(), watch.size) &&
|
|
std::memcmp(after.data(), watch.bytes.data(), watch.size) == 0,
|
|
"Source metadata changed during observation.");
|
|
}
|
|
}
|
|
private:
|
|
std::vector<SourceWatch> watches_;
|
|
};
|
|
struct SourceCameraIdentity {
|
|
std::uintptr_t context{}, manager{}, controller{}, selector{}, node{}, camera{}, view{}, view_manager{};
|
|
std::array<std::uintptr_t, 2> helpers{};
|
|
std::uint32_t priority{};
|
|
bool operator==(const SourceCameraIdentity&) const = default;
|
|
};
|
|
// Revert Boost strength multiplier range (the saved value; the menu slider ends at x5, a typed value can go higher).
|
|
inline constexpr float revert_boost_strength_min = 0.5f, revert_boost_strength_max = 100.0f;
|
|
struct InteractiveDebug {
|
|
bool camera_owned{}, camera_ambiguous{}, ui_owned{};
|
|
bool park_editor{}, editor_transition{}, editor_previous_camera{}, editor_previous_ui{}, editor_previous_noclip{};
|
|
bool editor_previous_first_person{};
|
|
// Shares Freecam's owned mode-1 camera; each tick publishes the head pose
|
|
// instead of a flight step.
|
|
bool first_person{}, first_person_waiting{};
|
|
// "Third person on foot": first person handed the camera back while the
|
|
// skater walks and takes it again once they are back on the board.
|
|
bool first_person_paused{}, first_person_last_on_foot{};
|
|
ULONGLONG first_person_foot_since{}, first_person_retry_after{};
|
|
// "First person in the camera cycle": after first person could not start, the camera height
|
|
// button switches height as the game does until this time.
|
|
ULONGLONG first_person_cycle_retry_after{};
|
|
// FreeCamera vertical FOV (+0xac): chosen value (0 = unchanged) and the value
|
|
// it held before first person took it, restored when first person ends.
|
|
float first_person_fov{}, first_person_saved_fov{};
|
|
// Freecam FOV (0 = the game's own) and the camera's own value, put back
|
|
// when Freecam ends or the setting returns to the default.
|
|
float free_camera_fov{}, free_camera_saved_fov{};
|
|
first_person::Settings first_person_settings;
|
|
SourceCameraIdentity camera_identity;
|
|
std::uintptr_t ui_object{};
|
|
std::uint8_t ui_original{}, ui_applied{};
|
|
DWORD thread{};
|
|
std::array<float, 16> flight_matrix{};
|
|
float flight_speed = 15.0f;
|
|
double flight_time{};
|
|
bool flight_ready{}, noclip{}, no_bail{};
|
|
std::uintptr_t noclip_entity{};
|
|
struct VelocityRequest {
|
|
std::uintptr_t client{}, entity{}, core{};
|
|
ULONGLONG expires{};
|
|
std::array<float, 3> velocity{};
|
|
bool valid{};
|
|
bool applied{};
|
|
} noclip_velocity;
|
|
VelocityRequest forward_velocity;
|
|
VelocityRequest up_velocity;
|
|
VelocityRequest offboard_up_velocity;
|
|
std::uint64_t noclip_velocity_updates{}, noclip_motion_updates{};
|
|
std::uint64_t forward_velocity_updates{};
|
|
float forward_velocity_speed = 0.0f; // 0: the boost is off, as the game shipped
|
|
std::uint64_t up_velocity_updates{};
|
|
std::uint64_t offboard_up_velocity_updates{};
|
|
float up_velocity_speed = 0.0f;
|
|
float offboard_up_velocity_speed = 0.0f;
|
|
// Revert Boost (see revert_boost_tick in client_debug.cpp). Both are saved in the local profile.
|
|
bool revert_boost{};
|
|
float revert_boost_speed = 1.0f; // strength multiplier, revert_boost_strength_min..max
|
|
std::uint64_t revert_boost_count{};
|
|
struct RevertTrack {
|
|
bool primed{}; // the landing count has been read once since Revert Boost was turned on
|
|
std::uint64_t seen{}; // the last landing (RevertLanding::sequence) handled
|
|
ULONGLONG cooldown_until{};
|
|
} revert_track;
|
|
// Dark Pop (see dark_pop_tick and probe_tick in client_debug.cpp).
|
|
bool dark_pop{};
|
|
bool dark_pop_unpatch{true}; // placement entered the pre-0.32.0 way at the dark landing (no_bail.cpp)
|
|
bool dark_pop_hold{true}; // the old hold (foot-attach mode 2) in the landing window
|
|
bool dark_pop_one_press{true}; // no re-arming until the D-pad has been let go after a landing
|
|
float dark_pop_route_stay{0.0f}; // ticks of 601 before the stay bit is cancelled (the sink length; 1.9: 0 by default)
|
|
float dark_pop_route_guard{12.0f}; // pop ticks guarded after a 601 exit
|
|
bool dark_pop_keep_speed{true}; // the landing's horizontal speed put back at the 601 exit
|
|
bool dark_pop_catch{true}; // 1.0: a dismount straight from the air on an armed dark flight is routed too
|
|
bool dark_pop_feet{false}; // 1.7 (1.9: off by default): the plant's own foot attaches cleared in the sink, a two-tick one requested
|
|
bool dark_pop_adaptive{true}; // 1.7: the sink ends the tick the board starts rolling over
|
|
bool dark_pop_timeout{true}; // 1.9: a landing 201 timed out on (board dark, resting) is routed to the sink too
|
|
float dark_pop_strength{8.0f}; // 2.4: the launch (m/s) the bodies' vertical velocity is lifted to on the pop's second and third ticks; 0 = off
|
|
bool dark_pop_hold_landing{true}; // 2.0: +0x13cc bit 20 raised around the chooser so a timed-out landing can be staged
|
|
bool dark_pop_dark_exit{true}; // 2.1: the board put back as it landed on the sink's exit tick
|
|
bool dark_pop_full_flight{true}; // 2.1: the landing countdown floored at the ballistic time on a pop's flight
|
|
bool dark_pop_hold_flight{true}; // 2.3: contact landings blocked on a pop's flight while the countdown runs
|
|
float dark_pop_stay_physical{6.0f}; // 2.5: ticks after the exit the pop stays in the physics air state; 0 = off
|
|
bool dark_pop_early_physics{true}; // 2.6: a pop whose jump fired on its first ticks stays physical for the whole flight
|
|
bool dark_pop_reaim{true};
|
|
bool dark_pop_hide_dpad{true}; // 3.1: in the air the game's own polls see the D-pad let go // 2.7: after Stay physical ticks the flight curve is re-aimed from the body and handed to the animation state
|
|
std::uint64_t dark_pop_route_seen{};
|
|
std::uint64_t dark_pop_count{}, dark_pop_seen{}, dark_pop_landings_seen{};
|
|
bool probe{}; // see probe_tick in client_debug.cpp
|
|
float noclip_altitude{};
|
|
bool noclip_altitude_valid{}, noclip_altitude_offboard{};
|
|
// Player choices kept in the local profile (see load_saved_debug):
|
|
// loaded once, re-applied after level-load resets, saved shortly after a
|
|
// change so dragging a slider does not write every frame.
|
|
bool saved_loaded{}, save_pending{};
|
|
ULONGLONG save_due{};
|
|
// A saved No Bail comes back once the player controls a skater again: it
|
|
// owns native skater state, and a level load needs that released first.
|
|
bool no_bail_restore{};
|
|
ULONGLONG no_bail_restore_after{};
|
|
std::string status;
|
|
};
|
|
// Engine-thread state for the interactive debug controls and the native camera
|
|
// functions they call.
|
|
struct SourceTrial {
|
|
InteractiveDebug debug;
|
|
std::uintptr_t base{};
|
|
SourceCameraMode camera_mode{};
|
|
SourceCameraTransform camera_transform{};
|
|
};
|
|
struct SourceState {
|
|
std::mutex initialization_mutex;
|
|
std::atomic<bool> initialized{};
|
|
std::atomic_flag busy = ATOMIC_FLAG_INIT;
|
|
bool velocity_guard_attempted{}; // Protected by initialization_mutex.
|
|
std::atomic<bool> velocity_guard_active{};
|
|
std::atomic<bool> free_camera_active{};
|
|
std::atomic<std::uintptr_t> offboard_boost_core{};
|
|
std::atomic<SourcePhysicsUpdate> velocity_update_original{};
|
|
std::atomic<SourceSkaterMotion> motion_original{};
|
|
SourceTrial trial;
|
|
};
|
|
struct SourceCameraSnapshot {
|
|
SourceCameraIdentity identity;
|
|
std::uintptr_t active{};
|
|
std::uint32_t mode{};
|
|
std::uint8_t input_active{}, view_enabled{};
|
|
};
|
|
struct NoclipBodies {
|
|
std::uintptr_t core{}, context{}, rig_wrapper{};
|
|
float seconds{}, board_height{};
|
|
bool offboard{};
|
|
bool wipeout{}; // Spread-eagle/torpedo use the native wipeout physics path.
|
|
std::array<float, 3> root{}; // Entity root: the camera target and the idle motion target.
|
|
std::array<std::uintptr_t, 32> parts{}; // Board 0..8, native skeleton velocity parts 1..23.
|
|
};
|
|
constexpr std::uintptr_t camera_fov_offset = 0xac;
|
|
constexpr float first_person_fov_min = 40.0f, first_person_fov_max = 120.0f;
|
|
struct FirstPersonArm {
|
|
std::atomic<DWORD> thread{};
|
|
std::uintptr_t component{}, camera{};
|
|
SourceCameraTransform transform{};
|
|
std::array<float, 16> matrix{};
|
|
float fov{};
|
|
first_person::Settings settings;
|
|
first_person::FrameSpring spring;
|
|
// Head poses captured right after each animation update of `watched`, on
|
|
// whichever thread ran it. Captures only read memory; the camera is always
|
|
// written by the engine thread.
|
|
struct Snapshot { std::array<float, 16> head{}; first_person::Vec3 origin{}; double time{}; };
|
|
std::mutex snapshot_mutex;
|
|
Snapshot latest;
|
|
std::uint64_t captures{};
|
|
std::atomic<std::uintptr_t> watched{}, watched_base{};
|
|
};
|
|
|
|
// client_source_spawn.cpp
|
|
SourceState& source_state();
|
|
bool source_writable(std::uintptr_t address, std::size_t size);
|
|
// source_spawn_camera.cpp
|
|
SourceCameraSnapshot source_camera_snapshot(const SourceTrial& trial, std::uintptr_t client);
|
|
std::array<float, 16> debug_view_matrix(const SourceTrial& trial, const SourceCameraSnapshot& camera);
|
|
// client_debug.cpp
|
|
double source_flight_now();
|
|
std::array<float, 16> debug_skater(std::uintptr_t base, std::uintptr_t client, overlay::DebugModel& model);
|
|
// client_noclip.cpp
|
|
void debug_stop_noclip(InteractiveDebug& debug) noexcept;
|
|
NoclipBodies debug_noclip_bodies(std::uintptr_t base, std::uintptr_t client, std::uintptr_t entity);
|
|
// client_first_person.cpp
|
|
FirstPersonArm& first_person_arm();
|
|
void first_person_disarm() noexcept;
|
|
bool first_person_read(std::uintptr_t address, void* out, std::size_t size) noexcept;
|
|
bool first_person_write_fov(std::uintptr_t camera, float fov) noexcept;
|
|
void first_person_restore_fov(InteractiveDebug& debug) noexcept;
|
|
void free_camera_restore_fov(InteractiveDebug& debug) noexcept;
|
|
std::uintptr_t first_person_component(std::uintptr_t base, std::uintptr_t client);
|
|
// Whether the local skater is walking (physics state Offboard); empty if unreadable.
|
|
std::optional<bool> first_person_on_foot(std::uintptr_t base, std::uintptr_t client) noexcept;
|
|
first_person::Vec3 first_person_head_matrix(std::uintptr_t base, std::uintptr_t component, std::array<float, 16>& matrix);
|
|
void first_person_on_render(std::uintptr_t animation_interface) noexcept;
|
|
void first_person_on_animation(std::uintptr_t component) noexcept;
|
|
|
|
template<class T> void debug_write(std::uintptr_t address, T value) {
|
|
SIZE_T written{};
|
|
source_require(source_writable(address, sizeof(T)) &&
|
|
WriteProcessMemory(GetCurrentProcess(), reinterpret_cast<void*>(address), &value, sizeof(T), &written) &&
|
|
written == sizeof(T), "Debug setting write failed.");
|
|
SourceReader reader;
|
|
source_require(reader.value<T>(address) == value, "Debug setting write could not be verified.");
|
|
}
|
|
}
|