mirror of
https://git.chinosk6.cn/chinosk/gkms-localify-dmm.git
synced 2026-10-07 23:21:43 +07:00
feat: 未拥有技能卡皮肤时也播放该卡的 cut-in 演出
考试内所有皮肤表现都由 ExamCardData.ProduceCardSkinId 派生:
PrePlayCardAsync -> PlayCutInAsync(skinId) -> TryGetCutInParams
-> mov_{id}_skillcard.usm + cutInType,以及 ExamCardButtonImage
-> GetThumbnailLoopMovieName(skinId) 的手牌循环缩略图。
于是在该 getter 上,把没挂皮肤的卡按 produceCardId 补成主表里
CutInType 最高的皮肤 id,一个 hook 覆盖 cut-in 与手牌缩略图。
映射表从 ProduceCardSkinMaster 惰性构建一次。
跟随 dbgMode 生效,不额外加配置项和 GUI 开关。
已知代价:该 getter 另有 ExamDataSerializeData 与 ExamParameterModel
两个读取方,伪造的 skinId 会进考试状态序列化和 play log,
竞赛回放会随之上传。
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
1 parent
098a41459e
commit
5af7956318
1 file changed
+74
@@ -1724,6 +1724,75 @@ namespace GakumasLocal::HookMain {
|
|||||||
return AddPhotographyIdolSkinItems(ret, characterId ? characterId->ToString() : "", getItem, getId);
|
return AddPhotographyIdolSkinItems(ret, characterId ? characterId->ToString() : "", getItem, getId);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ---- 技能卡皮肤(ProduceCardSkin):没有专属皮也播专属皮的演出 ----
|
||||||
|
// 考试内所有皮肤表现都由 ExamCardData.ProduceCardSkinId 派生:
|
||||||
|
// PrePlayCardAsync -> PlayCutInAsync(skinId) -> TryGetCutInParams -> mov_{id}_skillcard.usm + cutInType
|
||||||
|
// ExamCardButtonImage.SetCard -> GetThumbnailLoopMovieName(skinId) -> 手牌循环缩略图
|
||||||
|
// 所以在这个 getter 上,把「没挂皮肤」的卡补成主表里同 ProduceCardId、CutInType 最高的专属皮即可。
|
||||||
|
// 注意:该 getter 另有 ExamDataSerializeData / ExamParameterModel 两个读取方,
|
||||||
|
// 伪造的 id 会进考试状态序列化和 play log(竞赛回放会上传),纯本地表现之外的副作用仅此。
|
||||||
|
std::unordered_map<std::string, std::string> produceCardSkinCutInRemap{}; // produceCardId -> 专属皮 id
|
||||||
|
bool produceCardSkinCutInRemapBuilt = false;
|
||||||
|
|
||||||
|
void BuildProduceCardSkinCutInRemap() {
|
||||||
|
if (produceCardSkinCutInRemapBuilt) return;
|
||||||
|
static auto getMaster = Il2cppUtils::GetMethod("Assembly-CSharp.dll", "Campus.Common.Master", "MasterManager", "get_ProduceCardSkinMaster");
|
||||||
|
static auto masterKlass = Il2cppUtils::GetClass("Assembly-CSharp.dll", "Campus.Common.Master", "ProduceCardSkinMaster");
|
||||||
|
static auto getId = Il2cppUtils::GetMethod("Assembly-CSharp.dll", "Campus.Common.Proto.Client.Master", "ProduceCardSkin", "get_Id");
|
||||||
|
static auto getCardId = Il2cppUtils::GetMethod("Assembly-CSharp.dll", "Campus.Common.Proto.Client.Master", "ProduceCardSkin", "get_ProduceCardId");
|
||||||
|
static auto getCutInType = Il2cppUtils::GetMethod("Assembly-CSharp.dll", "Campus.Common.Proto.Client.Master", "ProduceCardSkin", "get_CutInType");
|
||||||
|
if (!getMaster || !masterKlass || !getId || !getCardId || !getCutInType) return;
|
||||||
|
// 取 1 参重载 GetAllWithSortByKey(sortType),末尾补隐藏 MethodInfo*
|
||||||
|
static auto getAll_mtd = Il2cppUtils::il2cpp_class_get_method_from_name(masterKlass->address, "GetAllWithSortByKey", 1);
|
||||||
|
if (!getAll_mtd) return;
|
||||||
|
auto getAll = reinterpret_cast<UnityResolve::UnityType::List<void*>* (*)(void*, int, void*)>(getAll_mtd->methodPointer);
|
||||||
|
|
||||||
|
auto master = getMaster->Invoke<void*>(nullptr);
|
||||||
|
if (!master) return;
|
||||||
|
auto list = getAll(master, 0, (void*)getAll_mtd);
|
||||||
|
if (!list) return;
|
||||||
|
|
||||||
|
// produceCardId -> (最高 cutInType, 皮肤 id)。没有 cut-in 的皮抄过来也没演出,直接跳过
|
||||||
|
std::unordered_map<std::string, std::pair<int, std::string>> best{};
|
||||||
|
int valid = 0, withCutIn = 0;
|
||||||
|
for (auto item : list->ToArray()->ToVector()) {
|
||||||
|
if (!item) continue; // ToVector 含 List 容量内的 null 槽位
|
||||||
|
auto id = getId->Invoke<Il2cppString*>(item);
|
||||||
|
auto cardId = getCardId->Invoke<Il2cppString*>(item);
|
||||||
|
if (!id || !cardId) continue;
|
||||||
|
++valid;
|
||||||
|
// ProduceCardSkinCutInType: Unknown=0 / Small=1 / Mid=2 / Grand=3
|
||||||
|
const int cutIn = getCutInType->Invoke<int>(item);
|
||||||
|
if (cutIn <= 0) continue;
|
||||||
|
++withCutIn;
|
||||||
|
auto& slot = best[cardId->ToString()];
|
||||||
|
if (cutIn > slot.first) slot = { cutIn, id->ToString() };
|
||||||
|
}
|
||||||
|
if (!valid) return; // 主表还没加载,下次再建
|
||||||
|
|
||||||
|
for (const auto& [cardId, slot] : best) produceCardSkinCutInRemap.emplace(cardId, slot.second);
|
||||||
|
produceCardSkinCutInRemapBuilt = true;
|
||||||
|
Log::InfoFmt("ProduceCardSkin cut-in remap built: %zu cards (rows=%d, withCutIn=%d)",
|
||||||
|
produceCardSkinCutInRemap.size(), valid, withCutIn);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 卡本身没挂皮肤时,按 produceCardId 补一个带 cut-in 的皮肤 id;
|
||||||
|
// 已经挂了皮肤的走原样,不覆盖玩家自己的选择
|
||||||
|
DEFINE_HOOK(Il2cppString*, ExamCardData_get_ProduceCardSkinId, (void* self, void* mtd)) {
|
||||||
|
auto ret = ExamCardData_get_ProduceCardSkinId_Orig(self, mtd);
|
||||||
|
if (!Config::dbgMode) return ret;
|
||||||
|
if (ret && !ret->ToString().empty()) return ret;
|
||||||
|
|
||||||
|
static auto ExamCardData_get_Id = Il2cppUtils::GetMethod("Assembly-CSharp.dll", "Campus.InGame.Card", "ExamCardData", "get_Id");
|
||||||
|
if (!ExamCardData_get_Id) return ret;
|
||||||
|
auto cardId = ExamCardData_get_Id->Invoke<Il2cppString*>(self);
|
||||||
|
if (!cardId) return ret;
|
||||||
|
|
||||||
|
BuildProduceCardSkinCutInRemap();
|
||||||
|
auto it = produceCardSkinCutInRemap.find(cardId->ToString());
|
||||||
|
return it == produceCardSkinCutInRemap.end() ? ret : Il2cppString::New(it->second);
|
||||||
|
}
|
||||||
|
|
||||||
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");
|
||||||
@@ -2480,6 +2549,11 @@ namespace GakumasLocal::HookMain {
|
|||||||
"ExchangeSpToNormal",
|
"ExchangeSpToNormal",
|
||||||
{ "Campus.Common.Proto.Client.Enums.ProduceStepType" }));
|
{ "Campus.Common.Proto.Client.Enums.ProduceStepType" }));
|
||||||
|
|
||||||
|
ADD_HOOK(ExamCardData_get_ProduceCardSkinId,
|
||||||
|
Il2cppUtils::GetMethodPointer(
|
||||||
|
"Assembly-CSharp.dll", "Campus.InGame.Card", "ExamCardData",
|
||||||
|
"get_ProduceCardSkinId"));
|
||||||
|
|
||||||
/* // 此 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", {"*", "*", "*", "*", "*"}));
|
||||||
|
|||||||
Reference in new issue
Block a user