mirror of
https://git.chinosk6.cn/chinosk/gkms-localify-dmm.git
synced 2026-10-06 05:18:06 +07:00
fix: 汉化后特效大字随机缺字
游戏的 TMP_FontAsset 是全局共享的 Dynamic 资产,特效大字与普通 UI 共用同一张 2048² 图集,单页只装约 735 个字形,主页几屏就会溢出到 第 1 页。第 1 页字形拿到第二个材质进入 meshInfo[1],而特效文字组件 只提交 meshInfo[0],于是这些字消失(保留 advance,表现为空格)。 修法(UpdateFont): - 替换字体时把图集抬到 4096²(约 2900 字形),开启多图集; - 用 ClearFontAssetData(false) 代替 UpdateFontAssetData,后者会把 原字符表全集重新加回来白占第 0 页; - 清空后广播 ON_FONT_PROPERTY_CHANGED,让已生成网格的文字重排, 否则 ruby 等先排版的文字会拿着失效的图集坐标显示错字或空白; - 以 (fontAsset 地址, InstanceID, sourceFont InstanceID) 追踪资产, 只有真正新资产才重建;Resources.UnloadUnusedAssets 回收替换字体 后重新赋值即可,不再清空图集。 - 图集溢出到第 1 页时记录错误日志,便于观察天花板。 Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
1 parent
5af7956318
commit
937c45ba8c
1 file changed
+97
-9
@@ -869,7 +869,16 @@ namespace GakumasLocal::HookMain {
|
||||
}
|
||||
#endif
|
||||
|
||||
std::unordered_set<void*> updatedFontPtrs{};
|
||||
// A managed object address can be reused after the original object is collected, and the
|
||||
// replacement Font is dropped by Resources.UnloadUnusedAssets on scene loads. Pair the address
|
||||
// with both InstanceIDs so a recycled address or a reloaded Font is set up again - and so the
|
||||
// map is never cleared wholesale, which would re-initialize every live asset and reshuffle the
|
||||
// atlas under text that is already on screen.
|
||||
struct UpdatedFontState {
|
||||
int fontAssetInstanceId;
|
||||
int sourceFontInstanceId;
|
||||
};
|
||||
std::unordered_map<void*, UpdatedFontState> updatedFontStates{};
|
||||
void UpdateFont(void* TMP_Textself) {
|
||||
if (!Config::replaceFont) return;
|
||||
static auto get_font = Il2cppUtils::GetMethod("Unity.TextMeshPro.dll",
|
||||
@@ -888,8 +897,39 @@ namespace GakumasLocal::HookMain {
|
||||
|
||||
static auto set_sourceFontFile = Il2cppUtils::GetMethod("Unity.TextMeshPro.dll", "TMPro",
|
||||
"TMP_FontAsset", "set_sourceFontFile");
|
||||
static auto UpdateFontAssetData = Il2cppUtils::GetMethod("Unity.TextMeshPro.dll", "TMPro",
|
||||
"TMP_FontAsset", "UpdateFontAssetData");
|
||||
static auto set_atlasPopulationMode = Il2cppUtils::GetMethod(
|
||||
"Unity.TextMeshPro.dll", "TMPro", "TMP_FontAsset", "set_atlasPopulationMode",
|
||||
{ "TMPro.AtlasPopulationMode" });
|
||||
static auto set_isMultiAtlasTexturesEnabled = Il2cppUtils::GetMethod(
|
||||
"Unity.TextMeshPro.dll", "TMPro", "TMP_FontAsset", "set_isMultiAtlasTexturesEnabled",
|
||||
{ "System.Boolean" });
|
||||
static auto ClearFontAssetData = Il2cppUtils::GetMethod(
|
||||
"Unity.TextMeshPro.dll", "TMPro", "TMP_FontAsset", "ClearFontAssetData",
|
||||
{ "System.Boolean" });
|
||||
static auto get_atlasWidth = Il2cppUtils::GetMethod("Unity.TextMeshPro.dll", "TMPro",
|
||||
"TMP_FontAsset", "get_atlasWidth");
|
||||
static auto set_atlasWidth = Il2cppUtils::GetMethod(
|
||||
"Unity.TextMeshPro.dll", "TMPro", "TMP_FontAsset", "set_atlasWidth",
|
||||
{ "System.Int32" });
|
||||
static auto set_atlasHeight = Il2cppUtils::GetMethod(
|
||||
"Unity.TextMeshPro.dll", "TMPro", "TMP_FontAsset", "set_atlasHeight",
|
||||
{ "System.Int32" });
|
||||
static auto get_atlasTextureCount = Il2cppUtils::GetMethod("Unity.TextMeshPro.dll", "TMPro",
|
||||
"TMP_FontAsset", "get_atlasTextureCount");
|
||||
static auto GetInstanceID = Il2cppUtils::GetMethod(
|
||||
"UnityEngine.CoreModule.dll", "UnityEngine", "Object", "GetInstanceID");
|
||||
// TMP's own "this font asset changed, re-layout everything using it" broadcast.
|
||||
static auto ON_FONT_PROPERTY_CHANGED = Il2cppUtils::GetMethod(
|
||||
"Unity.TextMeshPro.dll", "TMPro", "TMPro_EventManager", "ON_FONT_PROPERTY_CHANGED",
|
||||
{ "System.Boolean", "UnityEngine.Object" });
|
||||
|
||||
if (!get_font || !set_font || !get_name || !set_sourceFontFile || !set_atlasPopulationMode ||
|
||||
!set_isMultiAtlasTexturesEnabled || !ClearFontAssetData || !get_atlasWidth ||
|
||||
!set_atlasWidth || !set_atlasHeight || !get_atlasTextureCount || !GetInstanceID ||
|
||||
!ON_FONT_PROPERTY_CHANGED) {
|
||||
Log::Error("UpdateFont disabled: a TMP_FontAsset method could not be resolved");
|
||||
return;
|
||||
}
|
||||
|
||||
auto fontAsset = get_font->Invoke<void*>(TMP_Textself);
|
||||
if (!fontAsset) {
|
||||
@@ -910,13 +950,61 @@ namespace GakumasLocal::HookMain {
|
||||
auto newFont = GetReplaceFont();
|
||||
if (!newFont) return;
|
||||
|
||||
set_sourceFontFile->Invoke<void>(fontAsset, newFont);
|
||||
if (!updatedFontPtrs.contains(fontAsset)) {
|
||||
updatedFontPtrs.emplace(fontAsset);
|
||||
UpdateFontAssetData->Invoke<void>(fontAsset);
|
||||
const auto fontInstanceId = GetInstanceID->Invoke<int>(fontAsset);
|
||||
const auto sourceFontInstanceId = GetInstanceID->Invoke<int>(newFont);
|
||||
const auto known = updatedFontStates.find(fontAsset);
|
||||
const bool isNewAsset = known == updatedFontStates.end() ||
|
||||
known->second.fontAssetInstanceId != fontInstanceId;
|
||||
const bool sourceFontReloaded = !isNewAsset &&
|
||||
known->second.sourceFontInstanceId != sourceFontInstanceId;
|
||||
if (isNewAsset || sourceFontReloaded) {
|
||||
// Mark the asset before rebuilding it: TMP can re-enter one of the text hooks below
|
||||
// while the rebuild is in progress.
|
||||
updatedFontStates.insert_or_assign(
|
||||
fontAsset, UpdatedFontState{ fontInstanceId, sourceFontInstanceId });
|
||||
set_sourceFontFile->Invoke<void>(fontAsset, newFont);
|
||||
}
|
||||
if (updatedFontPtrs.size() > 200) updatedFontPtrs.clear();
|
||||
|
||||
// Only a genuinely new asset is rebuilt. Resources.UnloadUnusedAssets drops the replacement
|
||||
// Font on most scene loads, and re-clearing on every reload throws away glyphs that text
|
||||
// already on screen is still pointing at - it keeps its stale atlas coordinates and renders
|
||||
// the wrong characters, or nothing at all once the atlas is no longer refilled. The glyphs
|
||||
// already in the atlas came from an identical font file, so a reloaded Font can simply be
|
||||
// re-assigned without touching them.
|
||||
if (isNewAsset) {
|
||||
set_atlasPopulationMode->Invoke<void>(fontAsset, 1); // AtlasPopulationMode.Dynamic
|
||||
set_isMultiAtlasTexturesEnabled->Invoke<void>(fontAsset, true);
|
||||
|
||||
// Glyphs that do not fit on atlas page 0 get a second material, which puts them in
|
||||
// textInfo.meshInfo[1]. The game's stylized text only submits meshInfo[0], so those
|
||||
// characters vanish - correct advance, no quad - while ordinary UI still draws them.
|
||||
// Measured from a frame capture: shelf pitch 79px, ~735 glyphs per 2048x2048 page, and
|
||||
// page 0 was completely full, so the spill was guaranteed rather than occasional.
|
||||
// 4096x4096 holds ~2900 glyphs (16MB of A8); 20 minutes of play reaches about a third.
|
||||
// ponytail: still a ceiling. The atlasTextureCount check below reports when it is hit;
|
||||
// the real fix is making the stylized text submit its sub meshes.
|
||||
if (get_atlasWidth->Invoke<int>(fontAsset) < 4096) {
|
||||
set_atlasWidth->Invoke<void>(fontAsset, 4096);
|
||||
set_atlasHeight->Invoke<void>(fontAsset, 4096);
|
||||
}
|
||||
// Clear, not UpdateFontAssetData: the latter re-adds the asset's entire original
|
||||
// character table first, spending page 0 on glyphs the translation never shows.
|
||||
ClearFontAssetData->Invoke<void>(fontAsset, false);
|
||||
// Clearing invalidates the atlas coordinates baked into every mesh already generated
|
||||
// from this asset. Without this broadcast those texts keep the stale coordinates: the
|
||||
// game's ruby text objects, laid out before the rebuild, showed wrong glyphs when the
|
||||
// old character table was re-added and nothing at all once it was not.
|
||||
ON_FONT_PROPERTY_CHANGED->Invoke<void>(true, fontAsset);
|
||||
}
|
||||
|
||||
static int reportedAtlasPages = 1;
|
||||
const auto atlasPages = get_atlasTextureCount->Invoke<int>(fontAsset);
|
||||
if (atlasPages > reportedAtlasPages) {
|
||||
reportedAtlasPages = atlasPages;
|
||||
Log::ErrorFmt("Font atlas spilled to page %d on [%s] - stylized text will start "
|
||||
"dropping characters",
|
||||
atlasPages - 1, fontAssetName ? fontAssetName->ToString().c_str() : "?");
|
||||
}
|
||||
|
||||
set_font->Invoke<void>(TMP_Textself, fontAsset);
|
||||
|
||||
// auto fontMaterial = get_material->Invoke<void*>(fontAsset);
|
||||
|
||||
Reference in new issue
Block a user