Commit Graph
13 Commits
Author SHA1 Message Date
ThomasandClaude Sonnet 5 2b209cf17f Fix skill damage never actually scaling with level as its own comment claims
CCharacter::UseAtkSkill's hitmod formula was missing the "+ Stats->Level"
term that CCharacter::NormalAttack's equivalent hitmod already has --
despite the comment directly above it ("skill attacks now based on a
percentage of base attack power so that they scale with level")
promising exactly that. Net effect: a skill with atkpower=100 computed
the same base Attack as a plain auto-attack, but without the level
bonus or crit chance auto-attacks get, so skills ended up hitting no
harder (often weaker) than basic attacks.

This only touches genuine damage skills (skilltype 3/6 dispatched via
SKILL_ATTACK in worldpackets.cpp:pakStartSkill); buffs/heals/status
effects/resurrect (skilltype 9/11/13/20, SKILL_BUFF and friends) run
through entirely separate code and are untouched.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-09 08:09:20 +02:00
ThomasandClaude Sonnet 5 f053a06920 Fix core RNG collapsing to ~0-1 on Linux due to integer overflow
CWorldServer::RandNumber (and three inlined copies of the same pattern
in worldpackets.cpp's item-refine code) computed "range*rand()" in
integer arithmetic before dividing by (RAND_MAX + 1.0). On Windows
RAND_MAX is 32767 (15-bit), so this never overflowed. On Linux/glibc
RAND_MAX is 2147483647 (2^31-1), so "range*rand()" overflows a 32-bit
UINT for the vast majority of rand() outputs, wrapping to a small
value -- the division by ~2^31 then collapses the result to ~0 or 1
almost always, regardless of the requested range. This made every
percentage-based roll in the game (item drop selection, refine
durability/stats, and anything else using RandNumber) behave as if
called with range 1-2 instead of the real range, which is why item
drops effectively never triggered post-port. Cast to double before
the multiply so it can't overflow.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-09 07:24:14 +02:00
ThomasandClaude Sonnet 5 560f6a968f Rebrand welcome messages and re-enable drop-roll debug logging
Welcome message default/example changed from "Welcome to ROSE" to
"Welcome to ROSS", and the hardcoded post-login system message
replaced with a ROSS-specific credit line.

Also re-enable the codedebug SendPM lines in GetNewDrop that report
the raw randv/money/zulyrate/item/itemrate values behind each
zuly-vs-item roll (previously commented out) -- needed to diagnose
in-game why item drops are still rare/absent after the drop-rate and
item-index fixes, without guessing at STB drop-table values blind.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-09 07:16:01 +02:00
ThomasandClaude Sonnet 5 46e028c3ec Fix item-drop selection off-by-one and double/slow-drop config defaults
GetNewDrop's weighted item-index selection used a do-while that
increments before its first RunningTotal check, so index 0 (the first
item slot in every drop table) could never be selected -- for the 51
drop tables that only populate item slot 1 (all others zero), this
silently discarded every roll ("item at itemindex does not exist"),
so those mobs/maps never dropped an item at all. Switch to a pre-check
while loop so index 0 is reachable.

Also: battle.cpp's kill-drop loop runs GetDrop()/AddDrop() exactly
Stats->itemdroprate + Config.DROP_RATE times, not as a probability --
with the shipped drop_rate=2 every kill produced two independently
rolled drops (hence always-doubled Zuly boxes for characters with no
item-drop-rate gear). Default drop_rate to 1 so a kill yields one
roll by default; gear/GM commands that raise itemdroprate still grant
extra rolls as designed.

Also default dropdelay to 0: the killed-monster's loot became visible
to clients only after Config.DropDelay (1s) *plus* the next
VisualDelay tick (200ms), giving a ~1.2s lag before drops appeared.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-09 07:06:25 +02:00
ThomasandClaude Sonnet 5 42cf2944ba Trim trailing whitespace in charpackets.cpp
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-09 06:35:35 +02:00
ThomasandClaude Sonnet 5 582e29106b Fix new-account creation failing under MariaDB strict mode
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>
2026-07-09 06:35:31 +02:00
ThomasandClaude Sonnet 5 5f600d0e61 Fix memory leaks and add bounds checks in packet/drop handling
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>
2026-07-09 06:35:18 +02:00
ThomasandClaude Sonnet 5 f59158a95c Fix quest array off-by-ones and add QuestID bounds checks
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>
2026-07-09 06:35:11 +02:00
ThomasandClaude Sonnet 5 725620e23c Fix respawn/spawn timers racing ahead under multi-threading
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>
2026-07-09 06:35:05 +02:00
92thmsandClaude Sonnet 5 1f626c6b93 Fix DB warning and a client-connection-blocking accept() busy-loop
- 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>
2026-07-08 07:12:52 +00:00
92thmsandClaude Sonnet 5 764fcb3b3f Port server to Linux and add Docker Compose deployment
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>
2026-07-08 06:32:39 +00:00
purpleyouko a320bbe470 Modified packet logging to include server name and save both in and out to the same file 2025-12-26 14:28:59 -06:00
purpleyouko cfdc19df45 added source folders 2025-08-11 11:09:09 -05:00