1
0

支持解锁 Live, 支持自定义角色, 支持换头, 支持 FOV 调整

This commit is contained in:
chinosk 2024-05-26 00:14:40 +08:00
parent fb65a34684
commit acac544183
No known key found for this signature in database
GPG Key ID: 00610B08C1BF7BE9
10 changed files with 289 additions and 8 deletions

View File

@ -16,7 +16,7 @@
- [ ] 卡片信息、TIP等部分的文本 hook
- [x] 自定义配置
- [ ] 更多类型的文件替换
- [ ] 自由视角 FOV 调整
- [x] 自由视角 FOV 调整
... and more

View File

@ -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<Il2cppString*>(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<bool (*)(void*)>(Il2cppUtils::il2cpp_resolve_icall(
"UnityEngine.Camera::get_orthographic()"
));
static auto set_orthographic = reinterpret_cast<bool (*)(void*, bool)>(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<void*>(lastMusicId);
auto newItemModel = PictureBookLiveSelectMusicListItemModel_klass->New<void*>();
PictureBookLiveSelectMusicListItemModel_ctor->Invoke<void>(newItemModel, music, false);
return PictureBookLiveSelectScreenPresenter_OnSelectMusic_Orig(_this, newItemModel, isFirst, mtd);
}
if (itemModel) {
auto currMusic = GetCurrMusic->Invoke<void*>(itemModel);
auto musicId = GetMusicId->Invoke<Il2cppString*>(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

View File

@ -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());
}
}

View File

@ -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, ...);

View File

@ -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) {

View File

@ -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);
}

View File

@ -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")

View File

@ -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 = ""
)

View File

@ -96,6 +96,81 @@
android:text="@string/enable_free_camera" />
</TableRow>
<TableRow
android:layout_width="match_parent"
android:layout_height="match_parent">
<com.google.android.material.switchmaterial.SwitchMaterial
android:id="@+id/SwitchUnlockAllLive"
android:layout_width="match_parent"
android:layout_height="48dp"
android:checked="@={config.unlockAllLive}"
android:onCheckedChanged="@{(view, value) -> listener.onUnlockAllLiveChanged(value)}"
android:text="@string/unlockAllLive" />
</TableRow>
<TableRow
android:layout_width="match_parent"
android:layout_height="match_parent">
<com.google.android.material.switchmaterial.SwitchMaterial
android:id="@+id/SwitchLiveUseCustomeDress"
android:layout_width="match_parent"
android:layout_height="48dp"
android:checked="@={config.enableLiveCustomeDress}"
android:onCheckedChanged="@{(view, value) -> listener.onLiveCustomeDressChanged(value)}"
android:text="@string/liveUseCustomeDress" />
</TableRow>
<TableRow
android:layout_width="match_parent"
android:layout_height="match_parent">
<com.google.android.material.textfield.TextInputLayout
android:layout_width="match_parent"
android:layout_height="48dp"
app:boxBackgroundColor="@android:color/transparent"
android:background="@android:color/transparent"
android:hint="@string/live_costume_head_id" >
<com.google.android.material.textfield.TextInputEditText
android:id="@+id/editTextLiveCustomeCharaId"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:ems="10"
android:paddingStart="0dp"
android:paddingEnd="0dp"
android:paddingBottom="0dp"
android:text="@={config.liveCustomeHeadId}"
android:onTextChanged="@{(s, st, b, a) -> listener.onLiveCustomeHeadIdChanged(s, st, b, a)}"/>
</com.google.android.material.textfield.TextInputLayout>
</TableRow>
<TableRow
android:layout_width="match_parent"
android:layout_height="match_parent">
<com.google.android.material.textfield.TextInputLayout
android:layout_width="match_parent"
android:layout_height="48dp"
app:boxBackgroundColor="@android:color/transparent"
android:background="@android:color/transparent"
android:hint="@string/live_custome_dress_id" >
<com.google.android.material.textfield.TextInputEditText
android:id="@+id/editTextLiveCustomeCostumeId"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:ems="10"
android:paddingStart="0dp"
android:paddingEnd="0dp"
android:paddingBottom="0dp"
android:text="@={config.liveCustomeCostumeId}"
android:onTextChanged="@{(s, st, b, a) -> listener.onLiveCustomeCostumeIdChanged(s, st, b, a)}"/>
</com.google.android.material.textfield.TextInputLayout>
</TableRow>
<TableRow
android:layout_width="match_parent"
android:layout_height="match_parent">

View File

@ -5,4 +5,8 @@
<string name="enable_free_camera">启用自由视角(可热重载; 需使用实体键盘)</string>
<string name="start_game">以上述配置启动游戏/重载配置</string>
<string name="setFpsTitle">最大 FPS (0 为保持游戏原设置)</string>
<string name="unlockAllLive">解锁所有 Live</string>
<string name="liveUseCustomeDress">Live 使用自定义角色</string>
<string name="live_costume_head_id">Live 自定义头部 ID (例: costume_head_hski-cstm-0002)</string>
<string name="live_custome_dress_id">Live 自定义服装 ID (例: hski-cstm-0002)</string>
</resources>