Files
DragonHD 9eed020634 Chinese main menu: font atlas + GB2312 tables + Strings translation
- build_cjk.py: 2048x1024 UI atlas (original ASCII at 0,0 + full GB2312 at 12px) and
  cjk_glyph/cjk_metric tables.
- cn_strings.py + make_cn_strings.py: escape-aware GB2312 rewrite of Strings.dat (46 entries).
- install_cjk.py: one-shot deploy/restore (exe patch, atlas loose+res, strings).
- apply_cjk.py: fix metrics cave code byte order (was (second<<8)|first) so CJK width
  measures correctly and centred labels are no longer flushed right.
- docs: DEVLOG + patch notes for the atlas and the fix.
2026-09-20 10:22:49 +08:00

4.5 KiB

Font rendering map + double-byte patch

Font object layout (size 0x191C, embedded by value in a manager struct)

Two UI font objects live at manager + 0x3A8 and manager + 0x1CC4 (difference = 0x191C).

+0x00  texture pointer (D3D texture object)
+0x04  max width
+0x08  max height
+0x0C  SpaceWidth
+0x10  LineDistance
+0x14  CharDistance
+0x18  flag (1 = built)
+0x1C  256 glyph entries x 16 bytes  = 4 floats (u1,v1,u2,v2) normalized UV
+0x101C 256 metric entries x 8 bytes = { int advance, int height }
+0x181C 256 "defined" bytes

The table stride differs (16 vs 8 vs 1), which is why the tables cannot be enlarged in place and a side table is used for CJK.

Key code

VA role
0x4EBF90 parse/load Fonts.dat (generic config reader)
0x4E7DC0 set font texture
0x4E7E30 AddChar(code, x1,y1,x2,y2) — normalizes rect → stores glyph float[4] + metric[2]
0x4EC168 mov cl,[ebp] — parser takes only the first byte of Code
0x4E7FD0 measure text; loop body 0x4E7FF0; metric lookups 0x4E802C, 0x4E8033
0x4EAC40 draw text; loop head
0x4EACB0 movzx ecx,al — glyph lookup site in draw loop
0x4EACDC,0x4EAE9A metric lookups in draw loop
0x4E56A0 IsCharDefined(font, code)

Texture is bound once per draw call ([font] + vtable +0xF4), so ASCII and CJK glyphs must share one texture atlas.

Patch design (implemented)

A new PE section .cjk holds two code caves and two big tables:

.cjk RVA 0x566000 (VA 0x966000), size 0x1C0000
  0x00     draw_cave
  0x0100   metrics_cave
  0x0200   cjk_glyph  : 65536 x 16 bytes (u1,v1,u2,v2 floats)
  0x100200 cjk_metric : 65536 x 8 bytes  (advance,height ints)

Hooks (5-byte jmp rel32):

  • 0x4EACB0 -> draw_cave (was movzx ecx,al)
  • 0x4E8026 -> metrics_cave (was mov ebp,[ecx+0x14])

Behaviour of each cave:

  • al < 0x80: execute the original instructions, jump back to the next instruction.
  • al >= 0x80: decode a 2-byte code (al<<8)|[ptr+1]; copy the glyph float[4] into font glyph slot 0xFF and the metric into slot 0xFF; set ecx = 0xFF, advance the string pointer by one extra byte; jump back into the engine's unchanged quad-building code.

Slot 0xFF glyph lives at font+0x100C, its metric at font+0x1814; these do not collide with the real tables.

Byte-order detail (important)

Both caves must build the code identically as (first << 8) | second:

  • draw cave: movzx ecx,al; shl ecx,8; movzx eax,[edx+1]; or ecx,eax
  • metrics cave: movzx eax,al; shl eax,8; mov al,[ebx+1] (al is free after the shl)

The metrics cave originally used mov ah,[ebx+1] which yields (second<<8)|first; CJK then measured as width 0 and centred labels were flushed right. Fixed.

Font atlas (implemented, tools/build_cjk.py)

AddChar normalises rects by the actual D3D texture size, so replacing the UI font texture with a larger atlas that still contains the original ASCII art at pixel (0,0) keeps every existing Fonts.dat rect valid — no Fonts.dat edits needed.

Shrift_gb_Germany.TGA   2048 x 1024, 32bpp TGA (type 2, bottom-up, desc 0x08, 26-byte footer)
  (0,0) 256x128   original ASCII/German artwork (byte-identical copy)
  (0,128)+       12x12 cells, 170 per row, full GB2312 (7445 codes), simsun 12px, binary alpha

cjk_glyph holds normalised UVs (x/2048, y/1024, (x+12)/2048, (y+12)/1024); cjk_metric holds (12, 12) (advance, height). Deployed as a loose file and inside Textures.res (see tools/install_cjk.py).

Requirements / limits

  • The font texture must contain both ASCII and CJK glyphs (one texture per draw); done via the shared atlas above.
  • Lead bytes >= 0x80 are reserved for DBCS. ASCII is untouched.
  • Only the UI font atlas is extended; the OldUI / UISmall / Russian fonts still point at their own textures, so CJK drawn with those would need their own atlas/tables too.

Tools

  • tools/add_section.py — append an executable PE section.
  • tools/apply_cjk.py — assemble the caves (keystone), add .cjk, write hooks and tables.
  • tools/build_cjk.py — build the atlas TGA + cjk_glyph/cjk_metric from a charset.
  • tools/make_cn_strings.py + tools/cn_strings.py — GB2312 translation of Strings.dat.
  • tools/install_cjk.py — deploy/restore everything into the game folder.
  • tools/decode_dat.py — decode/encode the XOR .dat files.
  • tools/fontparse.py — parse Fonts.dat into blocks + glyph rects.
  • tools/xrefs.py, tools/disasm.py, tools/scan_disp.py — RE helpers.