mirror of
https://git.chinosk6.cn/chinosk/gkms-local.git
synced 2026-08-21 20:54:43 +07:00
相机修复 + 拍照页服装解锁 + SP 标记,版本号 v3.4.1
This commit is contained in:
+2
-2
@@ -16,7 +16,7 @@ android {
|
|||||||
minSdk 29
|
minSdk 29
|
||||||
targetSdk 34
|
targetSdk 34
|
||||||
versionCode 12
|
versionCode 12
|
||||||
versionName "v3.4.0"
|
versionName "v3.4.1"
|
||||||
buildConfigField "String", "VERSION_NAME", "\"${versionName}\""
|
buildConfigField "String", "VERSION_NAME", "\"${versionName}\""
|
||||||
|
|
||||||
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
|
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
|
||||||
@@ -132,4 +132,4 @@ dependencies {
|
|||||||
implementation(libs.shadowhook)
|
implementation(libs.shadowhook)
|
||||||
compileOnly(libs.xposed.api)
|
compileOnly(libs.xposed.api)
|
||||||
implementation(libs.kotlinx.serialization.json)
|
implementation(libs.kotlinx.serialization.json)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,7 +7,13 @@
|
|||||||
#include "Local.h"
|
#include "Local.h"
|
||||||
#include "MasterLocal.h"
|
#include "MasterLocal.h"
|
||||||
#include <unordered_set>
|
#include <unordered_set>
|
||||||
|
#include <unordered_map>
|
||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
|
#include <atomic>
|
||||||
|
#include <chrono>
|
||||||
|
#include <cmath>
|
||||||
|
#include <ctime>
|
||||||
|
#include <cstring>
|
||||||
#include "camera/camera.hpp"
|
#include "camera/camera.hpp"
|
||||||
#include "config/Config.hpp"
|
#include "config/Config.hpp"
|
||||||
// #include <jni.h>
|
// #include <jni.h>
|
||||||
@@ -116,14 +122,30 @@ namespace GakumasLocal::HookMain {
|
|||||||
|
|
||||||
UnityResolve::UnityType::Camera* mainCameraCache = nullptr;
|
UnityResolve::UnityType::Camera* mainCameraCache = nullptr;
|
||||||
UnityResolve::UnityType::Transform* cameraTransformCache = nullptr;
|
UnityResolve::UnityType::Transform* cameraTransformCache = nullptr;
|
||||||
void CheckAndUpdateMainCamera() {
|
|
||||||
|
void CheckAndUpdateMainCamera(UnityResolve::UnityType::Camera* fallbackCamera = nullptr) {
|
||||||
if (!Config::enableFreeCamera) return;
|
if (!Config::enableFreeCamera) return;
|
||||||
if (IsNativeObjectAlive(mainCameraCache) && IsNativeObjectAlive(cameraTransformCache)) return;
|
if (IsNativeObjectAlive(mainCameraCache) && IsNativeObjectAlive(cameraTransformCache)) return;
|
||||||
|
|
||||||
mainCameraCache = UnityResolve::UnityType::Camera::GetMain();
|
// 优先使用游戏传入的真实相机(渲染回调 / FOV hook 捕获)。
|
||||||
cameraTransformCache = mainCameraCache->GetTransform();
|
// 注意:不要用 UnityResolve 的 managed Invoke 直接调 Camera.get_main / get_transform,
|
||||||
|
// Unity 6 下该调用缺少 MethodInfo* 参数,可能返回垃圾指针(崩溃根因)。
|
||||||
|
if (!mainCameraCache || !IsNativeObjectAlive(mainCameraCache)) {
|
||||||
|
mainCameraCache = fallbackCamera;
|
||||||
|
}
|
||||||
|
// 不再回退到 Camera.GetMain()/GetCurrent():UnityResolve 的 managed Invoke 可能返回垃圾指针。
|
||||||
|
// 相机只从游戏自己的调用捕获(EndCameraRendering 参数 / FOV hook / get_main hook)。
|
||||||
|
// cameraTransformCache 由 Component.get_transform hook 在游戏调用时捕获。
|
||||||
|
|
||||||
|
if (!mainCameraCache) {
|
||||||
|
cameraTransformCache = nullptr;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool TryLookRotationQuat(const UnityResolve::UnityType::Vector3& forwardIn,
|
||||||
|
const UnityResolve::UnityType::Vector3& upIn,
|
||||||
|
UnityResolve::UnityType::Quaternion* outQuat);
|
||||||
|
|
||||||
Il2cppUtils::Resolution_t GetResolution() {
|
Il2cppUtils::Resolution_t GetResolution() {
|
||||||
static auto GetResolution = Il2cppUtils::GetMethod("UnityEngine.CoreModule.dll", "UnityEngine",
|
static auto GetResolution = Il2cppUtils::GetMethod("UnityEngine.CoreModule.dll", "UnityEngine",
|
||||||
"Screen", "get_currentResolution");
|
"Screen", "get_currentResolution");
|
||||||
@@ -144,6 +166,12 @@ namespace GakumasLocal::HookMain {
|
|||||||
|
|
||||||
DEFINE_HOOK(void, Unity_set_fieldOfView, (UnityResolve::UnityType::Camera* self, float value)) {
|
DEFINE_HOOK(void, Unity_set_fieldOfView, (UnityResolve::UnityType::Camera* self, float value)) {
|
||||||
if (Config::enableFreeCamera) {
|
if (Config::enableFreeCamera) {
|
||||||
|
// self 是游戏传的真实相机,直接捕获。
|
||||||
|
// Cinemachine 每帧会把镜头参数应用到输出相机,这里拿到的就是实际游戏相机。
|
||||||
|
if (self != mainCameraCache) {
|
||||||
|
mainCameraCache = self;
|
||||||
|
cameraTransformCache = nullptr; // 相机变了,Transform 需要重新解析
|
||||||
|
}
|
||||||
if (self == mainCameraCache) {
|
if (self == mainCameraCache) {
|
||||||
value = GKCamera::baseCamera.fov;
|
value = GKCamera::baseCamera.fov;
|
||||||
}
|
}
|
||||||
@@ -153,6 +181,10 @@ namespace GakumasLocal::HookMain {
|
|||||||
|
|
||||||
DEFINE_HOOK(float, Unity_get_fieldOfView, (UnityResolve::UnityType::Camera* self)) {
|
DEFINE_HOOK(float, Unity_get_fieldOfView, (UnityResolve::UnityType::Camera* self)) {
|
||||||
if (Config::enableFreeCamera) {
|
if (Config::enableFreeCamera) {
|
||||||
|
if (self != mainCameraCache) {
|
||||||
|
mainCameraCache = self;
|
||||||
|
cameraTransformCache = nullptr;
|
||||||
|
}
|
||||||
if (self == mainCameraCache) {
|
if (self == mainCameraCache) {
|
||||||
static auto get_orthographic = reinterpret_cast<bool (*)(void*)>(Il2cppUtils::il2cpp_resolve_icall(
|
static auto get_orthographic = reinterpret_cast<bool (*)(void*)>(Il2cppUtils::il2cpp_resolve_icall(
|
||||||
"UnityEngine.Camera::get_orthographic()"
|
"UnityEngine.Camera::get_orthographic()"
|
||||||
@@ -162,7 +194,9 @@ namespace GakumasLocal::HookMain {
|
|||||||
));
|
));
|
||||||
|
|
||||||
for (const auto& i : UnityResolve::UnityType::Camera::GetAllCamera()) {
|
for (const auto& i : UnityResolve::UnityType::Camera::GetAllCamera()) {
|
||||||
// Log::DebugFmt("get_orthographic: %d", get_orthographic(i));
|
if (get_orthographic) {
|
||||||
|
// Log::DebugFmt("get_orthographic: %d", get_orthographic(i));
|
||||||
|
}
|
||||||
// set_orthographic(i, false);
|
// set_orthographic(i, false);
|
||||||
Unity_set_fieldOfView_Orig(i, GKCamera::baseCamera.fov);
|
Unity_set_fieldOfView_Orig(i, GKCamera::baseCamera.fov);
|
||||||
}
|
}
|
||||||
@@ -175,21 +209,159 @@ namespace GakumasLocal::HookMain {
|
|||||||
return Unity_get_fieldOfView_Orig(self);
|
return Unity_get_fieldOfView_Orig(self);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 从游戏自己的调用里捕获真实对象指针:
|
||||||
|
// Camera.get_main / Component.get_transform 是带 MethodInfo* 尾参的 managed 方法,
|
||||||
|
// 用 hook(而不是 UnityResolve 的 managed Invoke)才能拿到可靠结果。
|
||||||
|
DEFINE_HOOK(UnityResolve::UnityType::Camera*, Camera_get_main, (void* mtd)) {
|
||||||
|
auto ret = Camera_get_main_Orig(mtd);
|
||||||
|
if (Config::enableFreeCamera && ret && ret != mainCameraCache) {
|
||||||
|
mainCameraCache = ret;
|
||||||
|
cameraTransformCache = nullptr;
|
||||||
|
}
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
DEFINE_HOOK(UnityResolve::UnityType::Transform*, Component_get_transform, (void* self, void* mtd)) {
|
||||||
|
auto ret = Component_get_transform_Orig(self, mtd);
|
||||||
|
if (Config::enableFreeCamera && mainCameraCache && self == mainCameraCache) {
|
||||||
|
cameraTransformCache = ret;
|
||||||
|
}
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
UnityResolve::UnityType::Transform* cacheTrans = nullptr;
|
UnityResolve::UnityType::Transform* cacheTrans = nullptr;
|
||||||
UnityResolve::UnityType::Quaternion cacheRotation{};
|
UnityResolve::UnityType::Quaternion cacheRotation{};
|
||||||
UnityResolve::UnityType::Vector3 cachePosition{};
|
UnityResolve::UnityType::Vector3 cachePosition{};
|
||||||
UnityResolve::UnityType::Vector3 cacheForward{};
|
UnityResolve::UnityType::Vector3 cacheForward{};
|
||||||
UnityResolve::UnityType::Vector3 cacheLookAt{};
|
UnityResolve::UnityType::Vector3 cacheLookAt{};
|
||||||
|
|
||||||
|
// 计算当前模式下的相机位置/朝向。返回 false 表示暂不可用(NaN/目标丢失)。
|
||||||
|
bool ComputeFreeCameraPose(UnityResolve::UnityType::Vector3* pos, UnityResolve::UnityType::Quaternion* rot) {
|
||||||
|
using Vector3 = UnityResolve::UnityType::Vector3;
|
||||||
|
using Quaternion = UnityResolve::UnityType::Quaternion;
|
||||||
|
|
||||||
|
Vector3 lookAt{};
|
||||||
|
switch (GKCamera::GetCameraMode()) {
|
||||||
|
case GKCamera::CameraMode::FREE: {
|
||||||
|
*pos = GKCamera::baseCamera.GetPos();
|
||||||
|
lookAt = GKCamera::baseCamera.GetLookAt();
|
||||||
|
} break;
|
||||||
|
case GKCamera::CameraMode::FIRST_PERSON: {
|
||||||
|
if (!cacheTrans || !IsNativeObjectAlive(cacheTrans)) return false;
|
||||||
|
*pos = GKCamera::CalcFirstPersonPosition(cachePosition, cacheForward, GKCamera::firstPersonPosOffset);
|
||||||
|
lookAt = cacheLookAt;
|
||||||
|
} break;
|
||||||
|
case GKCamera::CameraMode::FOLLOW: {
|
||||||
|
lookAt = GKCamera::CalcFollowModeLookAt(cachePosition, GKCamera::followPosOffset);
|
||||||
|
*pos = GKCamera::CalcPositionFromLookAt(lookAt, GKCamera::followPosOffset);
|
||||||
|
} break;
|
||||||
|
default:
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!std::isfinite(pos->x) || !std::isfinite(pos->y) || !std::isfinite(pos->z) ||
|
||||||
|
!std::isfinite(lookAt.x) || !std::isfinite(lookAt.y) || !std::isfinite(lookAt.z)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const auto forward = Vector3(lookAt.x - pos->x, lookAt.y - pos->y, lookAt.z - pos->z);
|
||||||
|
return TryLookRotationQuat(forward, Vector3(0, 1, 0), rot);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool IsCameraControlTransform(void* self) {
|
||||||
|
if (!self || !cameraTransformCache) return false;
|
||||||
|
return self == cameraTransformCache;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Cinemachine 把最终相机状态写入 Unity 相机的唯一入口(CinemachineBrain.PushStateToUnityCamera)。
|
||||||
|
// CameraState 是大结构体,按值传递时经隐藏指针传入(hook 的 state 参数即指向该副本),
|
||||||
|
// 直接改副本,游戏会用自己内部的原生路径应用——不需要调用任何 Transform API,绝对安全。
|
||||||
|
// 字段偏移来自 hook-reference dump.cs:
|
||||||
|
// 0x00 Lens.FieldOfView | 0x48 RawPosition | 0x54 RawOrientation
|
||||||
|
// 0x64 PositionDampingBypass | 0x74 PositionCorrection | 0x80 OrientationCorrection
|
||||||
|
DEFINE_HOOK(void, CinemachineBrain_PushStateToUnityCamera, (void* self, void* state, void* mtd)) {
|
||||||
|
if (Config::enableFreeCamera && state) {
|
||||||
|
CheckAndUpdateMainCamera();
|
||||||
|
|
||||||
|
using Vector3 = UnityResolve::UnityType::Vector3;
|
||||||
|
using Quaternion = UnityResolve::UnityType::Quaternion;
|
||||||
|
auto* rawPos = reinterpret_cast<Vector3*>(static_cast<unsigned char*>(state) + 0x48);
|
||||||
|
auto* rawRot = reinterpret_cast<Quaternion*>(static_cast<unsigned char*>(state) + 0x54);
|
||||||
|
auto* damping = reinterpret_cast<Vector3*>(static_cast<unsigned char*>(state) + 0x64);
|
||||||
|
auto* posCorr = reinterpret_cast<Vector3*>(static_cast<unsigned char*>(state) + 0x74);
|
||||||
|
auto* rotCorr = reinterpret_cast<Quaternion*>(static_cast<unsigned char*>(state) + 0x80);
|
||||||
|
auto* fov = reinterpret_cast<float*>(static_cast<unsigned char*>(state) + 0x00);
|
||||||
|
|
||||||
|
if (ComputeFreeCameraPose(rawPos, rawRot)) {
|
||||||
|
*damping = Vector3(0, 0, 0);
|
||||||
|
*posCorr = Vector3(0, 0, 0);
|
||||||
|
*rotCorr = Quaternion(0, 0, 0, 1);
|
||||||
|
*fov = GKCamera::baseCamera.fov;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return CinemachineBrain_PushStateToUnityCamera_Orig(self, state, mtd);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Cinemachine 3(Unity 6)用 SetPositionAndRotation 一次性写相机位置+旋转,
|
||||||
|
// set_position/set_rotation 拦截不到相机,必须在这里接管。
|
||||||
|
DEFINE_HOOK(void, Unity_SetPositionAndRotation_Injected, (UnityResolve::UnityType::Transform* self,
|
||||||
|
UnityResolve::UnityType::Vector3* position,
|
||||||
|
UnityResolve::UnityType::Quaternion* rotation)) {
|
||||||
|
if (Config::enableFreeCamera) {
|
||||||
|
CheckAndUpdateMainCamera();
|
||||||
|
|
||||||
|
const auto isControl = IsCameraControlTransform(self);
|
||||||
|
if (isControl) {
|
||||||
|
UnityResolve::UnityType::Vector3 pos{};
|
||||||
|
UnityResolve::UnityType::Quaternion rot{};
|
||||||
|
if (ComputeFreeCameraPose(&pos, &rot)) {
|
||||||
|
*position = pos;
|
||||||
|
*rotation = rot;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return Unity_SetPositionAndRotation_Injected_Orig(self, position, rotation);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 兜底:游戏若用局部坐标写相机(rig 挂载场景),位置同样接管。
|
||||||
|
DEFINE_HOOK(void, Unity_set_localPosition_Injected, (UnityResolve::UnityType::Transform* self,
|
||||||
|
UnityResolve::UnityType::Vector3* data)) {
|
||||||
|
if (Config::enableFreeCamera) {
|
||||||
|
CheckAndUpdateMainCamera();
|
||||||
|
|
||||||
|
const auto isControl = IsCameraControlTransform(self);
|
||||||
|
if (isControl) {
|
||||||
|
const auto cameraMode = GKCamera::GetCameraMode();
|
||||||
|
if (cameraMode == GKCamera::CameraMode::FIRST_PERSON) {
|
||||||
|
if (cacheTrans && IsNativeObjectAlive(cacheTrans)) {
|
||||||
|
*data = GKCamera::CalcFirstPersonPosition(cachePosition, cacheForward, GKCamera::firstPersonPosOffset);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (cameraMode == GKCamera::CameraMode::FOLLOW) {
|
||||||
|
auto newLookAtPos = GKCamera::CalcFollowModeLookAt(cachePosition, GKCamera::followPosOffset);
|
||||||
|
auto pos = GKCamera::CalcPositionFromLookAt(newLookAtPos, GKCamera::followPosOffset);
|
||||||
|
data->x = pos.x;
|
||||||
|
data->y = pos.y;
|
||||||
|
data->z = pos.z;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
auto& origCameraPos = GKCamera::baseCamera.pos;
|
||||||
|
data->x = origCameraPos.x;
|
||||||
|
data->y = origCameraPos.y;
|
||||||
|
data->z = origCameraPos.z;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return Unity_set_localPosition_Injected_Orig(self, data);
|
||||||
|
}
|
||||||
|
|
||||||
DEFINE_HOOK(void, Unity_set_rotation_Injected, (UnityResolve::UnityType::Transform* self, UnityResolve::UnityType::Quaternion* value)) {
|
DEFINE_HOOK(void, Unity_set_rotation_Injected, (UnityResolve::UnityType::Transform* self, UnityResolve::UnityType::Quaternion* value)) {
|
||||||
if (Config::enableFreeCamera) {
|
if (Config::enableFreeCamera) {
|
||||||
static auto lookat_injected = reinterpret_cast<void (*)(void*self,
|
const auto isControl = IsCameraControlTransform(self);
|
||||||
UnityResolve::UnityType::Vector3* worldPosition, UnityResolve::UnityType::Vector3* worldUp)>(
|
if (isControl) {
|
||||||
Il2cppUtils::il2cpp_resolve_icall(
|
|
||||||
"UnityEngine.Transform::Internal_LookAt_Injected(UnityEngine.Vector3&,UnityEngine.Vector3&)"));
|
|
||||||
static auto worldUp = UnityResolve::UnityType::Vector3(0, 1, 0);
|
|
||||||
|
|
||||||
if (cameraTransformCache == self) {
|
|
||||||
const auto cameraMode = GKCamera::GetCameraMode();
|
const auto cameraMode = GKCamera::GetCameraMode();
|
||||||
if (cameraMode == GKCamera::CameraMode::FIRST_PERSON) {
|
if (cameraMode == GKCamera::CameraMode::FIRST_PERSON) {
|
||||||
if (cacheTrans && IsNativeObjectAlive(cacheTrans)) {
|
if (cacheTrans && IsNativeObjectAlive(cacheTrans)) {
|
||||||
@@ -200,22 +372,39 @@ namespace GakumasLocal::HookMain {
|
|||||||
static GakumasLocal::Misc::FixedSizeQueue<float> recordsY(60);
|
static GakumasLocal::Misc::FixedSizeQueue<float> recordsY(60);
|
||||||
const auto newY = GKCamera::CheckNewY(cacheLookAt, true, recordsY);
|
const auto newY = GKCamera::CheckNewY(cacheLookAt, true, recordsY);
|
||||||
UnityResolve::UnityType::Vector3 newCacheLookAt{cacheLookAt.x, newY, cacheLookAt.z};
|
UnityResolve::UnityType::Vector3 newCacheLookAt{cacheLookAt.x, newY, cacheLookAt.z};
|
||||||
lookat_injected(self, &newCacheLookAt, &worldUp);
|
const auto pos = GKCamera::CalcFirstPersonPosition(
|
||||||
return;
|
cachePosition, cacheForward, GKCamera::firstPersonPosOffset);
|
||||||
|
UnityResolve::UnityType::Quaternion q{};
|
||||||
|
const auto forward = UnityResolve::UnityType::Vector3(
|
||||||
|
newCacheLookAt.x - pos.x, newCacheLookAt.y - pos.y, newCacheLookAt.z - pos.z);
|
||||||
|
if (TryLookRotationQuat(forward, UnityResolve::UnityType::Vector3(0, 1, 0), &q)) {
|
||||||
|
*value = q;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else if (cameraMode == GKCamera::CameraMode::FOLLOW) {
|
else if (cameraMode == GKCamera::CameraMode::FOLLOW) {
|
||||||
auto newLookAtPos = GKCamera::CalcFollowModeLookAt(cachePosition,
|
auto newLookAtPos = GKCamera::CalcFollowModeLookAt(cachePosition,
|
||||||
GKCamera::followPosOffset, true);
|
GKCamera::followPosOffset, true);
|
||||||
lookat_injected(self, &newLookAtPos, &worldUp);
|
const auto pos = GKCamera::CalcPositionFromLookAt(newLookAtPos, GKCamera::followPosOffset);
|
||||||
return;
|
UnityResolve::UnityType::Quaternion q{};
|
||||||
|
const auto forward = UnityResolve::UnityType::Vector3(
|
||||||
|
newLookAtPos.x - pos.x, newLookAtPos.y - pos.y, newLookAtPos.z - pos.z);
|
||||||
|
if (TryLookRotationQuat(forward, UnityResolve::UnityType::Vector3(0, 1, 0), &q)) {
|
||||||
|
*value = q;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
auto& origCameraLookat = GKCamera::baseCamera.lookAt;
|
auto& origCameraLookat = GKCamera::baseCamera.lookAt;
|
||||||
lookat_injected(self, &origCameraLookat, &worldUp);
|
auto& origCameraPos = GKCamera::baseCamera.pos;
|
||||||
// Log::DebugFmt("fov: %f, target: %f", Unity_get_fieldOfView_Orig(mainCameraCache), GKCamera::baseCamera.fov);
|
UnityResolve::UnityType::Quaternion q{};
|
||||||
return;
|
const auto forward = UnityResolve::UnityType::Vector3(
|
||||||
|
origCameraLookat.x - origCameraPos.x,
|
||||||
|
origCameraLookat.y - origCameraPos.y,
|
||||||
|
origCameraLookat.z - origCameraPos.z);
|
||||||
|
if (TryLookRotationQuat(forward, UnityResolve::UnityType::Vector3(0, 1, 0), &q)) {
|
||||||
|
*value = q;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -226,7 +415,8 @@ namespace GakumasLocal::HookMain {
|
|||||||
if (Config::enableFreeCamera) {
|
if (Config::enableFreeCamera) {
|
||||||
CheckAndUpdateMainCamera();
|
CheckAndUpdateMainCamera();
|
||||||
|
|
||||||
if (cameraTransformCache == self) {
|
const auto isControl = IsCameraControlTransform(self);
|
||||||
|
if (isControl) {
|
||||||
const auto cameraMode = GKCamera::GetCameraMode();
|
const auto cameraMode = GKCamera::GetCameraMode();
|
||||||
if (cameraMode == GKCamera::CameraMode::FIRST_PERSON) {
|
if (cameraMode == GKCamera::CameraMode::FIRST_PERSON) {
|
||||||
if (cacheTrans && IsNativeObjectAlive(cacheTrans)) {
|
if (cacheTrans && IsNativeObjectAlive(cacheTrans)) {
|
||||||
@@ -254,6 +444,64 @@ namespace GakumasLocal::HookMain {
|
|||||||
return Unity_set_position_Injected_Orig(self, data);
|
return Unity_set_position_Injected_Orig(self, data);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 用 forward/up 构造 Unity 兼容的四元数(等价 Quaternion.LookRotation)。
|
||||||
|
// 返回 false 表示 forward 与 up 平行(垂直看天/看地),此时不应写入旋转,保持当前朝向。
|
||||||
|
bool TryLookRotationQuat(const UnityResolve::UnityType::Vector3& forwardIn,
|
||||||
|
const UnityResolve::UnityType::Vector3& upIn,
|
||||||
|
UnityResolve::UnityType::Quaternion* outQuat) {
|
||||||
|
using Vector3 = UnityResolve::UnityType::Vector3;
|
||||||
|
using Quaternion = UnityResolve::UnityType::Quaternion;
|
||||||
|
|
||||||
|
Vector3 forward = forwardIn;
|
||||||
|
const auto fwdLenSq = forward.x * forward.x + forward.y * forward.y + forward.z * forward.z;
|
||||||
|
if (fwdLenSq < 1e-8f) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
const auto invFwdLen = 1.0f / std::sqrt(fwdLenSq);
|
||||||
|
forward = Vector3(forward.x * invFwdLen, forward.y * invFwdLen, forward.z * invFwdLen);
|
||||||
|
|
||||||
|
Vector3 right = Vector3(
|
||||||
|
upIn.y * forward.z - upIn.z * forward.y,
|
||||||
|
upIn.z * forward.x - upIn.x * forward.z,
|
||||||
|
upIn.x * forward.y - upIn.y * forward.x);
|
||||||
|
const auto rightLenSq = right.x * right.x + right.y * right.y + right.z * right.z;
|
||||||
|
if (rightLenSq < 1e-8f) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
const auto invRightLen = 1.0f / std::sqrt(rightLenSq);
|
||||||
|
right = Vector3(right.x * invRightLen, right.y * invRightLen, right.z * invRightLen);
|
||||||
|
|
||||||
|
const Vector3 up = Vector3(
|
||||||
|
forward.y * right.z - forward.z * right.y,
|
||||||
|
forward.z * right.x - forward.x * right.z,
|
||||||
|
forward.x * right.y - forward.y * right.x);
|
||||||
|
|
||||||
|
// 旋转矩阵列向量(right/up/forward)-> 标准矩阵转四元数
|
||||||
|
const float m00 = right.x, m01 = up.x, m02 = forward.x;
|
||||||
|
const float m10 = right.y, m11 = up.y, m12 = forward.y;
|
||||||
|
const float m20 = right.z, m21 = up.z, m22 = forward.z;
|
||||||
|
|
||||||
|
const float tr = m00 + m11 + m22;
|
||||||
|
if (tr > 0.0f) {
|
||||||
|
const float s = std::sqrt(tr + 1.0f) * 2.0f;
|
||||||
|
*outQuat = Quaternion((m21 - m12) / s, (m02 - m20) / s, (m10 - m01) / s, 0.25f * s);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (m00 > m11 && m00 > m22) {
|
||||||
|
const float s = std::sqrt(1.0f + m00 - m11 - m22) * 2.0f;
|
||||||
|
*outQuat = Quaternion(0.25f * s, (m01 + m10) / s, (m02 + m20) / s, (m21 - m12) / s);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (m11 > m22) {
|
||||||
|
const float s = std::sqrt(1.0f + m11 - m00 - m22) * 2.0f;
|
||||||
|
*outQuat = Quaternion((m01 + m10) / s, 0.25f * s, (m12 + m21) / s, (m02 - m20) / s);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
const float s = std::sqrt(1.0f + m22 - m00 - m11) * 2.0f;
|
||||||
|
*outQuat = Quaternion((m02 + m20) / s, (m12 + m21) / s, 0.25f * s, (m10 - m01) / s);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
#ifdef GKMS_WINDOWS
|
#ifdef GKMS_WINDOWS
|
||||||
DEFINE_HOOK(void*, InternalSetOrientationAsync, (void* retstr, void* self, int type, void* c, void* tc, void* mtd)) {
|
DEFINE_HOOK(void*, InternalSetOrientationAsync, (void* retstr, void* self, int type, void* c, void* tc, void* mtd)) {
|
||||||
switch (Config::gameOrientation) {
|
switch (Config::gameOrientation) {
|
||||||
@@ -277,10 +525,16 @@ namespace GakumasLocal::HookMain {
|
|||||||
DEFINE_HOOK(void, EndCameraRendering, (void* ctx, void* camera, void* method)) {
|
DEFINE_HOOK(void, EndCameraRendering, (void* ctx, void* camera, void* method)) {
|
||||||
EndCameraRendering_Orig(ctx, camera, method);
|
EndCameraRendering_Orig(ctx, camera, method);
|
||||||
|
|
||||||
if (Config::enableFreeCamera && mainCameraCache) {
|
if (Config::enableFreeCamera) {
|
||||||
Unity_set_fieldOfView_Orig(mainCameraCache, GKCamera::baseCamera.fov);
|
// Camera.main 可能拿不到 3.0.0 实际渲染相机,用当前正在渲染的相机兜底
|
||||||
if (GKCamera::GetCameraMode() == GKCamera::CameraMode::FIRST_PERSON) {
|
CheckAndUpdateMainCamera(static_cast<UnityResolve::UnityType::Camera*>(camera));
|
||||||
mainCameraCache->SetNearClipPlane(0.001f);
|
if (mainCameraCache && IsNativeObjectAlive(mainCameraCache)) {
|
||||||
|
// 注意:不能在渲染回调里写相机 Transform(UnityPlayer 会崩),
|
||||||
|
// 位置/朝向由独立驱动线程负责,这里只保留 FOV/近裁剪等属性设置。
|
||||||
|
Unity_set_fieldOfView_Orig(mainCameraCache, GKCamera::baseCamera.fov);
|
||||||
|
if (GKCamera::GetCameraMode() == GKCamera::CameraMode::FIRST_PERSON) {
|
||||||
|
mainCameraCache->SetNearClipPlane(0.001f);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -849,6 +1103,12 @@ namespace GakumasLocal::HookMain {
|
|||||||
EffectGroup_ctor_Orig(self, mtd);
|
EffectGroup_ctor_Orig(self, mtd);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 原样返回传入的 ProduceStepType,阻止 SP 被转换成 Normal,日程上才看得到 SP。
|
||||||
|
// 不能调 _Orig,调了就是原版行为,SP 会被抹平。
|
||||||
|
DEFINE_HOOK(int, ExamExtensions_ExchangeSpToNormal, (int type, void* mtd)) {
|
||||||
|
return Config::dbgMode ? type : ExamExtensions_ExchangeSpToNormal_Orig(type, mtd);
|
||||||
|
}
|
||||||
|
|
||||||
// 用于本地化 MasterDB
|
// 用于本地化 MasterDB
|
||||||
DEFINE_HOOK(void, MessageExtensions_MergeFrom, (void* message, void* span, void* mtd)) {
|
DEFINE_HOOK(void, MessageExtensions_MergeFrom, (void* message, void* span, void* mtd)) {
|
||||||
MessageExtensions_MergeFrom_Orig(message, span, mtd);
|
MessageExtensions_MergeFrom_Orig(message, span, mtd);
|
||||||
@@ -1139,55 +1399,331 @@ namespace GakumasLocal::HookMain {
|
|||||||
return origList;
|
return origList;
|
||||||
}
|
}
|
||||||
|
|
||||||
DEFINE_HOOK(void*, UserCostumeCollection_FindBy, (void* self, void* predicate, void* mtd)) {
|
// 把主表全部服装/头部 ID 补进用户服装列表;klassName 决定按 body 还是 head 处理
|
||||||
auto ret = UserCostumeCollection_FindBy_Orig(self, predicate, mtd);
|
void* AugmentUserCostumeList(const std::string& klassName, void* origList) {
|
||||||
if (!Config::unlockAllLiveCostume) return ret;
|
if (klassName == "UserCostumeHeadCollection") {
|
||||||
|
|
||||||
auto this_klass = Il2cppUtils::get_class_from_instance(self);
|
|
||||||
// auto predicate_klass = Il2cppUtils::get_class_from_instance(predicate); // System::Predicate`1
|
|
||||||
// Log::DebugFmt("UserCostumeCollection_FindBy this: %s::%s, predicate: %s::%s", this_klass->namespaze, this_klass->name,
|
|
||||||
// predicate_klass->namespaze, predicate_klass->name);
|
|
||||||
|
|
||||||
static auto UserCostumeCollection_klass = Il2cppUtils::GetClass("Assembly-CSharp.dll", "Campus.Common.User",
|
|
||||||
"UserCostumeCollection");
|
|
||||||
static auto UserCostumeCollection_GetAllList_mtd = Il2cppUtils::il2cpp_class_get_method_from_name(
|
|
||||||
UserCostumeCollection_klass->address, "GetAllList", 1);
|
|
||||||
static auto UserCostumeCollection_GetAllList = reinterpret_cast<void* (*)(void*, void*)>(UserCostumeCollection_GetAllList_mtd->methodPointer);
|
|
||||||
|
|
||||||
std::string thisKlassName(this_klass->name);
|
|
||||||
// Campus.Common.User::UserCostumeHeadCollection || Campus.Common.User::UserCostumeCollection
|
|
||||||
// 两个 class 的 GetAllList 均使用的父类 Qua.UserDataManagement.UserDataCollectionBase`2 的方法,地址一致
|
|
||||||
if (thisKlassName == "UserCostumeHeadCollection") {
|
|
||||||
static auto UserCostume_Clone = Il2cppUtils::GetMethod("Assembly-CSharp.dll", "Campus.Common.Proto.Client.Transaction", "UserCostumeHead", "Clone");
|
static auto UserCostume_Clone = Il2cppUtils::GetMethod("Assembly-CSharp.dll", "Campus.Common.Proto.Client.Transaction", "UserCostumeHead", "Clone");
|
||||||
static auto UserCostume_get_CostumeHeadId = Il2cppUtils::GetMethod("Assembly-CSharp.dll", "Campus.Common.Proto.Client.Transaction", "UserCostumeHead", "get_CostumeHeadId");
|
static auto UserCostume_get_CostumeHeadId = Il2cppUtils::GetMethod("Assembly-CSharp.dll", "Campus.Common.Proto.Client.Transaction", "UserCostumeHead", "get_CostumeHeadId");
|
||||||
static auto UserCostume_set_CostumeHeadId = Il2cppUtils::GetMethod("Assembly-CSharp.dll", "Campus.Common.Proto.Client.Transaction", "UserCostumeHead", "set_CostumeHeadId");
|
static auto UserCostume_set_CostumeHeadId = Il2cppUtils::GetMethod("Assembly-CSharp.dll", "Campus.Common.Proto.Client.Transaction", "UserCostumeHead", "set_CostumeHeadId");
|
||||||
|
|
||||||
// auto ret_klass = Il2cppUtils::get_class_from_instance(ret); // WhereEnumerableIterator
|
// 游戏改名/改版本时这些会是 null,直接返回原列表,别带着空指针往下走
|
||||||
auto origList = UserCostumeCollection_GetAllList(self, nullptr);
|
if (!UserCostume_Clone || !UserCostume_get_CostumeHeadId || !UserCostume_set_CostumeHeadId) return origList;
|
||||||
|
|
||||||
auto allIds = GetIdolMusicIdAll("", GetIdolIdType::CostumeHeadId);
|
auto allIds = GetIdolMusicIdAll("", GetIdolIdType::CostumeHeadId);
|
||||||
|
|
||||||
// List<Campus.Common.Proto.Client.Transaction.UserCostumeHead>
|
// List<Campus.Common.Proto.Client.Transaction.UserCostumeHead>
|
||||||
return AddIdsToUserDataCollectionFromMaster(origList, allIds, UserCostume_get_CostumeHeadId, UserCostume_set_CostumeHeadId, UserCostume_Clone);
|
return AddIdsToUserDataCollectionFromMaster(origList, allIds, UserCostume_get_CostumeHeadId, UserCostume_set_CostumeHeadId, UserCostume_Clone);
|
||||||
}
|
}
|
||||||
else if (thisKlassName == "UserCostumeCollection") {
|
if (klassName == "UserCostumeCollection") {
|
||||||
// static auto UserCostume_klass = Il2cppUtils::GetClass("Assembly-CSharp.dll", "Campus.Common.Proto.Client.Transaction", "UserCostume");
|
|
||||||
static auto UserCostume_Clone = Il2cppUtils::GetMethod("Assembly-CSharp.dll", "Campus.Common.Proto.Client.Transaction", "UserCostume", "Clone");
|
static auto UserCostume_Clone = Il2cppUtils::GetMethod("Assembly-CSharp.dll", "Campus.Common.Proto.Client.Transaction", "UserCostume", "Clone");
|
||||||
static auto UserCostume_get_CostumeId = Il2cppUtils::GetMethod("Assembly-CSharp.dll", "Campus.Common.Proto.Client.Transaction", "UserCostume", "get_CostumeId");
|
static auto UserCostume_get_CostumeId = Il2cppUtils::GetMethod("Assembly-CSharp.dll", "Campus.Common.Proto.Client.Transaction", "UserCostume", "get_CostumeId");
|
||||||
static auto UserCostume_set_CostumeId = Il2cppUtils::GetMethod("Assembly-CSharp.dll", "Campus.Common.Proto.Client.Transaction", "UserCostume", "set_CostumeId");
|
static auto UserCostume_set_CostumeId = Il2cppUtils::GetMethod("Assembly-CSharp.dll", "Campus.Common.Proto.Client.Transaction", "UserCostume", "set_CostumeId");
|
||||||
|
|
||||||
// auto ret_klass = Il2cppUtils::get_class_from_instance(ret); // WhereEnumerableIterator
|
if (!UserCostume_Clone || !UserCostume_get_CostumeId || !UserCostume_set_CostumeId) return origList;
|
||||||
auto origList = UserCostumeCollection_GetAllList(self, nullptr);
|
|
||||||
|
|
||||||
auto allIds = GetIdolMusicIdAll("", GetIdolIdType::CostumeId);
|
auto allIds = GetIdolMusicIdAll("", GetIdolIdType::CostumeId);
|
||||||
|
|
||||||
// List<Campus.Common.Proto.Client.Transaction.UserCostume>
|
// List<Campus.Common.Proto.Client.Transaction.UserCostume>
|
||||||
return AddIdsToUserDataCollectionFromMaster(origList, allIds, UserCostume_get_CostumeId, UserCostume_set_CostumeId, UserCostume_Clone);
|
return AddIdsToUserDataCollectionFromMaster(origList, allIds, UserCostume_get_CostumeId, UserCostume_set_CostumeId, UserCostume_Clone);
|
||||||
}
|
}
|
||||||
|
return origList;
|
||||||
|
}
|
||||||
|
|
||||||
|
DEFINE_HOOK(void*, UserCostumeCollection_FindBy, (void* self, void* predicate, void* mtd)) {
|
||||||
|
auto ret = UserCostumeCollection_FindBy_Orig(self, predicate, mtd);
|
||||||
|
if (!(Config::dbgMode && Config::unlockAllLiveCostume)) return ret;
|
||||||
|
|
||||||
|
auto this_klass = Il2cppUtils::get_class_from_instance(self);
|
||||||
|
|
||||||
|
std::string thisKlassName(this_klass->name);
|
||||||
|
// Campus.Common.User::UserCostumeHeadCollection || Campus.Common.User::UserCostumeCollection
|
||||||
|
// 两个 class 的 GetAllList 均使用的父类 Qua.UserDataManagement.UserDataCollectionBase`2 的方法,地址一致
|
||||||
|
if (thisKlassName == "UserCostumeHeadCollection" || thisKlassName == "UserCostumeCollection") {
|
||||||
|
auto getAllListMtd = Il2cppUtils::il2cpp_class_get_method_from_name(this_klass, "GetAllList", 1);
|
||||||
|
if (!getAllListMtd) return ret;
|
||||||
|
auto getAllList = reinterpret_cast<void* (*)(void*, void*, void*)>(getAllListMtd->methodPointer);
|
||||||
|
auto origList = getAllList(self, nullptr, (void*)getAllListMtd);
|
||||||
|
return AugmentUserCostumeList(thisKlassName, origList);
|
||||||
|
}
|
||||||
|
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// CostumePhotoGroup 主表收录的全部服装 ID——游戏自己的“可拍摄(已实装)”白名单
|
||||||
|
std::unordered_set<std::string> GetPhotoGroupCostumeIds() {
|
||||||
|
std::unordered_set<std::string> ret{};
|
||||||
|
auto getMaster = Il2cppUtils::GetMethod("Assembly-CSharp.dll", "Campus.Common.Master", "MasterManager", "get_CostumePhotoGroupMaster");
|
||||||
|
auto masterKlass = Il2cppUtils::GetClass("Assembly-CSharp.dll", "Campus.Common.Master", "CostumePhotoGroupMaster");
|
||||||
|
auto getCostumeIds = Il2cppUtils::GetMethod("Assembly-CSharp.dll", "Campus.Common.Proto.Client.Master", "CostumePhotoGroup", "get_CostumeIds");
|
||||||
|
if (!getMaster || !masterKlass || !getCostumeIds) return ret;
|
||||||
|
auto getAll_mtd = Il2cppUtils::il2cpp_class_get_method_from_name(masterKlass->address, "GetAllWithSortByKey", 1);
|
||||||
|
if (!getAll_mtd) return ret;
|
||||||
|
auto getAll = reinterpret_cast<UnityResolve::UnityType::List<void*>* (*)(void*, int, void*)>(getAll_mtd->methodPointer);
|
||||||
|
|
||||||
|
auto master = getMaster->Invoke<void*>(nullptr);
|
||||||
|
if (!master) return ret;
|
||||||
|
auto list = getAll(master, 0, (void*)getAll_mtd);
|
||||||
|
if (!list) return ret;
|
||||||
|
for (auto group : list->ToArray()->ToVector()) {
|
||||||
|
if (!group) continue; // ToVector 含 List 容量内的 null 槽位
|
||||||
|
auto rep = getCostumeIds->Invoke<void*>(group); // RepeatedField<string>
|
||||||
|
if (!rep) continue;
|
||||||
|
auto rep_klass = Il2cppUtils::get_class_from_instance(rep);
|
||||||
|
static auto count_mtd = Il2cppUtils::il2cpp_class_get_method_from_name(rep_klass, "get_Count", 0);
|
||||||
|
static auto item_mtd = Il2cppUtils::il2cpp_class_get_method_from_name(rep_klass, "get_Item", 1);
|
||||||
|
if (!count_mtd || !item_mtd) continue;
|
||||||
|
// 共享泛型:末尾传 MethodInfo*
|
||||||
|
auto getCount = reinterpret_cast<int (*)(void*, void*)>(count_mtd->methodPointer);
|
||||||
|
auto getItem = reinterpret_cast<Il2cppString* (*)(void*, int, void*)>(item_mtd->methodPointer);
|
||||||
|
int n = getCount(rep, (void*)count_mtd);
|
||||||
|
for (int i = 0; i < n; ++i) {
|
||||||
|
auto s = getItem(rep, i, (void*)item_mtd);
|
||||||
|
if (s) ret.emplace(s->ToString());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 主表补充 ID:异色 = 与基础服装(baseIds,IdolCardSkin 来源)共享 colorGroupId 的变体;
|
||||||
|
// 另加 CostumePhotoGroup 白名单条目;ViewStartTime 在未来的未实装条目跳过。
|
||||||
|
// isHead 时返回这些服装引用的头部 ID
|
||||||
|
std::vector<std::string> GetMasterCostumeIdsAll(bool isHead, const std::unordered_set<std::string>& baseIds) {
|
||||||
|
std::vector<std::string> ret{};
|
||||||
|
auto getMaster = Il2cppUtils::GetMethod("Assembly-CSharp.dll", "Campus.Common.Master", "MasterManager", "get_CostumeMaster");
|
||||||
|
auto masterKlass = Il2cppUtils::GetClass("Assembly-CSharp.dll", "Campus.Common.Master", "CostumeMaster");
|
||||||
|
auto getId = Il2cppUtils::GetMethod("Assembly-CSharp.dll", "Campus.Common.Proto.Client.Master", "Costume", "get_Id");
|
||||||
|
auto getHeadId = Il2cppUtils::GetMethod("Assembly-CSharp.dll", "Campus.Common.Proto.Client.Master", "Costume", "get_CostumeHeadId");
|
||||||
|
auto getDefaultHeadId = Il2cppUtils::GetMethod("Assembly-CSharp.dll", "Campus.Common.Proto.Client.Master", "Costume", "get_DefaultCostumeHeadId");
|
||||||
|
auto getColorGroupId = Il2cppUtils::GetMethod("Assembly-CSharp.dll", "Campus.Common.Proto.Client.Master", "Costume", "get_CostumeColorGroupId");
|
||||||
|
auto getViewStartTime = Il2cppUtils::GetMethod("Assembly-CSharp.dll", "Campus.Common.Proto.Client.Master", "Costume", "get_ViewStartTime");
|
||||||
|
if (!getMaster || !masterKlass || !getId || !getHeadId || !getDefaultHeadId || !getColorGroupId || !getViewStartTime) return ret;
|
||||||
|
// 取 1 参重载 GetAllWithSortByKey(sortType),末尾补隐藏 MethodInfo*
|
||||||
|
auto getAll_mtd = Il2cppUtils::il2cpp_class_get_method_from_name(masterKlass->address, "GetAllWithSortByKey", 1);
|
||||||
|
if (!getAll_mtd) return ret;
|
||||||
|
auto getAll = reinterpret_cast<UnityResolve::UnityType::List<void*>* (*)(void*, int, void*)>(getAll_mtd->methodPointer);
|
||||||
|
|
||||||
|
auto photoAllowed = GetPhotoGroupCostumeIds();
|
||||||
|
if (photoAllowed.empty()) return ret;
|
||||||
|
|
||||||
|
auto master = getMaster->Invoke<void*>(nullptr);
|
||||||
|
if (!master) return ret;
|
||||||
|
auto list = getAll(master, 0, (void*)getAll_mtd);
|
||||||
|
if (!list) return ret;
|
||||||
|
auto items = list->ToArray()->ToVector();
|
||||||
|
|
||||||
|
// 第一遍:基础服装(IdolCardSkin 来源)与拍摄白名单条目的颜色组 → 允许其全部异色变体
|
||||||
|
std::unordered_set<std::string> allowedColorGroups{};
|
||||||
|
for (auto item : items) {
|
||||||
|
if (!item) continue; // ToVector 含 List 容量内的 null 槽位
|
||||||
|
auto id = getId->Invoke<Il2cppString*>(item);
|
||||||
|
if (!id) continue;
|
||||||
|
auto idStr = id->ToString();
|
||||||
|
if (!photoAllowed.contains(idStr) && !baseIds.contains(idStr)) continue;
|
||||||
|
auto cg = getColorGroupId->Invoke<Il2cppString*>(item);
|
||||||
|
if (cg && !cg->ToString().empty()) allowedColorGroups.emplace(cg->ToString());
|
||||||
|
}
|
||||||
|
|
||||||
|
const auto nowSec = (int64_t)time(nullptr);
|
||||||
|
std::unordered_set<std::string> seen{};
|
||||||
|
int skippedFuture = 0;
|
||||||
|
for (auto item : items) {
|
||||||
|
if (!item) continue;
|
||||||
|
auto id = getId->Invoke<Il2cppString*>(item);
|
||||||
|
if (!id) continue;
|
||||||
|
bool allowed = photoAllowed.contains(id->ToString());
|
||||||
|
auto viewStart = getViewStartTime->Invoke<int64_t>(item);
|
||||||
|
if (viewStart > 4000000000LL) viewStart /= 1000; // 毫秒 → 秒
|
||||||
|
|
||||||
|
if (!allowed) {
|
||||||
|
auto cg = getColorGroupId->Invoke<Il2cppString*>(item);
|
||||||
|
allowed = cg && !cg->ToString().empty() && allowedColorGroups.contains(cg->ToString());
|
||||||
|
}
|
||||||
|
// 独立活动/商店服(如水手服泳装):无颜色组但有已到期的公开时间
|
||||||
|
if (!allowed) allowed = viewStart > 0 && viewStart <= nowSec;
|
||||||
|
if (!allowed) continue;
|
||||||
|
|
||||||
|
// 未实装(公开时间在未来)的条目没有资源,进拍摄页会黑屏卡死
|
||||||
|
if (viewStart > nowSec) { ++skippedFuture; continue; }
|
||||||
|
|
||||||
|
if (!isHead) {
|
||||||
|
if (seen.emplace(id->ToString()).second) ret.emplace_back(id->ToString());
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
for (auto headGetter : { getHeadId, getDefaultHeadId }) {
|
||||||
|
auto headId = headGetter->Invoke<Il2cppString*>(item);
|
||||||
|
if (!headId) continue;
|
||||||
|
auto headIdStr = headId->ToString();
|
||||||
|
if (!headIdStr.empty() && seen.emplace(headIdStr).second) ret.emplace_back(headIdStr);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (skippedFuture) Log::InfoFmt("GetMasterCostumeIdsAll(isHead=%d): skipped future entries=%d", isHead, skippedFuture);
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 把缺失的主表服装/头部克隆成用户记录,直接 Add 进集合本体;
|
||||||
|
// 这样 GetAll()(字典 Values 视图,拍摄页 CreateItemModels 用它)等所有读取路径都能看到
|
||||||
|
bool AddMissingCostumesToCollection(void* self, const std::string& klassName) {
|
||||||
|
UnityResolve::Method *Clone, *getId, *setId;
|
||||||
|
GetIdolIdType idType;
|
||||||
|
if (klassName == "UserCostumeHeadCollection") {
|
||||||
|
static auto head_Clone = Il2cppUtils::GetMethod("Assembly-CSharp.dll", "Campus.Common.Proto.Client.Transaction", "UserCostumeHead", "Clone");
|
||||||
|
static auto head_getId = Il2cppUtils::GetMethod("Assembly-CSharp.dll", "Campus.Common.Proto.Client.Transaction", "UserCostumeHead", "get_CostumeHeadId");
|
||||||
|
static auto head_setId = Il2cppUtils::GetMethod("Assembly-CSharp.dll", "Campus.Common.Proto.Client.Transaction", "UserCostumeHead", "set_CostumeHeadId");
|
||||||
|
Clone = head_Clone; getId = head_getId; setId = head_setId;
|
||||||
|
idType = GetIdolIdType::CostumeHeadId;
|
||||||
|
}
|
||||||
|
else if (klassName == "UserCostumeCollection") {
|
||||||
|
static auto body_Clone = Il2cppUtils::GetMethod("Assembly-CSharp.dll", "Campus.Common.Proto.Client.Transaction", "UserCostume", "Clone");
|
||||||
|
static auto body_getId = Il2cppUtils::GetMethod("Assembly-CSharp.dll", "Campus.Common.Proto.Client.Transaction", "UserCostume", "get_CostumeId");
|
||||||
|
static auto body_setId = Il2cppUtils::GetMethod("Assembly-CSharp.dll", "Campus.Common.Proto.Client.Transaction", "UserCostume", "set_CostumeId");
|
||||||
|
Clone = body_Clone; getId = body_getId; setId = body_setId;
|
||||||
|
idType = GetIdolIdType::CostumeId;
|
||||||
|
}
|
||||||
|
else return false;
|
||||||
|
if (!Clone || !getId || !setId) return false;
|
||||||
|
|
||||||
|
auto collectionKlass = Il2cppUtils::get_class_from_instance(self);
|
||||||
|
if (!collectionKlass || klassName != collectionKlass->name) return false;
|
||||||
|
// 共享泛型方法必须使用实际运行时集合类型的 MethodInfo,并传入末尾隐藏参数。
|
||||||
|
auto getAllListMtd = Il2cppUtils::il2cpp_class_get_method_from_name(collectionKlass, "GetAllList", 1);
|
||||||
|
auto addMtd = Il2cppUtils::il2cpp_class_get_method_from_name(collectionKlass, "Add", 1);
|
||||||
|
if (!getAllListMtd || !addMtd) return false;
|
||||||
|
auto getAllList = reinterpret_cast<void* (*)(void*, void*, void*)>(getAllListMtd->methodPointer);
|
||||||
|
auto collectionAdd = reinterpret_cast<void (*)(void*, void*, void*)>(addMtd->methodPointer);
|
||||||
|
// Exists 用于 Add 前复核:字典里可能存在 GetAllList 看不到的 key,
|
||||||
|
// 重复 Add 会在 native 栈上抛托管异常,直接崩
|
||||||
|
auto existsMtd = Il2cppUtils::il2cpp_class_get_method_from_name(collectionKlass, "Exists", 1);
|
||||||
|
auto collectionExists = existsMtd
|
||||||
|
? reinterpret_cast<bool (*)(void*, Il2cppString*, void*)>(existsMtd->methodPointer)
|
||||||
|
: nullptr;
|
||||||
|
|
||||||
|
auto list = getAllList(self, nullptr, (void*)getAllListMtd);
|
||||||
|
Il2cppUtils::Tools::CSListEditor listEditor(list);
|
||||||
|
if (listEditor.get_Count() <= 0) return false; // 无模板可克隆,数据未加载完,等下次调用
|
||||||
|
|
||||||
|
std::unordered_set<std::string> existIds{};
|
||||||
|
for (auto i : listEditor) {
|
||||||
|
auto id = getId->Invoke<Il2cppString*>(i);
|
||||||
|
if (id) existIds.emplace(id->ToString());
|
||||||
|
}
|
||||||
|
|
||||||
|
auto allIds = GetIdolMusicIdAll("", idType);
|
||||||
|
// 颜色组锚点始终用 body 服装 ID(head 的异色也是通过 body 行的颜色组关联的)
|
||||||
|
auto bodyIds = GetIdolMusicIdAll("", GetIdolIdType::CostumeId);
|
||||||
|
std::unordered_set<std::string> baseIds(bodyIds.begin(), bodyIds.end());
|
||||||
|
auto masterIds = GetMasterCostumeIdsAll(idType == GetIdolIdType::CostumeHeadId, baseIds);
|
||||||
|
allIds.insert(allIds.end(), masterIds.begin(), masterIds.end());
|
||||||
|
int added = 0;
|
||||||
|
for (auto& id : allIds) {
|
||||||
|
// emplace 兼做去重:两个来源有重叠,重复 Add 同一 key 会抛异常
|
||||||
|
if (id.empty() || !existIds.emplace(id).second) continue;
|
||||||
|
auto idStr = Il2cppString::New(id);
|
||||||
|
if (collectionExists && collectionExists(self, idStr, (void*)existsMtd)) continue;
|
||||||
|
auto clone = Clone->Invoke<void*>(listEditor.get_Item(0));
|
||||||
|
setId->Invoke<void>(clone, idStr);
|
||||||
|
collectionAdd(self, clone, (void*)addMtd);
|
||||||
|
++added;
|
||||||
|
}
|
||||||
|
Log::InfoFmt("Costume collection augment: %s, added=%d", klassName.c_str(), added);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 集合当前条目数,取不到返回 -1
|
||||||
|
int GetUserDataCollectionCount(void* self, void* klass) {
|
||||||
|
auto mtd = Il2cppUtils::il2cpp_class_get_method_from_name(klass, "get_Count", 0);
|
||||||
|
if (!mtd) return -1;
|
||||||
|
return reinterpret_cast<int (*)(void*, void*)>(mtd->methodPointer)(self, (void*)mtd);
|
||||||
|
}
|
||||||
|
|
||||||
|
DEFINE_HOOK(void*, UserDataCollection_GetAll, (void* self, void* mtd)) {
|
||||||
|
if (Config::dbgMode && Config::unlockAllLiveCostume) {
|
||||||
|
auto this_klass = Il2cppUtils::get_class_from_instance(self);
|
||||||
|
const char* klassName = this_klass ? this_klass->name : nullptr;
|
||||||
|
// GetAll 是父类共享泛型实现,游戏内所有 UserDataCollection 都会命中这里,
|
||||||
|
// 属于热路径,先用 strcmp 早退,别构造 std::string
|
||||||
|
if (klassName && (strcmp(klassName, "UserCostumeCollection") == 0
|
||||||
|
|| strcmp(klassName, "UserCostumeHeadCollection") == 0)) {
|
||||||
|
// 按实例指针记住"已补过",再用条目数复核:Boehm GC 不移动对象,但会复用
|
||||||
|
// 已释放的地址,新集合落到旧地址上时条目数对不上,于是重新补一次。
|
||||||
|
// 取不到 Count(返回 -1)时退化成"每个指针只补一次"的旧行为。
|
||||||
|
static std::unordered_map<void*, int> augmentedCounts{};
|
||||||
|
const auto it = augmentedCounts.find(self);
|
||||||
|
if (it == augmentedCounts.end() || it->second != GetUserDataCollectionCount(self, this_klass)) {
|
||||||
|
if (AddMissingCostumesToCollection(self, klassName)) {
|
||||||
|
augmentedCounts[self] = GetUserDataCollectionCount(self, this_klass);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return UserDataCollection_GetAll_Orig(self, mtd);
|
||||||
|
}
|
||||||
|
|
||||||
|
DEFINE_HOOK(bool, PhotographyCostumeSettingListItemModel_get_IsDisabled, (void* self, void* mtd)) {
|
||||||
|
return Config::dbgMode && Config::unlockAllLiveCostume
|
||||||
|
? false
|
||||||
|
: PhotographyCostumeSettingListItemModel_get_IsDisabled_Orig(self, mtd);
|
||||||
|
}
|
||||||
|
|
||||||
|
void* AddPhotographyIdolSkinItems(void* list, const std::string& characterId,
|
||||||
|
UnityResolve::Method* getItem, UnityResolve::Method* getId) {
|
||||||
|
if (!list || characterId.empty() || !getItem || !getId) return list;
|
||||||
|
|
||||||
|
static auto get_IdolCardSkinMaster = Il2cppUtils::GetMethod("Assembly-CSharp.dll", "Campus.Common.Master", "MasterManager", "get_IdolCardSkinMaster");
|
||||||
|
static auto Master_GetAllWithSortByKey = Il2cppUtils::GetMethod("Assembly-CSharp.dll", "Campus.Common.Master", "IdolCardSkinMaster", "GetAllWithSortByKey");
|
||||||
|
static auto IdolCardSkin_get_IdolCardId = Il2cppUtils::GetMethod("Assembly-CSharp.dll", "Campus.Common.Proto.Client.Master", "IdolCardSkin", "get_IdolCardId");
|
||||||
|
if (!get_IdolCardSkinMaster || !Master_GetAllWithSortByKey || !IdolCardSkin_get_IdolCardId) return list;
|
||||||
|
|
||||||
|
Il2cppUtils::Tools::CSListEditor listEditor(list);
|
||||||
|
std::unordered_set<std::string> ids;
|
||||||
|
for (auto item : listEditor) {
|
||||||
|
auto id = getId->Invoke<Il2cppString*>(item);
|
||||||
|
if (id) ids.emplace(id->ToString());
|
||||||
|
}
|
||||||
|
|
||||||
|
auto idolCardSkinMaster = get_IdolCardSkinMaster->Invoke<void*>(nullptr);
|
||||||
|
auto idolCardSkinList = idolCardSkinMaster
|
||||||
|
? Master_GetAllWithSortByKey->Invoke<UnityResolve::UnityType::List<void*>*>(idolCardSkinMaster, 0, nullptr)
|
||||||
|
: nullptr;
|
||||||
|
if (!idolCardSkinList) return list;
|
||||||
|
|
||||||
|
int added = 0;
|
||||||
|
const auto cardPrefix = "i_card-" + characterId;
|
||||||
|
for (auto idolCardSkin : idolCardSkinList->ToArray()->ToVector()) {
|
||||||
|
if (!idolCardSkin) continue; // ToVector 会包含 List 容量内的 null 槽位,与 GetIdolMusicIdAll 一致
|
||||||
|
auto idolCardId = IdolCardSkin_get_IdolCardId->Invoke<Il2cppString*>(idolCardSkin);
|
||||||
|
if (!idolCardId || !idolCardId->ToString().starts_with(cardPrefix)) continue;
|
||||||
|
|
||||||
|
auto item = getItem->Invoke<void*>(idolCardSkin);
|
||||||
|
auto id = item ? getId->Invoke<Il2cppString*>(item) : nullptr;
|
||||||
|
if (id && ids.emplace(id->ToString()).second) {
|
||||||
|
listEditor.Add(item);
|
||||||
|
++added;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Log::InfoFmt("Photography add: character=%s, added=%d", characterId.c_str(), added);
|
||||||
|
return list;
|
||||||
|
}
|
||||||
|
|
||||||
|
DEFINE_HOOK(void*, PhotographyCostumeSettingData_GetCostumes,
|
||||||
|
(void* self, Il2cppString* characterId, void* mtd)) {
|
||||||
|
auto ret = PhotographyCostumeSettingData_GetCostumes_Orig(self, characterId, mtd);
|
||||||
|
if (!(Config::dbgMode && Config::unlockAllLiveCostume)) return ret;
|
||||||
|
|
||||||
|
static auto getItem = Il2cppUtils::GetMethod("Assembly-CSharp.dll", "Campus.Common.Proto.Client.Master", "IdolCardSkin", "GetCostume");
|
||||||
|
static auto getId = Il2cppUtils::GetMethod("Assembly-CSharp.dll", "Campus.Common.Proto.Client.Master", "Costume", "get_Id");
|
||||||
|
return AddPhotographyIdolSkinItems(ret, characterId ? characterId->ToString() : "", getItem, getId);
|
||||||
|
}
|
||||||
|
|
||||||
|
DEFINE_HOOK(void*, PhotographyCostumeSettingData_GetCostumeHeads,
|
||||||
|
(void* self, Il2cppString* characterId, void* mtd)) {
|
||||||
|
auto ret = PhotographyCostumeSettingData_GetCostumeHeads_Orig(self, characterId, mtd);
|
||||||
|
if (!(Config::dbgMode && Config::unlockAllLiveCostume)) return ret;
|
||||||
|
|
||||||
|
static auto getItem = Il2cppUtils::GetMethod("Assembly-CSharp.dll", "Campus.Common.Proto.Client.Master", "IdolCardSkin", "GetCostumeHead");
|
||||||
|
static auto getId = Il2cppUtils::GetMethod("Assembly-CSharp.dll", "Campus.Common.Proto.Client.Master", "CostumeHead", "get_Id");
|
||||||
|
return AddPhotographyIdolSkinItems(ret, characterId ? characterId->ToString() : "", getItem, getId);
|
||||||
|
}
|
||||||
|
|
||||||
void* getCompletedUniTask() {
|
void* getCompletedUniTask() {
|
||||||
static auto unitask_klass = Il2cppUtils::GetClass("UniTask.dll", "Cysharp.Threading.Tasks", "UniTask");
|
static auto unitask_klass = Il2cppUtils::GetClass("UniTask.dll", "Cysharp.Threading.Tasks", "UniTask");
|
||||||
static auto CompletedTask_field = unitask_klass->Get<UnityResolve::Field>("CompletedTask");
|
static auto CompletedTask_field = unitask_klass->Get<UnityResolve::Field>("CompletedTask");
|
||||||
@@ -1270,16 +1806,23 @@ namespace GakumasLocal::HookMain {
|
|||||||
return PictureBookLiveSelectScreenPresenter_MoveLiveScene_Orig(self, produceLive, isPlayCharacterFocusCamera, mtd);
|
return PictureBookLiveSelectScreenPresenter_MoveLiveScene_Orig(self, produceLive, isPlayCharacterFocusCamera, mtd);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 进入 Live 时歌词默认显示:仅对每个 Presenter 实例的第一次设置强制 true,
|
||||||
|
// 之后(玩家的手动开关)原样透传,不再覆盖关闭操作。
|
||||||
|
std::unordered_set<void*> g_lyricsInitialForced{};
|
||||||
|
|
||||||
DEFINE_HOOK(void, LiveSceneModel_set_IsLyricsActive, (void* self, bool value, void* mtd)) {
|
DEFINE_HOOK(void, LiveSceneModel_set_IsLyricsActive, (void* self, bool value, void* mtd)) {
|
||||||
return LiveSceneModel_set_IsLyricsActive_Orig(self, Config::dbgMode && Config::unlockAllLive ? true : value, mtd);
|
return LiveSceneModel_set_IsLyricsActive_Orig(self, value, mtd);
|
||||||
}
|
}
|
||||||
|
|
||||||
DEFINE_HOOK(void, LiveScenePresenter_SetLyricsActive, (void* self, bool value, void* mtd)) {
|
DEFINE_HOOK(void, LiveScenePresenter_SetLyricsActive, (void* self, bool value, void* mtd)) {
|
||||||
return LiveScenePresenter_SetLyricsActive_Orig(self, Config::dbgMode && Config::unlockAllLive ? true : value, mtd);
|
if (Config::dbgMode && Config::unlockAllLive && self && g_lyricsInitialForced.insert(self).second) {
|
||||||
|
value = true;
|
||||||
|
}
|
||||||
|
return LiveScenePresenter_SetLyricsActive_Orig(self, value, mtd);
|
||||||
}
|
}
|
||||||
|
|
||||||
DEFINE_HOOK(void, LiveSceneContentView_SetLyricsTextActive, (void* self, bool value, void* mtd)) {
|
DEFINE_HOOK(void, LiveSceneContentView_SetLyricsTextActive, (void* self, bool value, void* mtd)) {
|
||||||
return LiveSceneContentView_SetLyricsTextActive_Orig(self, Config::dbgMode && Config::unlockAllLive ? true : value, mtd);
|
return LiveSceneContentView_SetLyricsTextActive_Orig(self, value, mtd);
|
||||||
}
|
}
|
||||||
|
|
||||||
// std::string lastMusicId;
|
// std::string lastMusicId;
|
||||||
@@ -1931,6 +2474,12 @@ namespace GakumasLocal::HookMain {
|
|||||||
ADD_HOOK(MessageExtensions_MergeFrom, Il2cppUtils::GetMethodPointer("Google.Protobuf.dll", "Google.Protobuf",
|
ADD_HOOK(MessageExtensions_MergeFrom, Il2cppUtils::GetMethodPointer("Google.Protobuf.dll", "Google.Protobuf",
|
||||||
"MessageExtensions", "MergeFrom", {"Google.Protobuf.IMessage", "System.ReadOnlySpan<System.Byte>"}));
|
"MessageExtensions", "MergeFrom", {"Google.Protobuf.IMessage", "System.ReadOnlySpan<System.Byte>"}));
|
||||||
|
|
||||||
|
ADD_HOOK(ExamExtensions_ExchangeSpToNormal,
|
||||||
|
Il2cppUtils::GetMethodPointer(
|
||||||
|
"Assembly-CSharp.dll", "Campus.InGame", "ExamExtensions",
|
||||||
|
"ExchangeSpToNormal",
|
||||||
|
{ "Campus.Common.Proto.Client.Enums.ProduceStepType" }));
|
||||||
|
|
||||||
/* // 此 block 为 MasterBase 相关的 hook,后来发现它们最后都会调用 MessageExtensions.MergeFrom 进行构造,遂停用。现留档以备用
|
/* // 此 block 为 MasterBase 相关的 hook,后来发现它们最后都会调用 MessageExtensions.MergeFrom 进行构造,遂停用。现留档以备用
|
||||||
// ADD_HOOK(MasterBase_GetAll, Il2cppUtils::GetMethodPointer("quaunity-master-manager.Runtime.dll", "Qua.Master",
|
// ADD_HOOK(MasterBase_GetAll, Il2cppUtils::GetMethodPointer("quaunity-master-manager.Runtime.dll", "Qua.Master",
|
||||||
// "MasterBase`2", "GetAll", {"*", "*", "*", "*", "*"}));
|
// "MasterBase`2", "GetAll", {"*", "*", "*", "*", "*"}));
|
||||||
@@ -1993,6 +2542,22 @@ namespace GakumasLocal::HookMain {
|
|||||||
ADD_HOOK(UserCostumeCollection_FindBy, UserCostumeCollection_FindBy_mtd->methodPointer);
|
ADD_HOOK(UserCostumeCollection_FindBy, UserCostumeCollection_FindBy_mtd->methodPointer);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
auto UserCostumeCollection_GetAll_mtd = Il2cppUtils::il2cpp_class_get_method_from_name(
|
||||||
|
UserCostumeCollection_klass->address, "GetAll", 0);
|
||||||
|
if (UserCostumeCollection_GetAll_mtd) {
|
||||||
|
ADD_HOOK(UserDataCollection_GetAll, UserCostumeCollection_GetAll_mtd->methodPointer);
|
||||||
|
}
|
||||||
|
|
||||||
|
ADD_HOOK(PhotographyCostumeSettingListItemModel_get_IsDisabled,
|
||||||
|
Il2cppUtils::GetMethodPointer("Assembly-CSharp.dll", "Campus.Photography",
|
||||||
|
"PhotographyCostumeSettingListItemModel", "get_IsDisabled"));
|
||||||
|
ADD_HOOK(PhotographyCostumeSettingData_GetCostumes,
|
||||||
|
Il2cppUtils::GetMethodPointer("Assembly-CSharp.dll", "Campus.Photography",
|
||||||
|
"PhotographyCostumeSettingData", "GetCostumes"));
|
||||||
|
ADD_HOOK(PhotographyCostumeSettingData_GetCostumeHeads,
|
||||||
|
Il2cppUtils::GetMethodPointer("Assembly-CSharp.dll", "Campus.Photography",
|
||||||
|
"PhotographyCostumeSettingData", "GetCostumeHeads"));
|
||||||
|
|
||||||
// 双端
|
// 双端
|
||||||
ADD_HOOK(PictureBookLiveThumbnailView_SetReleaseDataAsync,
|
ADD_HOOK(PictureBookLiveThumbnailView_SetReleaseDataAsync,
|
||||||
GetMethodPointerByArgCount("Assembly-CSharp.dll", "Campus.OutGame.PictureBook",
|
GetMethodPointerByArgCount("Assembly-CSharp.dll", "Campus.OutGame.PictureBook",
|
||||||
@@ -2142,6 +2707,17 @@ namespace GakumasLocal::HookMain {
|
|||||||
"UnityEngine.Transform::set_position_Injected(UnityEngine.Vector3&)"));
|
"UnityEngine.Transform::set_position_Injected(UnityEngine.Vector3&)"));
|
||||||
ADD_HOOK(Unity_set_rotation_Injected, Il2cppUtils::il2cpp_resolve_icall(
|
ADD_HOOK(Unity_set_rotation_Injected, Il2cppUtils::il2cpp_resolve_icall(
|
||||||
"UnityEngine.Transform::set_rotation_Injected(UnityEngine.Quaternion&)"));
|
"UnityEngine.Transform::set_rotation_Injected(UnityEngine.Quaternion&)"));
|
||||||
|
ADD_HOOK(Unity_SetPositionAndRotation_Injected, Il2cppUtils::il2cpp_resolve_icall(
|
||||||
|
"UnityEngine.Transform::SetPositionAndRotation_Injected(UnityEngine.Vector3&,UnityEngine.Quaternion&)"));
|
||||||
|
ADD_HOOK(Unity_set_localPosition_Injected, Il2cppUtils::il2cpp_resolve_icall(
|
||||||
|
"UnityEngine.Transform::set_localPosition_Injected(UnityEngine.Vector3&)"));
|
||||||
|
ADD_HOOK(Camera_get_main, Il2cppUtils::GetMethodPointer("UnityEngine.CoreModule.dll", "UnityEngine",
|
||||||
|
"Camera", "get_main"));
|
||||||
|
ADD_HOOK(CinemachineBrain_PushStateToUnityCamera,
|
||||||
|
Il2cppUtils::GetMethodPointer("Cinemachine.dll", "Cinemachine",
|
||||||
|
"CinemachineBrain", "PushStateToUnityCamera"));
|
||||||
|
ADD_HOOK(Component_get_transform, Il2cppUtils::GetMethodPointer("UnityEngine.CoreModule.dll", "UnityEngine",
|
||||||
|
"Component", "get_transform"));
|
||||||
ADD_HOOK(Unity_get_fieldOfView, Il2cppUtils::GetMethodPointer("UnityEngine.CoreModule.dll", "UnityEngine",
|
ADD_HOOK(Unity_get_fieldOfView, Il2cppUtils::GetMethodPointer("UnityEngine.CoreModule.dll", "UnityEngine",
|
||||||
"Camera", "get_fieldOfView"));
|
"Camera", "get_fieldOfView"));
|
||||||
ADD_HOOK(Unity_set_fieldOfView, Il2cppUtils::GetMethodPointer("UnityEngine.CoreModule.dll", "UnityEngine",
|
ADD_HOOK(Unity_set_fieldOfView, Il2cppUtils::GetMethodPointer("UnityEngine.CoreModule.dll", "UnityEngine",
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
#include "baseCamera.hpp"
|
#include "baseCamera.hpp"
|
||||||
#include "camera.hpp"
|
#include "camera.hpp"
|
||||||
#include <thread>
|
#include <thread>
|
||||||
|
#include <cmath>
|
||||||
#include "../Misc.hpp"
|
#include "../Misc.hpp"
|
||||||
#include "../BaseDefine.h"
|
#include "../BaseDefine.h"
|
||||||
#include "../../platformDefine.hpp"
|
#include "../../platformDefine.hpp"
|
||||||
@@ -411,6 +412,14 @@ namespace GKCamera {
|
|||||||
const auto currentY = targetPos.y;
|
const auto currentY = targetPos.y;
|
||||||
static auto lastRetY = currentY;
|
static auto lastRetY = currentY;
|
||||||
|
|
||||||
|
// NaN/Inf 防护:目标 Y 非法时保持上一次有效值,避免污染平滑队列后相机飞走
|
||||||
|
if (!std::isfinite(currentY)) {
|
||||||
|
return std::isfinite(lastRetY) ? lastRetY : 0.0f;
|
||||||
|
}
|
||||||
|
if (!std::isfinite(lastRetY)) {
|
||||||
|
lastRetY = currentY;
|
||||||
|
}
|
||||||
|
|
||||||
if (followModeY == FollowModeY::APPLY_Y) {
|
if (followModeY == FollowModeY::APPLY_Y) {
|
||||||
lastRetY = currentY;
|
lastRetY = currentY;
|
||||||
return currentY;
|
return currentY;
|
||||||
@@ -423,6 +432,10 @@ namespace GKCamera {
|
|||||||
recordsY.Push(currentY);
|
recordsY.Push(currentY);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!std::isfinite(currentAvg)) {
|
||||||
|
return lastRetY;
|
||||||
|
}
|
||||||
|
|
||||||
if (abs(currentY - currentAvg) < 0.02) {
|
if (abs(currentY - currentAvg) < 0.02) {
|
||||||
return lastRetY;
|
return lastRetY;
|
||||||
}
|
}
|
||||||
@@ -460,7 +473,17 @@ namespace GKCamera {
|
|||||||
|
|
||||||
// 计算角色的右方向
|
// 计算角色的右方向
|
||||||
Vector3 up(0, 1, 0); // Y轴方向
|
Vector3 up(0, 1, 0); // Y轴方向
|
||||||
Vector3 right = forward.cross(up).Normalize();
|
Vector3 right = forward.cross(up);
|
||||||
|
|
||||||
|
// 修正归一化:原 UnityResolve::Vector3::Normalize 除的是平方长度,且零向量会产出 NaN
|
||||||
|
const auto rightLenSq = right.x * right.x + right.y * right.y + right.z * right.z;
|
||||||
|
if (rightLenSq < 1e-8f) {
|
||||||
|
// forward 与 up 平行/接近或为零向量,无法求右向,退化为仅高度偏移
|
||||||
|
return Vector3(position.x, position.y + offset.y, position.z);
|
||||||
|
}
|
||||||
|
const auto invRightLen = 1.0f / std::sqrt(rightLenSq);
|
||||||
|
right = Vector3(right.x * invRightLen, right.y * invRightLen, right.z * invRightLen);
|
||||||
|
|
||||||
Vector3 fwd = forward;
|
Vector3 fwd = forward;
|
||||||
Vector3 pos = position;
|
Vector3 pos = position;
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user