Skip to content

Worktree realm extensibility - #47

Merged
jaenster merged 3 commits into
mainfrom
worktree-realm-extensibility
Aug 23, 2026
Merged

Worktree realm extensibility#47
jaenster merged 3 commits into
mainfrom
worktree-realm-extensibility

Conversation

@jaenster

Copy link
Copy Markdown
Owner

What this changes

How it was tested

Where the numbers came from

A realm that hosts several engines has to say which one a character belongs to. CHARLOGON already
refuses a cross-era one — the save format and the wire framing both moved between eras — and a
player deserves to see that before they pick, not after a popup.

The guild tag is the one field of the statstring the game hands straight back to the player, and
it is empty on a realm with no guilds. So the engine goes there and EVERY client shows it, a stock
1.14d as well as our own launcher: it renders as " {14}" after the name. An op of our own would
only ever have reached a client written for it.

TWO characters and never three. szGuildTag is char[3] filled by a strncpy of three, so a
three-character tag arrives with no terminator and the client's " {%s}" runs on into the
difficulty byte at 0x32D. Two leaves the caller's own terminator to land in the third. Two is also
exactly where the eras differ, so nothing is lost.
A character's engine was taken from the connection that created it, and that is the wrong place to
take it from: a launcher that lets the player pick one picks it on the CREATION screen, long after
the logon that would have decided it.

MCP_CHARCREATE's status word has a free high byte — the game only ever uses the low one, for
hardcore, died, expansion and ladder — so the era rides there as its own two digits. A stock
client sends zero, which still means "the engine of the client that made it", so nothing about a
single-version realm changes.

It resolves against the tags the realm is CONFIGURED with rather than a table of our own, so a
realm can only ever stamp a character with an engine it actually serves. Order is request, then
extension, then client: the request wins because it is the only one that knows what was picked.
There was no way to put an existing character on a realm. chars/copy clones one that is already
there, which is no help when the character you want is a .d2s on somebody's disk — and a realm with
no real characters on it cannot show whether it renders their gear.

The name is written into the save and the checksum repaired on the way in, the same rewrite
chars/copy does and for the same reason: a .d2s carries its own name, and a client refuses one
whose name is not the character it asked for. So a save exported under one name lands correctly
under another.

It will not overwrite. Of everything this API does, a silent replace is the one with no way back.

Base64 because this is a JSON API and a save is binary.
@jaenster
jaenster merged commit 75352c8 into main Aug 23, 2026
5 checks passed
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