From 2147ed169ec55c7cbb12c17a6c0aab3a2e2ba753 Mon Sep 17 00:00:00 2001 From: Dongeun <28642090+sharrrkcat@users.noreply.github.com> Date: Thu, 9 Jul 2026 13:41:57 +0800 Subject: [PATCH 1/3] Add CJK text rendering support (DBCS pairing) to Mod.dll The retail Engine.dll is an ANSI build: text drawing iterates strings byte by byte, so CharRemap keys above 0xFF can never be produced and DBCS text renders as blanks. This detours UCanvas::WrappedPrint, UCanvas::ClippedPrint (the menu text path), UCanvas::ClippedStrLen and FCanvasUtil::DrawString with reimplementations that pair GBK/Shift-JIS lead+trail bytes into 16-bit CharRemap keys - the same scheme the official Japanese release used with its modified Engine.dll. The hooks are only installed when the new EnableCJKText config property on SWRCFix is set (System.ini [Mod.SWRCFix], default off), so existing installs are unaffected: DBCS pairing could otherwise mis-pair extended-Latin bytes in the EFIGS localizations (e.g. 0xFC followed by a trail-range byte). --- Mod/Classes/SWRCFix.uc | 1 + Mod/Inc/ModClasses.h | 1 + Mod/Mod.vcproj | 3 + Mod/Src/CJKText.cpp | 479 +++++++++++++++++++++++++++++++++++++++++ Mod/Src/SWRCFix.cpp | 6 + 5 files changed, 490 insertions(+) create mode 100644 Mod/Src/CJKText.cpp diff --git a/Mod/Classes/SWRCFix.uc b/Mod/Classes/SWRCFix.uc index 8fdf96e9..d25a61e4 100644 --- a/Mod/Classes/SWRCFix.uc +++ b/Mod/Classes/SWRCFix.uc @@ -23,6 +23,7 @@ var() config bool AutoFOV; var() config bool EnableCustomMenu; var() config bool EnableEditorSelectionFix; var() config bool DisableWindowPositionVerification; +var() config bool EnableCJKText; var FunctionOverride CTPlayerEndZoomOverride; var FunctionOverride CTPlayerResetFOVOverride; diff --git a/Mod/Inc/ModClasses.h b/Mod/Inc/ModClasses.h index 2a58b4fb..0c5703bb 100644 --- a/Mod/Inc/ModClasses.h +++ b/Mod/Inc/ModClasses.h @@ -61,6 +61,7 @@ class MOD_API USWRCFix : public UObject BITFIELD EnableCustomMenu:1; BITFIELD EnableEditorSelectionFix:1; BITFIELD DisableWindowPositionVerification:1; + BITFIELD EnableCJKText:1; class UFunctionOverride* CTPlayerEndZoomOverride GCC_PACK(4); class UFunctionOverride* CTPlayerResetFOVOverride; class UFunctionOverride* PlayerControllerShakeViewOverride; diff --git a/Mod/Mod.vcproj b/Mod/Mod.vcproj index a265ef66..b567389b 100644 --- a/Mod/Mod.vcproj +++ b/Mod/Mod.vcproj @@ -131,6 +131,9 @@ xcopy /E /I /Y "$(ProjectDir)\PropertyOverrides" "$(OutDir)\Prope + + diff --git a/Mod/Src/CJKText.cpp b/Mod/Src/CJKText.cpp new file mode 100644 index 00000000..9f2df2a1 --- /dev/null +++ b/Mod/Src/CJKText.cpp @@ -0,0 +1,479 @@ +#include "Mod.h" +#include "CodeInjection.h" + +/* + * CJK text rendering support (Chinese localization). + * + * The retail Engine.dll is an ANSI build (TCHAR == char): UCanvas::WrappedPrint + * iterates strings byte by byte, so CharRemap keys > 0xFF can never be produced + * and DBCS text renders as blanks. This file replaces WrappedPrint with a + * binary-compatible reimplementation (reference: Engine.dll decompilation, + * UCanvas::WrappedPrint @103E9580, glyph emitter @103E8990) that pairs + * GBK/Shift-JIS lead+trail bytes into 16-bit CharRemap keys, the same scheme + * the official Japanese release used. + * + * Only installed when the EnableCJKText config property on SWRCFix is set + * (System.ini [Mod.SWRCFix]); default off, so existing installs are unaffected. + * + * NOTE: The in-memory FFontCharacter has a TextureIndex byte (stride 20) that + * the reconstructed CT headers comment out, and this build's TArray is 8 bytes + * (Data, Num). Raw layouts below are verified against the decompilation. + */ + +struct FGlyphChar{ + INT StartU; + INT StartV; + INT USize; + INT VSize; + BYTE TextureIndex; + BYTE Pad[3]; +}; + +struct FRemapPair{ + INT Next; + WORD Key; + WORD Value; +}; + +struct FRawFont{ // UFont + BYTE UObjectPad[40]; + FGlyphChar* Chars; INT NumCharsBits; // +40 +44 (TArray num is a 29-bit field) + UMaterial** Textures; INT NumTexturesBits; // +48 +52 + FRemapPair* Pairs; INT NumPairsBits; // +56 +60 + INT* Hash; INT HashCount; // +64 +68 + INT IsRemapped; // +72 + INT Kerning; // +76 + + INT NumChars() const { return NumCharsBits & 0x1FFFFFFF; } + INT NumTextures() const{ return NumTexturesBits & 0x1FFFFFFF; } +}; + +typedef void(__thiscall* FDrawTileFunc)(UCanvas* Canvas, UMaterial* Material, + FLOAT X, FLOAT Y, FLOAT XL, FLOAT YL, + FLOAT U, FLOAT V, FLOAT UL, FLOAT VL, + FLOAT Z, const FPlane& Color, const FPlane& Fog); +#define CANVAS_DRAWTILE_VTABLE_INDEX 32 // glyph emitter calls vtable offset 128 + +static inline bool IsDBCSLead(BYTE B) { return B >= 0x81 && B <= 0xFE; } +static inline bool IsDBCSTrail(BYTE B){ return B >= 0x40 && B <= 0xFE && B != 0x7F; } + +// Reads the next character, pairing DBCS lead+trail bytes into a 16-bit key +static WORD NextCharKey(const BYTE* S, INT& i){ + BYTE B = S[i++]; + + if(IsDBCSLead(B) && IsDBCSTrail(S[i])) + return static_cast((B << 8) | S[i++]); + + return B; +} + +static INT RemapKey(const FRawFont* F, WORD Key){ + if(!F->IsRemapped) + return Key; + + for(INT i = F->Hash[Key & (F->HashCount - 1)]; i != -1; i = F->Pairs[i].Next){ + if(F->Pairs[i].Key == Key) + return F->Pairs[i].Value; + } + + return 32; // space +} + +// Reimplementation of the per-line glyph emitter (@103E8990, ampersand path +// unused by callers omitted). bClip enables clipping against ClipX/ClipY as in +// the ClippedPrint mode of the original. Returns the accumulated advance. +static INT DrawGlyphRun(UCanvas* C, const FRawFont* F, FLOAT ScaleX, FLOAT ScaleY, + INT X, INT Y, const BYTE* Text, INT NumBytes, const FPlane& Color, + bool bClip = false){ + if(!C->pCanvasUtil) + return 0; + + FDrawTileFunc DrawTile = (*reinterpret_cast(C))[CANVAS_DRAWTILE_VTABLE_INDEX]; + FPlane Fog(0.0f, 0.0f, 0.0f, 0.0f); + INT Width = 0; + + for(INT i = 0; i < NumBytes && Text[i];){ + WORD Key = NextCharKey(Text, i); + INT Glyph = RemapKey(F, Key); + INT Advance = 0; + + if(Glyph >= 0 && Glyph < F->NumChars()){ + const FGlyphChar& G = F->Chars[Glyph]; + + Advance = G.USize; + + if(G.TextureIndex < F->NumTextures()){ + UMaterial* Material = F->Textures[G.TextureIndex]; + + if(Material && G.USize > 0){ + INT GX = Width + X; + FLOAT U = (FLOAT)G.StartU; + FLOAT V = (FLOAT)G.StartV; + FLOAT UL = (FLOAT)G.USize; + FLOAT VL = (FLOAT)G.VSize; + + if(bClip){ // right/bottom clip as in the original clip mode + if((FLOAT)(GX + UL * ScaleX) > C->ClipX) + UL = Max(0.0f, (C->ClipX - GX) / ScaleX); + + if((FLOAT)(Y + VL * ScaleY) > C->ClipY) + VL = Max(0.0f, (C->ClipY - Y) / ScaleY); + } + + if(UL > 0.0f && VL > 0.0f){ + DrawTile(C, Material, + static_cast(appFloor((FLOAT)GX + C->OrgX)), + static_cast(appFloor((FLOAT)Y + C->OrgY)), + UL * ScaleX, VL * ScaleY, + U, V, UL, VL, + 1.0f, Color, Fog); + } + } + } + } + + Width += appFloor((F->Kerning + C->SpaceX + Advance) * ScaleX); + } + + return Width; +} + +/* + * Replacement for UCanvas::WrappedPrint(INT Style, INT& XL, INT& YL, UFont*, + * FLOAT ScaleX, FLOAT ScaleY, UBOOL Center, const TCHAR* Text). + * Behavior mirrors the original except characters are fetched DBCS-aware and + * line breaks are additionally allowed at DBCS character boundaries. + */ +static void __fastcall WrappedPrintOverride(UCanvas* C, DWORD, INT Style, INT* XL, INT* YL, + UFont* InFont, FLOAT ScaleX, FLOAT ScaleY, + INT Center, const TCHAR* Text){ + const FRawFont* F = reinterpret_cast(InFont); + const BYTE* S = reinterpret_cast(Text); + + if(!S || !F || !(C->ClipX >= 0.0f && C->ClipY >= 0.0f)) + return; + + const BYTE* Cb = reinterpret_cast(C) + 88; // FColor (B,G,R,A) + const FLOAT K = 1.0f / 255.0f; + FPlane Color(Cb[2] * K, Cb[1] * K, Cb[0] * K, Cb[3] * K); + FLOAT EntryCurX = C->CurX; + + *XL = 0; + *YL = 0; + + for(;;){ + INT LineH = 0; // running max glyph height (incl. overflow char) + INT X = appFloor(C->CurX); + INT FitLen = 0; // bytes up to the last usable break point + INT FitEndX = X; // absolute end X at that break point + INT FitH = 0; + bool HaveBreak = false; + INT i = 0; + + for(;;){ + BYTE B = S[i]; + + if(!B || B == 10) + break; + + INT j = i; + WORD Key = NextCharKey(S, j); + INT Glyph = RemapKey(F, Key); + INT W = 0; + INT H = 0; + + if(Glyph >= 0 && Glyph < F->NumChars()){ + W = F->Chars[Glyph].USize; + H = F->Chars[Glyph].VSize; + } + + X += appFloor((F->Kerning + C->SpaceX + W) * ScaleX); + + INT GH = appFloor((H + appFloor(C->SpaceY)) * ScaleY); + + if(GH > LineH) + LineH = GH; + + if((FLOAT)X > C->ClipX) + break; // overflow: char not included, wrap at last break point + + i = j; + + BYTE Next = S[i]; + bool Breakable = Key > 0xFF || Next == 0 || Next == 10 || IsDBCSLead(Next) || + (Next == 32 && !(S[i + 1] == '!' || S[i + 1] == '?' || S[i + 1] == ':')); + + if(Breakable || !HaveBreak){ + FitLen = i; + FitEndX = X; + FitH = LineH; + HaveBreak = HaveBreak || Breakable; + } + } + + if(!FitLen) + break; + + if(Style){ + FLOAT AbsY = C->OrgY + C->CurY; + INT ViewH = *reinterpret_cast(*reinterpret_cast(reinterpret_cast(C) + 132) + 132); // Viewport->SizeY + + if((FLOAT)ViewH > AbsY && (FLOAT)FitH + AbsY > 0.0f){ + INT DX; + + if(Center == 1) + DX = appFloor((C->ClipX - FitEndX) * 0.5f + C->CurX); + else if(Center == 2) + DX = appFloor((C->ClipX - FitEndX) - C->CurX); + else + DX = appFloor(C->CurX); + + INT Drawn = DrawGlyphRun(C, F, ScaleX, ScaleY, DX, appFloor(C->CurY), S, FitLen, Color); + + C->CurX = static_cast(Drawn + DX); + } + } + + C->CurX = EntryCurX; + C->CurY += FitH; + *YL += FitH; + + if(FitEndX > *XL) + *XL = FitEndX; + + S += FitLen; + + if(*S == 10) + ++S; + + while(*S == 32) + ++S; + + if(!*S) + break; + } +} + +/* + * Replacement for FCanvasUtil::DrawString(INT X, INT Y, const TCHAR* Text, UFont*, + * FColor, FLOAT ScaleX, FLOAT ScaleY, UBOOL Center) — the menu/util text path + * (reference: @104C9A50 in the decompilation). Glyphs are emitted through the + * original exported FCanvasUtil::DrawTile. Returns the final X advance. + * + * '&' marks a shortcut character: the following character is drawn with an + * underscore glyph overlaid ("&&" is a literal '&'). + */ +typedef void(__thiscall* FCUDrawTileFunc)(void* CanvasUtil, + FLOAT X1, FLOAT Y1, FLOAT X2, FLOAT Y2, + FLOAT U1, FLOAT V1, FLOAT U2, FLOAT V2, + FLOAT Z, UMaterial* Material, FColor Color); +static FCUDrawTileFunc OriginalFCUDrawTile = NULL; + +static const FGlyphChar* GetGlyph(const FRawFont* F, WORD Key, UMaterial** OutMaterial){ + INT Glyph = RemapKey(F, Key); + + if(Glyph < 0 || Glyph >= F->NumChars()) + return NULL; + + const FGlyphChar& G = F->Chars[Glyph]; + + *OutMaterial = G.TextureIndex < F->NumTextures() ? F->Textures[G.TextureIndex] : NULL; + + return &G; +} + +static INT __fastcall DrawStringOverride(void* U, DWORD, INT StartX, INT StartY, const TCHAR* Text, + UFont* InFont, FColor Color, FLOAT ScaleX, FLOAT ScaleY, + UBOOL Center){ + const FRawFont* F = reinterpret_cast(InFont); + const BYTE* S = reinterpret_cast(Text); + + if(!S || !F || !OriginalFCUDrawTile) + return 0; + + INT X = 0; + INT Y = 0; + + if(Center){ // center on StartX/StartY: offset by half the total extent + INT TotalW = 0; + INT MaxH = 0; + + for(INT i = 0; S[i];){ + if(S[i] == '&' && S[i + 1] && S[i + 1] != '&') + ++i; // shortcut marker: the char itself is measured on the next round + + WORD Key = NextCharKey(S, i); + UMaterial* Material; + const FGlyphChar* G = GetGlyph(F, Key, &Material); + + if(G && Material){ + TotalW += appFloor(G->USize * ScaleX); + + INT H = appFloor(G->VSize * ScaleY); + + if(H > MaxH) + MaxH = H; + } + } + + X = TotalW / -2; + Y = MaxH / -2; + } + + FColor DrawColor = Color; + + if(GIsOpenGL) + Exchange(DrawColor.R, DrawColor.B); + + WORD UnderscoreFor = 0; // pending shortcut char key; 0 = none + + for(INT i = 0; S[i];){ + WORD Key; + + if(UnderscoreFor){ + Key = '_'; // overlay pass at the previous char's position + } + else if(S[i] == '&' && S[i + 1] && S[i + 1] != '&'){ + ++i; + Key = NextCharKey(S, i); + UnderscoreFor = Key; + } + else{ + Key = NextCharKey(S, i); + } + + UMaterial* Material; + const FGlyphChar* G = GetGlyph(F, Key, &Material); + + if(G && Material){ + INT W = appFloor(G->USize * ScaleX); + INT H = appFloor(G->VSize * ScaleY); + + if(UnderscoreFor && Key == '_'){ // underscore no wider than the char it underlines + UMaterial* CharMaterial; + const FGlyphChar* CharG = GetGlyph(F, UnderscoreFor, &CharMaterial); + INT CharW = CharG ? appFloor(CharG->USize * ScaleX) : W; + + if(CharW < W) + W = CharW; + } + + OriginalFCUDrawTile(U, + (FLOAT)(X + StartX), (FLOAT)(Y + StartY), + (FLOAT)(X + StartX + W), (FLOAT)(Y + StartY + H), + (FLOAT)G->StartU, (FLOAT)G->StartV, + (FLOAT)W / ScaleX + G->StartU, (FLOAT)(G->StartV + G->VSize), + 0.0f, Material, DrawColor); + } + + if(UnderscoreFor){ + if(Key == '_'){ // overlay drawn: now advance past the shortcut char + UMaterial* CharMaterial; + const FGlyphChar* CharG = GetGlyph(F, UnderscoreFor, &CharMaterial); + + if(CharG && CharMaterial) + X += appFloor(CharG->USize * ScaleX); + + UnderscoreFor = 0; + } + // else: shortcut char just drawn, don't advance yet (underscore overlays it) + } + else if(G && Material){ + X += appFloor(G->USize * ScaleX); + } + } + + return X; +} + +/* + * Replacement for UCanvas::ClippedPrint(UFont*, FLOAT ScaleX, FLOAT ScaleY, + * UBOOL CheckHotKey, const TCHAR* Text) — single-line print at CurX/CurY with + * clipping (@103EA170: thin wrapper around the glyph emitter with bClip=1; + * the CheckHotKey argument is ignored by the original as well). + */ +static void __fastcall ClippedPrintOverride(UCanvas* C, DWORD, UFont* InFont, FLOAT ScaleX, + FLOAT ScaleY, INT /*CheckHotKey*/, const TCHAR* Text){ + const FRawFont* F = reinterpret_cast(InFont); + const BYTE* S = reinterpret_cast(Text); + + if(!S || !F) + return; + + const BYTE* Cb = reinterpret_cast(C) + 88; // FColor (B,G,R,A) + const FLOAT K = 1.0f / 255.0f; + FPlane Color(Cb[2] * K, Cb[1] * K, Cb[0] * K, Cb[3] * K); + + DrawGlyphRun(C, F, ScaleX, ScaleY, appFloor(C->CurX), appFloor(C->CurY), S, MAXINT, Color, true); +} + +/* + * Replacement for UCanvas::ClippedStrLen(UFont*, FLOAT ScaleX, FLOAT ScaleY, + * INT& XL, INT& YL, const TCHAR* Text) — single-line measurement (@103E9EB0). + * Kerning+SpaceX is added after every character except the last, as in the + * original. + */ +static void __fastcall ClippedStrLenOverride(UCanvas* C, DWORD, UFont* InFont, FLOAT ScaleX, + FLOAT ScaleY, INT* XL, INT* YL, const TCHAR* Text){ + const FRawFont* F = reinterpret_cast(InFont ? InFont : C->Font); + const BYTE* S = reinterpret_cast(Text); + + *XL = 0; + *YL = 0; + + if(!S || !F) + return; + + for(INT i = 0; S[i];){ + WORD Key = NextCharKey(S, i); + INT Glyph = RemapKey(F, Key); + INT W = 0; + INT H = 0; + + if(Glyph >= 0 && Glyph < F->NumChars()){ + W = F->Chars[Glyph].USize; + H = F->Chars[Glyph].VSize; + } + + FLOAT CharW = S[i] ? (FLOAT)(F->Kerning + C->SpaceX + W) : (FLOAT)W; + + *XL += appFloor(CharW * ScaleX); + + INT CharH = appFloor(H * ScaleY); + + if(CharH > *YL) + *YL = CharH; + } +} + +void InitCJKText(void){ + void* Handle = appGetDllHandle("Engine.dll"); + + if(!Handle){ + debugf(NAME_Error, "CJKText: failed to get Engine.dll handle"); + + return; + } + + void* WrappedPrint = appGetDllExport(Handle, "?WrappedPrint@UCanvas@@AAEXHAAH0PAVUFont@@MMHPBD@Z"); + void* DrawString = appGetDllExport(Handle, "?DrawString@FCanvasUtil@@QAEHHHPBDPAVUFont@@VFColor@@MM_N@Z"); + void* ClippedPrint = appGetDllExport(Handle, "?ClippedPrint@UCanvas@@UAEXPAVUFont@@MMHPBD@Z"); + void* ClippedStrLen = appGetDllExport(Handle, "?ClippedStrLen@UCanvas@@UAEXPAVUFont@@MMAAH1PBD@Z"); + + OriginalFCUDrawTile = reinterpret_cast(appGetDllExport(Handle, "?DrawTile@FCanvasUtil@@QAEXMMMMMMMMMPAVUMaterial@@VFColor@@@Z")); + + appFreeDllHandle(Handle); + + if(!WrappedPrint || !DrawString || !ClippedPrint || !ClippedStrLen || !OriginalFCUDrawTile){ + debugf(NAME_Error, "CJKText: Engine.dll export not found (WrappedPrint=%p DrawString=%p ClippedPrint=%p ClippedStrLen=%p DrawTile=%p)", + WrappedPrint, DrawString, ClippedPrint, ClippedStrLen, OriginalFCUDrawTile); + + return; + } + + RedirectFunction(WrappedPrint, WrappedPrintOverride); + RedirectFunction(DrawString, DrawStringOverride); + RedirectFunction(ClippedPrint, ClippedPrintOverride); + RedirectFunction(ClippedStrLen, ClippedStrLenOverride); + debugf(NAME_Init, "CJKText: WrappedPrint/DrawString/ClippedPrint/ClippedStrLen hooked (DBCS pairing enabled)"); +} diff --git a/Mod/Src/SWRCFix.cpp b/Mod/Src/SWRCFix.cpp index 9219c8ec..02f06417 100644 --- a/Mod/Src/SWRCFix.cpp +++ b/Mod/Src/SWRCFix.cpp @@ -19,6 +19,12 @@ void CDECL InitSWRCFix(void) FName("SWRCFixInstance")); USWRCFix::Instance->AddToRoot(); // This object should never be garbage collected USWRCFix::Instance->Init(); + + if(USWRCFix::Instance->EnableCJKText) + { + extern void InitCJKText(void); + InitCJKText(); + } } USWRCFix::RenderingReady = !GIsEditor; From fa8acf35504a6bc8c40a1fc9a97e3ff7382ddbaf Mon Sep 17 00:00:00 2001 From: Dongeun <28642090+sharrrkcat@users.noreply.github.com> Date: Thu, 9 Jul 2026 22:00:10 +0800 Subject: [PATCH 2/3] Fix VS2003 CJK text build compatibility --- Mod/Inc/ModClasses.h | 2 +- Mod/Mod.vcproj | 6 +++--- Mod/Src/CJKText.cpp | 31 ++++++++++++------------------- 3 files changed, 16 insertions(+), 23 deletions(-) diff --git a/Mod/Inc/ModClasses.h b/Mod/Inc/ModClasses.h index 0c5703bb..6722605c 100644 --- a/Mod/Inc/ModClasses.h +++ b/Mod/Inc/ModClasses.h @@ -61,7 +61,7 @@ class MOD_API USWRCFix : public UObject BITFIELD EnableCustomMenu:1; BITFIELD EnableEditorSelectionFix:1; BITFIELD DisableWindowPositionVerification:1; - BITFIELD EnableCJKText:1; + BITFIELD EnableCJKText:1; class UFunctionOverride* CTPlayerEndZoomOverride GCC_PACK(4); class UFunctionOverride* CTPlayerResetFOVOverride; class UFunctionOverride* PlayerControllerShakeViewOverride; diff --git a/Mod/Mod.vcproj b/Mod/Mod.vcproj index b567389b..fcfe52d9 100644 --- a/Mod/Mod.vcproj +++ b/Mod/Mod.vcproj @@ -131,9 +131,9 @@ xcopy /E /I /Y "$(ProjectDir)\PropertyOverrides" "$(OutDir)\Prope - - + + diff --git a/Mod/Src/CJKText.cpp b/Mod/Src/CJKText.cpp index 9f2df2a1..9b5269f5 100644 --- a/Mod/Src/CJKText.cpp +++ b/Mod/Src/CJKText.cpp @@ -1,4 +1,4 @@ -#include "Mod.h" +#include "Mod.h" #include "CodeInjection.h" /* @@ -48,12 +48,6 @@ struct FRawFont{ // UFont INT NumTextures() const{ return NumTexturesBits & 0x1FFFFFFF; } }; -typedef void(__thiscall* FDrawTileFunc)(UCanvas* Canvas, UMaterial* Material, - FLOAT X, FLOAT Y, FLOAT XL, FLOAT YL, - FLOAT U, FLOAT V, FLOAT UL, FLOAT VL, - FLOAT Z, const FPlane& Color, const FPlane& Fog); -#define CANVAS_DRAWTILE_VTABLE_INDEX 32 // glyph emitter calls vtable offset 128 - static inline bool IsDBCSLead(BYTE B) { return B >= 0x81 && B <= 0xFE; } static inline bool IsDBCSTrail(BYTE B){ return B >= 0x40 && B <= 0xFE && B != 0x7F; } @@ -88,7 +82,6 @@ static INT DrawGlyphRun(UCanvas* C, const FRawFont* F, FLOAT ScaleX, FLOAT Scale if(!C->pCanvasUtil) return 0; - FDrawTileFunc DrawTile = (*reinterpret_cast(C))[CANVAS_DRAWTILE_VTABLE_INDEX]; FPlane Fog(0.0f, 0.0f, 0.0f, 0.0f); INT Width = 0; @@ -121,12 +114,12 @@ static INT DrawGlyphRun(UCanvas* C, const FRawFont* F, FLOAT ScaleX, FLOAT Scale } if(UL > 0.0f && VL > 0.0f){ - DrawTile(C, Material, - static_cast(appFloor((FLOAT)GX + C->OrgX)), - static_cast(appFloor((FLOAT)Y + C->OrgY)), - UL * ScaleX, VL * ScaleY, - U, V, UL, VL, - 1.0f, Color, Fog); + C->DrawTile(Material, + static_cast(appFloor((FLOAT)GX + C->OrgX)), + static_cast(appFloor((FLOAT)Y + C->OrgY)), + UL * ScaleX, VL * ScaleY, + U, V, UL, VL, + 1.0f, Color, Fog); } } } @@ -256,14 +249,14 @@ static void __fastcall WrappedPrintOverride(UCanvas* C, DWORD, INT Style, INT* X /* * Replacement for FCanvasUtil::DrawString(INT X, INT Y, const TCHAR* Text, UFont*, - * FColor, FLOAT ScaleX, FLOAT ScaleY, UBOOL Center) — the menu/util text path + * FColor, FLOAT ScaleX, FLOAT ScaleY, UBOOL Center) - the menu/util text path * (reference: @104C9A50 in the decompilation). Glyphs are emitted through the * original exported FCanvasUtil::DrawTile. Returns the final X advance. * * '&' marks a shortcut character: the following character is drawn with an * underscore glyph overlaid ("&&" is a literal '&'). */ -typedef void(__thiscall* FCUDrawTileFunc)(void* CanvasUtil, +typedef void(__fastcall* FCUDrawTileFunc)(void* CanvasUtil, DWORD Edx, FLOAT X1, FLOAT Y1, FLOAT X2, FLOAT Y2, FLOAT U1, FLOAT V1, FLOAT U2, FLOAT V2, FLOAT Z, UMaterial* Material, FColor Color); @@ -358,7 +351,7 @@ static INT __fastcall DrawStringOverride(void* U, DWORD, INT StartX, INT StartY, W = CharW; } - OriginalFCUDrawTile(U, + OriginalFCUDrawTile(U, 0, (FLOAT)(X + StartX), (FLOAT)(Y + StartY), (FLOAT)(X + StartX + W), (FLOAT)(Y + StartY + H), (FLOAT)G->StartU, (FLOAT)G->StartV, @@ -388,7 +381,7 @@ static INT __fastcall DrawStringOverride(void* U, DWORD, INT StartX, INT StartY, /* * Replacement for UCanvas::ClippedPrint(UFont*, FLOAT ScaleX, FLOAT ScaleY, - * UBOOL CheckHotKey, const TCHAR* Text) — single-line print at CurX/CurY with + * UBOOL CheckHotKey, const TCHAR* Text) - single-line print at CurX/CurY with * clipping (@103EA170: thin wrapper around the glyph emitter with bClip=1; * the CheckHotKey argument is ignored by the original as well). */ @@ -409,7 +402,7 @@ static void __fastcall ClippedPrintOverride(UCanvas* C, DWORD, UFont* InFont, FL /* * Replacement for UCanvas::ClippedStrLen(UFont*, FLOAT ScaleX, FLOAT ScaleY, - * INT& XL, INT& YL, const TCHAR* Text) — single-line measurement (@103E9EB0). + * INT& XL, INT& YL, const TCHAR* Text) - single-line measurement (@103E9EB0). * Kerning+SpaceX is added after every character except the last, as in the * original. */ From 81a5c8382832ab43190e365ab01bc59ac3dfc5ea Mon Sep 17 00:00:00 2001 From: Dongeun <28642090+sharrrkcat@users.noreply.github.com> Date: Sat, 11 Jul 2026 11:51:54 +0800 Subject: [PATCH 3/3] Auto-enable CJK text hooks based on Language setting --- Mod/Classes/SWRCFix.uc | 1 - Mod/Inc/ModClasses.h | 1 - Mod/Src/SWRCFix.cpp | 4 +++- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Mod/Classes/SWRCFix.uc b/Mod/Classes/SWRCFix.uc index d25a61e4..8fdf96e9 100644 --- a/Mod/Classes/SWRCFix.uc +++ b/Mod/Classes/SWRCFix.uc @@ -23,7 +23,6 @@ var() config bool AutoFOV; var() config bool EnableCustomMenu; var() config bool EnableEditorSelectionFix; var() config bool DisableWindowPositionVerification; -var() config bool EnableCJKText; var FunctionOverride CTPlayerEndZoomOverride; var FunctionOverride CTPlayerResetFOVOverride; diff --git a/Mod/Inc/ModClasses.h b/Mod/Inc/ModClasses.h index 6722605c..2a58b4fb 100644 --- a/Mod/Inc/ModClasses.h +++ b/Mod/Inc/ModClasses.h @@ -61,7 +61,6 @@ class MOD_API USWRCFix : public UObject BITFIELD EnableCustomMenu:1; BITFIELD EnableEditorSelectionFix:1; BITFIELD DisableWindowPositionVerification:1; - BITFIELD EnableCJKText:1; class UFunctionOverride* CTPlayerEndZoomOverride GCC_PACK(4); class UFunctionOverride* CTPlayerResetFOVOverride; class UFunctionOverride* PlayerControllerShakeViewOverride; diff --git a/Mod/Src/SWRCFix.cpp b/Mod/Src/SWRCFix.cpp index 02f06417..bfbf2ded 100644 --- a/Mod/Src/SWRCFix.cpp +++ b/Mod/Src/SWRCFix.cpp @@ -20,7 +20,9 @@ void CDECL InitSWRCFix(void) USWRCFix::Instance->AddToRoot(); // This object should never be garbage collected USWRCFix::Instance->Init(); - if(USWRCFix::Instance->EnableCJKText) + const TCHAR* Lang = UObject::GetLanguage(); + + if(!appStricmp(Lang, "cht") || !appStricmp(Lang, "jpt")) { extern void InitCJKText(void); InitCJKText();