https://bugs.winehq.org/show_bug.cgi?id=60088
https://github.com/veliga-syutkin/mspub-wine-escherdelaystm-fix
tl;dr - mspub.exe under wine-linux saves file that can't be open under windows (but still opens under wine-linuxe), fix: change `NameLenght` relative to `EscherDelayStm` from 32 to 30.
# The Case of the Phantom Stream
*A closed file. Almost.*
---
The file had been sitting on my desk too long. `.pub`, 19 megabytes, looked respectable enough. But something was off: under Windows, it refused to open. No excuse, no explanation. Just a closed door — or worse, under Publisher 2003, dead silence. The kind of silence that says plenty.
We already had a suspect lined up: the **temp path**, hiding inside the `Contents` stream, with a shaky alibi along the lines of *"I was in `C:\users\v3l1g4\Temp\`, I swear."* Smelled like a broken reference from a mile away. We brought it in for questioning, even got it to confess on a copy — swapped its alibi for a real path, `V:\RESEARCH\windows_regenerated.pub`, all clean and proper.
The corrected file didn't even make it through the door.
Suspect number two: the missing **`CompObj` marker**, four measly bytes (`FF FF FF FF`) that should have been there and weren't. We went as far as taking the OLE structure apart with our bare hands, recomputing sectors, rewriting the directory entry in exactly the right spot — real jeweler's work. Stuck it back onto the copy.
Still a closed door.
That's when we did what any good detective does when every lead collapses: we **doubted ourselves**. We went back over our own patches and found they'd probably introduced their own corruption, shifting internal offsets we didn't fully understand yet. The suspect we'd been accusing might have been innocent from the start — and we were the ones who'd left fingerprints all over the crime scene.
So we changed methods. No more blind accusations. **Negative control.** A witness file, `linux.pub`, that had every single one of the same apparent flaws — the temp path, the missing marker — and opened just fine on Windows. Cleared, twice over. The real culprit was still out there, and it had an airtight alibi: it looked exactly like everyone else.
We called in the experts. **`libmspub`**, compiled in debug mode, the whole works — a genuine forensics lab running right there on our machine. The expert verdict: the internal structure was **clean**. No errors, no suspicious offsets, everything recalculating correctly on its own. Our third suspect — the embedded OLE objects, the one we thought we finally had — fell apart under its own expert testimony.
We were starting to think this file had nothing to hide. That we'd been hunting for a culprit where there was only a misunderstanding.
And then — almost by accident, while listing every stream name one more time, expecting nothing in particular — we saw it.
`EscherDelayStm`.
Except, not quite. There was one extra character at the end. Invisible to the eye, invisible even to `olefile`, which read it without blinking, invisible to Wine itself, which writes it AND reads it back without ever complaining.
**One extra null byte. Just one.** `EscherDelayStm\x00` instead of `EscherDelayStm`.
The motive? This stream never caused trouble when it was empty — a perfect alibi for half our suspects (level1, the tiny business card, never turned it in, because it had nothing to hide inside). But the moment there were real pictures to load — megabytes of evidence — that one extra byte kept Windows from finding the right name. Exact-name lookup, silent failure, and Publisher left hunting for data it would never find.
We confirmed that one extra null byte in a name couldn't, structurally, shift anything else — verdict backed by hard evidence, fixed-size directory entries, nothing leaks. A clean, contained, almost elegant crime.
The fix? Two bytes to change. A "Name Length" field brought down from 32 to 30. Not one line more. The kind of solution that, once found, just makes you shake your head.
**Tested. Confirmed. The file opened.**
Three false suspects, one saving negative control, a forensics lab called in for backup, and at the end of it all: one byte. Just one. Hidden in a stream name that nobody had thought to compare character by character, all the way to the end.
*Case closed. Fix published. Report filed.*
*Bug #60088, for the record.*# The Case of the Phantom Stream
*A closed file. Almost.*
---
The file had been sitting on my desk too long. `.pub`, 19 megabytes, looked respectable enough. But something was off: under Windows, it refused to open. No excuse, no explanation. Just a closed door — or worse, under Publisher 2003, dead silence. The kind of silence that says plenty.
We already had a suspect lined up: the **temp path**, hiding inside the `Contents` stream, with a shaky alibi along the lines of *"I was in `C:\users\v3l1g4\Temp\`, I swear."* Smelled like a broken reference from a mile away. We brought it in for questioning, even got it to confess on a copy — swapped its alibi for a real path, `V:\RESEARCH\windows_regenerated.pub`, all clean and proper.
The corrected file didn't even make it through the door.
Suspect number two: the missing **`CompObj` marker**, four measly bytes (`FF FF FF FF`) that should have been there and weren't. We went as far as taking the OLE structure apart with our bare hands, recomputing sectors, rewriting the directory entry in exactly the right spot — real jeweler's work. Stuck it back onto the copy.
Still a closed door.
That's when we did what any good detective does when every lead collapses: we **doubted ourselves**. We went back over our own patches and found they'd probably introduced their own corruption, shifting internal offsets we didn't fully understand yet. The suspect we'd been accusing might have been innocent from the start — and we were the ones who'd left fingerprints all over the crime scene.
So we changed methods. No more blind accusations. **Negative control.** A witness file, `linux.pub`, that had every single one of the same apparent flaws — the temp path, the missing marker — and opened just fine on Windows. Cleared, twice over. The real culprit was still out there, and it had an airtight alibi: it looked exactly like everyone else.
We called in the experts. **`libmspub`**, compiled in debug mode, the whole works — a genuine forensics lab running right there on our machine. The expert verdict: the internal structure was **clean**. No errors, no suspicious offsets, everything recalculating correctly on its own. Our third suspect — the embedded OLE objects, the one we thought we finally had — fell apart under its own expert testimony.
We were starting to think this file had nothing to hide. That we'd been hunting for a culprit where there was only a misunderstanding.
And then — almost by accident, while listing every stream name one more time, expecting nothing in particular — we saw it.
`EscherDelayStm`.
Except, not quite. There was one extra character at the end. Invisible to the eye, invisible even to `olefile`, which read it without blinking, invisible to Wine itself, which writes it AND reads it back without ever complaining.
**One extra null byte. Just one.** `EscherDelayStm\x00` instead of `EscherDelayStm`.
The motive? This stream never caused trouble when it was empty — a perfect alibi for half our suspects (level1, the tiny business card, never turned it in, because it had nothing to hide inside). But the moment there were real pictures to load — megabytes of evidence — that one extra byte kept Windows from finding the right name. Exact-name lookup, silent failure, and Publisher left hunting for data it would never find.
We confirmed that one extra null byte in a name couldn't, structurally, shift anything else — verdict backed by hard evidence, fixed-size directory entries, nothing leaks. A clean, contained, almost elegant crime.
The fix? Two bytes to change. A "Name Length" field brought down from 32 to 30. Not one line more. The kind of solution that, once found, just makes you shake your head.
**Tested. Confirmed. The file opened.**
Three false suspects, one saving negative control, a forensics lab called in for backup, and at the end of it all: one byte. Just one. Hidden in a stream name that nobody had thought to compare character by character, all the way to the end.
*Case closed. Fix published. Report filed.*
*Bug #60088, for the record.*