Client/, 3Ddata*, KTRose_pre-evo.zip are large local-only assets used
for end-to-end login/game testing, not part of the server source tree.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Adds an nginx:alpine service serving webtools/ (item/map lookup
frontend) on port 80 alongside the existing login/char/world stack.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
accounts.lastlogin/logtime had no defaults and were NOT NULL, but the
auto-create-account INSERT never set them, so new accounts failed
under MariaDB strict mode (silently accepted under the old MySQL
5.0 target). Insert explicit empty/zero values and give lastlogin a
default; also widen it to varchar(32) to match the runtime.sql fix
already applied to the starttime column.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
GetNewDrop() allocated newdrop up front but returned NULL without
freeing it on every rejection path (failed drop-rate check, missing
drop table, unknown item type, etc.), leaking on every non-drop roll.
Free it on all early-return paths, and only assign a clientid once a
drop is actually going to be returned.
CPacket::AddByte/Word/DWord/QWord/Float now bounds-check against the
buffer before writing, preventing an overflow if a packet is built
past its allocated size.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Several quest-loading loops used <=N over arrays sized N (Variables,
Switches, Items, EpisodeVar, JobVar, PlanetVar, UnionVar), writing one
element past the end on every character load. Also guard STB_QUEST
table lookups with a rowcount bounds check before indexing by
QuestID, which could otherwise read out of bounds for stale/invalid
quest ids.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
POSIX clock() sums CPU time across every thread in the process, unlike
Windows' clock() which tracks wall-clock milliseconds. With several
worker threads running, respawn/spawn/AI timers based on clock() raced
far ahead of real elapsed time. Switch spawn-timer bookkeeping to
time(NULL)/time_t (seconds) throughout the world server, and add a
portable clock() shim in sockets.h (backed by CLOCK_MONOTONIC,
millisecond resolution) for remaining call sites so CLOCKS_PER_SEC==1000
still holds like it did on Windows.
Also: fix MapDelay/VisualDelay/WorldDelay being passed to usleep() (us)
without converting from their configured milliseconds; fix the shutdown
timer likewise; stop leaking the temporary actor used to run an NPC's
AIP script; fix CleanDrops() skipping the element that shifts into
position after an erase.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
- runtime.sql: widen starttime to varchar(32). ctime() produces 25 chars;
MariaDB's default strict mode rejects the 20-char column instead of
silently truncating like the original MySQL 5.0 target did.
- SocketServer.cpp/SocketBase.cpp: the accept-loop's select() used `maxfd`
set only from `sock`, never updated for the ISC sockets (sckISC/sckISCII).
Any fd beyond nfds is left however FD_SET last set it, since select()
never examines it -- so those bits stayed permanently "set", and the
server treated them as always-ready. For the login server specifically
this was fatal: its sckISC is created but never listen()'d (the login
server has no inbound ISC use), so accept() on it returned EINVAL on
every single loop iteration, spinning the login server's CPU and
spamming the log without ever getting to the real client-facing accept()
in a timely way. Root-caused with a real client connecting for the first
time (this path was never exercised end-to-end before). Fixed by
including the ISC sockets in maxfd and skipping the ISC accept branch
entirely for the login server. Also initialize sckISC/sckISCII to
INVALID_SOCKET in the constructor -- they were previously left
uninitialized for server types that never touch them.
- tools/rose_login_test.cpp: protocol-level login smoke test. Links
directly against Src/Common/rosecrypt.cpp (the server's own encryption
code) rather than reimplementing the crypto, so there's no risk of a
second, subtly-wrong implementation. Documents the wire handshake
(0x703 encryption request -> 0x7FF -> 0x708 login, password[32]+username[16]
payload) for future reference.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Rebuilds (not Wine) the login/char/world servers natively for Linux via
CMake, and ships a single Docker image plus docker-compose stack (MariaDB +
3 servers) with all settings configurable via .env.
Linux portability:
- Src/Common/compat.h shims MSVC-only CRT calls (fopen_s, _strdup,
strncpy_s, sprintf_s, SYSTEMTIME/GetLocalTime, WSAGetLastError, __cdecl)
and adds a case-insensitive/backslash-tolerant path resolver for the
Windows-authored STB/AIP data file paths.
- Fixed case-sensitive header includes and filenames (charserver.h vs
CharServer.h, worldserver.h vs WorldServer.h, ../common vs ../Common).
- rose_inet_addr() resolves hostnames (not just literal IPs) for the
server-to-server links, so docker-compose service names work.
Fixed three real bugs that only crashed on 64-bit Linux (silently tolerated
on 32-bit Windows):
- CIntStb::LoadSTB had an inverted fopen success/failure check, so STB game
data (zones, items, etc.) was never actually loaded.
- GenerateCryptTables allocated its pointer table with sizeof(unsigned)
instead of sizeof(unsigned*) -- a 1x vs 2x-size heap overflow on 64-bit.
- Crypto bit-twiddling relies throughout on signed-overflow-as-wraparound,
which is UB in C++; GCC's -O3 exploited it and miscompiled
GenerateCryptTables (Release-only crash). Fixed via -fwrapv.
Plus smaller fixes: off-by-one array bounds (inventory/storage/sell data),
SO_REUSEADDR on server sockets (containers restarting in the same netns hit
TIME_WAIT otherwise), and a va_list reused across multiple vprintf/vfprintf
calls in Log() that garbled every formatted log line on the Linux ABI.
Docker:
- CMakeLists.txt builds all three servers + shared Common code.
- Dockerfile: multi-stage build, one runtime image for all three servers,
server role selected via `command: [login|char|world]`.
- docker-compose.yml: MariaDB (auto-imports database sql files/ on first
run) + login/char/world (single channel), config rendered from
docker/conf/*.conf.tmpl via envsubst at container start.
- .env.example documents every setting (ports, server names, rates,
inter-server passwords, PUBLIC_HOST for internet-facing deployments).
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>