mirror of
https://git.chinosk6.cn/chinosk/gkms-localify-dmm.git
synced 2026-08-21 21:14:48 +07:00
拍照页服装解锁 + 修复 SP 标记
拍照(Photography)页支持解锁服装: - 新增 UserDataCollection_GetAll hook,把主表缺失的服装/头部克隆成用户 记录 Add 进集合本体。拍照页 CreateItemModels 读的是字典 Values 视图, 走不到原有的 FindBy 路径,必须从集合本体补。 - 主表补充范围:CostumePhotoGroup 白名单 + 与基础服装共享 colorGroupId 的异色变体 + 已过 ViewStartTime 的活动/商店服;未实装条目会黑屏卡死, 按 ViewStartTime 跳过。 - 新增 PhotographyCostumeSettingData_GetCostumes/GetCostumeHeads,按角色 从 IdolCardSkin 主表补齐卡面专属服装;get_IsDisabled 恒 false 取消置灰。 修复 UserCostumeCollection_FindBy 的共享泛型调用:改用运行时集合类型的 MethodInfo 并传入末尾隐藏参数。此前头部集合误用了服装集合的泛型上下文, 会在字典扩容时抛 ArrayTypeMismatchException,表现为换装页无限 Loading。 修复 ExamExtensions.ExchangeSpToNormal 被误改成调用原函数导致 SP 标记 失效。此 hook 必须原样返回传入的 type,调 _Orig 就是原版行为。 以上功能统一收到 dbgMode 门下,与 unlockAllLive 系列保持一致。 hook-reference/ 加入 .gitignore(IL2CPP 逆向工作目录,数 GB)。 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5
parent
43da4e8313
commit
66bfb38fba
@@ -153,6 +153,9 @@ user*.bat
|
||||
resources/text_dumper/obj
|
||||
resources/text_dumper/bin
|
||||
|
||||
# IL2CPP 逆向工作目录(metadata / dump.cs / IDA 数据库,数 GB)
|
||||
hook-reference/
|
||||
|
||||
.cache/
|
||||
.vscode/
|
||||
/backend
|
||||
|
||||
+332
-25
@@ -7,7 +7,10 @@
|
||||
#include "Local.h"
|
||||
#include "MasterLocal.h"
|
||||
#include <unordered_set>
|
||||
#include <unordered_map>
|
||||
#include <algorithm>
|
||||
#include <ctime>
|
||||
#include <cstring>
|
||||
#include "camera/camera.hpp"
|
||||
#include "config/Config.hpp"
|
||||
// #include <jni.h>
|
||||
@@ -849,6 +852,12 @@ namespace GakumasLocal::HookMain {
|
||||
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
|
||||
DEFINE_HOOK(void, MessageExtensions_MergeFrom, (void* message, void* span, void* mtd)) {
|
||||
MessageExtensions_MergeFrom_Orig(message, span, mtd);
|
||||
@@ -1139,55 +1148,331 @@ namespace GakumasLocal::HookMain {
|
||||
return origList;
|
||||
}
|
||||
|
||||
DEFINE_HOOK(void*, UserCostumeCollection_FindBy, (void* self, void* predicate, void* mtd)) {
|
||||
auto ret = UserCostumeCollection_FindBy_Orig(self, predicate, mtd);
|
||||
if (!Config::unlockAllLiveCostume) return ret;
|
||||
|
||||
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") {
|
||||
// 把主表全部服装/头部 ID 补进用户服装列表;klassName 决定按 body 还是 head 处理
|
||||
void* AugmentUserCostumeList(const std::string& klassName, void* origList) {
|
||||
if (klassName == "UserCostumeHeadCollection") {
|
||||
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_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
|
||||
auto origList = UserCostumeCollection_GetAllList(self, nullptr);
|
||||
// 游戏改名/改版本时这些会是 null,直接返回原列表,别带着空指针往下走
|
||||
if (!UserCostume_Clone || !UserCostume_get_CostumeHeadId || !UserCostume_set_CostumeHeadId) return origList;
|
||||
|
||||
auto allIds = GetIdolMusicIdAll("", GetIdolIdType::CostumeHeadId);
|
||||
|
||||
// List<Campus.Common.Proto.Client.Transaction.UserCostumeHead>
|
||||
return AddIdsToUserDataCollectionFromMaster(origList, allIds, UserCostume_get_CostumeHeadId, UserCostume_set_CostumeHeadId, UserCostume_Clone);
|
||||
}
|
||||
else if (thisKlassName == "UserCostumeCollection") {
|
||||
// static auto UserCostume_klass = Il2cppUtils::GetClass("Assembly-CSharp.dll", "Campus.Common.Proto.Client.Transaction", "UserCostume");
|
||||
if (klassName == "UserCostumeCollection") {
|
||||
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_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
|
||||
auto origList = UserCostumeCollection_GetAllList(self, nullptr);
|
||||
if (!UserCostume_Clone || !UserCostume_get_CostumeId || !UserCostume_set_CostumeId) return origList;
|
||||
|
||||
auto allIds = GetIdolMusicIdAll("", GetIdolIdType::CostumeId);
|
||||
|
||||
// List<Campus.Common.Proto.Client.Transaction.UserCostume>
|
||||
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;
|
||||
}
|
||||
|
||||
// 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() {
|
||||
static auto unitask_klass = Il2cppUtils::GetClass("UniTask.dll", "Cysharp.Threading.Tasks", "UniTask");
|
||||
static auto CompletedTask_field = unitask_klass->Get<UnityResolve::Field>("CompletedTask");
|
||||
@@ -1931,6 +2216,12 @@ namespace GakumasLocal::HookMain {
|
||||
ADD_HOOK(MessageExtensions_MergeFrom, Il2cppUtils::GetMethodPointer("Google.Protobuf.dll", "Google.Protobuf",
|
||||
"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 进行构造,遂停用。现留档以备用
|
||||
// ADD_HOOK(MasterBase_GetAll, Il2cppUtils::GetMethodPointer("quaunity-master-manager.Runtime.dll", "Qua.Master",
|
||||
// "MasterBase`2", "GetAll", {"*", "*", "*", "*", "*"}));
|
||||
@@ -1993,6 +2284,22 @@ namespace GakumasLocal::HookMain {
|
||||
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,
|
||||
GetMethodPointerByArgCount("Assembly-CSharp.dll", "Campus.OutGame.PictureBook",
|
||||
|
||||
Reference in New Issue
Block a user