mirror of
https://github.com/Dingo-Shenanigans/ReSkate.git
synced 2026-10-29 03:46:50 +01:00
skater.pose changes the skater relative to the game's animation. Some of those changes were applied twice every third frame, so a posed skater shook in a three-frame rhythm, and their board with them. The more a plugin changed, the worse it got: an avatar with the hips scaled and offsets on most of the body needed workarounds in its plugin to hold still. Now every pose is applied exactly once, and a plugin can give skater.pose the values it means. Why: ReSkate draws a pose by writing it into the game's bone buffer just before the render, and the game evaluates the next frame from that same buffer without overwriting all of it. Two of three frames it resets the buffer itself; in the third the pose drawn was still there and the next pose went on top of it. Now the game's own values go back as soon as the renderer has its copy, so the game, the body physics and the next animation update only ever see the game's pose. This replaces own_scale, the earlier fix for the same cause (the Filmer's forearm at 0.001 shrinking to nothing). It covered only scale, and put it back after the game had already run on the posed values, so rotations and offsets still doubled. The new way covers every part of every joint, for pose, carry and reach, with nothing to track per joint and nothing to clean up after a pose ends. Checked in the game: the avatar with the hips at 0.667 and offsets on 48 joints holds still with only skater.pose (shoulder distance 0.117 m every frame, where it was 0.117 0.117 0.067 ...). The Filmer 1.0.0's camcorder preview looks and measures as on main, and the skater is back to normal after it. Details: the renderer copies the bones in submit_render_pose (0x4c7e7a0), which the render pose caller (0x2c20d99) calls right after render_pose with the same render data; found with a guard page on the bone buffer that logged every access with its call stack. native_skater hooks it and, for the render data the pose override just wrote, calls the new set_pose_override_drawn_listener. skater_animation keeps what on_render wrote over and puts it back in on_drawn. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
128 lines
6.0 KiB
C++
128 lines
6.0 KiB
C++
#pragma once
|
|
#include "native_skater.h"
|
|
#include "Engine/Core/Platform/memory.h"
|
|
#include "Engine/Game/Build/addresses.h"
|
|
#include "Engine/Game/Build/20260929/native_skater.h"
|
|
#include "Engine/Game/Build/20260929/skater_entities.h"
|
|
#include <Windows.h>
|
|
#include <array>
|
|
#include <atomic>
|
|
#include <cstddef>
|
|
#include <cstdint>
|
|
#include <format>
|
|
#include <mutex>
|
|
#include <optional>
|
|
#include <source_location>
|
|
#include <stdexcept>
|
|
#include <string>
|
|
#include <vector>
|
|
|
|
// Remote actor state shared by native_skater.cpp and native_skater_spawn.cpp.
|
|
namespace dingosdk::multiplayer::native_skater_detail {
|
|
namespace entities = addr::skater_entities;
|
|
namespace native = addr::native_skater;
|
|
struct Unavailable : std::runtime_error {
|
|
using std::runtime_error::runtime_error;
|
|
};
|
|
void require(bool value, const char *reason);
|
|
bool readable(std::uintptr_t address, void *out, std::size_t size);
|
|
template <class T>
|
|
T read(std::uintptr_t object, std::uintptr_t offset = 0,
|
|
const std::source_location &location = std::source_location::current()) {
|
|
T value{};
|
|
if (object < 0x10000 || object > memory::highest_user_address ||
|
|
offset > memory::highest_user_address - object ||
|
|
!readable(object + offset, &value, sizeof(value)))
|
|
throw Unavailable(
|
|
std::format("Native skater read failed: object={:#x}, offset={:#x}, bytes={}, line={}.", object,
|
|
offset, sizeof(value), location.line()));
|
|
return value;
|
|
}
|
|
std::uintptr_t ptr(std::uintptr_t object, std::uintptr_t offset = 0,
|
|
const std::source_location &location = std::source_location::current());
|
|
bool write(std::uintptr_t address, const void *data, std::size_t size);
|
|
struct alignas(16) NativeBone {
|
|
std::array<float, 4> scale, rotation, position;
|
|
};
|
|
static_assert(sizeof(NativeBone) == 0x30);
|
|
using AnimationUpdate = void (*)(std::uintptr_t, std::uintptr_t);
|
|
using DestroyEntity = void (*)(std::uintptr_t, std::uintptr_t);
|
|
using RenderPose = bool (*)(std::uintptr_t, std::uintptr_t);
|
|
using SubmitRenderPose = std::uintptr_t (*)(std::uintptr_t, std::uintptr_t);
|
|
struct HookState {
|
|
std::uintptr_t base{};
|
|
DWORD engine_thread{};
|
|
bool hooks{}, install_attempted{};
|
|
AnimationUpdate original_animation{};
|
|
DestroyEntity original_destroy{};
|
|
RenderPose original_render_pose{};
|
|
SubmitRenderPose original_submit_render_pose{};
|
|
std::atomic<EntityDestroyed> destroyed_listener{};
|
|
std::atomic<AnimationEvaluated> evaluated_listener{};
|
|
std::atomic<LocalPoseFilter> pose_filter{};
|
|
std::atomic<LocalSentPoseFilter> sent_pose_filter{};
|
|
// A pose override of the local skater (Extension/Skater/skater_animation.cpp), after the one above.
|
|
std::atomic<AnimationEvaluated> override_listener{};
|
|
std::atomic<RenderPosePublished> override_render_listener{};
|
|
std::atomic<RenderPosePublished> override_drawn_listener{};
|
|
std::atomic<BoardPosePublished> override_board_listener{};
|
|
std::atomic<RenderPosePublished> render_listener{};
|
|
};
|
|
// Keys the per-entity hooks match, one contiguous array per kind in slot order.
|
|
// Every native skater, board and entity callback looks its key up here: a few
|
|
// cache lines up to the highest slot ever watched, not one Remote per slot.
|
|
using WatchedArray = std::array<std::atomic<std::uintptr_t>, max_remote_players>;
|
|
struct WatchedKeys {
|
|
WatchedArray component{}, entity{}, board{}, board_holder{};
|
|
// One past the highest slot that has stored a key. Only grows, and grows
|
|
// before the key is stored, so a lookup never stops short of a watched key.
|
|
std::atomic<std::size_t> bound{};
|
|
};
|
|
WatchedKeys &watched();
|
|
// Stores key for the current peer slot (0 stops watching).
|
|
void watch(WatchedArray &keys, std::uintptr_t key) noexcept;
|
|
// The slot whose key equals key, or max_remote_players.
|
|
std::size_t watching(const WatchedArray &keys, std::uintptr_t key) noexcept;
|
|
struct Remote {
|
|
std::uintptr_t entity{}, component{}, parent{}, context{}, local_entity{};
|
|
std::uintptr_t board_entity{}, board_component{}, board_parent{};
|
|
bool failed{};
|
|
std::mutex mutex;
|
|
Pose target;
|
|
// Bumped whenever target is replaced. The skeleton buffer last written and the
|
|
// target revision it holds let an animation callback that did not re-evaluate
|
|
// the native graph skip writing the same pose again.
|
|
std::uint64_t target_revision{}, written_revision{};
|
|
std::uintptr_t written_buffer{};
|
|
// Bumped whenever this slot's actor is created, removed or destroyed, so state
|
|
// cached for one actor (its sound binding) is never used for the next.
|
|
std::atomic<std::uint64_t> generation{};
|
|
// Frozen with entity placement, independent of newer network snapshots.
|
|
std::vector<Transform> board_render_pose;
|
|
std::atomic<bool> board_ready{};
|
|
std::atomic<std::uint64_t> applied{};
|
|
std::atomic<std::uint64_t> board_applied{};
|
|
std::atomic<bool> pose_driven{};
|
|
std::atomic<std::uint64_t> native_evaluated{}, native_skipped{}, native_evaluation_us{}, apply_us{};
|
|
ULONGLONG next_native_animation{}, next_safety_audit{}, next_entity_audit{},
|
|
next_board_safety_audit{}, next_board_status{}, next_status{};
|
|
// A skateboard that went away is not created again before this: one creation is a heavy
|
|
// native pass, and a player's board may come and go with every pose.
|
|
ULONGLONG next_board_create{};
|
|
bool board_failed{};
|
|
std::string board_issue;
|
|
std::string board_status;
|
|
std::string issue;
|
|
std::optional<CosmeticRecipe> skater_appearance, board_appearance;
|
|
// The last appearance the native copy or catalog check rejected, retried at
|
|
// next_appearance_retry instead of on every cosmetics pass. Client thread.
|
|
std::optional<Appearance> rejected_appearance;
|
|
ULONGLONG next_appearance_retry{};
|
|
};
|
|
HookState &shared();
|
|
Remote &remote();
|
|
void install(std::uintptr_t base);
|
|
void place_actor(std::uintptr_t base, std::uintptr_t entity, const Transform &root);
|
|
void place_board(std::uintptr_t base, std::uintptr_t entity, const Transform &root);
|
|
} // namespace dingosdk::multiplayer::native_skater_detail
|