Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 17 additions & 11 deletions editor_patch/lightmap.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -589,26 +589,32 @@ void ApplyLightmapPatches()
write_mem<u8>(0x004ae73a, 0xB7); // FUN_004ae360: shadow calculation
write_mem<u8>(0x004ae81f, 0xB7);
write_mem<u8>(0x004aed4e, 0xB7);
write_mem<u8>(0x004a45b1, 0xB7); // solid_write: level file export

// MOVSX -> MOVZX for face+0x36 reads from register (5 sites)
// These follow 16-bit MOV AX,[face+0x36] and sign-extend AX to 32-bit before use as index
write_mem<u8>(0x004a45b1, 0xB7); // solid_write: MOVSX→MOVZX (sentinel 0xFFFF→65535 in file, truncated back to 0xFFFF by reader's 16-bit store)
write_mem<u8>(0x004a4655, 0x74); // solid_write: JLE->JE for lightmap UV write decision
write_mem<u8>(0x004a39ce, 0x74); // solid_read: JLE->JE for lightmap UV read decision
write_mem<u8>(0x0048778b, 0xB7); // FUN_004872e0: lightmap surface container lookup
write_mem<u8>(0x00487862, 0xB7);
write_mem<u8>(0x0049b955, 0xB7); // FUN_0049b550: geo-cache build
write_mem<u8>(0x0049bea0, 0xB7);
write_mem<u8>(0x0049bed3, 0xB7);

// Fix sentinel checks: JLE -> JE (0x7E -> 0x74)
// Original: CMP AX,0xFFFF; JLE skip — skips all negative values (wrong for groups > 32767)
// Fixed: CMP AX,0xFFFF; JE skip — skips only the 0xFFFF "no group" sentinel
write_mem<u8>(0x00487781, 0x74); // FUN_004872e0
write_mem<u8>(0x00487858, 0x74);
write_mem<u8>(0x0049b94b, 0x74); // FUN_0049b550

// Note: CMP AX,BX; JLE at 0x0049be96 and 0x0049bec9 left as signed — BX can be
// 0xFFFF ("no batch" sentinel), and unsigned JBE would cause all faces to be skipped.
// Signed comparison is correct for group IDs 0-32767 and safe for the sentinel case.
// Fix signed "< 0" checks that destroy valid surface indices > 32767 during geometry build.
// FUN_004a8660 and FUN_004a9270 reset faces with negative (signed) surface_index to 0xFFFF.
// This destroys valid high indices when loading an RFL with >32767 surface groups.
write_mem<u8>(0x004a8fa9, 0xFF); // FUN_004a8660: CMP word, 0 → CMP word, -1
write_mem<u8>(0x004a8faa, 0x75); // JGE → JNE (only sentinel triggers reset)
write_mem<u8>(0x004a9294, 0xFF); // FUN_004a9270: CMP word, 0 → CMP word, -1
write_mem<u8>(0x004a9295, 0x75); // JGE → JNE

// Note: The editor's D3D8 viewport rendering has additional signed surface_index checks
// in the geo-cache build (FUN_0049b550) and rendering functions (FUN_005040b0,
// FUN_00493570, FUN_00494d70, FUN_00496a30, etc.) that cause fullbright on faces with
// surface_index > 32767. The D3D8 rendering path also has architectural batch count limitations
// for rooms with many unique surface groups. The D3D11 game renderer handles all of this
// correctly. Editor viewport fullbright on high-surface-count levels is a known limitation.

// -smoothlights: experimental lightmap seam fix at portal boundaries
// Note: GetCommandLineA() is used instead of argv/argc because this runs during DLL
Expand Down
2 changes: 1 addition & 1 deletion game_patch/graphics/d3d11/gr_d3d11_solid.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -341,7 +341,7 @@ namespace df::gr::d3d11
FaceRenderType render_type = determine_face_render_type(face);
int face_tex = face->attributes.bitmap_id;
int lightmap_tex = -1;
if (!is_sky_ && render_type != FaceRenderType::liquid && face->attributes.surface_index >= 0) {
if (!is_sky_ && render_type != FaceRenderType::liquid && face->attributes.surface_index != 0xFFFF) {
if (face->attributes.surface_index >= solid->surfaces.size()) {
xlog::warn("add_face: surface_index {} out of bounds (size {}), skipping face",
face->attributes.surface_index, solid->surfaces.size());
Expand Down
2 changes: 1 addition & 1 deletion game_patch/misc/destruction.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1412,7 +1412,7 @@ static void add_cap_faces_from_loop(

rf::GFaceAttributes attrs{};
attrs.bitmap_id = bitmap_id;
attrs.surface_index = -1;
attrs.surface_index = 0xFFFF; // no surface/lightmap sentinel

// UV mapping: project relative to loop centroid
auto compute_uv = [&](rf::GVertex* v) -> std::pair<float, float> {
Expand Down
37 changes: 37 additions & 0 deletions game_patch/misc/g_solid.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -763,6 +763,43 @@ void g_solid_do_patch()
// Set PPM for geo crater texture based on its resolution instead of static value of 32.0
levelmod_do_blast_autotexture_ppm_patch.install();

// Fix lightmap surface group ID overflow: face+0x36 (surface_index) is a signed short.
// When >32767 lightmap surface groups exist, the signed short wraps negative.
// Fix raises the limit from 32767 to 65535 groups.

// Note: FUN_004d4c60 (0x004d4cf6) and FUN_00561650 (0x005619dd) are additional D3D8 rendering
// functions with 32-bit sentinel guards (TEST EAX,EAX and CMP EAX,-1) that fail when
// MOVZX produces 65535 instead of -1. Left unpatched, D3D11 renderer doesn't use them,
// and D3D8 can't render such levels anyway.
write_mem<u8>(0x004e4d50, 0xB7); // FUN_004e4a60: lightmap surface grouping
write_mem<u8>(0x004e4d79, 0xB7);
write_mem<u8>(0x004e4d96, 0xB7);
write_mem<u8>(0x004e4ded, 0xB7);
write_mem<u8>(0x004e4f56, 0xB7);
write_mem<u8>(0x004e51a9, 0xB7); // FUN_004e5040: batch lightmap calculator
write_mem<u8>(0x004e51da, 0xB7);
write_mem<u8>(0x004eeb41, 0xB7); // FUN_004ee550: lightmap surface setup
write_mem<u8>(0x004f3471, 0xB7); // FUN_004f3390: shadow geometry
write_mem<u8>(0x004f34aa, 0xB7);
write_mem<u8>(0x004f449c, 0xB7); // FUN_004f4280: shadow calculation
write_mem<u8>(0x004f496a, 0xB7); // FUN_004f4590: shadow calculation
write_mem<u8>(0x004f4a4f, 0xB7);
write_mem<u8>(0x004f4f7e, 0xB7);

// MOVSX -> MOVZX for face+0x36 reads from register (3 sites)
write_mem<u8>(0x0047f0f2, 0xB7); // surface container lookup
write_mem<u8>(0x0047f277, 0xB7);
write_mem<u8>(0x004ed772, 0xB7); // FUN_004ed520: solid_read

// Fix sentinel checks: JLE -> JE (0x7E -> 0x74)
// Original: CMP AX,0xFFFF; JLE skip — skips all negative values (wrong for groups > 32767)
// Fixed: CMP AX,0xFFFF; JE skip — skips only the 0xFFFF "no group" sentinel
write_mem<u8>(0x004d8b71, 0x74); // FUN_004d86d0: surface container lookup
write_mem<u8>(0x004d8c48, 0x74);
write_mem<u8>(0x004edf5e, 0x74); // FUN_004ed520: solid_read
write_mem<u8>(0x004eebe5, 0x74); // FUN_004ee550: lightmap surface setup
write_mem<u8>(0x0047f274, 0x74); // surface container lookup

// Commands
max_decals_cmd.register_cmd();
dbg_room_clip_wnd_cmd.register_cmd();
Expand Down
2 changes: 1 addition & 1 deletion game_patch/rf/geometry.h
Original file line number Diff line number Diff line change
Expand Up @@ -282,7 +282,7 @@ namespace rf
};
int bitmap_id;
short portal_id; // portal index + 2 or 0
short surface_index;
unsigned short surface_index; // 0xFFFF = no surface/lightmap (sentinel)
int face_id;
int smoothing_groups; // bitfield of smoothing groups

Expand Down
Loading