From 486be65a38d4d38d8be1338076c0e35a6c7698fe Mon Sep 17 00:00:00 2001 From: Houmgaor Date: Fri, 20 Feb 2026 14:17:40 +0100 Subject: [PATCH] fix(protbot,channelserver): fix sign protocol and entrance parsing, guard nil save data The protbot sent "DSGN:\x00" as the sign request type, but the server strips the last 3 characters as a version suffix. Send "DSGN:041" (ZZ client mode 41) to match the real client format. The entrance channel entry parser read 14 bytes for remaining fields but the server writes 18 bytes (9 uint16, not 7), causing a panic when parsing the server list. The channel server panicked on disconnect when a session had no decompressed save data (e.g. protbot or early client disconnect). Guard Save() against nil decompSave. Also fix docker-compose volume mount for Postgres 18 which changed its data directory layout. --- cmd/protbot/protocol/entrance.go | 4 ++-- cmd/protbot/protocol/sign.go | 5 +++-- docker/docker-compose.yml | 2 +- server/channelserver/handlers_character.go | 7 +++++++ 4 files changed, 13 insertions(+), 5 deletions(-) diff --git a/cmd/protbot/protocol/entrance.go b/cmd/protbot/protocol/entrance.go index dcab094e1..d7c516a3f 100644 --- a/cmd/protbot/protocol/entrance.go +++ b/cmd/protbot/protocol/entrance.go @@ -115,13 +115,13 @@ func parseServerEntries(data []byte, entryCount uint16) ([]ServerEntry, error) { name += string(nameBytes[j]) } - // Read channel entries + // Read channel entries (14 x uint16 = 28 bytes each) for j := uint16(0); j < channelCount; j++ { port := bf.ReadUint16() _ = bf.ReadUint16() // channelIdx | 16 _ = bf.ReadUint16() // maxPlayers _ = bf.ReadUint16() // currentPlayers - _ = bf.ReadBytes(14) // remaining channel fields (7 x uint16) + _ = bf.ReadBytes(18) // remaining channel fields (9 x uint16: 6 zeros + unk319 + unk254 + unk255) _ = bf.ReadUint16() // 12345 serverIP := ip.String() diff --git a/cmd/protbot/protocol/sign.go b/cmd/protbot/protocol/sign.go index 490c49739..4f6670b6f 100644 --- a/cmd/protbot/protocol/sign.go +++ b/cmd/protbot/protocol/sign.go @@ -27,10 +27,11 @@ func DoSign(addr, username, password string) (*SignResult, error) { } defer c.Close() - // Build DSGN request: "DSGN:\x00" + SJIS(user) + "\x00" + SJIS(pass) + "\x00" + "\x00" + // Build DSGN request: "DSGN:041" + \x00 + SJIS(user) + \x00 + SJIS(pass) + \x00 + \x00 // The server reads: null-terminated request type, null-terminated user, null-terminated pass, null-terminated unk. + // The request type has a 3-char version suffix (e.g. "041" for ZZ client mode 41) that the server strips. bf := byteframe.NewByteFrame() - bf.WriteNullTerminatedBytes([]byte("DSGN:\x00")) // reqType (server strips last 3 chars to get "DSGN:") + bf.WriteNullTerminatedBytes([]byte("DSGN:041")) // reqType with version suffix (server strips last 3 chars to get "DSGN:") bf.WriteNullTerminatedBytes(stringsupport.UTF8ToSJIS(username)) bf.WriteNullTerminatedBytes(stringsupport.UTF8ToSJIS(password)) bf.WriteUint8(0) // Unk null-terminated empty string diff --git a/docker/docker-compose.yml b/docker/docker-compose.yml index 759b16b87..34535e2ef 100644 --- a/docker/docker-compose.yml +++ b/docker/docker-compose.yml @@ -13,7 +13,7 @@ services: ports: - "5432:5432" volumes: - - ./db-data/:/var/lib/postgresql/data/ + - ./db-data/:/var/lib/postgresql/ - ../schemas/:/schemas/ - ./init/setup.sh:/docker-entrypoint-initdb.d/setup.sh healthcheck: diff --git a/server/channelserver/handlers_character.go b/server/channelserver/handlers_character.go index c5558e839..14cd59084 100644 --- a/server/channelserver/handlers_character.go +++ b/server/channelserver/handlers_character.go @@ -48,6 +48,13 @@ func GetCharacterSaveData(s *Session, charID uint32) (*CharacterSaveData, error) } func (save *CharacterSaveData) Save(s *Session) { + if save.decompSave == nil { + s.logger.Warn("No decompressed save data, skipping save", + zap.Uint32("charID", save.CharID), + ) + return + } + if !s.kqfOverride { s.kqf = save.KQF } else {