Files
dragon-hd/docs/patch-notes.md

154 lines
6.6 KiB
Markdown

# 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
### Option-title centering
Four original title sites push reference x=620 and directly draw text. The
panel/Bind button center is reference x=640 (`0x4D8646`). Patch the title
anchors to x=640 and redirect their draw calls to `.cjk+0x180500`:
| Page | Anchor instruction | Draw call |
|---|---|---|
| Audio | `0x4D748C` | `0x4D74A6` |
| Game | `0x4D9A1C` | `0x4D9A36` |
| Video | `0x4DDAB4` | `0x4DDACE` |
| Controls | `0x4DE8C8` | `0x4DE8E2` |
The wrapper preserves registers, measures the supplied font/text using
`0x4E7FD0`, subtracts half the measured width from the already transformed
screen point, and tail-jumps to `0x4EAB70`. Ordinary option rows and unrelated
draw calls retain their existing placement. Input opcodes are checked before
these title edits. `tools/test_cjk_titles.py` tests the actual call-site code,
coordinate transformation, measurement, and resulting draw positions.
### Wrapped-text layout correction
`0x4ED380` wraps/aligns tooltip paragraphs separately from the plain draw and
measure loops. Its one-byte temporary string must be extended to a full GB2312
character, otherwise a Chinese-only line has zero measured glyph height.
New hooks (all addresses apply to the documented Steam executable):
| Hook | Cave offset in .cjk | Purpose |
|---|---|---|
| `0x4ED430` | `0x180200` | Prepare a NUL-terminated one/two-byte character; preserve control-code flags |
| `0x4ED50E` | `0x180300` | Append complete character, advance byte index, retain width/height at CJK break |
| `0x4ED4E9` | `0x180400` | Preserve an overwide first CJK glyph instead of consuming only its lead byte |
Layout locals: `esp+0x18..0x1A` character plus NUL, `+0x1B` byte length,
`+0x14` source byte index, `+0x24` output byte count, `+0x1C` last wrap
checkpoint. Empty output initializes the checkpoint to `source_index-1`.
The caves use spare space after the metric table; UVs and atlas pixels are
unchanged. `tools/test_cjk_layout.py` runs the original layout and measurement
machine code in Unicorn and captures line bytes and coordinates at the draw
call boundary. This checks layout behavior without replacing it with a Python
reimplementation.
- `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.