diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..8c89eb1 --- /dev/null +++ b/.dockerignore @@ -0,0 +1,9 @@ +.git +build/ +Servers/.vs/ +Binary/3DData/ +Binary/*.exe +Binary/*.dll +tools/ +database sql files/ +*.md diff --git a/.env.example b/.env.example new file mode 100644 index 0000000..e588088 --- /dev/null +++ b/.env.example @@ -0,0 +1,36 @@ +# Copy to .env and adjust. All server settings are controlled from here - +# docker compose renders them into the .conf files at container start. + +# --- Database ----------------------------------------------------------- +DB_NAME=ktrose +DB_USER=rose +DB_PASSWORD=change_me + +# --- Public address ------------------------------------------------------- +# The address game clients connect to. For a LAN-only server this can stay +# 127.0.0.1 / the LAN IP; for an internet-exposed server this MUST be the +# public IP or DNS name of this host, otherwise clients won't be able to +# reach the char/world server after the login server hands them off. +PUBLIC_HOST=127.0.0.1 + +# --- Ports (published to the host) ---------------------------------------- +LOGIN_PORT=29000 +CHAR_PORT=29100 +WORLD_PORT=29200 + +# --- Server / channel naming ----------------------------------------------- +LOGIN_SERVER_NAME=ROSE Login +CHAR_SERVER_NAME=Server 1 +WORLD_CHANNEL_NAME=Channel 1 +WORLD_WELCOME_MSG=Welcome to ROSE + +# --- Gameplay knobs --------------------------------------------------------- +WORLD_MAX_CONNECTIONS=500 +WORLD_SAVE_TIME=3600 +CHAR_DELETE_TIME=3600 +LOGIN_CREATE_ACCOUNT=1 + +# --- Inter-server auth (must just be consistent across the 3 servers) ------- +SHARED_LOGIN_PASS=789456 +SHARED_CHAR_PASS=456123 +SHARED_WORLD_PASS=321654 diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..231cecc --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +build/ +build-*/ +.env diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..0959fb0 --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,150 @@ +cmake_minimum_required(VERSION 3.16) +project(ROSEServers CXX) + +set(CMAKE_CXX_STANDARD 14) +set(CMAKE_CXX_STANDARD_REQUIRED ON) +set(CMAKE_CXX_EXTENSIONS ON) + +if(NOT CMAKE_BUILD_TYPE) + set(CMAKE_BUILD_TYPE Release) +endif() + +find_package(Threads REQUIRED) + +# MariaDB/MySQL client library and headers (provided by the distro, e.g. +# libmariadb-dev). Falls back to pkg-config if the CMake package isn't found. +find_path(MYSQL_INCLUDE_DIR + NAMES mysql.h + PATH_SUFFIXES mysql mariadb +) +find_library(MYSQL_LIBRARY + NAMES mariadb mysqlclient +) +if(NOT MYSQL_INCLUDE_DIR OR NOT MYSQL_LIBRARY) + find_package(PkgConfig REQUIRED) + pkg_check_modules(MYSQL REQUIRED libmariadb) +endif() + +add_compile_options( + -Wno-write-strings + -Wno-narrowing + # This 2006-era codebase relies throughout on signed-integer overflow + # wrapping (packet/crypto bit twiddling). That's UB in C++; at -O2/-O3 + # GCC exploits it and miscompiles (observed: GenerateCryptTables SIGSEGV + # in Release builds only). -fwrapv makes overflow well-defined instead + # of auditing every arithmetic expression in the tree. + -fwrapv + -include "${CMAKE_SOURCE_DIR}/Src/Common/compat.h" +) +add_compile_definitions(_CRT_SECURE_NO_WARNINGS) + +set(COMMON_SOURCES + "Src/Common/config.cpp" + "Src/Common/database/database.cpp" + "Src/Common/globals.cpp" + "Src/Common/log.cpp" + "Src/Common/rosecrypt.cpp" + "Src/Common/seh.cpp" + "Src/Common/SocketBase.cpp" + "Src/Common/SocketClient.cpp" + "Src/Common/SocketServer.cpp" +) + +set(COMMON_INCLUDES + "${CMAKE_SOURCE_DIR}/Src/Common" + "${CMAKE_SOURCE_DIR}/Src/Common/database" + ${MYSQL_INCLUDE_DIR} + ${MYSQL_INCLUDE_DIRS} +) + +set(COMMON_LIBS + Threads::Threads + ${MYSQL_LIBRARY} + ${MYSQL_LIBRARIES} +) + +# --------------------------------------------------------------------------- +# Login Server +# --------------------------------------------------------------------------- +add_executable(loginserver + ${COMMON_SOURCES} + "Src/Login Server/extrafunction.cpp" + "Src/Login Server/loginclient.cpp" + "Src/Login Server/loginpackets.cpp" + "Src/Login Server/loginserver.cpp" + "Src/Login Server/main.cpp" + "Src/Login Server/signal.cpp" +) +target_include_directories(loginserver PRIVATE ${COMMON_INCLUDES} "${CMAKE_SOURCE_DIR}/Src/Login Server") +target_link_libraries(loginserver PRIVATE ${COMMON_LIBS}) + +# --------------------------------------------------------------------------- +# Char Server +# --------------------------------------------------------------------------- +add_executable(charserver + ${COMMON_SOURCES} + "Src/Char Server/charclient.cpp" + "Src/Char Server/charpackets.cpp" + "Src/Char Server/charserver.cpp" + "Src/Char Server/clan.cpp" + "Src/Char Server/community.cpp" + "Src/Char Server/extrafunctions.cpp" + "Src/Char Server/main.cpp" + "Src/Char Server/signal.cpp" +) +target_include_directories(charserver PRIVATE ${COMMON_INCLUDES} "${CMAKE_SOURCE_DIR}/Src/Char Server") +target_link_libraries(charserver PRIVATE ${COMMON_LIBS}) + +# --------------------------------------------------------------------------- +# World Server +# --------------------------------------------------------------------------- +add_executable(worldserver + ${COMMON_SOURCES} + "Src/World Server/aip/AiActions.cpp" + "Src/World Server/aip/AiConditions.cpp" + "Src/World Server/aip/Aip.cpp" + "Src/World Server/battle.cpp" + "Src/World Server/Buff.cpp" + "Src/World Server/character.cpp" + "Src/World Server/charevent.cpp" + "Src/World Server/charfunctions.cpp" + "Src/World Server/charstats.cpp" + "Src/World Server/clan.cpp" + "Src/World Server/extrafunctions.cpp" + "Src/World Server/gmcmds.cpp" + "Src/World Server/itemdata.cpp" + "Src/World Server/main.cpp" + "Src/World Server/MainProcess.cpp" + "Src/World Server/mapfunctions.cpp" + "Src/World Server/Monster.cpp" + "Src/World Server/MonsterDamage.cpp" + "Src/World Server/MonsterEvents.cpp" + "Src/World Server/monsterfunctions.cpp" + "Src/World Server/MonsterStats.cpp" + "Src/World Server/Party.cpp" + "Src/World Server/partyfunctions.cpp" + "Src/World Server/player.cpp" + "Src/World Server/playerdata.cpp" + "Src/World Server/playerfunctions.cpp" + "Src/World Server/playerquest.cpp" + "Src/World Server/playerstats.cpp" + "Src/World Server/quest/QuestActions.cpp" + "Src/World Server/quest/QuestConditions.cpp" + "Src/World Server/quest/Quests.cpp" + "Src/World Server/ServerFunctions.cpp" + "Src/World Server/Signal.cpp" + "Src/World Server/startup.cpp" + "Src/World Server/stbhandler.cpp" + "Src/World Server/stb_manager.cpp" + "Src/World Server/stlhandler.cpp" + "Src/World Server/strhashing.cpp" + "Src/World Server/worldmap.cpp" + "Src/World Server/WorldMonster.cpp" + "Src/World Server/worldpackets.cpp" + "Src/World Server/Worldprocess.cpp" + "Src/World Server/worldserver.cpp" +) +target_include_directories(worldserver PRIVATE ${COMMON_INCLUDES} "${CMAKE_SOURCE_DIR}/Src/World Server") +target_link_libraries(worldserver PRIVATE ${COMMON_LIBS}) + +install(TARGETS loginserver charserver worldserver RUNTIME DESTINATION bin) diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..3cc1eaa --- /dev/null +++ b/Dockerfile @@ -0,0 +1,46 @@ +# syntax=docker/dockerfile:1 + +# --------------------------------------------------------------------------- +# Builder +# --------------------------------------------------------------------------- +FROM debian:bookworm-slim AS builder + +RUN apt-get update && apt-get install -y --no-install-recommends \ + cmake \ + g++ \ + make \ + libmariadb-dev \ + pkg-config \ + && rm -rf /var/lib/apt/lists/* + +WORKDIR /src +COPY CMakeLists.txt ./ +COPY Src ./Src + +RUN cmake -S . -B build -DCMAKE_BUILD_TYPE=Release \ + && cmake --build build -j"$(nproc)" + +# --------------------------------------------------------------------------- +# Runtime +# --------------------------------------------------------------------------- +FROM debian:bookworm-slim AS runtime + +RUN apt-get update && apt-get install -y --no-install-recommends \ + libmariadb3 \ + gettext-base \ + && rm -rf /var/lib/apt/lists/* + +COPY --from=builder /src/build/loginserver /app/bin/loginserver +COPY --from=builder /src/build/charserver /app/bin/charserver +COPY --from=builder /src/build/worldserver /app/bin/worldserver +COPY docker/conf/ /app/conf-templates/ +COPY docker/entrypoint.sh /app/entrypoint.sh +COPY Binary/clanmark/ /app/seed/clanmark/ + +RUN chmod +x /app/entrypoint.sh && mkdir -p /data /var/log/roseserver + +WORKDIR /data +VOLUME ["/data", "/var/log/roseserver"] + +ENTRYPOINT ["/app/entrypoint.sh"] +CMD ["world"] diff --git a/Src/Char Server/charpackets.cpp b/Src/Char Server/charpackets.cpp index d77108d..40e3dfa 100644 --- a/Src/Char Server/charpackets.cpp +++ b/Src/Char Server/charpackets.cpp @@ -405,7 +405,7 @@ bool CCharServer::pakWSReady ( CCharClient* thisclient, CPacket* P ) } struct sockaddr_in ain; ain.sin_family = AF_INET; - ain.sin_addr.s_addr = inet_addr(newchannel->ip); + ain.sin_addr.s_addr = rose_inet_addr(newchannel->ip); ain.sin_port = htons( newchannel->port ); memset(&(ain.sin_zero), '\0', 8); if ( connect( newchannel->sock , (SOCKADDR*) &ain, sizeof(ain) ) == SOCKET_ERROR) diff --git a/Src/Char Server/charserver.h b/Src/Char Server/charserver.h index 40726c3..905cb38 100644 --- a/Src/Char Server/charserver.h +++ b/Src/Char Server/charserver.h @@ -6,7 +6,7 @@ #define __ROSE_SERVERS__ #include -#include "../common/sockets.h" +#include "../Common/sockets.h" #include "datatypes.h" #define ClearItem(i) { i.durability=0; i.itemnum=0; i.itemtype=0; i.lifespan=0; i.refine=0; } using namespace std; diff --git a/Src/Char Server/clan.cpp b/Src/Char Server/clan.cpp index 51a3217..6858ae2 100644 --- a/Src/Char Server/clan.cpp +++ b/Src/Char Server/clan.cpp @@ -18,7 +18,7 @@ depeloped with Main erose/hrose source server + some change from the original eich source */ -#include "CharServer.h" +#include "charserver.h" // Send Clan information bool CCharServer::SendClanInfo (CCharClient* thisclient) diff --git a/Src/Char Server/datatypes.h b/Src/Char Server/datatypes.h index 61ed83e..37796ee 100644 --- a/Src/Char Server/datatypes.h +++ b/Src/Char Server/datatypes.h @@ -4,7 +4,7 @@ */ #ifndef __ROSE_DATATYPES__ #define __ROSE_DATATYPES__ -#include "../common/sockets.h" +#include "../Common/sockets.h" // List of clan members struct CClanMembers { diff --git a/Src/Char Server/signal.cpp b/Src/Char Server/signal.cpp index cfb1532..8baf74b 100644 --- a/Src/Char Server/signal.cpp +++ b/Src/Char Server/signal.cpp @@ -38,7 +38,9 @@ void StartSignal( ) signal( SIGFPE, HandleSignal ); signal( SIGSEGV, HandleSignal ); signal( SIGTERM, HandleSignal ); + #ifdef _WIN32 signal( SIGBREAK, HandleSignal ); + #endif signal( SIGABRT, HandleSignal ); } @@ -49,7 +51,9 @@ void StopSignal( ) signal( SIGFPE, 0 ); signal( SIGSEGV, 0 ); signal( SIGTERM, 0 ); + #ifdef _WIN32 signal( SIGBREAK, 0 ); + #endif signal( SIGABRT, 0 ); } diff --git a/Src/Common/SocketServer.cpp b/Src/Common/SocketServer.cpp index a2acb5c..e3c59a0 100644 --- a/Src/Common/SocketServer.cpp +++ b/Src/Common/SocketServer.cpp @@ -45,7 +45,6 @@ CServerSocket::~CServerSocket( ) bool CServerSocket::StartServer( ) { //struct sockaddr_in ain; - int test; sock = socket( AF_INET, SOCK_STREAM, 0 ); if (sock == INVALID_SOCKET) { @@ -59,16 +58,16 @@ bool CServerSocket::StartServer( ) Log(MSG_ERROR, "setsockopt:SO_KEEPALIVE" ); } setsockopt(sock, IPPROTO_TCP,TCP_NODELAY,(const char*)&optval,sizeof(optval)); + // Lets a restarted server (docker restart, crash-loop) rebind immediately + // instead of failing on a TIME_WAIT socket from its own previous run. + setsockopt(sock, SOL_SOCKET,SO_REUSEADDR,(const char*)&optval,sizeof(optval)); ain.sin_family = AF_INET; ain.sin_addr.s_addr = INADDR_ANY; ain.sin_port = htons( port ); memset(&(ain.sin_zero), '\0', 8); if ( bind( sock, (const sockaddr*)&ain, sizeof( struct sockaddr ) ) ) { - Log( MSG_FATALERROR, "Could not bind socket" ); - std::cout <<"socket " << sock << std::endl; - std::cin >> test; closesocket( sock ); sock = INVALID_SOCKET; return false; @@ -94,6 +93,7 @@ bool CServerSocket::StartServer( ) Log(MSG_ERROR, "setsockopt:SO_KEEPALIVE" ); } setsockopt(sckISC, IPPROTO_TCP,TCP_NODELAY,(const char*)&optval,sizeof(optval)); + setsockopt(sckISC, SOL_SOCKET,SO_REUSEADDR,(const char*)&optval,sizeof(optval)); sain.sin_family = AF_INET; sain.sin_addr.s_addr = INADDR_ANY; sain.sin_port = htons( Config.CharsPort);//29110 ); @@ -125,7 +125,7 @@ bool CServerSocket::StartServer( ) return false; } aina.sin_family = AF_INET; - aina.sin_addr.s_addr = inet_addr(Config.CharIP);//( "127.0.0.1" ); + aina.sin_addr.s_addr = rose_inet_addr(Config.CharIP);//( "127.0.0.1" ); aina.sin_port = htons( Config.CharPort);//29110 ); if ( connect( sckISC, (const sockaddr*)&aina, sizeof( aina ) ) == SOCKET_ERROR ) { Log( MSG_ERROR, "Could not connect to ISC (WSK2 ERROR: %i)", WSAGetLastError( ) ); @@ -146,6 +146,7 @@ bool CServerSocket::StartServer( ) Log(MSG_ERROR, "setsockopt:SO_KEEPALIVE" ); } setsockopt(sckISCII, IPPROTO_TCP,TCP_NODELAY,(const char*)&optval,sizeof(optval)); + setsockopt(sckISCII, SOL_SOCKET,SO_REUSEADDR,(const char*)&optval,sizeof(optval)); sain.sin_family = AF_INET; sain.sin_addr.s_addr = INADDR_ANY; sain.sin_port = htons( Config.WorldsPort );//29210 ); diff --git a/Src/Common/compat.h b/Src/Common/compat.h new file mode 100644 index 0000000..14e0904 --- /dev/null +++ b/Src/Common/compat.h @@ -0,0 +1,181 @@ +/* + Linux compatibility shims for MSVC-only CRT calls used throughout the + original Windows codebase (fopen_s, _strdup, _stricmp, sprintf_s, ...), + plus a path resolver: the shipped STB/AIP/quest data files embed + Windows-style paths ("3DDATA\AI\FOO.AIP") with arbitrary case, which + don't resolve on a case-sensitive Linux filesystem. rose_fopen() rewrites + backslashes to '/' and, if that still doesn't exist, resolves each path + component case-insensitively against the real directory contents before + opening the file. +*/ +#ifndef __ROSE_COMPAT_H__ +#define __ROSE_COMPAT_H__ + +#ifndef _WIN32 + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define __cdecl + +typedef int errno_t; +typedef int INT; +typedef unsigned int UINT; +typedef long LONG; +typedef unsigned long ULONG; + +inline FILE* rose_fopen(const char* path, const char* mode) +{ + char norm[1024]; + size_t n = 0; + for (; path[n] != 0 && n < sizeof(norm) - 1; ++n) + norm[n] = (path[n] == '\\') ? '/' : path[n]; + norm[n] = 0; + + struct stat st; + if (stat(norm, &st) == 0) + return fopen(norm, mode); + + char resolved[1024]; + size_t rlen = 0; + resolved[0] = 0; + const char* p = norm; + if (*p == '/') { resolved[0] = '/'; resolved[1] = 0; rlen = 1; ++p; } + + while (*p) + { + const char* slash = strchr(p, '/'); + size_t len = slash ? (size_t)(slash - p) : strlen(p); + char component[256]; + if (len >= sizeof(component)) len = sizeof(component) - 1; + memcpy(component, p, len); + component[len] = 0; + + char candidate[1024]; + snprintf(candidate, sizeof(candidate), "%s%s", resolved, component); + + if (stat(candidate, &st) != 0) + { + char dirbuf[1024]; + const char* dirpath = "."; + if (rlen > 0) + { + size_t copylen = (rlen > 1 && resolved[rlen - 1] == '/') ? rlen - 1 : rlen; + snprintf(dirbuf, sizeof(dirbuf), "%.*s", (int)copylen, resolved); + dirpath = dirbuf[0] ? dirbuf : "/"; + } + DIR* d = opendir(dirpath); + if (d) + { + struct dirent* de; + while ((de = readdir(d)) != NULL) + { + if (strcasecmp(de->d_name, component) == 0) + { + snprintf(candidate, sizeof(candidate), "%s/%s", dirpath, de->d_name); + break; + } + } + closedir(d); + } + } + + rlen = snprintf(resolved, sizeof(resolved), "%s/", candidate); + p = slash ? slash + 1 : p + len; + } + + if (rlen > 0 && resolved[rlen - 1] == '/') + resolved[rlen - 1] = 0; + + return fopen(resolved, mode); +} + +inline errno_t rose_fopen_s(FILE** fh, const char* path, const char* mode) +{ + *fh = rose_fopen(path, mode); + return (*fh != NULL) ? 0 : 1; +} +#define fopen_s(pfh, path, mode) rose_fopen_s(pfh, path, mode) + +#define _strdup strdup +#define _stricmp strcasecmp +#define _strcmpi strcasecmp + +#define sprintf_s(buf, ...) sprintf(buf, __VA_ARGS__) +#define vsprintf_s(buf, fmt, ap) vsprintf(buf, fmt, ap) + +inline errno_t rose_ctime_s(char* buf, size_t bufsz, const time_t* t) +{ + return (ctime_r(t, buf) != NULL) ? 0 : 1; +} +#define ctime_s(buf, bufsz, t) rose_ctime_s(buf, bufsz, t) + +inline errno_t rose_strncpy_s(char* dst, const char* src, size_t count) +{ + strncpy(dst, src, count); + dst[count] = 0; + return 0; +} +#define strncpy_s(dst, src, count) rose_strncpy_s(dst, src, count) + +// Resolves a literal IPv4 address ("10.0.0.5") *or* a hostname (docker +// compose service name, e.g. "charserver") to a network-order IPv4 address, +// so inter-server links (server2server config values) work with either. +inline unsigned long rose_inet_addr(const char* host) +{ + struct in_addr addr; + if (inet_pton(AF_INET, host, &addr) == 1) + return addr.s_addr; + + struct addrinfo hints; + memset(&hints, 0, sizeof(hints)); + hints.ai_family = AF_INET; + hints.ai_socktype = SOCK_STREAM; + + struct addrinfo* res = NULL; + if (getaddrinfo(host, NULL, &hints, &res) != 0 || res == NULL) + return INADDR_NONE; + + unsigned long result = ((struct sockaddr_in*)res->ai_addr)->sin_addr.s_addr; + freeaddrinfo(res); + return result; +} + +struct SYSTEMTIME +{ + unsigned short wYear; + unsigned short wMonth; + unsigned short wDayOfWeek; + unsigned short wDay; + unsigned short wHour; + unsigned short wMinute; + unsigned short wSecond; + unsigned short wMilliseconds; +}; + +inline void GetLocalTime(SYSTEMTIME* out) +{ + time_t rawtime = time(NULL); + struct tm ltm; + localtime_r(&rawtime, <m); + out->wYear = (unsigned short)(ltm.tm_year + 1900); + out->wMonth = (unsigned short)(ltm.tm_mon + 1); + out->wDayOfWeek = (unsigned short)ltm.tm_wday; + out->wDay = (unsigned short)ltm.tm_mday; + out->wHour = (unsigned short)ltm.tm_hour; + out->wMinute = (unsigned short)ltm.tm_min; + out->wSecond = (unsigned short)ltm.tm_sec; + out->wMilliseconds = 0; +} + +#endif // !_WIN32 +#endif // __ROSE_COMPAT_H__ diff --git a/Src/Common/log.cpp b/Src/Common/log.cpp index 184aea6..84dae62 100644 --- a/Src/Common/log.cpp +++ b/Src/Common/log.cpp @@ -4,6 +4,8 @@ #include #endif #include +#include +#include #include #include #include "log.h" @@ -59,9 +61,14 @@ void Log( enum msg_type flag, const char *Format, ... ) switch (flag) { case MSG_NONE: // direct printf replacement + { textcolor(WHITE); - vprintf( Format, ap ); + va_list apcopy; + va_copy(apcopy, ap); + vprintf( Format, apcopy ); + va_end(apcopy); break; + } case MSG_STATUS: textcolor(GREEN); printf("[STATUS]: "); @@ -107,10 +114,15 @@ void Log( enum msg_type flag, const char *Format, ... ) printf("[GM ACTION]: "); break; case MSG_START: + { textcolor(MAGENTA); - vprintf( Format, ap ); + va_list apcopy; + va_copy(apcopy, ap); + vprintf( Format, apcopy ); + va_end(apcopy); printf( "\r\n" ); break; + } //case MSG_SDEBUG: // textcolor(MAGENTA); // printf("[SDEBUG]: "); @@ -128,7 +140,12 @@ void Log( enum msg_type flag, const char *Format, ... ) if(flag!=MSG_QUERY) { printf( "%s- ", timestamp ); - vprintf( Format, ap ); + { + va_list apcopy; + va_copy(apcopy, ap); + vprintf( Format, apcopy ); + va_end(apcopy); + } printf( (flag==MSG_LOAD) ? "\r" : "\n" ); } FILE *fh = NULL; diff --git a/Src/Common/rosecrypt.cpp b/Src/Common/rosecrypt.cpp index 46e42d6..00228f2 100644 --- a/Src/Common/rosecrypt.cpp +++ b/Src/Common/rosecrypt.cpp @@ -113,7 +113,7 @@ void GenerateCryptTables( CCryptTable* &CryptTables, unsigned ModValue ) // Allocate memory for structure CryptTables = (CCryptTable*)malloc( sizeof( CCryptTable ) ); - CryptTables->Tables = (unsigned **)calloc( 16, sizeof( unsigned ) ); + CryptTables->Tables = (unsigned **)calloc( 16, sizeof( unsigned* ) ); // Prepare values ModValue = (int(ModValue * 0x41C64E6D) + 0x3039); @@ -133,8 +133,12 @@ void GenerateCryptTables( CCryptTable* &CryptTables, unsigned ModValue ) ControlStruct.CurTableMod = 0; for ( table=0; table<0x11; ++table ) { - ivalue = ctGenerateNextValue( CryptTables, &ControlStruct ) & 0x80000003; - if ( ivalue<0 ) ivalue = ( ( ivalue - 1 ) | 0xFFFFFFFC ) + 1; + // Signed-overflow-safe rewrite of the original ivalue-based bit trick: + // GCC's -O3 exploited UB from (INT_MIN - 1) when the mask below set the + // sign bit, corrupting unrelated registers. Same bit pattern, unsigned math. + unsigned uvalue = ctGenerateNextValue( CryptTables, &ControlStruct ) & 0x80000003u; + if ( (int)uvalue<0 ) uvalue = ( ( uvalue - 1 ) | 0xFFFFFFFCu ) + 1; + ivalue = (int)uvalue; TableFuncList[ table ] = ( unsigned char )ivalue; } @@ -196,7 +200,7 @@ void GenerateLoginServerCryptTables( CCryptTable* &CryptTables ) // Allocate memory for structure CryptTables = (CCryptTable*)malloc( sizeof( CCryptTable ) ); - CryptTables->Tables = (unsigned **)calloc( 16, sizeof( unsigned ) ); + CryptTables->Tables = (unsigned **)calloc( 16, sizeof( unsigned* ) ); // Set default startvalues for ( table=0; table<4; ++table ) @@ -237,10 +241,11 @@ void GenerateLoginServerCryptTables( CCryptTable* &CryptTables ) do { // Generate next value for table + // Unsigned multiply: signed overflow here is UB and gets miscompiled at -O3. ControlStruct.CountTableMod[1]; - ivalue = ControlStruct.ValueTableMod[1]; - ivalue *= 0x3E39B193; - value = (0x3039 - (unsigned)ivalue) & 0x7FFFFFFF; + unsigned umulvalue = (unsigned)ControlStruct.ValueTableMod[1]; + umulvalue *= 0x3E39B193u; + value = (0x3039 - umulvalue) & 0x7FFFFFFF; ControlStruct.ValueTableMod[1] = value; // Don't save, if value already exist diff --git a/Src/Common/sockets.h b/Src/Common/sockets.h index 68f578d..99fc610 100644 --- a/Src/Common/sockets.h +++ b/Src/Common/sockets.h @@ -31,15 +31,16 @@ #endif #define FD_SETSIZE 1024 #else - #include #include #include #include #include #include #include + #include #include #include + #define WSAGetLastError() errno #define SOCKET int #define INVALID_SOCKET -1 #define SOCKET_ERROR -1 @@ -69,7 +70,9 @@ #include "rosecrypt.hpp" #include "config.h" #include "database/database.h" +#ifdef _WIN32 #pragma comment(lib, "Ws2_32.lib") +#endif // Just some defs for easier use typedef char SBYTE; typedef unsigned char BYTE; @@ -79,10 +82,18 @@ typedef long SDWORD; typedef unsigned long DWORD; typedef long long SQWORD; typedef unsigned long long QWORD; +#ifdef _WIN32 typedef unsigned __int8 byte; typedef unsigned __int16 word; typedef unsigned __int32 dword; typedef unsigned __int64 qword; +#else +typedef unsigned char byte; +typedef unsigned short word; +typedef unsigned int dword; +typedef unsigned long long qword; +typedef unsigned int UINT; +#endif typedef char* strings; using std::cout; using std::endl; diff --git a/Src/Login Server/loginserver.h b/Src/Login Server/loginserver.h index c250d0b..34c1b6e 100644 --- a/Src/Login Server/loginserver.h +++ b/Src/Login Server/loginserver.h @@ -21,7 +21,7 @@ #ifndef __ROSE_SERVER__ #define __ROSE_SERVER__ -#include "../common/sockets.h" +#include "../Common/sockets.h" #include "datatype.h" // Player class diff --git a/Src/Login Server/signal.cpp b/Src/Login Server/signal.cpp index 37bb9db..d106d9e 100644 --- a/Src/Login Server/signal.cpp +++ b/Src/Login Server/signal.cpp @@ -38,7 +38,9 @@ void StartSignal( ) signal( SIGFPE, HandleSignal ); signal( SIGSEGV, HandleSignal ); signal( SIGTERM, HandleSignal ); + #ifdef _WIN32 signal( SIGBREAK, HandleSignal ); + #endif signal( SIGABRT, HandleSignal ); } @@ -49,7 +51,9 @@ void StopSignal( ) signal( SIGFPE, 0 ); signal( SIGSEGV, 0 ); signal( SIGTERM, 0 ); + #ifdef _WIN32 signal( SIGBREAK, 0 ); + #endif signal( SIGABRT, 0 ); } diff --git a/Src/World Server/MainProcess.cpp b/Src/World Server/MainProcess.cpp index 55c02d0..6cf46ea 100644 --- a/Src/World Server/MainProcess.cpp +++ b/Src/World Server/MainProcess.cpp @@ -434,7 +434,7 @@ PVOID WorldProcess( PVOID TS ) // Shutdown Server Process void ShutdownServer(PVOID TS) { - int minutes = (int)TS; + int minutes = (int)(intptr_t)TS; #ifdef _WIN32 Sleep(minutes*60000); #else diff --git a/Src/World Server/Signal.cpp b/Src/World Server/Signal.cpp index bfc133a..5313055 100644 --- a/Src/World Server/Signal.cpp +++ b/Src/World Server/Signal.cpp @@ -38,7 +38,9 @@ void StartSignal( ) signal( SIGFPE, HandleSignal ); signal( SIGSEGV, HandleSignal ); signal( SIGTERM, HandleSignal ); + #ifdef _WIN32 signal( SIGBREAK, HandleSignal ); + #endif signal( SIGABRT, HandleSignal ); } @@ -49,7 +51,9 @@ void StopSignal( ) signal( SIGFPE, 0 ); signal( SIGSEGV, 0 ); signal( SIGTERM, 0 ); + #ifdef _WIN32 signal( SIGBREAK, 0 ); + #endif signal( SIGABRT, 0 ); } diff --git a/Src/World Server/aip/Aip.cpp b/Src/World Server/aip/Aip.cpp index 9be2ded..28d5ce3 100644 --- a/Src/World Server/aip/Aip.cpp +++ b/Src/World Server/aip/Aip.cpp @@ -79,7 +79,7 @@ void CWorldServer::ReadAIP(strings path, dword index) void CWorldServer::LoadAipData() { - CStrStb* stbAip = new CStrStb("3DDATA\\STB\\FILE_AI.STB"); + CStrStb* stbAip = new CStrStb("3DData/STB/FILE_AI.STB"); Log(MSG_INFO, "Loading AIP files"); for(dword i = 1; i < stbAip->Rows(); i++) { diff --git a/Src/World Server/datatypes.h b/Src/World Server/datatypes.h index 5db59b1..0b411fb 100644 --- a/Src/World Server/datatypes.h +++ b/Src/World Server/datatypes.h @@ -473,7 +473,7 @@ -#include "../common/sockets.h" +#include "../Common/sockets.h" //LTB strings struct CLTBstring diff --git a/Src/World Server/player.h b/Src/World Server/player.h index a328103..fe51daf 100644 --- a/Src/World Server/player.h +++ b/Src/World Server/player.h @@ -55,9 +55,10 @@ class CPlayer: public CCharacter CRespawnPoints GMRespawnPoints; // Inventory/storage - CItem storageitems[MAX_STORAGE]; + // +1: slot indices throughout the codebase run 0..MAX_* inclusive + CItem storageitems[MAX_STORAGE + 1]; unsigned int nstorageitems; - CItem items[MAX_INVENTORY]; + CItem items[MAX_INVENTORY + 1]; // skills/quickbar SKILLS cskills[MAX_SKILL]; diff --git a/Src/World Server/quest/CIntStb.hpp b/Src/World Server/quest/CIntStb.hpp index a83d877..d69f751 100644 --- a/Src/World Server/quest/CIntStb.hpp +++ b/Src/World Server/quest/CIntStb.hpp @@ -13,7 +13,11 @@ public: unsigned dataoffset = 0; unsigned short fieldlen = 0; - if ( (fh = fopen( filename, "rb" )) != 0 ) { +#ifdef _WIN32 + if ( (fh = fopen( filename, "rb" )) == 0 ) { +#else + if ( (fh = rose_fopen( filename, "rb" )) == 0 ) { +#endif Log( MSG_ERROR, "Could not load STB '%s'", filename ); return 1; }else{ diff --git a/Src/World Server/quest/CRoseFile.hpp b/Src/World Server/quest/CRoseFile.hpp index b70a3c3..c8ecc6a 100644 --- a/Src/World Server/quest/CRoseFile.hpp +++ b/Src/World Server/quest/CRoseFile.hpp @@ -219,7 +219,11 @@ class CRoseFile if( fopen_s( &fh, fname, fasciimode ) == 0 ) { #else - if( ( fh = fopen( fname, fasciimode ) ) != NULL ) + #ifdef _WIN32 + if( ( fh = fopen( fname, fasciimode ) ) != NULL ) + #else + if( ( fh = rose_fopen( fname, fasciimode ) ) != NULL ) + #endif { #endif delete[] fasciimode; @@ -242,8 +246,10 @@ class CRoseFile } #ifndef CFILE_NODEPRECATE fopen_s( &fh, fname, fasciimode ); - #else + #elif defined(_WIN32) fh = fopen( fname, fasciimode ); + #else + fh = rose_fopen( fname, fasciimode ); #endif SetAbsPos( 2 ); ftype = FT_UNICODE; diff --git a/Src/World Server/quest/QuestActions.cpp b/Src/World Server/quest/QuestActions.cpp index 5c297df..91e0049 100644 --- a/Src/World Server/quest/QuestActions.cpp +++ b/Src/World Server/quest/QuestActions.cpp @@ -184,8 +184,8 @@ QUESTREWD(002) for(dword i = 0; i < data->iDataCnt; i++) { - dword address = i * sizeof(STR_QUEST_DATA); - address += (dword)data; + uintptr_t address = i * sizeof(STR_QUEST_DATA); + address += (uintptr_t)data; address += 4; STR_QUEST_DATA* curQst = (STR_QUEST_DATA*)address; if( client->questdebug ) @@ -208,8 +208,8 @@ QUESTREWD(003) //byte btOp; pos 0x0c for(dword i = 0; i < data->iDataCnt; i++) { - //dword address = i * 0x0C; - //address += (dword)data; + //uintptr_t address = i * 0x0C; + //address += (uintptr_t)data; //address += 4; //STR_ABIL_DATA* curAbil = (STR_ABIL_DATA*)address; if( client->questdebug ) diff --git a/Src/World Server/quest/QuestConditions.cpp b/Src/World Server/quest/QuestConditions.cpp index e417033..f0db911 100644 --- a/Src/World Server/quest/QuestConditions.cpp +++ b/Src/World Server/quest/QuestConditions.cpp @@ -37,8 +37,8 @@ QUESTCOND(001) if( client->questdebug )server->SendPM(client, "Data Count for this quest %i",data->iDataCnt); for(int i = 0; i < data->iDataCnt; i++) { - dword address = i * sizeof(STR_QUEST_DATA); - address += (dword)data; + uintptr_t address = i * sizeof(STR_QUEST_DATA); + address += (uintptr_t)data; address += 4; STR_QUEST_DATA* curQst = (STR_QUEST_DATA*)address; word nValue = curQst->nValue; @@ -96,8 +96,8 @@ QUESTCOND(003) GETCONDDATA(003); for(int i = 0; i < data->iDataCnt; i++) { - dword address = i * 0x0C; - address += (dword)data; + uintptr_t address = i * 0x0C; + address += (uintptr_t)data; address += 4; STR_ABIL_DATA* curAbil = (STR_ABIL_DATA*)address; if( client->questdebug ) @@ -175,8 +175,8 @@ QUESTCOND(004) GETCONDDATA(004); for(int i = 0; i < data->iDataCnt; i++) { - dword address = i * 0x10; - address += (dword)data; + uintptr_t address = i * 0x10; + address += (uintptr_t)data; address += 4; STR_ITEM_DATA* curItem = (STR_ITEM_DATA*)address; if( client->questdebug ) diff --git a/Src/World Server/quest/Quests.cpp b/Src/World Server/quest/Quests.cpp index aa7d74f..6c896da 100644 --- a/Src/World Server/quest/Quests.cpp +++ b/Src/World Server/quest/Quests.cpp @@ -78,7 +78,7 @@ void CWorldServer::ReadQSD(strings path, dword index){ } void CWorldServer::LoadQuestData(){ - CStrStb* stbQuest = new CStrStb("3DDATA\\STB\\LIST_QUESTDATA.STB"); + CStrStb* stbQuest = new CStrStb("3DData/STB/LIST_QUESTDATA.STB"); Log(MSG_INFO, "Loading QSD files"); for(dword i = 1; i < stbQuest->Rows(); i++){ if(stbQuest->Data(i, 0)){ diff --git a/Src/World Server/startup.cpp b/Src/World Server/startup.cpp index 5346e4a..d4b655e 100644 --- a/Src/World Server/startup.cpp +++ b/Src/World Server/startup.cpp @@ -22,31 +22,31 @@ bool CWorldServer::LoadSTBData( ) { Log( MSG_LOAD, "STB Data " ); - STBStoreData( "3DData\\STB\\LIST_NPC.STB", &STB_NPC ); - STBStoreData( "3DData\\STB\\LIST_SKILL.STB", &STB_SKILL ); - STBStoreData( "3DData\\STB\\LIST_STATUS.STB", &STB_STATUS ); - STBStoreData( "3DData\\STB\\LIST_QUEST.STB", &STB_QUEST ); - STBStoreData( "3DData\\STB\\LIST_FACEITEM.STB", &STB_ITEM[0] ); - STBStoreData( "3DData\\STB\\LIST_CAP.STB", &STB_ITEM[1] ); - STBStoreData( "3DData\\STB\\LIST_BODY.STB", &STB_ITEM[2] ); - STBStoreData( "3DData\\STB\\LIST_ARMS.STB", &STB_ITEM[3] ); - STBStoreData( "3DData\\STB\\LIST_FOOT.STB", &STB_ITEM[4] ); - STBStoreData( "3DData\\STB\\LIST_BACK.STB", &STB_ITEM[5] ); - STBStoreData( "3DData\\STB\\LIST_JEWEL.STB", &STB_ITEM[6] ); - STBStoreData( "3DData\\STB\\LIST_WEAPON.STB", &STB_ITEM[7] ); - STBStoreData( "3DData\\STB\\LIST_SUBWPN.STB", &STB_ITEM[8] ); - STBStoreData( "3DData\\STB\\LIST_USEITEM.STB", &STB_ITEM[9] ); - STBStoreData( "3DData\\STB\\LIST_JEMITEM.STB", &STB_ITEM[10] ); - STBStoreData( "3DData\\STB\\LIST_NATURAL.STB", &STB_ITEM[11] ); - STBStoreData( "3DData\\STB\\LIST_QUESTITEM.STB", &STB_ITEM[12] ); - STBStoreData( "3DData\\STB\\LIST_PAT.STB", &STB_ITEM[13] ); - STBStoreData( "3DData\\STB\\LIST_PRODUCT.STB", &STB_PRODUCT ); - STBStoreData( "3DData\\STB\\LIST_BREAK.STB", &STB_BREAK ); - STBStoreData( "3DData\\STB\\LIST_SELL.STB", &STB_SELL ); - STBStoreData( "3DData\\STB\\LIST_ZONE.STB", &STB_ZONE ); - //STBStoreData( "3DData\\STB\\ITEM_DROP.STB", &STB_DROP ); - STBStoreData("3DData\\STB\\LIST_GRADE.STB", &ListGrade); - STBStoreData("3DData\\STB\\LIST_UPGRADE.STB", &ListUpGrade); + STBStoreData( "3DData/STB/LIST_NPC.STB", &STB_NPC ); + STBStoreData( "3DData/STB/LIST_SKILL.STB", &STB_SKILL ); + STBStoreData( "3DData/STB/LIST_STATUS.STB", &STB_STATUS ); + STBStoreData( "3DData/STB/LIST_QUEST.STB", &STB_QUEST ); + STBStoreData( "3DData/STB/LIST_FACEITEM.STB", &STB_ITEM[0] ); + STBStoreData( "3DData/STB/LIST_CAP.STB", &STB_ITEM[1] ); + STBStoreData( "3DData/STB/LIST_BODY.STB", &STB_ITEM[2] ); + STBStoreData( "3DData/STB/LIST_ARMS.STB", &STB_ITEM[3] ); + STBStoreData( "3DData/STB/LIST_FOOT.STB", &STB_ITEM[4] ); + STBStoreData( "3DData/STB/LIST_BACK.STB", &STB_ITEM[5] ); + STBStoreData( "3DData/STB/LIST_JEWEL.STB", &STB_ITEM[6] ); + STBStoreData( "3DData/STB/LIST_WEAPON.STB", &STB_ITEM[7] ); + STBStoreData( "3DData/STB/LIST_SUBWPN.STB", &STB_ITEM[8] ); + STBStoreData( "3DData/STB/LIST_USEITEM.STB", &STB_ITEM[9] ); + STBStoreData( "3DData/STB/LIST_JEMITEM.STB", &STB_ITEM[10] ); + STBStoreData( "3DData/STB/LIST_NATURAL.STB", &STB_ITEM[11] ); + STBStoreData( "3DData/STB/LIST_QUESTITEM.STB", &STB_ITEM[12] ); + STBStoreData( "3DData/STB/LIST_PAT.STB", &STB_ITEM[13] ); + STBStoreData( "3DData/STB/LIST_PRODUCT.STB", &STB_PRODUCT ); + STBStoreData( "3DData/STB/LIST_BREAK.STB", &STB_BREAK ); + STBStoreData( "3DData/STB/LIST_SELL.STB", &STB_SELL ); + STBStoreData( "3DData/STB/LIST_ZONE.STB", &STB_ZONE ); + //STBStoreData( "3DData/STB/ITEM_DROP.STB", &STB_DROP ); + STBStoreData("3DData/STB/LIST_GRADE.STB", &ListGrade); + STBStoreData("3DData/STB/LIST_UPGRADE.STB", &ListUpGrade); return true; } @@ -882,9 +882,18 @@ bool CWorldServer::LoadEquip( ) for(int k=0;k<2;k++) newequip->stat2[k] = STB_ITEM[j].rows[i][(27+k)]; - newequip->refinetype = STB_ITEM[j].rows[i][45]; //new this is the recipe for refining the item - newequip->itemgrade = STB_ITEM[j].rows[i][46]; - newequip->raretype = STB_ITEM[j].rows[i][47]; + if (STB_ITEM[j].fieldcount>47) + { + newequip->refinetype = STB_ITEM[j].rows[i][45]; //new this is the recipe for refining the item + newequip->itemgrade = STB_ITEM[j].rows[i][46]; + newequip->raretype = STB_ITEM[j].rows[i][47]; + } + else + { + newequip->refinetype = 0; + newequip->itemgrade = 0; + newequip->raretype = 0; + } if(newequip->id > 4999) { diff --git a/Src/World Server/worldmap.cpp b/Src/World Server/worldmap.cpp index 27b6fa0..8458c74 100644 --- a/Src/World Server/worldmap.cpp +++ b/Src/World Server/worldmap.cpp @@ -51,7 +51,7 @@ CMap::~CMap( ) { for(UINT i=0;iConfig.SpawnType == 0) + if(GServer && GServer->Config.SpawnType == 0) { for(UINT i = 0; i < MobGroupList.size(); i++) { diff --git a/Src/World Server/worldmonster.h b/Src/World Server/worldmonster.h new file mode 120000 index 0000000..6ff1b00 --- /dev/null +++ b/Src/World Server/worldmonster.h @@ -0,0 +1 @@ +WorldMonster.h \ No newline at end of file diff --git a/Src/World Server/worldserver.cpp b/Src/World Server/worldserver.cpp index 670edd7..224ac8d 100644 --- a/Src/World Server/worldserver.cpp +++ b/Src/World Server/worldserver.cpp @@ -100,7 +100,7 @@ CWorldServer::CWorldServer( string fn ) PatList.nullpat = nullpat; CCSellData* nullsell = new CCSellData; nullsell->id = 0; - for(UINT i=0;i<50;i++) + for(UINT i=0;i<49;i++) nullsell->item[i] = 0; SellList.nullsell = nullsell ; for(UINT i=0;i<1000;i++) @@ -228,7 +228,6 @@ CWorldServer::~CWorldServer( ) delete SellList.nullsell; delete UseList.nulluse; delete MapList.nullzone; - delete [] GradeList; delete DB; } @@ -608,7 +607,7 @@ bool CWorldServer::OnServerReady( ) } struct sockaddr_in ain; ain.sin_family = AF_INET; - ain.sin_addr.s_addr = inet_addr( Config.CharIP ); + ain.sin_addr.s_addr = rose_inet_addr( Config.CharIP ); ain.sin_port = htons( Config.CharPort ); if ( connect( csock, (SOCKADDR*) &ain, sizeof(ain) ) == SOCKET_ERROR ) Log( MSG_WARNING, "Could not access charserver" ); diff --git a/Src/World Server/worldserver.h b/Src/World Server/worldserver.h new file mode 120000 index 0000000..b643ff2 --- /dev/null +++ b/Src/World Server/worldserver.h @@ -0,0 +1 @@ +WorldServer.h \ No newline at end of file diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..71610eb --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,87 @@ +x-app: &app + build: + context: . + dockerfile: Dockerfile + image: rose-server:latest + restart: unless-stopped + environment: &app-env + DB_HOST: db + DB_PORT: "3306" + DB_USER: ${DB_USER:-rose} + DB_PASSWORD: ${DB_PASSWORD:-change_me} + DB_NAME: ${DB_NAME:-ktrose} + PUBLIC_HOST: ${PUBLIC_HOST:-127.0.0.1} + CHAR_HOST: charserver + LOGIN_PORT: ${LOGIN_PORT:-29000} + LOGIN_SERVER_NAME: ${LOGIN_SERVER_NAME:-ROSE Login} + LOGIN_CREATE_ACCOUNT: ${LOGIN_CREATE_ACCOUNT:-1} + CHAR_PORT: ${CHAR_PORT:-29100} + CHAR_S2S_PORT: ${CHAR_S2S_PORT:-29110} + CHAR_SERVER_NAME: ${CHAR_SERVER_NAME:-Server 1} + CHAR_DELETE_TIME: ${CHAR_DELETE_TIME:-3600} + WORLD_PORT: ${WORLD_PORT:-29200} + WORLD_S2S_PORT: ${WORLD_S2S_PORT:-29210} + WORLD_CHANNEL_NAME: ${WORLD_CHANNEL_NAME:-Channel 1} + WORLD_MAX_CONNECTIONS: ${WORLD_MAX_CONNECTIONS:-500} + WORLD_WELCOME_MSG: ${WORLD_WELCOME_MSG:-Welcome to ROSE} + WORLD_SAVE_TIME: ${WORLD_SAVE_TIME:-3600} + SHARED_LOGIN_PASS: ${SHARED_LOGIN_PASS:-789456} + SHARED_CHAR_PASS: ${SHARED_CHAR_PASS:-456123} + SHARED_WORLD_PASS: ${SHARED_WORLD_PASS:-321654} + depends_on: + db: + condition: service_healthy + +services: + db: + image: mariadb:11 + restart: unless-stopped + environment: + MYSQL_RANDOM_ROOT_PASSWORD: "1" + MYSQL_DATABASE: ${DB_NAME:-ktrose} + MYSQL_USER: ${DB_USER:-rose} + MYSQL_PASSWORD: ${DB_PASSWORD:-change_me} + volumes: + - db-data:/var/lib/mysql + - "./database sql files:/docker-entrypoint-initdb.d:ro" + healthcheck: + test: ["CMD", "healthcheck.sh", "--connect", "--innodb_initialized"] + interval: 5s + timeout: 5s + retries: 20 + + loginserver: + <<: *app + command: ["login"] + ports: + - "${LOGIN_PORT:-29000}:${LOGIN_PORT:-29000}" + volumes: + - logs:/var/log/roseserver + + charserver: + <<: *app + command: ["char"] + ports: + - "${CHAR_PORT:-29100}:${CHAR_PORT:-29100}" + volumes: + - logs:/var/log/roseserver + - clanmark:/data/clanmark + + worldserver: + <<: *app + command: ["world"] + ports: + - "${WORLD_PORT:-29200}:${WORLD_PORT:-29200}" + volumes: + - logs:/var/log/roseserver + - "./Binary/3DData:/data/3DData:ro" + depends_on: + db: + condition: service_healthy + charserver: + condition: service_started + +volumes: + db-data: + logs: + clanmark: diff --git a/docker/conf/charserver.conf.tmpl b/docker/conf/charserver.conf.tmpl new file mode 100644 index 0000000..2b90482 --- /dev/null +++ b/docker/conf/charserver.conf.tmpl @@ -0,0 +1,25 @@ +[DATABASE] +mysql_host=${DB_HOST} +mysql_user=${DB_USER} +mysql_pass=${DB_PASSWORD} +mysql_database=${DB_NAME} +mysql_port=${DB_PORT} + +[SERVERDATA] +serverid=1 +serverip=${PUBLIC_HOST} +serverport=${CHAR_PORT} +servername=${CHAR_SERVER_NAME} +parentid=0 +accesslevel=100 + +[CHARSERVER] +deletetime=${CHAR_DELETE_TIME} +usethreads=0 +[server2server] +CharsPort=${CHAR_S2S_PORT} + +[PASSWORDS] +loginpass=${SHARED_LOGIN_PASS} +charpass=${SHARED_CHAR_PASS} +worldpass=${SHARED_WORLD_PASS} diff --git a/docker/conf/loginserver.conf.tmpl b/docker/conf/loginserver.conf.tmpl new file mode 100644 index 0000000..f3f55b0 --- /dev/null +++ b/docker/conf/loginserver.conf.tmpl @@ -0,0 +1,23 @@ +[DATABASE] +mysql_host=${DB_HOST} +mysql_user=${DB_USER} +mysql_pass=${DB_PASSWORD} +mysql_database=${DB_NAME} +mysql_port=${DB_PORT} + +[SERVERDATA] +serverid=0 +serverip=${PUBLIC_HOST} +serverport=${LOGIN_PORT} +servername=${LOGIN_SERVER_NAME} + +[LOGINSERVER] +accesslevel=100 +CreateLoginAccount=${LOGIN_CREATE_ACCOUNT} +usethreads=0 +Testserver=0 + +[PASSWORDS] +loginpass=${SHARED_LOGIN_PASS} +charpass=${SHARED_CHAR_PASS} +worldpass=${SHARED_WORLD_PASS} diff --git a/docker/conf/worldserver.conf.tmpl b/docker/conf/worldserver.conf.tmpl new file mode 100644 index 0000000..e2e06fd --- /dev/null +++ b/docker/conf/worldserver.conf.tmpl @@ -0,0 +1,43 @@ +[DATABASE] +mysql_host=${DB_HOST} +mysql_user=${DB_USER} +mysql_pass=${DB_PASSWORD} +mysql_database=${DB_NAME} +mysql_port=${DB_PORT} + +[SERVERDATA] +serverid=2 +serverip=${PUBLIC_HOST} +serverport=${WORLD_PORT} +servername=${WORLD_CHANNEL_NAME} +maxconnections=${WORLD_MAX_CONNECTIONS} +parentid=1 + +[WORLDSERVER] +accesslevel=100 +drop_type=2 +welcome_msg=${WORLD_WELCOME_MSG} +autosave=1 +savetime=${WORLD_SAVE_TIME} +mapdelay=10 +visualdelay=200 +worlddelay=700 +partygap=10 +maxstat=300 +fairy=1 +fairystay=45 +fairywait=30 +fairytestmode=0 +playerdmg=100 +monsterdmg=100 +cfmode=2 + +[server2server] +charip=${CHAR_HOST} +charport=${CHAR_S2S_PORT} +WorldsPort=${WORLD_S2S_PORT} + +[PASSWORDS] +loginpass=${SHARED_LOGIN_PASS} +charpass=${SHARED_CHAR_PASS} +worldpass=${SHARED_WORLD_PASS} diff --git a/docker/entrypoint.sh b/docker/entrypoint.sh new file mode 100755 index 0000000..55f6681 --- /dev/null +++ b/docker/entrypoint.sh @@ -0,0 +1,69 @@ +#!/bin/sh +set -e + +: "${DB_HOST:=db}" +: "${DB_PORT:=3306}" +: "${DB_USER:=rose}" +: "${DB_PASSWORD:=change_me}" +: "${DB_NAME:=ktrose}" + +: "${PUBLIC_HOST:=127.0.0.1}" +: "${CHAR_HOST:=charserver}" + +: "${LOGIN_PORT:=29000}" +: "${LOGIN_SERVER_NAME:=ROSE Login}" +: "${LOGIN_CREATE_ACCOUNT:=1}" + +: "${CHAR_PORT:=29100}" +: "${CHAR_S2S_PORT:=29110}" +: "${CHAR_SERVER_NAME:=Server 1}" +: "${CHAR_DELETE_TIME:=3600}" + +: "${WORLD_PORT:=29200}" +: "${WORLD_S2S_PORT:=29210}" +: "${WORLD_CHANNEL_NAME:=Channel 1}" +: "${WORLD_MAX_CONNECTIONS:=500}" +: "${WORLD_WELCOME_MSG:=Welcome to ROSE}" +: "${WORLD_SAVE_TIME:=3600}" + +: "${SHARED_LOGIN_PASS:=789456}" +: "${SHARED_CHAR_PASS:=456123}" +: "${SHARED_WORLD_PASS:=321654}" + +export DB_HOST DB_PORT DB_USER DB_PASSWORD DB_NAME PUBLIC_HOST CHAR_HOST \ + LOGIN_PORT LOGIN_SERVER_NAME LOGIN_CREATE_ACCOUNT \ + CHAR_PORT CHAR_S2S_PORT CHAR_SERVER_NAME CHAR_DELETE_TIME \ + WORLD_PORT WORLD_S2S_PORT WORLD_CHANNEL_NAME WORLD_MAX_CONNECTIONS \ + WORLD_WELCOME_MSG WORLD_SAVE_TIME \ + SHARED_LOGIN_PASS SHARED_CHAR_PASS SHARED_WORLD_PASS + +mkdir -p /data/conf /data/clanmark /var/log/roseserver + +for tmpl in /app/conf-templates/*.conf.tmpl; do + name=$(basename "$tmpl" .tmpl) + envsubst < "$tmpl" > "/data/conf/$name" +done + +# Seed the clan-mark counter file once; charserver opens it "r+b" and fails +# if it doesn't already exist. +if [ ! -f /data/clanmark/clanmark.cnt ]; then + cp -r /app/seed/clanmark/. /data/clanmark/ +fi + +cd /data + +case "$1" in + login) + exec /app/bin/loginserver /data/conf/loginserver.conf + ;; + char) + exec /app/bin/charserver /data/conf/charserver.conf + ;; + world) + exec /app/bin/worldserver /data/conf/worldserver.conf + ;; + *) + echo "Usage: entrypoint.sh {login|char|world}" >&2 + exit 1 + ;; +esac