Files

359 lines
13 KiB
C++

// SPDX-License-Identifier: GPL-3.0-or-later
#include "mouse_camera.h"
#include "input.h"
#include "mouse_turn.h"
#include "../engine/engine_hooks.h"
#include "../engine/online_fixes.h"
#include <atomic>
#include <chrono>
#include <cmath>
#include <cstring>
#include <iterator>
#include <mutex>
#ifdef _WIN32
#define LANTERN_STUB_SYMBOL
#else
#define LANTERN_STUB_SYMBOL __attribute__((visibility("hidden")))
#endif
extern "C" {
LANTERN_STUB_SYMBOL std::uint64_t lantern_free_camera_resume = 0;
LANTERN_STUB_SYMBOL std::uint8_t lantern_free_camera_ran = 0;
LANTERN_STUB_SYMBOL __attribute__((sysv_abi)) void lantern_free_camera_turn(std::uint8_t* camera,
std::uint8_t* had_input,
float stick_pitch, float stick_yaw);
void lantern_free_camera_site();
void lantern_fast_turn_exit();
}
asm(R"(
.text
.p2align 4
.globl lantern_free_camera_site
lantern_free_camera_site:
vmovss %xmm0, 0x140(%r13)
vextractps $1, %xmm1, 0x144(%r13)
push %rbx
push %rax
push %rcx
push %rdx
push %rsi
push %rdi
push %r8
push %r9
push %r10
push %r11
mov %rsp, %rbx
and $-32, %rsp
sub $512, %rsp
vmovdqu %ymm0, 0x000(%rsp)
vmovdqu %ymm1, 0x020(%rsp)
vmovdqu %ymm2, 0x040(%rsp)
vmovdqu %ymm3, 0x060(%rsp)
vmovdqu %ymm4, 0x080(%rsp)
vmovdqu %ymm5, 0x0a0(%rsp)
vmovdqu %ymm6, 0x0c0(%rsp)
vmovdqu %ymm7, 0x0e0(%rsp)
vmovdqu %ymm8, 0x100(%rsp)
vmovdqu %ymm9, 0x120(%rsp)
vmovdqu %ymm10, 0x140(%rsp)
vmovdqu %ymm11, 0x160(%rsp)
vmovdqu %ymm12, 0x180(%rsp)
vmovdqu %ymm13, 0x1a0(%rsp)
vmovdqu %ymm14, 0x1c0(%rsp)
vmovdqu %ymm15, 0x1e0(%rsp)
mov %r13, %rdi
lea -0x3c4(%rbp), %rsi
vmovaps %xmm10, %xmm0
vmovshdup %xmm10, %xmm1
vzeroupper
call lantern_free_camera_turn
vmovdqu 0x000(%rsp), %ymm0
vmovdqu 0x020(%rsp), %ymm1
vmovdqu 0x040(%rsp), %ymm2
vmovdqu 0x060(%rsp), %ymm3
vmovdqu 0x080(%rsp), %ymm4
vmovdqu 0x0a0(%rsp), %ymm5
vmovdqu 0x0c0(%rsp), %ymm6
vmovdqu 0x0e0(%rsp), %ymm7
vmovdqu 0x100(%rsp), %ymm8
vmovdqu 0x120(%rsp), %ymm9
vmovdqu 0x140(%rsp), %ymm10
vmovdqu 0x160(%rsp), %ymm11
vmovdqu 0x180(%rsp), %ymm12
vmovdqu 0x1a0(%rsp), %ymm13
vmovdqu 0x1c0(%rsp), %ymm14
vmovdqu 0x1e0(%rsp), %ymm15
mov %rbx, %rsp
pop %r11
pop %r10
pop %r9
pop %r8
pop %rdi
pop %rsi
pop %rdx
pop %rcx
pop %rax
pop %rbx
vmovss 0x140(%r13), %xmm0
jmp *lantern_free_camera_resume(%rip)
.p2align 4
.globl lantern_fast_turn_exit
lantern_fast_turn_exit:
vmovss %xmm0, 0x140(%r13)
movb $1, lantern_free_camera_ran(%rip)
jmp *lantern_free_camera_resume(%rip)
)");
namespace MouseCamera {
namespace {
using Clock = std::chrono::steady_clock;
constexpr std::size_t kUpdate = 0x143ac60;
constexpr std::uint8_t kUpdateEntry[] = {0x55, 0x48, 0x89, 0xe5, 0x41, 0x57, 0x41, 0x56, 0x41, 0x55,
0x41, 0x54, 0x53, 0x48, 0x81, 0xec, 0xd8, 0x03, 0x00, 0x00};
constexpr std::size_t kFreeStore = 0x143ce54, kAfterStore = 0x143ce67;
constexpr std::uint8_t kFreeStoreCode[] = {0xc4, 0xc1, 0x7a, 0x11, 0x85, 0x40, 0x01, 0x00, 0x00, 0xc4,
0xc3, 0x79, 0x17, 0x8d, 0x44, 0x01, 0x00, 0x00, 0x01};
struct FastTurnExit {
std::size_t at;
std::uint8_t code[14];
};
constexpr FastTurnExit kFastTurnExits[] = {
{0x143f9bc, {0xc4, 0xc1, 0x7a, 0x11, 0x85, 0x40, 0x01, 0x00, 0x00, 0xe9, 0x9d, 0xd4, 0xff, 0xff}},
{0x143fabf, {0xc4, 0xc1, 0x7a, 0x11, 0x85, 0x40, 0x01, 0x00, 0x00, 0xe9, 0x9a, 0xd3, 0xff, 0xff}},
};
constexpr std::size_t kPitch = 0x140, kYaw = 0x144, kHighest = 0x1ec, kLowest = 0x1f0;
struct AutoRotationStore {
std::size_t at;
std::uint8_t code[9];
bool held_by_mouse;
};
constexpr AutoRotationStore kAutoRotation[] = {
{0x143c6e8, {0xc4, 0xc1, 0x7a, 0x11, 0x85, 0x44, 0x01, 0x00, 0x00}, true},
{0x143c870, {0xc4, 0xc1, 0x7a, 0x11, 0xad, 0x44, 0x01, 0x00, 0x00}, false},
{0x143c984, {0xc4, 0xc1, 0x7a, 0x11, 0x8d, 0x30, 0x01, 0x00, 0x00}, true},
{0x143dde6, {0xc4, 0xc1, 0x7a, 0x11, 0x85, 0x94, 0x02, 0x00, 0x00}, true},
};
constexpr std::uint8_t kNops[9] = {0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90};
constexpr float kFlickRate = 3000.0f, kFlickRearmRate = 1500.0f, kFlickCooldown = 0.5f;
constexpr auto kFlickShown = std::chrono::milliseconds(80);
std::uint8_t* g_image = nullptr;
bool g_installed = false;
std::mutex g_counts_lock;
float g_count_x = 0, g_count_y = 0;
std::atomic<float> g_degrees_per_count{MouseTurn::DegreesPerCount(5)};
std::atomic<bool> g_invert_x{false}, g_invert_y{false}, g_hold{false};
float g_update_x = 0, g_update_y = 0;
bool g_auto_rotation_nops = false;
bool g_patched_out = false;
bool g_store_is_games[std::size(kAutoRotation)] = {};
bool g_free_last_update = true;
std::mutex g_flick_lock;
float g_flick_x = 0, g_flick_y = 0;
unsigned g_flick_updates = 0;
Clock::time_point g_last_read{};
float g_cooldown = 0;
float g_shown_x = 0, g_shown_y = 0;
Clock::time_point g_shown_until{};
std::atomic<unsigned> g_turns{0}, g_flicks{0}, g_free_changes{0};
std::uint8_t* At(std::size_t offset) { return g_image + offset; }
void HoldAutoRotation(bool hold) {
if (g_patched_out || hold == g_auto_rotation_nops) return;
for (std::size_t i = 0; i < std::size(kAutoRotation); ++i) {
const AutoRotationStore& s = kAutoRotation[i];
if (s.held_by_mouse && g_store_is_games[i]) BbEngine::WriteCode(At(s.at), hold ? kNops : s.code, sizeof s.code);
}
g_auto_rotation_nops = hold;
}
std::int64_t __attribute__((sysv_abi)) OnCameraUpdate(std::uint64_t, const std::uint64_t*) {
{
std::scoped_lock lock(g_counts_lock);
g_update_x = g_count_x;
g_update_y = g_count_y;
g_count_x = g_count_y = 0;
}
HoldAutoRotation(g_hold.load(std::memory_order_relaxed));
const bool free = __atomic_exchange_n(&lantern_free_camera_ran, 0, __ATOMIC_RELAXED) != 0;
if (!free) {
std::scoped_lock lock(g_flick_lock);
g_flick_x += g_update_x;
g_flick_y += g_update_y;
++g_flick_updates;
}
static unsigned updates = 0;
if (++updates > 2 && free != g_free_last_update && g_free_changes.fetch_add(1, std::memory_order_relaxed) < 8)
host_log("mouse camera: the free camera %s", free ? "is back" : "stopped (locked on, or the game turns it)");
g_free_last_update = free;
return 0;
}
bool JumpFrom(std::uint8_t* site, std::size_t size, void (*to)()) {
std::uint8_t code[32];
std::memset(code, 0xcc, sizeof code);
const auto target = reinterpret_cast<std::uint64_t>(to);
code[0] = 0xff;
code[1] = 0x25;
std::memset(code + 2, 0, 4);
std::memcpy(code + 6, &target, 8);
return size >= 14 && size <= sizeof code && BbEngine::WriteCode(site, code, size);
}
}
bool Install(std::uint8_t* image, std::size_t size) {
g_image = image;
const auto matches = [&](std::size_t at, const std::uint8_t* code, std::size_t n) {
return at + n <= size && std::memcmp(image + at, code, n) == 0;
};
if (!matches(kFreeStore, kFreeStoreCode, sizeof kFreeStoreCode)) {
host_log("mouse camera: off, the follow camera's store is not 1.09's");
return false;
}
for (const FastTurnExit& exit : kFastTurnExits) {
if (!matches(exit.at, exit.code, sizeof exit.code)) {
host_log("mouse camera: off, the follow camera's fast turn is not 1.09's");
return false;
}
}
bool patched = false;
for (std::size_t i = 0; i < std::size(kAutoRotation); ++i) {
const AutoRotationStore& s = kAutoRotation[i];
g_store_is_games[i] = matches(s.at, s.code, sizeof s.code);
patched = patched || matches(s.at, kNops, sizeof kNops);
}
BbEngine::Hook entry;
if (!BbEngine::Prepare(entry, image, size, kUpdate, kUpdateEntry, OnCameraUpdate)) {
host_log("mouse camera: off, the follow camera's update is not 1.09's");
return false;
}
g_patched_out = patched;
lantern_free_camera_resume = reinterpret_cast<std::uint64_t>(At(kAfterStore));
bool placed = JumpFrom(At(kFreeStore), sizeof kFreeStoreCode, lantern_free_camera_site);
for (const FastTurnExit& exit : kFastTurnExits) placed = placed && JumpFrom(At(exit.at), sizeof exit.code, lantern_fast_turn_exit);
if (!placed) {
BbEngine::WriteCode(At(kFreeStore), kFreeStoreCode, sizeof kFreeStoreCode);
for (const FastTurnExit& exit : kFastTurnExits) BbEngine::WriteCode(At(exit.at), exit.code, sizeof exit.code);
BbEngine::Discard(entry);
host_log("mouse camera: off, the follow camera's code could not be written");
return false;
}
BbEngine::Commit(std::span(&entry, 1));
g_installed = true;
host_log("mouse camera: %.3f degrees a count at sensitivity 5, after the stick and only without it; a flick "
"switches the lock-on target%s",
MouseTurn::DegreesPerCount(5), g_patched_out ? "; a patch holds the camera's auto-rotation already" : "");
return true;
}
bool Installed() { return g_installed; }
void AddCounts(float dx, float dy) {
std::scoped_lock lock(g_counts_lock);
g_count_x += dx;
g_count_y += dy;
}
void Reset() {
{
std::scoped_lock lock(g_counts_lock);
g_count_x = g_count_y = 0;
}
std::scoped_lock lock(g_flick_lock);
g_flick_x = g_flick_y = 0;
g_flick_updates = 0;
}
void Publish(int sensitivity, bool invert_x, bool invert_y, bool hold_auto_rotation) {
g_degrees_per_count.store(MouseTurn::DegreesPerCount(sensitivity), std::memory_order_relaxed);
g_invert_x.store(invert_x, std::memory_order_relaxed);
g_invert_y.store(invert_y, std::memory_order_relaxed);
static unsigned changes = 0;
if (g_hold.exchange(hold_auto_rotation, std::memory_order_relaxed) != hold_auto_rotation && changes++ < 6)
host_log("mouse camera: auto-rotation %s",
hold_auto_rotation ? "held (the keyboard and mouse in use)" : "the game's own");
}
bool Flick(float& x, float& y) {
const Clock::time_point now = Clock::now();
float dt = g_last_read == Clock::time_point{} ? 0.0f : std::chrono::duration<float>(now - g_last_read).count();
dt = std::min(dt, 0.25f);
g_last_read = now;
float dx, dy;
unsigned updates;
{
std::scoped_lock lock(g_flick_lock);
dx = g_flick_x;
dy = g_flick_y;
updates = g_flick_updates;
g_flick_x = g_flick_y = 0;
g_flick_updates = 0;
}
if (updates && dt > 0) {
const float distance = std::hypot(dx, dy);
const float rate = distance / dt;
g_cooldown = rate < kFlickRearmRate ? 0.0f : std::max(0.0f, g_cooldown - dt);
if (g_cooldown <= 0 && rate > kFlickRate) {
g_shown_x = dx / distance;
g_shown_y = dy / distance;
g_shown_until = now + kFlickShown;
g_cooldown = kFlickCooldown;
if (g_flicks.fetch_add(1, std::memory_order_relaxed) < 4)
host_log("mouse camera: a flick of %.0f counts (%.0f a second) switches the lock-on target toward "
"%+.2f,%+.2f", distance, rate, g_shown_x, g_shown_y);
}
}
if (now >= g_shown_until) return false;
x = g_shown_x;
y = g_shown_y;
return true;
}
}
extern "C" __attribute__((sysv_abi)) void lantern_free_camera_turn(std::uint8_t* camera, std::uint8_t* had_input,
float stick_pitch, float stick_yaw) {
using namespace MouseCamera;
__atomic_store_n(&lantern_free_camera_ran, 1, __ATOMIC_RELAXED);
{
float seen;
std::memcpy(&seen, camera + kYaw, 4);
BbEngine::OnlineFixes::SetCameraYaw(seen);
}
if (g_update_x == 0 && g_update_y == 0) return;
if (stick_pitch != 0 || stick_yaw != 0) return;
float pitch, yaw, highest, lowest;
std::memcpy(&pitch, camera + kPitch, 4);
std::memcpy(&yaw, camera + kYaw, 4);
std::memcpy(&highest, camera + kHighest, 4);
std::memcpy(&lowest, camera + kLowest, 4);
const float was_pitch = pitch, was_yaw = yaw;
const MouseTurn::Settings s{g_degrees_per_count.load(std::memory_order_relaxed),
g_invert_x.load(std::memory_order_relaxed), g_invert_y.load(std::memory_order_relaxed)};
if (!MouseTurn::Turn(g_update_x, g_update_y, s, pitch, yaw, lowest, highest)) return;
std::memcpy(camera + kPitch, &pitch, 4);
std::memcpy(camera + kYaw, &yaw, 4);
*had_input = 1;
if (g_turns.fetch_add(1, std::memory_order_relaxed) == 0)
host_log("mouse camera: first turn, %+.0f,%+.0f counts: pitch %.4f -> %.4f, yaw %.4f -> %.4f", g_update_x,
g_update_y, was_pitch, pitch, was_yaw, yaw);
}