From acac5441838ed5c5dc74cb80a25bfcf36a285895 Mon Sep 17 00:00:00 2001 From: chinosk <2248589280@qq.com> Date: Sun, 26 May 2024 00:14:40 +0800 Subject: [PATCH] =?UTF-8?q?=E6=94=AF=E6=8C=81=E8=A7=A3=E9=94=81=20Live,=20?= =?UTF-8?q?=E6=94=AF=E6=8C=81=E8=87=AA=E5=AE=9A=E4=B9=89=E8=A7=92=E8=89=B2?= =?UTF-8?q?,=20=E6=94=AF=E6=8C=81=E6=8D=A2=E5=A4=B4,=20=E6=94=AF=E6=8C=81?= =?UTF-8?q?=20FOV=20=E8=B0=83=E6=95=B4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 2 +- app/src/main/cpp/GakumasLocalify/Hook.cpp | 139 +++++++++++++++++- app/src/main/cpp/GakumasLocalify/Log.cpp | 24 +++ app/src/main/cpp/GakumasLocalify/Log.h | 1 + .../cpp/GakumasLocalify/config/Config.cpp | 17 ++- .../cpp/GakumasLocalify/config/Config.hpp | 5 + .../chinosk/gakumas/localify/MainActivity.kt | 24 +++ .../gakumas/localify/models/GakumasConfig.kt | 6 +- app/src/main/res/layout/activity_main.xml | 75 ++++++++++ app/src/main/res/values/strings.xml | 4 + 10 files changed, 289 insertions(+), 8 deletions(-) diff --git a/README.md b/README.md index 5acc10c..da06d7d 100644 --- a/README.md +++ b/README.md @@ -16,7 +16,7 @@ - [ ] 卡片信息、TIP等部分的文本 hook - [x] 自定义配置 - [ ] 更多类型的文件替换 -- [ ] 自由视角 FOV 调整 +- [x] 自由视角 FOV 调整 ... and more diff --git a/app/src/main/cpp/GakumasLocalify/Hook.cpp b/app/src/main/cpp/GakumasLocalify/Hook.cpp index 4240d2b..9fd86d5 100644 --- a/app/src/main/cpp/GakumasLocalify/Hook.cpp +++ b/app/src/main/cpp/GakumasLocalify/Hook.cpp @@ -75,13 +75,14 @@ namespace GakumasLocal::HookMain { DEFINE_HOOK(void, Internal_LogException, (void* ex, void* obj)) { Internal_LogException_Orig(ex, obj); - // Log::LogFmt(ANDROID_LOG_VERBOSE, "UnityLog - Internal_LogException"); + static auto Exception_ToString = Il2cppUtils::GetMethod("mscorlib.dll", "System", "Exception", "ToString"); + Log::LogUnityLog(ANDROID_LOG_ERROR, "UnityLog - Internal_LogException:\n%s", Exception_ToString->Invoke(ex)->ToString().c_str()); } DEFINE_HOOK(void, Internal_Log, (int logType, int logOption, UnityResolve::UnityType::String* content, void* context)) { Internal_Log_Orig(logType, logOption, content, context); // 2022.3.21f1 - // Log::LogFmt(ANDROID_LOG_VERBOSE, "UnityLog - Internal_Log: %s", content->ToString().c_str()); + Log::LogUnityLog(ANDROID_LOG_VERBOSE, "Internal_Log:\n%s", content->ToString().c_str()); } UnityResolve::UnityType::Camera* mainCameraCache = nullptr; @@ -97,6 +98,39 @@ namespace GakumasLocal::HookMain { cameraTransformCache = mainCameraCache->GetTransform(); } + DEFINE_HOOK(void, Unity_set_fieldOfView, (UnityResolve::UnityType::Camera* _this, float value)) { + if (Config::enableFreeCamera) { + if (_this == mainCameraCache) { + value = GKCamera::baseCamera.fov; + } + } + Unity_set_fieldOfView_Orig(_this, value); + } + + DEFINE_HOOK(float, Unity_get_fieldOfView, (UnityResolve::UnityType::Camera* _this)) { + if (Config::enableFreeCamera) { + if (_this == mainCameraCache) { + static auto get_orthographic = reinterpret_cast(Il2cppUtils::il2cpp_resolve_icall( + "UnityEngine.Camera::get_orthographic()" + )); + static auto set_orthographic = reinterpret_cast(Il2cppUtils::il2cpp_resolve_icall( + "UnityEngine.Camera::set_orthographic(System.Boolean)" + )); + + for (const auto& i : UnityResolve::UnityType::Camera::GetAllCamera()) { + // Log::DebugFmt("get_orthographic: %d", get_orthographic(i)); + // set_orthographic(i, false); + Unity_set_fieldOfView_Orig(i, GKCamera::baseCamera.fov); + } + Unity_set_fieldOfView_Orig(_this, GKCamera::baseCamera.fov); + + // Log::DebugFmt("main - get_orthographic: %d", get_orthographic(_this)); + return GKCamera::baseCamera.fov; + } + } + return Unity_get_fieldOfView_Orig(_this); + } + DEFINE_HOOK(void, Unity_set_position_Injected, (UnityResolve::UnityType::Transform* _this, UnityResolve::UnityType::Vector3* data)) { if (Config::enableFreeCamera) { CheckAndUpdateMainCamera(); @@ -123,13 +157,21 @@ namespace GakumasLocal::HookMain { "UnityEngine.Transform::Internal_LookAt_Injected(UnityEngine.Vector3&,UnityEngine.Vector3&)")); static auto worldUp = UnityResolve::UnityType::Vector3(0, 1, 0); lookat_injected(_this, &origCameraLookat, &worldUp); - // TODO 相机 FOV + // Log::DebugFmt("fov: %f, target: %f", Unity_get_fieldOfView_Orig(mainCameraCache), GKCamera::baseCamera.fov); return; } } return Unity_set_rotation_Injected_Orig(_this, value); } + DEFINE_HOOK(void, EndCameraRendering, (void* ctx, void* camera, void* method)) { + EndCameraRendering_Orig(ctx, camera, method); + + if (Config::enableFreeCamera && mainCameraCache) { + Unity_set_fieldOfView_Orig(mainCameraCache, GKCamera::baseCamera.fov); + } + } + DEFINE_HOOK(void, Unity_set_targetFrameRate, (int value)) { const auto configFps = Config::targetFrameRate; return Unity_set_targetFrameRate_Orig(configFps == 0 ? value: configFps); @@ -321,6 +363,76 @@ namespace GakumasLocal::HookMain { // return UnityResolve::UnityType::String::New("[I18]" + ret->ToString()); } + DEFINE_HOOK(void, PictureBookLiveThumbnailView_SetData, (void* _this, void* liveData, bool isUnlocked, bool isNew)) { + // Log::DebugFmt("PictureBookLiveThumbnailView_SetData: isUnlocked: %d, isNew: %d", isUnlocked, isNew); + if (Config::unlockAllLive) { + isUnlocked = true; + } + PictureBookLiveThumbnailView_SetData_Orig(_this, liveData, isUnlocked, isNew); + } + + + DEFINE_HOOK(void*, PictureBookLiveSelectScreenPresenter_MoveLiveScene, (void* _this, void* produceLive, + Il2cppString* characterId, Il2cppString* costumeId, Il2cppString* costumeHeadId)) { + + Log::InfoFmt("MoveLiveScene: characterId: %s, costumeId: %s, costumeHeadId: %s,", + characterId->ToString().c_str(), costumeId->ToString().c_str(), costumeHeadId->ToString().c_str()); + + /* + characterId: hski, costumeId: hski-cstm-0002, costumeHeadId: costume_head_hski-cstm-0002, + characterId: shro, costumeId: shro-cstm-0006, costumeHeadId: costume_head_shro-cstm-0006, + */ + + if (Config::enableLiveCustomeDress) { + // 修改 LiveFixedData_GetCharacter 可以更改 Loading 角色和演唱者名字,而不变更实际登台人 + return PictureBookLiveSelectScreenPresenter_MoveLiveScene_Orig(_this, produceLive, characterId, + Config::liveCustomeCostumeId.empty() ? costumeId : Il2cppString::New(Config::liveCustomeCostumeId), + Config::liveCustomeHeadId.empty() ? costumeHeadId : Il2cppString::New(Config::liveCustomeHeadId)); + } + + return PictureBookLiveSelectScreenPresenter_MoveLiveScene_Orig(_this, produceLive, characterId, costumeId, costumeHeadId); + } + + // std::string lastMusicId; + DEFINE_HOOK(void, PictureBookLiveSelectScreenPresenter_OnSelectMusic, (void* _this, void* itemModel, bool isFirst, void* mtd)) { + /* // 修改角色后,Live 结束返回时, itemModel 为 null + Log::DebugFmt("OnSelectMusic itemModel at %p", itemModel); + + static auto GetMusic = Il2cppUtils::GetMethod("Assembly-CSharp.dll", "Campus.OutGame", + "PlaylistMusicContext", "GetMusic"); + static auto GetCurrMusic = Il2cppUtils::GetMethod("Assembly-CSharp.dll", "Campus.OutGame.PictureBook", + "PictureBookLiveSelectMusicListItemModel", "get_Music"); + static auto GetMusicId = Il2cppUtils::GetMethod("Assembly-CSharp.dll", "Campus.Common.Proto.Client.Master", + "Music", "get_Id"); + + static auto PictureBookLiveSelectMusicListItemModel_klass = Il2cppUtils::GetClass("Assembly-CSharp.dll", "Campus.OutGame.PictureBook", + "PictureBookLiveSelectMusicListItemModel"); + static auto PictureBookLiveSelectMusicListItemModel_ctor = Il2cppUtils::GetMethod("Assembly-CSharp.dll", "Campus.OutGame.PictureBook", + "PictureBookLiveSelectMusicListItemModel", ".ctor", {"*", "*"}); + + if (!itemModel) { + Log::DebugFmt("OnSelectMusic block", itemModel); + auto music = GetMusic->Invoke(lastMusicId); + auto newItemModel = PictureBookLiveSelectMusicListItemModel_klass->New(); + PictureBookLiveSelectMusicListItemModel_ctor->Invoke(newItemModel, music, false); + + return PictureBookLiveSelectScreenPresenter_OnSelectMusic_Orig(_this, newItemModel, isFirst, mtd); + } + + if (itemModel) { + auto currMusic = GetCurrMusic->Invoke(itemModel); + auto musicId = GetMusicId->Invoke(currMusic); + lastMusicId = musicId->ToString(); + }*/ + if (!itemModel) return; + return PictureBookLiveSelectScreenPresenter_OnSelectMusic_Orig(_this, itemModel, isFirst, mtd); + } + + DEFINE_HOOK(bool, VLDOF_IsActive, (void* _this)) { + if (Config::enableFreeCamera) return false; + return VLDOF_IsActive_Orig(_this); + } + void StartInjectFunctions() { const auto hookInstaller = Plugin::GetInstance().GetHookInstaller(); UnityResolve::Init(xdl_open(hookInstaller->m_il2cppLibraryPath.c_str(), RTLD_NOW), UnityResolve::Mode::Il2Cpp); @@ -361,6 +473,21 @@ namespace GakumasLocal::HookMain { Il2cppUtils::GetMethodPointer("Octo.dll", "Octo", "OnDownloadProgress", "Invoke")); + ADD_HOOK(PictureBookLiveThumbnailView_SetData, + Il2cppUtils::GetMethodPointer("Assembly-CSharp.dll", "Campus.OutGame.PictureBook", + "PictureBookLiveThumbnailView", "SetData")); + + ADD_HOOK(PictureBookLiveSelectScreenPresenter_MoveLiveScene, + Il2cppUtils::GetMethodPointer("Assembly-CSharp.dll", "Campus.OutGame", + "PictureBookLiveSelectScreenPresenter", "MoveLiveScene")); + ADD_HOOK(PictureBookLiveSelectScreenPresenter_OnSelectMusic, + Il2cppUtils::GetMethodPointer("Assembly-CSharp.dll", "Campus.OutGame", + "PictureBookLiveSelectScreenPresenter", "OnSelectMusic")); + + ADD_HOOK(VLDOF_IsActive, + Il2cppUtils::GetMethodPointer("Unity.RenderPipelines.Universal.Runtime.dll", "VL.Rendering", + "VLDOF", "IsActive")); + ADD_HOOK(Internal_LogException, Il2cppUtils::il2cpp_resolve_icall( "UnityEngine.DebugLogHandler::Internal_LogException(System.Exception,UnityEngine.Object)")); ADD_HOOK(Internal_Log, Il2cppUtils::il2cpp_resolve_icall( @@ -370,8 +497,14 @@ namespace GakumasLocal::HookMain { "UnityEngine.Transform::set_position_Injected(UnityEngine.Vector3&)")); ADD_HOOK(Unity_set_rotation_Injected, Il2cppUtils::il2cpp_resolve_icall( "UnityEngine.Transform::set_rotation_Injected(UnityEngine.Quaternion&)")); + ADD_HOOK(Unity_get_fieldOfView, Il2cppUtils::GetMethodPointer("UnityEngine.CoreModule.dll", "UnityEngine", + "Camera", "get_fieldOfView")); + ADD_HOOK(Unity_set_fieldOfView, Il2cppUtils::GetMethodPointer("UnityEngine.CoreModule.dll", "UnityEngine", + "Camera", "set_fieldOfView")); ADD_HOOK(Unity_set_targetFrameRate, Il2cppUtils::il2cpp_resolve_icall( "UnityEngine.Application::set_targetFrameRate(System.Int32)")); + ADD_HOOK(EndCameraRendering, Il2cppUtils::GetMethodPointer("UnityEngine.CoreModule.dll", "UnityEngine.Rendering", + "RenderPipeline", "EndCameraRendering")); } // 77 2640 5000 diff --git a/app/src/main/cpp/GakumasLocalify/Log.cpp b/app/src/main/cpp/GakumasLocalify/Log.cpp index 6b53ef8..d052a50 100644 --- a/app/src/main/cpp/GakumasLocalify/Log.cpp +++ b/app/src/main/cpp/GakumasLocalify/Log.cpp @@ -139,4 +139,28 @@ namespace GakumasLocal::Log { Debug(result.c_str()); } + + void LogUnityLog(int prio, const char* fmt, ...) { + va_list args; + va_start(args, fmt); + va_list args_copy; + va_copy(args_copy, args); + + // 计算格式化后的字符串长度 + int size = vsnprintf(nullptr, 0, fmt, args_copy) + 1; // 加上额外的终止符空间 + va_end(args_copy); + + // 动态分配缓冲区 + char* buffer = new char[size]; + + // 格式化字符串 + vsnprintf(buffer, size, fmt, args); + + va_end(args); + + std::string result(buffer); + delete[] buffer; // 释放缓冲区 + + __android_log_write(prio, "GakumasLog", result.c_str()); + } } diff --git a/app/src/main/cpp/GakumasLocalify/Log.h b/app/src/main/cpp/GakumasLocalify/Log.h index 035df2e..83f9f02 100644 --- a/app/src/main/cpp/GakumasLocalify/Log.h +++ b/app/src/main/cpp/GakumasLocalify/Log.h @@ -2,6 +2,7 @@ #define GAKUMAS_LOCALIFY_LOG_H namespace GakumasLocal::Log { + void LogUnityLog(int prio, const char* fmt, ...); void LogFmt(int prio, const char* fmt, ...); void Info(const char* msg); void InfoFmt(const char* fmt, ...); diff --git a/app/src/main/cpp/GakumasLocalify/config/Config.cpp b/app/src/main/cpp/GakumasLocalify/config/Config.cpp index cb5658c..4915d36 100644 --- a/app/src/main/cpp/GakumasLocalify/config/Config.cpp +++ b/app/src/main/cpp/GakumasLocalify/config/Config.cpp @@ -8,14 +8,25 @@ namespace GakumasLocal::Config { bool enabled = true; bool enableFreeCamera = false; int targetFrameRate = 0; + bool unlockAllLive = false; + + bool enableLiveCustomeDress = false; + std::string liveCustomeHeadId = ""; + std::string liveCustomeCostumeId = ""; void LoadConfig(const std::string& configStr) { try { const auto config = nlohmann::json::parse(configStr); - enabled = config["enabled"]; - targetFrameRate = config["targetFrameRate"]; - enableFreeCamera = config["enableFreeCamera"]; + #define GetConfigItem(name) if (config.contains(#name)) name = config[#name] + + GetConfigItem(enabled); + GetConfigItem(targetFrameRate); + GetConfigItem(enableFreeCamera); + GetConfigItem(unlockAllLive); + GetConfigItem(enableLiveCustomeDress); + GetConfigItem(liveCustomeHeadId); + GetConfigItem(liveCustomeCostumeId); } catch (std::exception& e) { diff --git a/app/src/main/cpp/GakumasLocalify/config/Config.hpp b/app/src/main/cpp/GakumasLocalify/config/Config.hpp index edf2d73..d6c1dea 100644 --- a/app/src/main/cpp/GakumasLocalify/config/Config.hpp +++ b/app/src/main/cpp/GakumasLocalify/config/Config.hpp @@ -8,6 +8,11 @@ namespace GakumasLocal::Config { extern bool enabled; extern bool enableFreeCamera; extern int targetFrameRate; + extern bool unlockAllLive; + + extern bool enableLiveCustomeDress; + extern std::string liveCustomeHeadId; + extern std::string liveCustomeCostumeId; void LoadConfig(const std::string& configStr); } diff --git a/app/src/main/java/io/github/chinosk/gakumas/localify/MainActivity.kt b/app/src/main/java/io/github/chinosk/gakumas/localify/MainActivity.kt index 26c9241..acd1f7d 100644 --- a/app/src/main/java/io/github/chinosk/gakumas/localify/MainActivity.kt +++ b/app/src/main/java/io/github/chinosk/gakumas/localify/MainActivity.kt @@ -19,6 +19,10 @@ interface ConfigListener { fun onEnabledChanged(value: Boolean) fun onEnableFreeCameraChanged(value: Boolean) fun onTargetFpsChanged(s: CharSequence, start: Int, before: Int, count: Int) + fun onUnlockAllLiveChanged(value: Boolean) + fun onLiveCustomeDressChanged(value: Boolean) + fun onLiveCustomeHeadIdChanged(s: CharSequence, start: Int, before: Int, count: Int) + fun onLiveCustomeCostumeIdChanged(s: CharSequence, start: Int, before: Int, count: Int) } class MainActivity : AppCompatActivity(), ConfigListener { @@ -85,6 +89,11 @@ class MainActivity : AppCompatActivity(), ConfigListener { saveConfig() } + override fun onUnlockAllLiveChanged(value: Boolean) { + config.unlockAllLive = value + saveConfig() + } + override fun onTargetFpsChanged(s: CharSequence, start: Int, before: Int, count: Int) { try { val valueStr = s.toString() @@ -102,6 +111,21 @@ class MainActivity : AppCompatActivity(), ConfigListener { } } + override fun onLiveCustomeDressChanged(value: Boolean) { + config.enableLiveCustomeDress = value + saveConfig() + } + + override fun onLiveCustomeCostumeIdChanged(s: CharSequence, start: Int, before: Int, count: Int) { + config.liveCustomeCostumeId = s.toString() + saveConfig() + } + + override fun onLiveCustomeHeadIdChanged(s: CharSequence, start: Int, before: Int, count: Int) { + config.liveCustomeHeadId = s.toString() + saveConfig() + } + override fun onClickStartGame() { val intent = Intent().apply { setClassName("com.bandainamcoent.idolmaster_gakuen", "com.google.firebase.MessagingUnityPlayerActivity") diff --git a/app/src/main/java/io/github/chinosk/gakumas/localify/models/GakumasConfig.kt b/app/src/main/java/io/github/chinosk/gakumas/localify/models/GakumasConfig.kt index 23309b4..55b7ca6 100644 --- a/app/src/main/java/io/github/chinosk/gakumas/localify/models/GakumasConfig.kt +++ b/app/src/main/java/io/github/chinosk/gakumas/localify/models/GakumasConfig.kt @@ -3,6 +3,10 @@ package io.github.chinosk.gakumas.localify.models data class GakumasConfig( var enabled: Boolean = true, var enableFreeCamera: Boolean = false, - var targetFrameRate: Int = 0 + var targetFrameRate: Int = 0, + var unlockAllLive: Boolean = false, + var enableLiveCustomeDress: Boolean = false, + var liveCustomeHeadId: String = "", + var liveCustomeCostumeId: String = "" ) diff --git a/app/src/main/res/layout/activity_main.xml b/app/src/main/res/layout/activity_main.xml index 0d64364..63e08da 100644 --- a/app/src/main/res/layout/activity_main.xml +++ b/app/src/main/res/layout/activity_main.xml @@ -96,6 +96,81 @@ android:text="@string/enable_free_camera" /> + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index e4446de..12d3458 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -5,4 +5,8 @@ 启用自由视角(可热重载; 需使用实体键盘) 以上述配置启动游戏/重载配置 最大 FPS (0 为保持游戏原设置) + 解锁所有 Live + Live 使用自定义角色 + Live 自定义头部 ID (例: costume_head_hski-cstm-0002) + Live 自定义服装 ID (例: hski-cstm-0002) \ No newline at end of file