Skip to content

Add mod-fix-patrol-letter-crash - #14

Open
stevenbg wants to merge 2 commits into
P3Modding:masterfrom
stevenbg:fix-patrol-letter-crash
Open

stevenbg wants to merge 2 commits into
P3Modding:masterfrom
stevenbg:fix-patrol-letter-crash

Conversation

@stevenbg

@stevenbg stevenbg commented Aug 19, 2026 •

Copy link
Copy Markdown

There's an old bug where the game generates a message with invalid pointer for the town name. When it doesn't lead to crash, that manifests as an empty town name:
image

https://patrician3.fandom.com/wiki/Forum:Recieving_letter_glitch
https://steamcommunity.com/app/33570/discussions/0/1474222595298102350/

Got a reproducible save and nailed it. It is fixed the same way in the 1.1b exe, but we are based on 1.1.


The patrol letter crash

Every letter in the game carries a town byte, which the letters list uses to look up a town name and print it in the town column. The list trusts that byte: it uses it as an index into a 40-entry table of town names with no range check.

Patrol missions send a "Patrol destination" letter whose town byte is garbage. The mission is a little script, and the script tells the letter to take its town from variable 131 — but the script only has 25 variables. The game reads variable 131 anyway, which lands about 400 bytes past the end of the variable array, so the town byte ends up being whatever unrelated data happens to sit there.

That is why the bug looks random. If the stray value is under 40 the row prints a wrong town name; if it is larger, the lookup reads past the name table and hands the text renderer something that isn't a string, and the game crashes the moment the list is drawn. Which of the two happens depends on what is in memory at the time, so it varies by save, resolution and session.

Only the list is affected — the letter's own text is built through a bounded lookup, so opening the letter is always safe.

The fix bounds the lookup: town bytes under 40 behave exactly as before, anything else draws an empty town column — the same blank cell the unpatched game shows whenever the stray read happens not to crash.

Also included is missions_addon/patrouille.p2m, the mission script with that one byte corrected so the letter reads its town from variable 0 — the town it is actually about. Copy it next to Patrician3.exe and the game prefers it over the archived copy. It only helps letters created from then on: a town byte is written when the letter is created and saved with the game, so letters already in a mailbox keep their garbage byte, which is what the DLL is still for.

stevenbg and others added 2 commits August 19, 2026 22:04
Fixes the vanilla crash-to-desktop when the personal letters list shows a
scripted letter whose town byte is not a town index, most prominently the
escort/patrol mission's "Patrol destination" letters. The letter-creation
script command (0x4ed4a0) stores the low byte of a script variable as the
message's town unvalidated, and the list's town column looks it up in the
40-slot town-name bank without a bounds check (0x47d928), passing the
result to ddraw_Dll's unguarded text draw. The fix detours the lookup:
out-of-range town bytes draw an empty string, matching what the unpatched
game shows whenever the wild read happens to survive.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The crash is one wrong byte in one file. The missions are script blobs in
p2arch0_eng.cpr rather than code, and command 37 of missions_addon/patrouille.p2m
- the "Patrol destination" letter - names variable 131 as the letter's town in a
script that declares 25 variables. The variable array is malloc(count * 4), so
the create-letter command reads 424 bytes past a 100-byte block and stores
whatever heap data follows it as the town byte the letters list then indexes the
name bank with.

Ship the corrected script alongside the DLL: variable 0 is the town the letter is
about, which the route command writes, the letter body prints twice, the deadline
and the arrival check both use, and every other letter in the script already
passes. The game prefers a loose file over the archive, and no command changes
length, so savegames keep working.

The DLL is still what makes an existing save openable - a letter's town byte is
written at creation and saved with the game, so letters that already exist keep
their garbage byte.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant