diff --git a/src/GakumasLocalify/GyroEmu.cpp b/src/GakumasLocalify/GyroEmu.cpp index 5c38af1..f29a625 100644 --- a/src/GakumasLocalify/GyroEmu.cpp +++ b/src/GakumasLocalify/GyroEmu.cpp @@ -38,12 +38,8 @@ namespace GakumasLocal::GyroEmu { dragging = false; } - // 小键盘:4/6 yaw, 8/2 pitch, 7/9 roll, 5 复位 + // 小键盘:7/9 翻滚, 5 复位。4/6/8/2 是游戏自己拍摄页的镜头键,不占用 const float step = 60.f * dt * Config::gyroSensitivity; - if (Down(VK_NUMPAD4)) yaw -= step * sx; - if (Down(VK_NUMPAD6)) yaw += step * sx; - if (Down(VK_NUMPAD8)) pitch -= step * sy; - if (Down(VK_NUMPAD2)) pitch += step * sy; if (Down(VK_NUMPAD7)) roll += step; if (Down(VK_NUMPAD9)) roll -= step; if (Down(VK_NUMPAD5)) Reset(); diff --git a/src/GakumasLocalify/Hook.cpp b/src/GakumasLocalify/Hook.cpp index 8cdc60f..5363f03 100644 --- a/src/GakumasLocalify/Hook.cpp +++ b/src/GakumasLocalify/Hook.cpp @@ -234,52 +234,92 @@ namespace GakumasLocal::HookMain { return Photography3DPresenter_get_IsGyroSupported_Orig(self, mtd); } - // PC 版把 LiveUtility.IsPanoramaGyroDisabledOnPlatform()(恒 true)内联成常量,CampusGyroController 根本不会被 - // 创建/初始化(log 证实 IsGyroAvailable 从未被查询),IsGyroReady 永远 false。这里在 LateUpdate 里自己把它建出来。 + // PC 版把 LiveUtility.IsPanoramaGyroDisabledOnPlatform()(恒 true)内联成常量,CampusGyroController 的 InitializeAsync + // 被整段跳过(log 证实 IsGyroAvailable 从未被查询),IsGyroReady 永远 false。Live 与拍摄各持有一个控制器, + // 所以在构造函数 hook 里统一把它置成初始化完成态,再在两条链路各留一个“控制器为空就自己建”的兜底。 // 字段偏移来自 3.3.0 il2cpp.h(CampusGyroController / VLGyro / LivePanoramaCameraManager)。 namespace GyroForceInit { using Quaternion = UnityResolve::UnityType::Quaternion; template T& F(void* obj, size_t off) { return *reinterpret_cast(reinterpret_cast(obj) + off); } - void Ensure(void* manager) { - static auto ctrlClass = Il2cppUtils::GetClass("campus-submodule.Runtime.dll", "Campus.Common", "CampusGyroController"); - static auto gyroClass = Il2cppUtils::GetClass("vl-unity.Runtime.dll", "VL", "VLGyro"); - static auto ctrlCtor = reinterpret_cast(Il2cppUtils::GetMethodPointer("campus-submodule.Runtime.dll", "Campus.Common", "CampusGyroController", ".ctor")); - static auto gyroCtor = reinterpret_cast(Il2cppUtils::GetMethodPointer("vl-unity.Runtime.dll", "VL", "VLGyro", ".ctor")); - static auto gyroReset = reinterpret_cast(Il2cppUtils::GetMethodPointer("vl-unity.Runtime.dll", "VL", "VLGyro", "ResetRotation")); - static auto gyroRaw = reinterpret_cast(Il2cppUtils::GetMethodPointer("vl-unity.Runtime.dll", "VL", "VLGyro", "get_gyroRotationRaw")); - static bool reported = false; - if (!ctrlClass || !gyroClass || !ctrlCtor || !gyroCtor || !gyroReset || !gyroRaw) { - if (!reported) { reported = true; Log::ErrorFmt("[GyroEmu] force-init: missing class/method (%p %p %p %p %p %p)", ctrlClass, gyroClass, ctrlCtor, gyroCtor, gyroReset, gyroRaw); } - return; - } + struct Api { + UnityResolve::Class* ctrlClass; UnityResolve::Class* gyroClass; + void (*ctrlCtor)(void*, void*); void (*gyroCtor)(void*, void*); void (*gyroReset)(void*, void*); + Quaternion* (*gyroRaw)(Quaternion*, void*, void*); + }; + static const Api* GetApi() { + static Api api{ + Il2cppUtils::GetClass("campus-submodule.Runtime.dll", "Campus.Common", "CampusGyroController"), + Il2cppUtils::GetClass("vl-unity.Runtime.dll", "VL", "VLGyro"), + reinterpret_cast(Il2cppUtils::GetMethodPointer("campus-submodule.Runtime.dll", "Campus.Common", "CampusGyroController", ".ctor")), + reinterpret_cast(Il2cppUtils::GetMethodPointer("vl-unity.Runtime.dll", "VL", "VLGyro", ".ctor")), + reinterpret_cast(Il2cppUtils::GetMethodPointer("vl-unity.Runtime.dll", "VL", "VLGyro", "ResetRotation")), + reinterpret_cast(Il2cppUtils::GetMethodPointer("vl-unity.Runtime.dll", "VL", "VLGyro", "get_gyroRotationRaw")), + }; + static const bool ok = [] { + if (api.ctrlClass && api.gyroClass && api.ctrlCtor && api.gyroCtor && api.gyroReset && api.gyroRaw) return true; + Log::ErrorFmt("[GyroEmu] force-init: missing class/method (%p %p %p %p %p %p)", api.ctrlClass, api.gyroClass, api.ctrlCtor, api.gyroCtor, api.gyroReset, api.gyroRaw); + return false; + }(); + return ok ? &api : nullptr; + } + + // 把一个已构造的 CampusGyroController 直接置成“初始化完成”态(等价于它自己 InitializeAsync 成功后的状态) + void InitController(void* ctrl) { + auto api = GetApi(); + if (!api || !ctrl || F(ctrl, 0x18)) return; // _initialized const Quaternion identity(0, 0, 0, 1); - auto& ctrl = F(manager, 0x60); // _gyroController - if (!ctrl) { ctrl = ctrlClass->New(); ctrlCtor(ctrl, nullptr); } - if (F(ctrl, 0x18)) return; // _initialized auto& gyro = F(ctrl, 0x10); // _gyro - if (!gyro) { gyro = gyroClass->New(); gyroCtor(gyro, nullptr); } + if (!gyro) { gyro = api->gyroClass->New(); api->gyroCtor(gyro, nullptr); } F(gyro, 0x14) = 1; // _updateType = Gyro F(gyro, 0x29) = true; // gyroSupported F(gyro, 0x2A) = true; // _defaultGyroEnable for (size_t off : {0x2C, 0x3C, 0x4C, 0x5C}) F(gyro, off) = identity; F(gyro, 0x6C) = true; // _initialized F(gyro, 0x6D) = true; // _firstUpdate - gyroReset(gyro, nullptr); - Quaternion raw; gyroRaw(&raw, gyro, nullptr); + api->gyroReset(gyro, nullptr); + Quaternion raw; api->gyroRaw(&raw, gyro, nullptr); F(ctrl, 0x1C) = Quaternion(-raw.x, -raw.y, -raw.z, raw.w); // _baseRotation = Inverse(raw) F(ctrl, 0x2C) = INT_MIN; // _lastAccessFrame F(ctrl, 0x30) = identity; // _rotation F(ctrl, 0x18) = true; // _initialized Log::InfoFmt("[GyroEmu] force-init done: controller %p gyro %p", ctrl, gyro); } + + void* NewController() { + auto api = GetApi(); + if (!api) return nullptr; + auto ctrl = api->ctrlClass->New(); + api->ctrlCtor(ctrl, nullptr); // 构造函数 hook 会顺手 InitController + InitController(ctrl); + return ctrl; + } } + // 所有 CampusGyroController 一出生就初始化完成,Live / 拍摄 / 其他持有者都不用再关心 PC 上被跳过的 InitializeAsync + DEFINE_HOOK(void, CampusGyroController_ctor, (void* self, void* mtd)) { + CampusGyroController_ctor_Orig(self, mtd); + if (Config::enableGyroEmu) GyroForceInit::InitController(self); + } + + // Live:PC 版可能连控制器都没建,LateUpdate 里兜底 DEFINE_HOOK(void, LivePanoramaCameraManager_LateUpdate, (void* self, void* mtd)) { - if (Config::enableGyroEmu && self) GyroForceInit::Ensure(self); + if (Config::enableGyroEmu && self) { + auto& ctrl = GyroForceInit::F(self, 0x60); // _gyroController + if (!ctrl) ctrl = GyroForceInit::NewController(); + else GyroForceInit::InitController(ctrl); + } LivePanoramaCameraManager_LateUpdate_Orig(self, mtd); } + // 拍摄:PhotographySceneManager 把控制器交给 PhotographyActivityController,这里是它进相机控制器的唯一入口 + DEFINE_HOOK(void, PhotographyActivityController_Initialize, (void* self, void* volumeProfile, void* gyroController, float arg, void* mtd)) { + if (Config::enableGyroEmu) { + if (!gyroController) gyroController = GyroForceInit::NewController(); + else GyroForceInit::InitController(gyroController); + } + PhotographyActivityController_Initialize_Orig(self, volumeProfile, gyroController, arg, mtd); + } // PC 版 MSVC 把 SystemInfo.supportsGyroscope / Gyroscope.* 这些托管包装内联进了调用方,hook 函数本体拦不到。 // il2cpp 的 icall 包装里有一个 static 缓存指针(_il2cpp_icall_func),内联后所有副本共用同一个变量, // 直接把这个变量改成我们的函数即可,不碰任何机器码,也不受链接器 ICF 合并影响。 @@ -1459,10 +1499,21 @@ namespace GakumasLocal::HookMain { return ret; }*/ + // 名片编辑页的 P 偶像页签用 UserIdolCardSkinList.Exists(skin.Id) 过滤“拥有的皮肤”,随后按底卡去 + // UserIdolCardList 查 UserIdolCard 取 IllustrationStage。下面 Exists 对所有 i_card-skin-* 返回 true 会把 + // 没有底卡的皮肤也放进去,查到 null 直接 NRE,整个界面初始化中断(表现为卡死)。该模型构造期间放行真实结果。 + static bool meishiModelConstructing = false; + DEFINE_HOOK(void, MeishiBasePIdolTabPageModel_ctor, (void* self, void* mtd)) { + meishiModelConstructing = true; + MeishiBasePIdolTabPageModel_ctor_Orig(self, mtd); + meishiModelConstructing = false; + } + DEFINE_HOOK(bool, UserIdolCardSkinCollection_Exists, (void* self, Il2cppString* id, void* mtd)) { // Live默认选择 auto ret = UserIdolCardSkinCollection_Exists_Orig(self, id, mtd); // Log::DebugFmt("UserIdolCardSkinCollection_Exists: %s, ret: %d", id->ToString().c_str(), ret); if (!(Config::dbgMode && Config::unlockAllLive)) return ret; + if (meishiModelConstructing) return ret; if (id) { std::string idStr = id->ToString(); @@ -2842,6 +2893,8 @@ namespace GakumasLocal::HookMain { if (UserIdolCardSkinCollection_Exists_mtd) { ADD_HOOK(UserIdolCardSkinCollection_Exists, UserIdolCardSkinCollection_Exists_mtd->methodPointer); } + ADD_HOOK(MeishiBasePIdolTabPageModel_ctor, Il2cppUtils::GetMethodPointer("Assembly-CSharp.dll", "Campus.OutGame", + "MeishiBasePIdolTabPageModel", ".ctor")); auto UserCostumeCollection_klass = Il2cppUtils::GetClass("Assembly-CSharp.dll", "Campus.Common.User", "UserCostumeCollection"); @@ -3044,6 +3097,10 @@ namespace GakumasLocal::HookMain { GyroIcall::Install(); ADD_HOOK(LivePanoramaCameraManager_LateUpdate, Il2cppUtils::GetMethodPointer("campus-submodule.Runtime.dll", "Campus.Live", "LivePanoramaCameraManager", "LateUpdate")); + ADD_HOOK(CampusGyroController_ctor, Il2cppUtils::GetMethodPointer("campus-submodule.Runtime.dll", "Campus.Common", + "CampusGyroController", ".ctor")); + ADD_HOOK(PhotographyActivityController_Initialize, Il2cppUtils::GetMethodPointer("campus-submodule.Runtime.dll", "Campus.Photography", + "PhotographyActivityController", "Initialize")); ADD_HOOK(WindowHandle_SetWindowLong, Il2cppUtils::GetMethodPointer("Assembly-CSharp.dll", "Campus.Common.StandAloneWindow", "WindowHandle", "SetWindowLong")); diff --git a/src/gkmsGUI/i18nData/strings_en.hpp b/src/gkmsGUI/i18nData/strings_en.hpp index 770a5ab..ecf5a6f 100644 --- a/src/gkmsGUI/i18nData/strings_en.hpp +++ b/src/gkmsGUI/i18nData/strings_en.hpp @@ -17,7 +17,7 @@ namespace I18nData { { "lazy_init", "Fast Initialization (Lazy loading)" }, { "enable_free_camera", "Enable Free Camera" }, { "enable_gyro_emu", "Enable Gyroscope Emulation (Live/Photo)" }, - { "gyro_emu_help", "Hold right mouse button and drag to rotate; Numpad 4/6 8/2 yaw/pitch, 7/9 roll, 5 reset" }, + { "gyro_emu_help", "Hold right mouse button and drag to rotate; Numpad 7/9 roll, 5 reset" }, { "gyro_sensitivity", "Gyro Sensitivity" }, { "gyro_invert_x", "Invert X" }, { "gyro_invert_y", "Invert Y" }, diff --git a/src/gkmsGUI/i18nData/strings_ja.hpp b/src/gkmsGUI/i18nData/strings_ja.hpp index e06b5c6..c1c4d80 100644 --- a/src/gkmsGUI/i18nData/strings_ja.hpp +++ b/src/gkmsGUI/i18nData/strings_ja.hpp @@ -58,7 +58,7 @@ namespace I18nData { { "enable_breast_param", "胸のパラメーターを有効化" }, { "enable_free_camera", "フリーカメラを有効化" }, { "enable_gyro_emu", "ジャイロ模擬を有効化(ライブ/撮影)" }, - { "gyro_emu_help", "右クリックドラッグで視点回転;テンキー 4/6 8/2 左右上下, 7/9 ロール, 5 リセット" }, + { "gyro_emu_help", "右クリックドラッグで視点回転;テンキー 7/9 ロール, 5 リセット" }, { "gyro_sensitivity", "ジャイロ感度" }, { "gyro_invert_x", "左右反転" }, { "gyro_invert_y", "上下反転" }, diff --git a/src/gkmsGUI/i18nData/strings_zh-rCN.hpp b/src/gkmsGUI/i18nData/strings_zh-rCN.hpp index 6b54102..15d65fa 100644 --- a/src/gkmsGUI/i18nData/strings_zh-rCN.hpp +++ b/src/gkmsGUI/i18nData/strings_zh-rCN.hpp @@ -17,7 +17,7 @@ namespace I18nData { { "lazy_init", "快速初始化(懒加载配置)" }, { "enable_free_camera", "启用自由视角(可热重载; 需使用实体键盘)" }, { "enable_gyro_emu", "启用陀螺仪模拟(Live/拍摄)" }, - { "gyro_emu_help", "按住鼠标右键拖动转动视角;小键盘 4/6 8/2 左右上下, 7/9 翻滚, 5 复位" }, + { "gyro_emu_help", "按住鼠标右键拖动转动视角;小键盘 7/9 翻滚, 5 复位" }, { "gyro_sensitivity", "陀螺仪灵敏度" }, { "gyro_invert_x", "反转左右" }, { "gyro_invert_y", "反转上下" }, diff --git a/src/gkmsGUI/i18nData/strings_zh-rTW.hpp b/src/gkmsGUI/i18nData/strings_zh-rTW.hpp index 3606db0..e9dbd09 100644 --- a/src/gkmsGUI/i18nData/strings_zh-rTW.hpp +++ b/src/gkmsGUI/i18nData/strings_zh-rTW.hpp @@ -17,7 +17,7 @@ namespace I18nData { { "lazy_init", "快速初始化(懶人設定)" }, { "enable_free_camera", "啟用自由視角(可熱重載; 需使用實體鍵盤)" }, { "enable_gyro_emu", "啟用陀螺儀模擬(Live/拍攝)" }, - { "gyro_emu_help", "按住滑鼠右鍵拖動轉動視角;數字鍵盤 4/6 8/2 左右上下, 7/9 翻滾, 5 重設" }, + { "gyro_emu_help", "按住滑鼠右鍵拖動轉動視角;數字鍵盤 7/9 翻滾, 5 重設" }, { "gyro_sensitivity", "陀螺儀靈敏度" }, { "gyro_invert_x", "反轉左右" }, { "gyro_invert_y", "反轉上下" },