Skip to content

Instantly share code, notes, and snippets.

@awdavies
Last active November 29, 2022 08:52
Show Gist options
  • Save awdavies/4c4563969cbc6a67e66f7e0a5b80e23a to your computer and use it in GitHub Desktop.
Save awdavies/4c4563969cbc6a67e66f7e0a5b80e23a to your computer and use it in GitHub Desktop.
Skyrim 3rd Person Walk Fix Redux (using CommonLibSSE)
#include <polyhook2/Detour/x64Detour.hpp>
#include <polyhook2/Enums.hpp>
#include <polyhook2/Virtuals/VFuncSwapHook.hpp>
#include <polyhook2/ZydisDisassembler.hpp>
namespace
{
void InitializeLog()
{
#ifndef NDEBUG
auto sink = std::make_shared<spdlog::sinks::msvc_sink_mt>();
#else
auto path = logger::log_directory();
if (!path) {
util::report_and_fail("Failed to find standard logging directory"sv);
}
*path /= fmt::format("{}.log"sv, Plugin::NAME);
auto sink = std::make_shared<spdlog::sinks::basic_file_sink_mt>(path->string(), true);
#endif
#ifndef NDEBUG
const auto level = spdlog::level::trace;
#else
const auto level = spdlog::level::info;
#endif
auto log = std::make_shared<spdlog::logger>("global log"s, std::move(sink));
log->set_level(level);
log->flush_on(level);
spdlog::set_default_logger(std::move(log));
spdlog::set_pattern("%g(%#): [%^%l%$] %v"s);
}
void SetupState()
{
logger::info(
"Walking library compatible. Loaded version {}. Setting state.",
Plugin::VERSION.string());
}
}
static std::unique_ptr<PLH::VFuncSwapHook> g_player_character_hooks;
static constexpr uint16_t g_speed_struct_offset = 8;
static PLH::VFuncMap g_player_originals;
static std::unique_ptr<PLH::VFuncSwapHook> g_player_controls_hooks;
static PLH::VFuncMap g_player_controls_originals;
static bool g_player_running = false;
const SKSE::MessagingInterface* g_messaging = nullptr;
[[nodiscard]] inline bool player_character_is_walking() noexcept
{
// This works better than checking the built-in function IsRunning() from the PC.
// If we were to use that function it would recurse indefinitely in our use-case.
return RE::PlayerCharacter::GetSingleton()->actorState1.walking != 0;
}
[[nodiscard]] inline float* ag_as_vec3(void* ag_struct) noexcept
{
return reinterpret_cast<float*>(reinterpret_cast<uint64_t>(ag_struct) + 16);
}
bool UpdateAnimationGraph(RE::PlayerCharacter* pc, void* animation_graph)
{
auto overridden_fn = PLH::FnCast(reinterpret_cast<void*>(g_player_originals[g_speed_struct_offset]), &UpdateAnimationGraph);
auto result = overridden_fn(pc, animation_graph);
if (result) {
auto vec3 = ag_as_vec3(animation_graph);
if (player_character_is_walking()) {
vec3[1] = vec3[0] * 1.01f;
} else {
vec3[0] = vec3[1] / 1.01f;
}
}
return result;
}
void AttachPlayerHooks()
{
auto player_character = RE::PlayerCharacter::GetSingleton();
PLH::VFuncMap redirects;
redirects.insert({ static_cast<uint16_t>(g_speed_struct_offset), reinterpret_cast<uint64_t>(&UpdateAnimationGraph) });
// This offset, unfortunately, just has to be kinda figured out. It's (I think) pointing to ActorState, overwriting the
// IMovementHandler function number eight.
//
// For versions up to 1.6.353 this is player_character + 0xb8, but for 1.6.640 (and I presume anything after) it's
// player_character + 0xc0
g_player_character_hooks.reset(
new PLH::VFuncSwapHook(
reinterpret_cast<uint64_t>(player_character) + 0xb8,
redirects,
&g_player_originals));
if (g_player_character_hooks->hook()) {
logger::info("Player character hooks established. :)");
} else {
logger::critical("Player character hooks could not be established. This mod will not run. :(");
}
}
static void SKSEMessageHandler(SKSE::MessagingInterface::Message* message)
{
switch (message->type) {
case SKSE::MessagingInterface::kNewGame:
case SKSE::MessagingInterface::kPostLoadGame:
{
AttachPlayerHooks();
}
case SKSE::MessagingInterface::kPostLoad:
default:
break;
}
}
extern "C" DLLEXPORT constinit auto SKSEPlugin_Version = []() {
SKSE::PluginVersionData v;
v.PluginVersion(Plugin::VERSION);
v.PluginName(Plugin::NAME);
v.UsesAddressLibrary(true);
v.CompatibleVersions({ SKSE::RUNTIME_LATEST });
return v;
}();
extern "C" DLLEXPORT bool SKSEAPI SKSEPlugin_Query(const SKSE::QueryInterface* q_skse, SKSE::PluginInfo* a_info)
{
logger::info("Querying...");
a_info->infoVersion = SKSE::PluginInfo::kVersion;
a_info->name = std::string(Plugin::NAME).data();
a_info->version = 1;
logger::info("Did the module query dance");
return true;
}
extern "C" DLLEXPORT bool SKSEAPI SKSEPlugin_Load(const SKSE::LoadInterface* a_skse)
{
logger::info("Loading...");
InitializeLog();
logger::info("{} v{}"sv, Plugin::NAME, Plugin::VERSION.string());
SKSE::Init(a_skse);
g_messaging = reinterpret_cast<SKSE::MessagingInterface*>(a_skse->QueryInterface(SKSE::LoadInterface::kMessaging));
if (!g_messaging) {
logger::critical("Failed to load messaging interface! This error is fatal, will not load.");
return false;
}
g_messaging->RegisterListener("SKSE", SKSEMessageHandler);
logger::info("Loading successful.");
return true;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment