paavohtl Claude Opus 4.6 (1M context) commited on
Commit
3fc929d
Β·
1 Parent(s): dad7427

feat: port DisplayGfx font vtable methods (slots 7-10, 36) to Rust

Browse files

Port 5 font-related DisplayGfx vtable methods as thin wrappers that
validate font_id bounds, look up the font object, then delegate to
the internal WA font functions via naked asm bridges.

- Slot 7: draw_text_on_bitmap (usercall EAX=bitmap, EDX=a8, ESI=font)
- Slot 8: get_font_info (usercall EAX=font, EDX=out2, EDI=out1)
- Slot 9: get_font_metric (usercall AL=char, EDX=out1, EDI=out2)
- Slot 10: set_font_param (usercall ECX=p4, EDX=font)
- Slot 36: set_font_palette (usercall ESI=font)

Also fixes vtable param counts for slots 8 and 9, adds 0x3C (CMP AL)
to valid startup check prologues, and adds FONT_OBJ_* address constants.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

crates/openwa-core/src/address.rs CHANGED
@@ -241,6 +241,19 @@ pub mod va {
241
  fn/Thiscall DISPLAY_GFX_LOAD_SPRITE_EX = 0x0052_3310;
242
  }
243
 
 
 
 
 
 
 
 
 
 
 
 
 
 
244
  /// "sprite" type-tag string in .rdata β€” returned by Sprite/SpriteBank GetInfo
245
  global STR_SPRITE = 0x0066_4170;
246
 
 
241
  fn/Thiscall DISPLAY_GFX_LOAD_SPRITE_EX = 0x0052_3310;
242
  }
243
 
244
+ class "Font" {
245
+ /// Font object: draw text β€” usercall(EAX=h_align, EDX=bitmap, ESI=font_obj) + 5 stack, RET 0x14
246
+ fn/Usercall FONT_OBJ_DRAW_TEXT = 0x004F_A4E0;
247
+ /// Font object: set param β€” usercall(ECX=p4, EDX=font_obj) + 2 stack(p3, p5), RET 0x8
248
+ fn/Usercall FONT_OBJ_SET_PARAM = 0x004F_A720;
249
+ /// Font object: get metric β€” usercall(AL=char, EDX=out1, EDI=out2) + 1 stack(font_obj), RET 0x4
250
+ fn/Usercall FONT_OBJ_GET_METRIC = 0x004F_A780;
251
+ /// Font object: get info β€” usercall(EAX=font_obj, EDX=out2, EDI=out1), plain RET
252
+ fn/Usercall FONT_OBJ_GET_INFO = 0x004F_A7D0;
253
+ /// Font object: set palette β€” usercall(ESI=font_obj) + 1 stack(palette_value), RET 0x4
254
+ fn/Usercall FONT_OBJ_SET_PALETTE = 0x004F_9F20;
255
+ }
256
+
257
  /// "sprite" type-tag string in .rdata β€” returned by Sprite/SpriteBank GetInfo
258
  global STR_SPRITE = 0x0066_4170;
259
 
crates/openwa-core/src/render/display/vtable.rs CHANGED
@@ -75,11 +75,25 @@ pub struct DisplayVtable {
75
  a8: i32,
76
  ) -> i32,
77
  /// get font info for a font slot (0x523790, RET 0xC)
 
 
 
78
  #[slot(8)]
79
- pub get_font_info: fn(this: *mut DisplayGfx, font_id: i32, p3: u32) -> u32,
80
- /// get font metric (0x523750, RET 0x10)
 
 
 
 
 
81
  #[slot(9)]
82
- pub get_font_metric: fn(this: *mut DisplayGfx, font_id: i32, p3: u32, p4: u32) -> u32,
 
 
 
 
 
 
83
  /// set font rendering parameter (0x523710, RET 0x10)
84
  #[slot(10)]
85
  pub set_font_param: fn(this: *mut DisplayGfx, font_id: i32, p3: u32, p4: u32, p5: u32) -> u32,
 
75
  a8: i32,
76
  ) -> i32,
77
  /// get font info for a font slot (0x523790, RET 0xC)
78
+ ///
79
+ /// Reads two signed shorts from the font object and writes them to output pointers.
80
+ /// Returns 1 on success, 0 if font_id is out of range or font slot is empty.
81
  #[slot(8)]
82
+ pub get_font_info:
83
+ fn(this: *mut DisplayGfx, font_id: i32, out_1: *mut u32, out_2: *mut u32) -> u32,
84
+ /// get font metric for a character (0x523750, RET 0x10)
85
+ ///
86
+ /// Queries character metrics from the font object. `char_code` is passed
87
+ /// as a byte (low 8 bits). Returns result via two output pointers.
88
+ /// Returns 1 on success, 0 if font_id is out of range or font slot is empty.
89
  #[slot(9)]
90
+ pub get_font_metric: fn(
91
+ this: *mut DisplayGfx,
92
+ font_id: i32,
93
+ char_code: u32,
94
+ out_1: *mut u32,
95
+ out_2: *mut u32,
96
+ ) -> u32,
97
  /// set font rendering parameter (0x523710, RET 0x10)
98
  #[slot(10)]
99
  pub set_font_param: fn(this: *mut DisplayGfx, font_id: i32, p3: u32, p4: u32, p5: u32) -> u32,
crates/openwa-dll/src/replacements/render.rs CHANGED
@@ -1306,6 +1306,259 @@ unsafe extern "thiscall" fn draw_scaled_sprite(
1306
  }
1307
  }
1308
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1309
  // ---------------------------------------------------------------------------
1310
  // Display installation
1311
  // ---------------------------------------------------------------------------
@@ -1347,6 +1600,10 @@ fn install_display() -> Result<(), String> {
1347
  set_layer_color => display_vtable::set_layer_color,
1348
  set_active_layer => display_vtable::set_active_layer,
1349
  get_sprite_info => display_vtable::get_sprite_info,
 
 
 
 
1350
  draw_polyline => display_vtable::draw_polyline,
1351
  draw_line => display_vtable::draw_line,
1352
  draw_line_clipped => display_vtable::draw_line_clipped,
@@ -1363,9 +1620,10 @@ fn install_display() -> Result<(), String> {
1363
  draw_scaled_sprite => draw_scaled_sprite,
1364
  set_layer_visibility => display_vtable::set_layer_visibility,
1365
  update_palette => display_vtable::update_palette,
 
1366
  slot 19 => blit_sprite,
1367
  })?;
1368
- let _ = log_line("[Display] DisplayGfx: patched 21 methods -> Rust");
1369
  }
1370
 
1371
  Ok(())
 
1306
  }
1307
  }
1308
 
1309
+ // =========================================================================
1310
+ // Font vtable method bridges and implementations
1311
+ // =========================================================================
1312
+ //
1313
+ // Font methods are thin wrappers: validate font_id (1..31), check font slot
1314
+ // is populated, then delegate to an internal usercall function on the font
1315
+ // object. The font object pointer is stored at DisplayBase+0x309C[font_id].
1316
+
1317
+ /// Port of DisplayGfx::SetFontPalette (vtable slot 36, 0x523690).
1318
+ ///
1319
+ /// Loads font object from font_table[font_count], then calls
1320
+ /// Font__SetPalette (0x4f9f20): usercall(ESI=font_obj) + stack(palette_value), RET 0x4.
1321
+ ///
1322
+ /// The original has NO bounds check on font_count β€” it trusts the caller.
1323
+ unsafe extern "thiscall" fn set_font_palette(
1324
+ this: *mut DisplayGfx,
1325
+ font_count: u32,
1326
+ palette_value: u32,
1327
+ ) {
1328
+ let font_obj = (*this).base.font_table[font_count as usize];
1329
+ wa_font_set_palette(font_obj, palette_value, rb(va::FONT_OBJ_SET_PALETTE));
1330
+ }
1331
+
1332
+ /// Bridge to Font__SetPalette (0x4f9f20).
1333
+ /// Usercall: ESI=font_obj, stack(palette_value), RET 0x4.
1334
+ #[unsafe(naked)]
1335
+ unsafe extern "cdecl" fn wa_font_set_palette(_font_obj: u32, _palette_value: u32, _target: u32) {
1336
+ core::arch::naked_asm!(
1337
+ "push esi",
1338
+ "mov esi, [esp+8]", // font_obj
1339
+ "mov ecx, [esp+16]", // target
1340
+ "push dword ptr [esp+12]", // palette_value
1341
+ "call ecx", // RET 0x4 cleans 1 param
1342
+ "pop esi",
1343
+ "ret",
1344
+ );
1345
+ }
1346
+
1347
+ /// Port of DisplayGfx::SetFontParam (vtable slot 10, 0x523710).
1348
+ ///
1349
+ /// Validates font_id, then calls Font__SetParam (0x4fa720):
1350
+ /// usercall(ECX=p4, EDX=font_obj) + stack(p3, p5), RET 0x8.
1351
+ unsafe extern "thiscall" fn set_font_param(
1352
+ this: *mut DisplayGfx,
1353
+ font_id: i32,
1354
+ p3: u32,
1355
+ p4: u32,
1356
+ p5: u32,
1357
+ ) -> u32 {
1358
+ if !(1..=31).contains(&font_id) {
1359
+ return 0;
1360
+ }
1361
+ let font_obj = (*this).base.font_table[font_id as usize];
1362
+ if font_obj == 0 {
1363
+ return 0;
1364
+ }
1365
+ wa_font_set_param(font_obj, p3, p4, p5, rb(va::FONT_OBJ_SET_PARAM));
1366
+ 1
1367
+ }
1368
+
1369
+ /// Bridge to Font__SetParam (0x4fa720).
1370
+ /// Usercall: ECX=p4, EDX=font_obj, stack(p3, p5), RET 0x8.
1371
+ #[unsafe(naked)]
1372
+ unsafe extern "cdecl" fn wa_font_set_param(
1373
+ _font_obj: u32,
1374
+ _p3: u32,
1375
+ _p4: u32,
1376
+ _p5: u32,
1377
+ _target: u32,
1378
+ ) {
1379
+ core::arch::naked_asm!(
1380
+ "mov edx, [esp+4]", // font_obj β†’ EDX
1381
+ "mov ecx, [esp+12]", // p4 β†’ ECX
1382
+ "mov eax, [esp+20]", // target
1383
+ "push dword ptr [esp+16]", // p5
1384
+ "push dword ptr [esp+12]", // p3 (shifted +4 by push)
1385
+ "call eax", // RET 0x8 cleans 2 params
1386
+ "ret",
1387
+ );
1388
+ }
1389
+
1390
+ /// Port of DisplayGfx::GetFontInfo (vtable slot 8, 0x523790).
1391
+ ///
1392
+ /// Validates font_id, then calls Font__GetInfo (0x4fa7d0):
1393
+ /// usercall(EAX=font_obj, EDX=out_1, EDI=out_2), plain RET.
1394
+ unsafe extern "thiscall" fn get_font_info(
1395
+ this: *mut DisplayGfx,
1396
+ font_id: i32,
1397
+ out_1: *mut u32,
1398
+ out_2: *mut u32,
1399
+ ) -> u32 {
1400
+ if !(1..=31).contains(&font_id) {
1401
+ return 0;
1402
+ }
1403
+ let font_obj = (*this).base.font_table[font_id as usize];
1404
+ if font_obj == 0 {
1405
+ return 0;
1406
+ }
1407
+ wa_font_get_info(font_obj, out_1, out_2, rb(va::FONT_OBJ_GET_INFO))
1408
+ }
1409
+
1410
+ /// Bridge to Font__GetInfo (0x4fa7d0).
1411
+ /// Usercall: EAX=font_obj, EDX=out_2, EDI=out_1, no stack params, plain RET.
1412
+ ///
1413
+ /// Register mapping verified from caller at 0x523790:
1414
+ /// EDX ← [ESP+0xC] = out_2 (3rd vtable param)
1415
+ /// EDI ← [ESP+0x8] after PUSH EDI = out_1 (2nd vtable param)
1416
+ #[unsafe(naked)]
1417
+ unsafe extern "cdecl" fn wa_font_get_info(
1418
+ _font_obj: u32,
1419
+ _out_1: *mut u32,
1420
+ _out_2: *mut u32,
1421
+ _target: u32,
1422
+ ) -> u32 {
1423
+ core::arch::naked_asm!(
1424
+ "push edi",
1425
+ "mov eax, [esp+8]", // font_obj β†’ EAX
1426
+ "mov edx, [esp+16]", // out_2 β†’ EDX
1427
+ "mov edi, [esp+12]", // out_1 β†’ EDI
1428
+ "mov ecx, [esp+20]", // target
1429
+ "call ecx", // plain RET
1430
+ "pop edi",
1431
+ "ret",
1432
+ );
1433
+ }
1434
+
1435
+ /// Port of DisplayGfx::GetFontMetric (vtable slot 9, 0x523750).
1436
+ ///
1437
+ /// Validates font_id, then calls Font__GetMetric (0x4fa780):
1438
+ /// usercall(AL=char_code, EDX=out_1, EDI=out_2) + stack(font_obj), RET 0x4.
1439
+ unsafe extern "thiscall" fn get_font_metric(
1440
+ this: *mut DisplayGfx,
1441
+ font_id: i32,
1442
+ char_code: u32,
1443
+ out_1: *mut u32,
1444
+ out_2: *mut u32,
1445
+ ) -> u32 {
1446
+ if !(1..=31).contains(&font_id) {
1447
+ return 0;
1448
+ }
1449
+ let font_obj = (*this).base.font_table[font_id as usize];
1450
+ if font_obj == 0 {
1451
+ return 0;
1452
+ }
1453
+ wa_font_get_metric(
1454
+ font_obj,
1455
+ char_code,
1456
+ out_1,
1457
+ out_2,
1458
+ rb(va::FONT_OBJ_GET_METRIC),
1459
+ )
1460
+ }
1461
+
1462
+ /// Bridge to Font__GetMetric (0x4fa780).
1463
+ /// Usercall: AL=char_code, EDX=out_1, EDI=out_2, stack(font_obj), RET 0x4.
1464
+ #[unsafe(naked)]
1465
+ unsafe extern "cdecl" fn wa_font_get_metric(
1466
+ _font_obj: u32,
1467
+ _char_code: u32,
1468
+ _out_1: *mut u32,
1469
+ _out_2: *mut u32,
1470
+ _target: u32,
1471
+ ) -> u32 {
1472
+ core::arch::naked_asm!(
1473
+ "push edi",
1474
+ "mov al, [esp+12]", // char_code (byte) β†’ AL
1475
+ "mov edx, [esp+16]", // out_1 β†’ EDX
1476
+ "mov edi, [esp+20]", // out_2 β†’ EDI
1477
+ "mov ecx, [esp+24]", // target
1478
+ "push dword ptr [esp+8]", // font_obj β†’ stack (shifted +4 by push edi)
1479
+ "call ecx", // RET 0x4 cleans 1 param
1480
+ "pop edi",
1481
+ "ret",
1482
+ );
1483
+ }
1484
+
1485
+ /// Port of DisplayGfx::DrawTextOnBitmap (vtable slot 7, 0x5236B0).
1486
+ ///
1487
+ /// Extracts font_id from low 16 bits, extra flags from high 16 bits.
1488
+ /// Validates font_id, then calls Font__DrawText (0x4fa4e0):
1489
+ /// usercall(EAX=bitmap, EDX=a8, ESI=font_obj) + stack(h_align, v_align, msg, a7, high_bits), RET 0x14.
1490
+ ///
1491
+ /// Calling convention verified by tracing the wrapper at 0x5236B0:
1492
+ /// EAX receives bitmap (loaded last via [ESP+0x20] after 5 pushes)
1493
+ /// EDX receives a8 (loaded via [ESP+0x30] after 4 pushes)
1494
+ /// Stack params pushed: h_align, v_align, msg, a7, high_bits
1495
+ unsafe extern "thiscall" fn draw_text_on_bitmap(
1496
+ this: *mut DisplayGfx,
1497
+ font_id: i32,
1498
+ bitmap: i32,
1499
+ h_align: i32,
1500
+ v_align: i32,
1501
+ msg: *const core::ffi::c_char,
1502
+ a7: i32,
1503
+ a8: i32,
1504
+ ) -> i32 {
1505
+ let font_id_low = (font_id as u32) & 0xFFFF;
1506
+ if !(1..=31).contains(&font_id_low) {
1507
+ return 0;
1508
+ }
1509
+ let font_obj = (*this).base.font_table[font_id_low as usize];
1510
+ if font_obj == 0 {
1511
+ return 0;
1512
+ }
1513
+ let font_id_high = ((font_id as u32) >> 16) as i32;
1514
+ wa_font_draw_text(
1515
+ font_obj,
1516
+ bitmap,
1517
+ a8,
1518
+ h_align,
1519
+ v_align,
1520
+ msg,
1521
+ a7,
1522
+ font_id_high,
1523
+ rb(va::FONT_OBJ_DRAW_TEXT),
1524
+ )
1525
+ }
1526
+
1527
+ /// Bridge to Font__DrawText (0x4fa4e0).
1528
+ /// Usercall: EAX=bitmap, EDX=a8, ESI=font_obj, stack(h_align, v_align, msg, a7, high_bits), RET 0x14.
1529
+ #[unsafe(naked)]
1530
+ unsafe extern "cdecl" fn wa_font_draw_text(
1531
+ _font_obj: u32,
1532
+ _bitmap: i32,
1533
+ _a8: i32,
1534
+ _h_align: i32,
1535
+ _v_align: i32,
1536
+ _msg: *const core::ffi::c_char,
1537
+ _a7: i32,
1538
+ _high_bits: i32,
1539
+ _target: u32,
1540
+ ) -> i32 {
1541
+ core::arch::naked_asm!(
1542
+ "push esi",
1543
+ // After push esi: +8=font_obj, +12=bitmap, +16=a8, +20=h_align,
1544
+ // +24=v_align, +28=msg, +32=a7, +36=high_bits, +40=target
1545
+ "mov esi, [esp+8]", // font_obj β†’ ESI
1546
+ "mov eax, [esp+12]", // bitmap β†’ EAX
1547
+ "mov edx, [esp+16]", // a8 β†’ EDX
1548
+ "mov ecx, [esp+40]", // target
1549
+ // Push 5 stack params in reverse order. Each push shifts ESP by -4,
1550
+ // and the next param is 4 bytes lower, so the offset stays constant.
1551
+ "push dword ptr [esp+36]", // high_bits
1552
+ "push dword ptr [esp+36]", // a7
1553
+ "push dword ptr [esp+36]", // msg
1554
+ "push dword ptr [esp+36]", // v_align
1555
+ "push dword ptr [esp+36]", // h_align
1556
+ "call ecx", // RET 0x14 cleans 5 params
1557
+ "pop esi",
1558
+ "ret",
1559
+ );
1560
+ }
1561
+
1562
  // ---------------------------------------------------------------------------
1563
  // Display installation
1564
  // ---------------------------------------------------------------------------
 
1600
  set_layer_color => display_vtable::set_layer_color,
1601
  set_active_layer => display_vtable::set_active_layer,
1602
  get_sprite_info => display_vtable::get_sprite_info,
1603
+ draw_text_on_bitmap => draw_text_on_bitmap,
1604
+ get_font_info => get_font_info,
1605
+ get_font_metric => get_font_metric,
1606
+ set_font_param => set_font_param,
1607
  draw_polyline => display_vtable::draw_polyline,
1608
  draw_line => display_vtable::draw_line,
1609
  draw_line_clipped => display_vtable::draw_line_clipped,
 
1620
  draw_scaled_sprite => draw_scaled_sprite,
1621
  set_layer_visibility => display_vtable::set_layer_visibility,
1622
  update_palette => display_vtable::update_palette,
1623
+ set_font_palette => set_font_palette,
1624
  slot 19 => blit_sprite,
1625
  })?;
1626
+ let _ = log_line("[Display] DisplayGfx: patched 26 methods -> Rust");
1627
  }
1628
 
1629
  Ok(())
crates/openwa-dll/src/startup_checks.rs CHANGED
@@ -15,6 +15,7 @@ use openwa_core::registry::{self, AddrKind};
15
  const VALID_PROLOGUES: &[u8] = &[
16
  0x2B, // sub reg, r/m32 (e.g. SpriteBank__GetInfo starts with SUB EAX, [ECX+8])
17
  0x33, // xor reg, reg
 
18
  0x51, // push ecx
19
  0x52, // push edx
20
  0x53, // push ebx
 
15
  const VALID_PROLOGUES: &[u8] = &[
16
  0x2B, // sub reg, r/m32 (e.g. SpriteBank__GetInfo starts with SUB EAX, [ECX+8])
17
  0x33, // xor reg, reg
18
+ 0x3C, // cmp al, imm8 (usercall with AL input, e.g. Font__GetMetric)
19
  0x51, // push ecx
20
  0x52, // push edx
21
  0x53, // push ebx