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