CameraController::step eases the camera's position, orbit angles and zoom a share k (0.4) of the way to their targets at each tick; between ticks, the adapter's getCameraTransform (its entry in CameraController's exported vtable, replaced after checking it) sets them 1 - (1 - k)^fraction of the way for IMVU's code to make the transform from, then puts them back. k is read from the constant CameraController::step uses, checked to be referenced there. Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
1 parent
2471b1fd4f
commit
d1437a3923
3 files changed
+100
-6
No files matched your search
+90
-4
@@ -4,6 +4,7 @@
|
||||
|
||||
#include <algorithm>
|
||||
#include <atomic>
|
||||
#include <cmath>
|
||||
#include <cstring>
|
||||
#include <mutex>
|
||||
#include <unordered_map>
|
||||
@@ -19,10 +20,15 @@ using Method = void(__thiscall*)(void* self);
|
||||
|
||||
Method originalStep = nullptr;
|
||||
Method originalUpdateCalSkeleton = nullptr;
|
||||
// IMVU's CameraController::getCameraTransform(): returns a Transform by value (constructed in result).
|
||||
using GetTransform = void*(__thiscall*)(const void* self, void* result);
|
||||
GetTransform originalGetCameraTransform = nullptr;
|
||||
float cameraEasing = 0; // the share of the way to its target the camera moves at each tick
|
||||
std::atomic<std::int64_t> stepTime = 0;
|
||||
std::atomic<std::uint64_t> steps = 0;
|
||||
std::atomic<bool> stepHooked = false;
|
||||
std::atomic<bool> poseHooked = false;
|
||||
std::atomic<bool> cameraHooked = false;
|
||||
|
||||
// zero.dll's constants: a tick, in milliseconds (33), and an animation frame, in seconds (1/30).
|
||||
const unsigned* millisecondsPerFrame = nullptr;
|
||||
@@ -155,6 +161,53 @@ void __fastcall updateCalSkeleton(void* model, void* /*edx*/)
|
||||
}
|
||||
}
|
||||
|
||||
// The camera (SceneWindow.dll's CameraController::step, at each tick): each of these values moves a share k of the way
|
||||
// to its target (k is 0.4): the camera's position (+0x118, 3 floats, to +0xf0), and its orbit angles and zoom, in
|
||||
// degrees (+0x140, 3 floats, to +0x134; the difference taken between -180 and 180).
|
||||
// A fraction f of a tick on, the same easing has moved it 1 - (1 - k)^f of the way: exactly, nothing guessed.
|
||||
constexpr std::size_t cameraPosition = 0x118;
|
||||
constexpr std::size_t cameraPositionTarget = 0xf0;
|
||||
constexpr std::size_t cameraAngles = 0x140;
|
||||
constexpr std::size_t cameraAnglesTarget = 0x134;
|
||||
// Nonzero: the camera shows a fixed view ("take"), not eased.
|
||||
constexpr std::size_t cameraTake = 0xec;
|
||||
|
||||
float degrees180(float degrees)
|
||||
{
|
||||
float wrapped = std::fmod(degrees + 180.0f, 360.0f);
|
||||
if (wrapped < 0) {
|
||||
wrapped += 360.0f;
|
||||
}
|
||||
return wrapped - 180.0f;
|
||||
}
|
||||
|
||||
// Our CameraController::getCameraTransform: between ticks, the camera where its easing has brought it since the tick.
|
||||
// The eased values are set ahead for IMVU's code to make the transform from, then put back. __fastcall takes ECX like
|
||||
// __thiscall, and like it the callee pops the stack argument.
|
||||
void* __fastcall getCameraTransform(void* camera, void* /*edx*/, void* result)
|
||||
{
|
||||
const float fraction = tick_fraction();
|
||||
if (fraction < 0.01f || field<std::int32_t>(camera, cameraTake) != 0) {
|
||||
return originalGetCameraTransform(camera, result);
|
||||
}
|
||||
const float moved = 1.0f - std::pow(1.0f - cameraEasing, fraction);
|
||||
float saved[6];
|
||||
for (int i = 0; i < 3; ++i) {
|
||||
float& position = field<float>(camera, cameraPosition + i * 4);
|
||||
saved[i] = position;
|
||||
position += (field<float>(camera, cameraPositionTarget + i * 4) - position) * moved;
|
||||
float& angle = field<float>(camera, cameraAngles + i * 4);
|
||||
saved[3 + i] = angle;
|
||||
angle = degrees180(angle + degrees180(field<float>(camera, cameraAnglesTarget + i * 4) - angle) * moved);
|
||||
}
|
||||
void* transform = originalGetCameraTransform(camera, result);
|
||||
for (int i = 0; i < 3; ++i) {
|
||||
field<float>(camera, cameraPosition + i * 4) = saved[i];
|
||||
field<float>(camera, cameraAngles + i * 4) = saved[3 + i];
|
||||
}
|
||||
return transform;
|
||||
}
|
||||
|
||||
// Replaces the vtable entry at slot, if it holds expected; returns whether it did.
|
||||
bool replace(void** slot, void* expected, void* replacement)
|
||||
{
|
||||
@@ -231,6 +284,38 @@ void install_pose(HMODULE sceneWindow)
|
||||
poseHooked = true;
|
||||
}
|
||||
|
||||
void install_camera(HMODULE sceneWindow)
|
||||
{
|
||||
auto* vtable = reinterpret_cast<void**>(GetProcAddress(sceneWindow, "??_7CameraController@@6B@"));
|
||||
auto* get = GetProcAddress(sceneWindow, "?getCameraTransform@CameraController@@UBE?AVTransform@@XZ");
|
||||
auto* step = reinterpret_cast<const std::byte*>(GetProcAddress(sceneWindow, "?step@CameraController@@UAEXXZ"));
|
||||
if (vtable == nullptr || get == nullptr || step == nullptr) {
|
||||
log::info("scene hooks: CameraController's vtable, getCameraTransform or step not found");
|
||||
return;
|
||||
}
|
||||
// The easing share: a float constant CameraController::step reads (at SceneWindow.dll's 0x100cb934 when loaded at
|
||||
// its preferred address), checked to be referenced by step's code and to be the expected 0.4.
|
||||
const auto* base = reinterpret_cast<const std::byte*>(sceneWindow);
|
||||
const auto* easing = reinterpret_cast<const float*>(base + 0xcb934);
|
||||
const auto address = static_cast<std::uint32_t>(reinterpret_cast<std::uintptr_t>(easing));
|
||||
bool referenced = false;
|
||||
for (std::size_t i = 0; i + 4 <= 0x700 && !referenced; ++i) {
|
||||
referenced = std::memcmp(step + i, &address, 4) == 0;
|
||||
}
|
||||
if (!referenced || *easing != 0.4f) {
|
||||
log::info("scene hooks: CameraController::step's easing not where expected");
|
||||
return;
|
||||
}
|
||||
cameraEasing = *easing;
|
||||
// CameraController's vtable: getCameraTransform is its 6th entry.
|
||||
originalGetCameraTransform = reinterpret_cast<GetTransform>(get);
|
||||
if (!replace(&vtable[5], reinterpret_cast<void*>(get), reinterpret_cast<void*>(&getCameraTransform))) {
|
||||
log::info("scene hooks: CameraController's vtable doesn't hold its getCameraTransform");
|
||||
return;
|
||||
}
|
||||
cameraHooked = true;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
void install()
|
||||
@@ -243,11 +328,12 @@ void install()
|
||||
return;
|
||||
}
|
||||
install_step(sceneWindow);
|
||||
if (stepHooked) {
|
||||
install_pose(sceneWindow); // posing between ticks needs the ticks
|
||||
if (stepHooked) { // drawing between ticks needs the ticks
|
||||
install_pose(sceneWindow);
|
||||
install_camera(sceneWindow);
|
||||
}
|
||||
log::info("scene hooks: ticks {}, posing between ticks {}", stepHooked ? "recorded" : "not recorded",
|
||||
poseHooked ? "on" : "off");
|
||||
log::info("scene hooks: ticks {}, posing between ticks {}, camera between ticks {}",
|
||||
stepHooked ? "recorded" : "not recorded", poseHooked ? "on" : "off", cameraHooked ? "on" : "off");
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
Reference in new issue
Block a user