Compare commits

..

15 Commits

Author SHA1 Message Date
David Markowitz
8f6ee6327e add deep copy of config data 2024-05-24 21:17:56 -07:00
David Markowitz
d572ce1f1c add back deleter 2024-05-24 03:56:38 -07:00
David Markowitz
85cd609168 remove extra item code 2024-05-23 20:10:44 -07:00
David Markowitz
d4e6939dbd Merge branch 'main' into naming-of-models 2024-05-23 19:51:45 -07:00
David Markowitz
f8d5110290 Update PropertyManagementComponent.cpp
Revert "fix stale reference"

This reverts commit 03b17c0374787bca6dfec2da663da74ac76c26fb.

fix stale reference
2024-05-23 14:42:15 -07:00
David Markowitz
a153d0a78c add settings if they dont exist
add helper for spawnerinfo config data
2024-05-21 04:27:17 -07:00
David Markowitz
bfeb10c972 update ldf logic 2024-05-21 04:06:55 -07:00
David Markowitz
82507e642a add erase var helper for entity 2024-05-21 04:06:16 -07:00
David Markowitz
abd978c348 names and descriptions work 2024-05-20 22:39:56 -07:00
David Markowitz
10f8d40b69 saving changes 2024-05-20 21:24:52 -07:00
David Markowitz
37c2c5db5d remove extra map 2024-05-20 20:51:03 -07:00
David Markowitz
693a2fef35 move logic to Item 2024-05-20 20:49:42 -07:00
David Markowitz
787237c930 modularize loading for all possible extra data 2024-05-20 14:33:10 -07:00
David Markowitz
0464f37fab add declaration to header 2024-05-20 13:39:40 -07:00
David Markowitz
78ae8bc6b6 stubbing for saving item extra data 2024-05-20 13:38:59 -07:00
459 changed files with 48809 additions and 180774 deletions

View File

@@ -73,4 +73,4 @@ cpp_space_around_assignment_operator=insert
cpp_space_pointer_reference_alignment=left cpp_space_pointer_reference_alignment=left
cpp_space_around_ternary_operator=insert cpp_space_around_ternary_operator=insert
cpp_wrap_preserve_blocks=one_liners cpp_wrap_preserve_blocks=one_liners
cpp_indent_comment=false cpp_indent_comment=fasle

View File

@@ -3,20 +3,12 @@ CLIENT_PATH=./client
# Updates NET_VERSION in CMakeVariables.txt # Updates NET_VERSION in CMakeVariables.txt
NET_VERSION=171022 NET_VERSION=171022
# make sure this is a long random string # make sure this is a long random string
# generate a "SHA 256-bit Key" from here: https://gchq.github.io/CyberChef/#recipe=Pseudo-Random_Number_Generator(256,'Hex') # grab a "SHA 256-bit Key" from here: https://keygen.io/
ACCOUNT_MANAGER_SECRET= ACCOUNT_MANAGER_SECRET=
# Should be the externally facing IP of your server host # Should be the externally facing IP of your server host
EXTERNAL_IP=localhost EXTERNAL_IP=localhost
# The database type that will be used.
# Acceptable values are `sqlite`, `mysql`, `mariadb`, `maria`.
# Case insensitive.
DATABASE_TYPE=mariadb
SQLITE_DATABASE_PATH=resServer/dlu.sqlite
# Database values # Database values
# Be careful with special characters here. It is more safe to use normal characters and/or numbers. # Be careful with special characters here. It is more safe to use normal characters and/or numbers.
MARIADB_USER=darkflame MARIADB_USER=darkflame
MARIADB_PASSWORD= MARIADB_PASSWORD=
MARIADB_DATABASE=darkflame MARIADB_DATABASE=darkflame
SKIP_ACCOUNT_CREATION=1

View File

@@ -16,10 +16,7 @@ body:
I have validated that this issue is not a syntax error of either MySQL or SQLite. I have validated that this issue is not a syntax error of either MySQL or SQLite.
required: true required: true
- label: > - label: >
I have downloaded/pulled the latest version of the main branch of DarkflameServer and have confirmed that the issue exists there. I have pulled the latest version of the main branch of DarkflameServer and have confirmed that the issue exists there.
required: true
- label: >
I have verified that my boot.cfg is configured as per the [README](https://github.com/DarkflameUniverse/DarkflameServer?tab=readme-ov-file#allowing-a-user-to-connect-to-your-server).
required: true required: true
- type: input - type: input
id: server-version id: server-version

View File

@@ -16,12 +16,12 @@ jobs:
os: [ windows-2022, ubuntu-22.04, macos-13 ] os: [ windows-2022, ubuntu-22.04, macos-13 ]
steps: steps:
- uses: actions/checkout@85e6279cec87321a52edac9c87bce653a07cf6c2 - uses: actions/checkout@v3
with: with:
submodules: true submodules: true
- name: Add msbuild to PATH (Windows only) - name: Add msbuild to PATH (Windows only)
if: ${{ matrix.os == 'windows-2022' }} if: ${{ matrix.os == 'windows-2022' }}
uses: microsoft/setup-msbuild@767f00a3f09872d96a0cb9fcd5e6a4ff33311330 uses: microsoft/setup-msbuild@v1.1
with: with:
vs-version: '[17,18)' vs-version: '[17,18)'
msbuild-architecture: x64 msbuild-architecture: x64
@@ -30,27 +30,24 @@ jobs:
run: | run: |
brew install openssl@3 brew install openssl@3
sudo xcode-select -s /Applications/Xcode_15.2.app/Contents/Developer sudo xcode-select -s /Applications/Xcode_15.2.app/Contents/Developer
- name: Get CMake 3.x
uses: lukka/get-cmake@28983e0d3955dba2bb0a6810caae0c6cf268ec0c
with:
cmakeVersion: "~3.25.0" # <--= optional, use most recent 3.25.x version
- name: cmake - name: cmake
uses: lukka/run-cmake@67c73a83a46f86c4e0b96b741ac37ff495478c38 uses: lukka/run-cmake@v10
with: with:
workflowPreset: "ci-${{matrix.os}}" configurePreset: "ci-${{matrix.os}}"
buildPreset: "ci-${{matrix.os}}"
testPreset: "ci-${{matrix.os}}"
- name: artifacts - name: artifacts
uses: actions/upload-artifact@6027e3dd177782cd8ab9af838c04fd81a07f1d47 uses: actions/upload-artifact@v3
with: with:
name: build-${{matrix.os}} name: build-${{matrix.os}}
path: | path: |
build/*/*Server* build/*Server*
build/*/*.ini build/*.ini
build/*/*.so build/*.so
build/*/*.dll build/*.dll
build/*/*.dylib build/vanity/
build/*/vanity/ build/navmeshes/
build/*/navmeshes/ build/migrations/
build/*/migrations/ build/*.dcf
build/*/*.dcf !build/*.pdb
!build/*/*.pdb !build/d*/
!build/*/d*/

5
.gitignore vendored
View File

@@ -6,6 +6,7 @@ docker/configs
# Third party libraries # Third party libraries
thirdparty/mysql/ thirdparty/mysql/
thirdparty/mysql_linux/ thirdparty/mysql_linux/
CMakeVariables.txt
# Build folders # Build folders
build/ build/
@@ -94,7 +95,6 @@ ipch/
# Exceptions: # Exceptions:
CMakeSettings.json CMakeSettings.json
CMakeUserPresets.json
*.vcxproj *.vcxproj
*.filters *.filters
*.cmake *.cmake
@@ -122,7 +122,4 @@ docker/__pycache__
docker-compose.override.yml docker-compose.override.yml
!*Test.bin !*Test.bin
# CMake scripts
!cmake/* !cmake/*
!cmake/toolchains/*

3
.gitmodules vendored
View File

@@ -1,3 +1,6 @@
[submodule "thirdparty/cpp-httplib"]
path = thirdparty/cpp-httplib
url = https://github.com/yhirose/cpp-httplib
[submodule "thirdparty/tinyxml2"] [submodule "thirdparty/tinyxml2"]
path = thirdparty/tinyxml2 path = thirdparty/tinyxml2
url = https://github.com/leethomason/tinyxml2 url = https://github.com/leethomason/tinyxml2

View File

@@ -1,8 +1,5 @@
cmake_minimum_required(VERSION 3.25) cmake_minimum_required(VERSION 3.25)
project(Darkflame project(Darkflame)
HOMEPAGE_URL "https://github.com/DarkflameUniverse/DarkflameServer"
LANGUAGES C CXX
)
# check if the path to the source directory contains a space # check if the path to the source directory contains a space
if("${CMAKE_SOURCE_DIR}" MATCHES " ") if("${CMAKE_SOURCE_DIR}" MATCHES " ")
@@ -11,10 +8,8 @@ endif()
include(CTest) include(CTest)
set(CMAKE_C_STANDARD 99)
set(CMAKE_CXX_STANDARD 20) set(CMAKE_CXX_STANDARD 20)
set(CMAKE_C_STANDARD_REQUIRED ON) set(CXX_STANDARD_REQUIRED ON)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
set(CMAKE_EXPORT_COMPILE_COMMANDS ON) # Export the compile commands for debugging set(CMAKE_EXPORT_COMPILE_COMMANDS ON) # Export the compile commands for debugging
set(CMAKE_POLICY_DEFAULT_CMP0063 NEW) # Set CMAKE visibility policy to NEW on project and subprojects set(CMAKE_POLICY_DEFAULT_CMP0063 NEW) # Set CMAKE visibility policy to NEW on project and subprojects
set(CMAKE_VISIBILITY_INLINES_HIDDEN ON) # Set C and C++ symbol visibility to hide inlined functions set(CMAKE_VISIBILITY_INLINES_HIDDEN ON) # Set C and C++ symbol visibility to hide inlined functions
@@ -66,37 +61,35 @@ set(RECASTNAVIGATION_EXAMPLES OFF CACHE BOOL "" FORCE)
# Disabled no-register # Disabled no-register
# Disabled unknown pragmas because Linux doesn't understand Windows pragmas. # Disabled unknown pragmas because Linux doesn't understand Windows pragmas.
if(UNIX) if(UNIX)
add_link_options("-Wl,-rpath,$ORIGIN/") set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -O2 -Wuninitialized -fPIC")
add_compile_options("-fPIC")
add_compile_definitions(_GLIBCXX_USE_CXX11_ABI=0 _GLIBCXX_USE_CXX17_ABI=0) add_compile_definitions(_GLIBCXX_USE_CXX11_ABI=0 _GLIBCXX_USE_CXX17_ABI=0)
# For all except Clang and Apple Clang if(NOT APPLE)
if(NOT CMAKE_CXX_COMPILER_ID MATCHES "Clang") set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -static-libgcc -lstdc++fs")
add_compile_options("-static-libgcc" "-lstdc++fs")
endif() endif()
if(${DYNAMIC} AND CMAKE_CXX_COMPILER_ID STREQUAL "GNU") if(${DYNAMIC} AND CMAKE_CXX_COMPILER_ID STREQUAL "GNU")
add_link_options("-export-dynamic") set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -rdynamic")
endif() endif()
if(${GGDB}) if(${GGDB})
add_compile_options("-ggdb") set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -ggdb")
endif() endif()
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -std=c99 -O2 -fPIC")
elseif(MSVC) elseif(MSVC)
# Skip warning for invalid conversion from size_t to uint32_t for all targets below for now # Skip warning for invalid conversion from size_t to uint32_t for all targets below for now
# Also disable non-portable MSVC volatile behavior # Also disable non-portable MSVC volatile behavior
add_compile_options("/wd4267" "/utf-8" "/volatile:iso" "/Zc:inline") add_compile_options("/wd4267" "/utf-8" "/volatile:iso")
elseif(WIN32) elseif(WIN32)
add_compile_definitions(_CRT_SECURE_NO_WARNINGS) add_compile_definitions(_CRT_SECURE_NO_WARNINGS)
endif() endif()
# Our output dir # Our output dir
set(CMAKE_BINARY_DIR ${PROJECT_BINARY_DIR})
#set(CMAKE_INCLUDE_CURRENT_DIR_IN_INTERFACE ON) # unfortunately, forces all libraries to be built in series, which will slow down the build process #set(CMAKE_INCLUDE_CURRENT_DIR_IN_INTERFACE ON) # unfortunately, forces all libraries to be built in series, which will slow down the build process
# TODO make this not have to override the build type directories # TODO make this not have to override the build type directories
set(CMAKE_RUNTIME_OUTPUT_DIRECTORY_DEBUG ${CMAKE_BINARY_DIR})
set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY_DEBUG ${CMAKE_BINARY_DIR})
set(CMAKE_LIBRARY_OUTPUT_DIRECTORY_DEBUG ${CMAKE_BINARY_DIR})
set(CMAKE_RUNTIME_OUTPUT_DIRECTORY_RELWITHDEBINFO ${CMAKE_BINARY_DIR}) set(CMAKE_RUNTIME_OUTPUT_DIRECTORY_RELWITHDEBINFO ${CMAKE_BINARY_DIR})
set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY_RELWITHDEBINFO ${CMAKE_BINARY_DIR}) set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY_RELWITHDEBINFO ${CMAKE_BINARY_DIR})
set(CMAKE_LIBRARY_OUTPUT_DIRECTORY_RELWITHDEBINFO ${CMAKE_BINARY_DIR}) set(CMAKE_LIBRARY_OUTPUT_DIRECTORY_RELWITHDEBINFO ${CMAKE_BINARY_DIR})
@@ -116,39 +109,31 @@ make_directory(${CMAKE_BINARY_DIR}/resServer)
# Create a /logs directory # Create a /logs directory
make_directory(${CMAKE_BINARY_DIR}/logs) make_directory(${CMAKE_BINARY_DIR}/logs)
# Get DLU config directory
if(DEFINED ENV{DLU_CONFIG_DIR})
set(DLU_CONFIG_DIR $ENV{DLU_CONFIG_DIR})
else()
set(DLU_CONFIG_DIR ${PROJECT_BINARY_DIR})
endif()
message(STATUS "Variable: DLU_CONFIG_DIR = ${DLU_CONFIG_DIR}")
# Copy resource files on first build # Copy resource files on first build
set(RESOURCE_FILES "sharedconfig.ini" "authconfig.ini" "chatconfig.ini" "worldconfig.ini" "masterconfig.ini" "blocklist.dcf") set(RESOURCE_FILES "sharedconfig.ini" "authconfig.ini" "chatconfig.ini" "worldconfig.ini" "masterconfig.ini" "blocklist.dcf")
message(STATUS "Checking resource file integrity") message(STATUS "Checking resource file integrity")
include(Utils) include(Utils)
UpdateConfigOption(${DLU_CONFIG_DIR}/authconfig.ini "port" "auth_server_port") UpdateConfigOption(${PROJECT_BINARY_DIR}/authconfig.ini "port" "auth_server_port")
UpdateConfigOption(${DLU_CONFIG_DIR}/chatconfig.ini "port" "chat_server_port") UpdateConfigOption(${PROJECT_BINARY_DIR}/chatconfig.ini "port" "chat_server_port")
UpdateConfigOption(${DLU_CONFIG_DIR}/masterconfig.ini "port" "master_server_port") UpdateConfigOption(${PROJECT_BINARY_DIR}/masterconfig.ini "port" "master_server_port")
foreach(resource_file ${RESOURCE_FILES}) foreach(resource_file ${RESOURCE_FILES})
set(file_size 0) set(file_size 0)
if(EXISTS ${DLU_CONFIG_DIR}/${resource_file}) if(EXISTS ${PROJECT_BINARY_DIR}/${resource_file})
file(SIZE ${DLU_CONFIG_DIR}/${resource_file} file_size) file(SIZE ${PROJECT_BINARY_DIR}/${resource_file} file_size)
endif() endif()
if(${file_size} EQUAL 0) if(${file_size} EQUAL 0)
configure_file( configure_file(
${CMAKE_SOURCE_DIR}/resources/${resource_file} ${DLU_CONFIG_DIR}/${resource_file} ${CMAKE_SOURCE_DIR}/resources/${resource_file} ${PROJECT_BINARY_DIR}/${resource_file}
COPYONLY COPYONLY
) )
message(STATUS "Moved " ${resource_file} " to DLU config directory") message(STATUS "Moved " ${resource_file} " to project binary directory")
elseif(resource_file MATCHES ".ini") elseif(resource_file MATCHES ".ini")
message(STATUS "Checking " ${resource_file} " for missing config options") message(STATUS "Checking " ${resource_file} " for missing config options")
file(READ ${DLU_CONFIG_DIR}/${resource_file} current_file_contents) file(READ ${PROJECT_BINARY_DIR}/${resource_file} current_file_contents)
string(REPLACE "\\\n" "" current_file_contents ${current_file_contents}) string(REPLACE "\\\n" "" current_file_contents ${current_file_contents})
string(REPLACE "\n" ";" current_file_contents ${current_file_contents}) string(REPLACE "\n" ";" current_file_contents ${current_file_contents})
set(parsed_current_file_contents "") set(parsed_current_file_contents "")
@@ -175,18 +160,16 @@ foreach(resource_file ${RESOURCE_FILES})
list(GET line_split 0 variable_name) list(GET line_split 0 variable_name)
if(NOT ${parsed_current_file_contents} MATCHES ${variable_name}) if(NOT ${parsed_current_file_contents} MATCHES ${variable_name})
# For backwards compatibility with older setup versions, dont add this option. message(STATUS "Adding missing config option " ${variable_name} " to " ${resource_file})
if(NOT ${variable_name} MATCHES "database_type") set(line_to_add ${line_to_add} ${line})
message(STATUS "Adding missing config option " ${variable_name} " to " ${resource_file})
set(line_to_add ${line_to_add} ${line})
foreach(line_to_append ${line_to_add}) foreach(line_to_append ${line_to_add})
file(APPEND ${DLU_CONFIG_DIR}/${resource_file} "\n" ${line_to_append}) file(APPEND ${PROJECT_BINARY_DIR}/${resource_file} "\n" ${line_to_append})
endforeach() endforeach()
file(APPEND ${DLU_CONFIG_DIR}/${resource_file} "\n") file(APPEND ${PROJECT_BINARY_DIR}/${resource_file} "\n")
endif()
endif() endif()
set(line_to_add "") set(line_to_add "")
else() else()
set(line_to_add ${line_to_add} ${line}) set(line_to_add ${line_to_add} ${line})
@@ -216,8 +199,21 @@ foreach(file ${VANITY_FILES})
endforeach() endforeach()
# Move our migrations for MasterServer to run # Move our migrations for MasterServer to run
file(REMOVE_RECURSE ${PROJECT_BINARY_DIR}/migrations) file(MAKE_DIRECTORY ${PROJECT_BINARY_DIR}/migrations/dlu/)
file(COPY ${CMAKE_SOURCE_DIR}/migrations DESTINATION ${CMAKE_BINARY_DIR}) file(GLOB SQL_FILES ${CMAKE_SOURCE_DIR}/migrations/dlu/*.sql)
foreach(file ${SQL_FILES})
get_filename_component(file ${file} NAME)
configure_file(${CMAKE_SOURCE_DIR}/migrations/dlu/${file} ${PROJECT_BINARY_DIR}/migrations/dlu/${file})
endforeach()
file(MAKE_DIRECTORY ${PROJECT_BINARY_DIR}/migrations/cdserver/)
file(GLOB SQL_FILES ${CMAKE_SOURCE_DIR}/migrations/cdserver/*.sql)
foreach(file ${SQL_FILES})
get_filename_component(file ${file} NAME)
configure_file(${CMAKE_SOURCE_DIR}/migrations/cdserver/${file} ${PROJECT_BINARY_DIR}/migrations/cdserver/${file})
endforeach()
# Add system specfic includes for Apple, Windows and Other Unix OS' (including Linux) # Add system specfic includes for Apple, Windows and Other Unix OS' (including Linux)
if (APPLE) if (APPLE)
@@ -235,23 +231,19 @@ include_directories(
"dNet" "dNet"
"dWeb"
"tests" "tests"
"tests/dCommonTests" "tests/dCommonTests"
"tests/dGameTests" "tests/dGameTests"
"tests/dGameTests/dComponentsTests" "tests/dGameTests/dComponentsTests"
SYSTEM SYSTEM "thirdparty/magic_enum/include/magic_enum"
"thirdparty/magic_enum/include/magic_enum" SYSTEM "thirdparty/raknet/Source"
"thirdparty/raknet/Source" SYSTEM "thirdparty/tinyxml2"
"thirdparty/tinyxml2" SYSTEM "thirdparty/recastnavigation"
"thirdparty/recastnavigation" SYSTEM "thirdparty/SQLite"
"thirdparty/SQLite" SYSTEM "thirdparty/cpplinq"
"thirdparty/cpplinq" SYSTEM "thirdparty/cpp-httplib"
"thirdparty/MD5" SYSTEM "thirdparty/MD5"
"thirdparty/nlohmann"
"thirdparty/mongoose"
) )
# Add system specfic includes for Apple, Windows and Other Unix OS' (including Linux) # Add system specfic includes for Apple, Windows and Other Unix OS' (including Linux)
@@ -260,17 +252,10 @@ if(APPLE)
include_directories("/usr/local/include/") include_directories("/usr/local/include/")
endif() endif()
# Set warning flags
if(MSVC)
# add_compile_options("/W4")
# Want to enable warnings eventually, but WAY too much noise right now
elseif(CMAKE_CXX_COMPILER_ID MATCHES "Clang|GNU")
add_compile_options("-Wuninitialized" "-Wold-style-cast")
else()
message(WARNING "Unknown compiler: '${CMAKE_CXX_COMPILER_ID}' - No warning flags enabled.")
endif()
# Add linking directories: # Add linking directories:
if (UNIX)
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wold-style-cast -Werror") # Warning flags
endif()
file( file(
GLOB HEADERS_DZONEMANAGER GLOB HEADERS_DZONEMANAGER
LIST_DIRECTORIES false LIST_DIRECTORIES false
@@ -303,10 +288,9 @@ add_subdirectory(dZoneManager)
add_subdirectory(dNavigation) add_subdirectory(dNavigation)
add_subdirectory(dPhysics) add_subdirectory(dPhysics)
add_subdirectory(dServer) add_subdirectory(dServer)
add_subdirectory(dWeb)
# Create a list of common libraries shared between all binaries # Create a list of common libraries shared between all binaries
set(COMMON_LIBRARIES "dCommon" "dDatabase" "dNet" "raknet" "magic_enum") set(COMMON_LIBRARIES "dCommon" "dDatabase" "dNet" "raknet" "MariaDB::ConnCpp" "magic_enum")
# Add platform specific common libraries # Add platform specific common libraries
if(UNIX) if(UNIX)

View File

@@ -1,638 +1,128 @@
{ {
"version": 6, "version": 3,
"cmakeMinimumRequired": { "cmakeMinimumRequired": {
"major": 3, "major": 3,
"minor": 25, "minor": 14,
"patch": 0 "patch": 0
}, },
"configurePresets": [ "configurePresets": [
{ {
"name": "default", "name": "default",
"displayName": "Default configure step", "displayName": "Default configure step",
"description": "Use 'build' dir and Unix makefiles", "description": "Use 'build' dir and Unix makefiles",
"binaryDir": "${sourceDir}/build", "binaryDir": "${sourceDir}/build",
"generator": "Unix Makefiles" "generator": "Unix Makefiles"
}, },
{ {
"name": "debug-config", "name": "ci-ubuntu-22.04",
"hidden": true, "displayName": "CI configure step for Ubuntu",
"cacheVariables": { "description": "Same as default, Used in GitHub actions workflow",
"CMAKE_BUILD_TYPE": "Debug" "inherits": "default"
} },
}, {
{ "name": "ci-macos-13",
"name": "relwithdebinfo-config", "displayName": "CI configure step for MacOS",
"hidden": true, "description": "Same as default, Used in GitHub actions workflow",
"cacheVariables": { "inherits": "default"
"CMAKE_BUILD_TYPE": "RelWithDebInfo" },
} {
}, "name": "ci-windows-2022",
{ "displayName": "CI configure step for Windows",
"name": "release-config", "description": "Set architecture to 64-bit (b/c RakNet)",
"hidden": true, "inherits": "default",
"cacheVariables": { "generator": "Visual Studio 17 2022",
"CMAKE_BUILD_TYPE": "Release" "architecture": {
} "value": "x64"
}, },
{ "cacheVariables": {
"name": "clang-config", "CMAKE_BUILD_TYPE": "RelWithDebInfo"
"hidden": true, }
"toolchainFile": "${sourceDir}/cmake/toolchains/linux-clang.cmake" },
}, {
{ "name": "windows-default",
"name": "gnu-config", "inherits": "ci-windows-2022",
"hidden": true, "displayName": "Windows only Configure Settings",
"toolchainFile": "${sourceDir}/cmake/toolchains/linux-gnu.cmake" "description": "Sets build and install directories",
}, "generator": "Ninja",
{ "architecture": {
"name": "windows-msvc", "value": "x64",
"inherits": "default", "strategy": "external"
"displayName": "[Multi] Windows (MSVC)", }
"description": "Set architecture to 64-bit (b/c RakNet)", }
"generator": "Visual Studio 17 2022", ],
"binaryDir": "${sourceDir}/build/msvc", "buildPresets": [
"architecture": { {
"value": "x64" "name": "default",
}, "configurePreset": "default",
"condition": { "displayName": "Default Build",
"type": "equals", "description": "Default Build",
"lhs": "${hostSystemName}", "jobs": 2
"rhs": "Windows" },
} {
}, "name": "ci-windows-2022",
{ "configurePreset": "ci-windows-2022",
"name": "windows-default", "displayName": "Windows CI Build",
"inherits": "windows-msvc", "description": "This preset is used by the CI build on windows",
"displayName": "Windows only Configure Settings", "configuration": "RelWithDebInfo",
"description": "Sets build and install directories", "jobs": 2
"generator": "Ninja", },
"condition": { {
"type": "equals", "name": "ci-ubuntu-22.04",
"lhs": "${hostSystemName}", "configurePreset": "ci-ubuntu-22.04",
"rhs": "Windows" "displayName": "Linux CI Build",
}, "description": "This preset is used by the CI build on linux",
"architecture": { "jobs": 2
"value": "x64" },
} {
}, "name": "ci-macos-13",
{ "configurePreset": "ci-macos-13",
"name": "linux-config", "displayName": "MacOS CI Build",
"inherits": "default", "description": "This preset is used by the CI build on MacOS",
"hidden": true, "jobs": 2
"condition": { }
"type": "equals", ],
"lhs": "${hostSystemName}", "testPresets": [
"rhs": "Linux" {
} "name": "ci-ubuntu-22.04",
}, "configurePreset": "ci-ubuntu-22.04",
{ "displayName": "CI Tests on Linux",
"name": "linux-clang-debug", "description": "Runs all tests on a linux configuration",
"inherits": [ "execution": {
"linux-config",
"clang-config",
"debug-config"
],
"displayName": "EXPERIMENTAL - [Debug] Linux (Clang)",
"description": "Create a debug build using the Clang toolchain for Linux",
"binaryDir": "${sourceDir}/build/clang-debug"
},
{
"name": "linux-clang-relwithdebinfo",
"inherits": [
"linux-config",
"clang-config",
"relwithdebinfo-config"
],
"displayName": "EXPERIMENTAL - [RelWithDebInfo] Linux (Clang)",
"description": "Create a release build with debug info using the Clang toolchain for Linux",
"binaryDir": "${sourceDir}/build/clang-relwithdebinfo"
},
{
"name": "linux-clang-release",
"inherits": [
"linux-config",
"clang-config",
"release-config"
],
"displayName": "EXPERIMENTAL - [Release] Linux (Clang)",
"description": "Create a release build using the Clang toolchain for Linux",
"binaryDir": "${sourceDir}/build/clang-release"
},
{
"name": "linux-gnu-debug",
"inherits": [
"linux-config",
"gnu-config",
"debug-config"
],
"displayName": "[Debug] Linux (GNU)",
"description": "Create a debug build using the GNU toolchain for Linux",
"binaryDir": "${sourceDir}/build/gnu-debug"
},
{
"name": "linux-gnu-relwithdebinfo",
"inherits": [
"linux-config",
"gnu-config",
"relwithdebinfo-config"
],
"displayName": "[RelWithDebInfo] Linux (GNU)",
"description": "Create a release build with debug info using the GNU toolchain for Linux",
"binaryDir": "${sourceDir}/build/gnu-relwithdebinfo"
},
{
"name": "linux-gnu-release",
"inherits": [
"linux-config",
"gnu-config",
"release-config"
],
"displayName": "[Release] Linux (GNU)",
"description": "Create a release build using the GNU toolchain for Linux",
"binaryDir": "${sourceDir}/build/gnu-release"
},
{
"name": "macos",
"inherits": "default",
"displayName": "[Multi] MacOS",
"description": "Create a build for MacOS",
"condition": {
"type": "equals",
"lhs": "${hostSystemName}",
"rhs": "Darwin"
},
"binaryDir": "${sourceDir}/build/macos"
}
],
"buildPresets": [
{
"name": "default",
"configurePreset": "default",
"displayName": "Default Build",
"description": "Default Build",
"jobs": 2 "jobs": 2
}, },
{ "output": {
"name": "windows-msvc-debug", "outputOnFailure": true
"inherits": "default", }
"configurePreset": "windows-msvc", },
"displayName": "[Debug] Windows (MSVC)", {
"description": "This preset is used to build in debug mode using the MSVC toolchain on Windows", "name": "ci-macos-13",
"configuration": "Debug" "configurePreset": "ci-macos-13",
"displayName": "CI Tests on MacOS",
"description": "Runs all tests on a Mac configuration",
"execution": {
"jobs": 2
}, },
{ "output": {
"name": "windows-msvc-relwithdebinfo", "outputOnFailure": true
"inherits": "default", }
"configurePreset": "windows-msvc", },
"displayName": "[RelWithDebInfo] Windows (MSVC)", {
"description": "This preset is used to build in debug mode using the MSVC toolchain on Windows", "name": "ci-windows-2022",
"configuration": "RelWithDebInfo" "configurePreset": "ci-windows-2022",
"displayName": "CI Tests on windows",
"description": "Runs all tests on a windows configuration",
"configuration": "RelWithDebInfo",
"execution": {
"jobs": 2
}, },
{ "output": {
"name": "windows-msvc-release", "outputOnFailure": true
"inherits": "default", },
"configurePreset": "windows-msvc", "filter": {
"displayName": "[Release] Windows (MSVC)", "exclude": {
"description": "This preset is used to build in release mode using the MSVC toolchain on Windows", "name": "((example)|(minigzip))+"
"configuration": "Release"
},
{
"name": "linux-clang-debug",
"inherits": "default",
"configurePreset": "linux-clang-debug",
"displayName": "EXPERIMENTAL - [Debug] Linux (Clang)",
"description": "This preset is used to build in debug mode using the Clang toolchain on Linux",
"configuration": "Debug"
},
{
"name": "linux-clang-relwithdebinfo",
"inherits": "default",
"configurePreset": "linux-clang-relwithdebinfo",
"displayName": "EXPERIMENTAL - [RelWithDebInfo] Linux (Clang)",
"description": "This preset is used to build in release mode with debug info using the Clang toolchain on Linux",
"configuration": "RelWithDebInfo"
},
{
"name": "linux-clang-release",
"inherits": "default",
"configurePreset": "linux-clang-release",
"displayName": "EXPERIMENTAL - [Release] Linux (Clang)",
"description": "This preset is used to build in release mode using the Clang toolchain on Linux",
"configuration": "Release"
},
{
"name": "linux-gnu-debug",
"inherits": "default",
"configurePreset": "linux-gnu-debug",
"displayName": "[Debug] Linux (GNU)",
"description": "This preset is used to build in debug mode using the GNU toolchain on Linux",
"configuration": "Debug"
},
{
"name": "linux-gnu-relwithdebinfo",
"inherits": "default",
"configurePreset": "linux-gnu-relwithdebinfo",
"displayName": "[RelWithDebInfo] Linux (GNU)",
"description": "This preset is used to build in release mode with debug info using the GNU toolchain on Linux",
"configuration": "RelWithDebInfo"
},
{
"name": "linux-gnu-release",
"inherits": "default",
"configurePreset": "linux-gnu-release",
"displayName": "[Release] Linux (GNU)",
"description": "This preset is used to build in release mode using the GNU toolchain on Linux",
"configuration": "Release"
},
{
"name": "macos-debug",
"inherits": "default",
"configurePreset": "macos",
"displayName": "[Debug] MacOS",
"description": "This preset is used to build in debug mode on MacOS",
"configuration": "Debug"
},
{
"name": "macos-relwithdebinfo",
"inherits": "default",
"configurePreset": "macos",
"displayName": "[RelWithDebInfo] MacOS",
"description": "This preset is used to build in release mode with debug info on MacOS",
"configuration": "RelWithDebInfo"
},
{
"name": "macos-release",
"inherits": "default",
"configurePreset": "macos",
"displayName": "[Release] MacOS",
"description": "This preset is used to build in release mode on MacOS",
"configuration": "Release"
}
],
"testPresets": [
{
"name": "default",
"configurePreset": "default",
"execution": {
"jobs": 2
},
"output": {
"outputOnFailure": true
} }
},
{
"name": "windows-msvc-test",
"inherits": "default",
"configurePreset": "windows-msvc",
"hidden": true,
"filter": {
"exclude": {
"name": "((example)|(minigzip))+"
}
}
},
{
"name": "windows-msvc-debug",
"inherits": "windows-msvc-test",
"configurePreset": "windows-msvc",
"displayName": "[Debug] Windows (MSVC)",
"description": "Runs all tests on a Windows configuration",
"configuration": "Debug"
},
{
"name": "windows-msvc-relwithdebinfo",
"inherits": "windows-msvc-test",
"configurePreset": "windows-msvc",
"displayName": "[RelWithDebInfo] Windows (MSVC)",
"description": "Runs all tests on a Windows configuration",
"configuration": "RelWithDebInfo"
},
{
"name": "windows-msvc-release",
"inherits": "windows-msvc-test",
"configurePreset": "windows-msvc",
"displayName": "[Release] Windows (MSVC)",
"description": "Runs all tests on a Windows configuration",
"configuration": "Release"
},
{
"name": "linux-clang-debug",
"inherits": "default",
"configurePreset": "linux-clang-debug",
"displayName": "EXPERIMENTAL - [Debug] Linux (Clang)",
"description": "Runs all tests on a Linux Clang configuration",
"configuration": "Release"
},
{
"name": "linux-clang-relwithdebinfo",
"inherits": "default",
"configurePreset": "linux-clang-relwithdebinfo",
"displayName": "EXPERIMENTAL - [RelWithDebInfo] Linux (Clang)",
"description": "Runs all tests on a Linux Clang configuration",
"configuration": "RelWithDebInfo"
},
{
"name": "linux-clang-release",
"inherits": "default",
"configurePreset": "linux-clang-release",
"displayName": "EXPERIMENTAL - [Release] Linux (Clang)",
"description": "Runs all tests on a Linux Clang configuration",
"configuration": "Release"
},
{
"name": "linux-gnu-debug",
"inherits": "default",
"configurePreset": "linux-gnu-debug",
"displayName": "[Debug] Linux (GNU)",
"description": "Runs all tests on a Linux GNU configuration",
"configuration": "Release"
},
{
"name": "linux-gnu-relwithdebinfo",
"inherits": "default",
"configurePreset": "linux-gnu-relwithdebinfo",
"displayName": "[RelWithDebInfo] Linux (GNU)",
"description": "Runs all tests on a Linux GNU configuration",
"configuration": "RelWithDebInfo"
},
{
"name": "linux-gnu-release",
"inherits": "default",
"configurePreset": "linux-gnu-release",
"displayName": "[Release] Linux (GNU)",
"description": "Runs all tests on a Linux GNU configuration",
"configuration": "Release"
},
{
"name": "macos-debug",
"inherits": "default",
"configurePreset": "macos",
"displayName": "[Debug] MacOS",
"description": "Runs all tests on a MacOS configuration",
"configuration": "Debug"
},
{
"name": "macos-relwithdebinfo",
"inherits": "default",
"configurePreset": "macos",
"displayName": "[RelWithDebInfo] MacOS",
"description": "Runs all tests on a MacOS configuration",
"configuration": "RelWithDebInfo"
},
{
"name": "macos-release",
"inherits": "default",
"configurePreset": "macos",
"displayName": "[Release] MacOS",
"description": "Runs all tests on a MacOS configuration",
"configuration": "Release"
} }
], }
"workflowPresets": [ ]
{ }
"name": "default",
"steps": [
{
"type": "configure",
"name": "default"
},
{
"type": "build",
"name": "default"
},
{
"type": "test",
"name": "default"
}
]
},
{
"name": "windows-msvc-debug",
"displayName": "[Debug] Windows (MSVC)",
"description": "MSVC debug workflow preset for Windows",
"steps": [
{
"type": "configure",
"name": "windows-msvc"
},
{
"type": "build",
"name": "windows-msvc-debug"
},
{
"type": "test",
"name": "windows-msvc-debug"
}
]
},
{
"name": "windows-msvc-relwithdebinfo",
"displayName": "[RelWithDebInfo] Windows (MSVC)",
"description": "MSVC release with debug info workflow preset for Windows",
"steps": [
{
"type": "configure",
"name": "windows-msvc"
},
{
"type": "build",
"name": "windows-msvc-relwithdebinfo"
},
{
"type": "test",
"name": "windows-msvc-relwithdebinfo"
}
]
},
{
"name": "ci-windows-2022",
"displayName": "[Release] Windows (MSVC)",
"description": "CI workflow preset for Windows",
"steps": [
{
"type": "configure",
"name": "windows-msvc"
},
{
"type": "build",
"name": "windows-msvc-release"
},
{
"type": "test",
"name": "windows-msvc-release"
}
]
},
{
"name": "linux-gnu-debug",
"displayName": "[Debug] Linux (GNU)",
"description": "GNU debug workflow preset for Linux",
"steps": [
{
"type": "configure",
"name": "linux-gnu-debug"
},
{
"type": "build",
"name": "linux-gnu-debug"
},
{
"type": "test",
"name": "linux-gnu-debug"
}
]
},
{
"name": "linux-gnu-relwithdebinfo",
"displayName": "[RelWithDebInfo] Linux (GNU)",
"description": "GNU release with debug info workflow preset for Linux",
"steps": [
{
"type": "configure",
"name": "linux-gnu-relwithdebinfo"
},
{
"type": "build",
"name": "linux-gnu-relwithdebinfo"
},
{
"type": "test",
"name": "linux-gnu-relwithdebinfo"
}
]
},
{
"name": "ci-ubuntu-22.04",
"displayName": "[Release] Linux (GNU)",
"description": "CI workflow preset for Ubuntu",
"steps": [
{
"type": "configure",
"name": "linux-gnu-release"
},
{
"type": "build",
"name": "linux-gnu-release"
},
{
"type": "test",
"name": "linux-gnu-release"
}
]
},
{
"name": "linux-clang-debug",
"displayName": "EXPERIMENTAL - [Debug] Linux (Clang)",
"description": "Clang debug workflow preset for Linux",
"steps": [
{
"type": "configure",
"name": "linux-clang-debug"
},
{
"type": "build",
"name": "linux-clang-debug"
},
{
"type": "test",
"name": "linux-clang-debug"
}
]
},
{
"name": "linux-clang-relwithdebinfo",
"displayName": "EXPERIMENTAL - [RelWithDebInfo] Linux (Clang)",
"description": "Clang release with debug info workflow preset for Linux",
"steps": [
{
"type": "configure",
"name": "linux-clang-relwithdebinfo"
},
{
"type": "build",
"name": "linux-clang-relwithdebinfo"
},
{
"type": "test",
"name": "linux-clang-relwithdebinfo"
}
]
},
{
"name": "linux-clang-release",
"displayName": "EXPERIMENTAL - [Release] Linux (Clang)",
"description": "Clang release workflow preset for Linux",
"steps": [
{
"type": "configure",
"name": "linux-clang-release"
},
{
"type": "build",
"name": "linux-clang-release"
},
{
"type": "test",
"name": "linux-clang-release"
}
]
},
{
"name": "macos-debug",
"displayName": "[Debug] MacOS",
"description": "Release workflow preset for MacOS",
"steps": [
{
"type": "configure",
"name": "macos"
},
{
"type": "build",
"name": "macos-debug"
},
{
"type": "test",
"name": "macos-debug"
}
]
},
{
"name": "macos-relwithdebinfo",
"displayName": "[RelWithDebInfo] MacOS",
"description": "Release with debug info workflow preset for MacOS",
"steps": [
{
"type": "configure",
"name": "macos"
},
{
"type": "build",
"name": "macos-relwithdebinfo"
},
{
"type": "test",
"name": "macos-relwithdebinfo"
}
]
},
{
"name": "ci-macos-13",
"displayName": "[Release] MacOS",
"description": "CI workflow preset for MacOS",
"steps": [
{
"type": "configure",
"name": "macos"
},
{
"type": "build",
"name": "macos-release"
},
{
"type": "test",
"name": "macos-release"
}
]
}
]
}

View File

@@ -1,5 +1,5 @@
PROJECT_VERSION_MAJOR=3 PROJECT_VERSION_MAJOR=2
PROJECT_VERSION_MINOR=0 PROJECT_VERSION_MINOR=3
PROJECT_VERSION_PATCH=0 PROJECT_VERSION_PATCH=0
# Debugging # Debugging

View File

@@ -11,12 +11,7 @@ COPY --chmod=0500 ./build.sh /app/
RUN sed -i 's/MARIADB_CONNECTOR_COMPILE_JOBS__=.*/MARIADB_CONNECTOR_COMPILE_JOBS__=2/' /app/CMakeVariables.txt RUN sed -i 's/MARIADB_CONNECTOR_COMPILE_JOBS__=.*/MARIADB_CONNECTOR_COMPILE_JOBS__=2/' /app/CMakeVariables.txt
RUN --mount=type=cache,target=/app/build,id=build-cache \ RUN ./build.sh
mkdir -p /app/build /tmp/persisted-build && \
cd /app/build && \
cmake .. && \
make -j$(nproc --ignore 1) && \
cp -r /app/build/* /tmp/persisted-build/
FROM debian:12 as runtime FROM debian:12 as runtime
@@ -28,23 +23,23 @@ RUN --mount=type=cache,id=build-apt-cache,target=/var/cache/apt \
rm -rf /var/lib/apt/lists/* rm -rf /var/lib/apt/lists/*
# Grab libraries and load them # Grab libraries and load them
COPY --from=build /tmp/persisted-build/mariadbcpp/libmariadbcpp.so /usr/local/lib/ COPY --from=build /app/build/mariadbcpp/libmariadbcpp.so /usr/local/lib/
RUN ldconfig RUN ldconfig
# Server bins # Server bins
COPY --from=build /tmp/persisted-build/*Server /app/ COPY --from=build /app/build/*Server /app/
# Necessary suplimentary files # Necessary suplimentary files
COPY --from=build /tmp/persisted-build/*.ini /app/configs/ COPY --from=build /app/build/*.ini /app/configs/
COPY --from=build /tmp/persisted-build/vanity/*.* /app/vanity/ COPY --from=build /app/build/vanity/*.* /app/vanity/*
COPY --from=build /tmp/persisted-build/navmeshes /app/navmeshes COPY --from=build /app/build/navmeshes /app/navmeshes
COPY --from=build /tmp/persisted-build/migrations /app/migrations COPY --from=build /app/build/migrations /app/migrations
COPY --from=build /tmp/persisted-build/*.dcf /app/ COPY --from=build /app/build/*.dcf /app/
# backup of config and vanity files to copy to the host incase # backup of config and vanity files to copy to the host incase
# of a mount clobbering the copy from above # of a mount clobbering the copy from above
COPY --from=build /tmp/persisted-build/*.ini /app/default-configs/ COPY --from=build /app/build/*.ini /app/default-configs/
COPY --from=build /tmp/persisted-build/vanity/*.* /app/default-vanity/ COPY --from=build /app/build/vanity/*.* /app/default-vanity/*
# needed as the container runs with the root user # needed as the container runs with the root user
# and therefore sudo doesn't exist # and therefore sudo doesn't exist

105
README.md
View File

@@ -13,35 +13,21 @@ Darkflame Universe is licensed under AGPLv3, please read [LICENSE](LICENSE). Som
* You must disclose any changes you make to the code when you distribute it * You must disclose any changes you make to the code when you distribute it
* Hosting a server for others counts as distribution * Hosting a server for others counts as distribution
## Disclaimers
### Setup difficulty
Throughout the entire build and setup process a level of familiarity with the command line and preferably a Unix-like development environment is greatly advantageous.
### Hosting a server ### Hosting a server
We do not recommend hosting public servers. Darkflame Universe is intended for small scale deployment, for example within a group of friends. It has not been tested for large scale deployment which comes with additional security risks. We do not recommend hosting public servers. Darkflame Universe is intended for small scale deployment, for example within a group of friends. It has not been tested for large scale deployment which comes with additional security risks.
### Supply of resource files ### Supply of resource files
Darkflame Universe is a server emulator and does not distribute any LEGO® Universe files. A separate game client is required to setup this server emulator and play the game, which we cannot supply. Users are strongly suggested to refer to the safe checksums listed [here](#verifying-your-client-files) to see if a client will work. Darkflame Universe is a server emulator and does not distribute any LEGO® Universe files. A separate game client is required to setup this server emulator and play the game, which we cannot supply. Users are strongly suggested to refer to the safe checksums listed [here](#verifying-your-client-files) to see if a client will work.
## Setting up a single player server ## Step by step walkthrough for a single-player server
* If you don't know what WSL is, skip this warning. If you would like a setup for a single player server only on a Windows machine, use the [Native Windows Setup Guide by HailStorm](https://gist.github.com/HailStorm32/169df65a47a104199b5cc57d10fa57de) and skip this README.
Warning: WSL version 1 does NOT support using sqlite as a database due to how it handles filesystem synchronization.
You must use Version 2 if you must run the server under WSL. Not doing so will result in save data loss.
* Single player installs now no longer require building the server from source or installing development tools.
* Download the [latest windows release](https://github.com/DarkflameUniverse/DarkflameServer/releases) (or whichever release you need) and extract the files into a folder inside your client. Note that this setup is expecting that when double clicking the folder that you put in the same folder as `legouniverse.exe`, the file `MasterServer.exe` is in there.
* You should be able to see the folder with the server files in the same folder as `legouniverse.exe`.
* Go into the server files folder and open `sharedconfig.ini`. Find the line that says `client_location` and put `..` after it so the line reads `client_location=..`.
* To run the server, double-click `MasterServer.exe`.
* You will be asked to create an account the first time you run the server. After you have created the account, the server will shutdown and need to be restarted.
* To connect to the server, either delete the file `boot.cfg` which is found in your LEGO Universe client, rename the file `boot.cfg` to something else or follow the steps [here](#allowing-a-user-to-connect-to-your-server) if you wish to keep the file.
* When shutting down the server, it is highly recommended to click the `MasterServer.exe` window and hold `ctrl` while pressing `c` to stop the server.
* We are working on a way to make it so when you close the game, the server stops automatically alongside when you open the game, the server starts automatically.
* If you are not setting a server up on mac, you can ignore this note
* Note: you'll need to allow through System Preferences `AuthServer`, `ChatServer`, `MasterServer`, `WorldServer` and `libmariadbcpp.dylib` to run. The initial pop-up will block it due to the binaries being unsigned, after allowing them to run the servers will run as normal.
<font size="32">**If you are not planning on hosting a server for others, working in the codebase or wanting to use MariaDB for a database, you can stop reading here.**</font> ## Steps to setup server
If you would like to use a MariaDB as a database instead of the default of sqlite, follow the steps [here](#database-setup).
# Steps to setup a development environment
* [Clone this repository](#clone-the-repository) * [Clone this repository](#clone-the-repository)
* [Setting up a development environment](#setting-up-a-development-environment)
* [Install dependencies](#install-dependencies) * [Install dependencies](#install-dependencies)
* [Database setup](#database-setup) * [Database setup](#database-setup)
* [Build the server](#build-the-server) * [Build the server](#build-the-server)
@@ -53,13 +39,6 @@ If you would like to use a MariaDB as a database instead of the default of sqlit
* [User Guide](#user-guide) * [User Guide](#user-guide)
* [Docker](#docker) * [Docker](#docker)
## Disclaimers
### Setup difficulty
Throughout the entire build and setup process a level of familiarity with the command line and preferably a Unix-like development environment is greatly advantageous.
## Step by step walkthrough for building a single-player Windows server from source
If you would like a setup for a single player server only on a Windows machine built from source, use the [Native Windows Setup Guide by HailStorm](https://gist.github.com/HailStorm32/169df65a47a104199b5cc57d10fa57de) and skip this README.
## Clone the repository ## Clone the repository
If you are on Windows, you will need to download and install git from [here](https://git-scm.com/download/win) If you are on Windows, you will need to download and install git from [here](https://git-scm.com/download/win)
@@ -70,15 +49,9 @@ git clone --recursive https://github.com/DarkflameUniverse/DarkflameServer
## Install dependencies ## Install dependencies
### Required compiler versions
- g++11 or greater
- MSVC unchecked
- clang unchecked
- appleclang unchecked
### Windows packages ### Windows packages
Ensure that you have either the [MSVC C++ compiler](https://visualstudio.microsoft.com/vs/features/cplusplus/) (recommended) or the [Clang compiler](https://github.com/llvm/llvm-project/releases/) installed. Ensure that you have either the [MSVC C++ compiler](https://visualstudio.microsoft.com/vs/features/cplusplus/) (recommended) or the [Clang compiler](https://github.com/llvm/llvm-project/releases/) installed.
You'll also need to download and install [CMake](https://cmake.org/download/) (<font size="4">**version 3.25**</font> up to <font size="4">**version 3.31**</font>!). You'll also need to download and install [CMake](https://cmake.org/download/) (version <font size="4">**CMake version 3.25**</font> or later!).
### MacOS packages ### MacOS packages
Ensure you have [brew](https://brew.sh) installed. Ensure you have [brew](https://brew.sh) installed.
@@ -100,7 +73,7 @@ sudo apt install build-essential gcc zlib1g-dev libssl-dev openssl mariadb-serve
``` ```
#### Required CMake version #### Required CMake version
This project uses <font size="4">**CMake version 3.25**</font> up to <font size="4">**version 3.31**</font> and as such you will need to ensure you have this version installed. This project uses <font size="4">**CMake version 3.25**</font> or higher and as such you will need to ensure you have this version installed.
You can check your CMake version by using the following command in a terminal. You can check your CMake version by using the following command in a terminal.
```bash ```bash
cmake --version cmake --version
@@ -210,7 +183,6 @@ If you would like to build the server faster, append `-j<number>` where number i
### Notes ### Notes
Depending on your operating system, you may need to adjust some pre-processor defines in [CMakeVariables.txt](./CMakeVariables.txt) before building: Depending on your operating system, you may need to adjust some pre-processor defines in [CMakeVariables.txt](./CMakeVariables.txt) before building:
* If you are on MacOS, ensure OPENSSL_ROOT_DIR is pointing to the openssl root directory. * If you are on MacOS, ensure OPENSSL_ROOT_DIR is pointing to the openssl root directory.
* By default it should be set to the correct directory.
* If you are using a Darkflame Universe client, ensure `client_net_version` in `build/sharedconfig.ini` is changed to 171023. * If you are using a Darkflame Universe client, ensure `client_net_version` in `build/sharedconfig.ini` is changed to 171023.
## Configuring your server ## Configuring your server
@@ -233,41 +205,28 @@ Navigate to `build/sharedconfig.ini` and fill in the following fields:
* `chatconfig.ini` contains a port option. * `chatconfig.ini` contains a port option.
* `masterconfig.ini` contains options related to permissions you want to run your servers with. * `masterconfig.ini` contains options related to permissions you want to run your servers with.
* `sharedconfig.ini` contains several options that are shared across all servers * `sharedconfig.ini` contains several options that are shared across all servers
* `worldconfig.ini` contains several options to turn on Quality of Life improvements should you want them. If you would like the most vanilla experience possible, you will need to turn some of these settings off. * `worldconfig.ini` contains several options to turn on QOL improvements should you want them. If you would like the most vanilla experience possible, you will need to turn some of these settings off.
## Verify your setup ## Verify your setup
Your build directory should contain at a minimum all of the following files. Your build directory should now look like this:
All listed files are required for a server to start. * AuthServer
`ini` files can be located at the environment variable `DLU_CONFIG_DIR` and do not need to be located in this directory. * ChatServer
(windows will have .exe at the end of the executables): * MasterServer
* sharedconfig.ini * WorldServer
* AuthServer(.exe)
* authconfig.ini * authconfig.ini
* ChatServer(.exe)
* chatconfig.ini * chatconfig.ini
* MasterServer(.exe)
* masterconfig.ini * masterconfig.ini
* WorldServer(.exe) * sharedconfig.ini
* worldconfig.ini * worldconfig.ini
* blocklist.dcf * ...
* migrations
* vanity
* navmeshes
* 1 of the following lists based on platform
* windows
* libmariadb.dll
* mariadbcpp.dll
* zlib.dll
* MacOS
* libmariadbcpp.dylib
* *nix
* libmariadbcpp.so
## Running the server ## Running the server
If everything has been configured correctly you should now be able to run the `MasterServer` binary which is located in the `build` directory. Darkflame Universe utilizes port numbers under 1024, so under Linux you have to give the `AuthServer` binary network permissions by running the following command: If everything has been configured correctly you should now be able to run the `MasterServer` binary which is located in the `build` directory. Darkflame Universe utilizes port numbers under 1024, so under Linux you either have to give the `AuthServer` binary network permissions or run it under sudo.
To give `AuthServer` network permissions and not require sudo, run the following command
```bash ```bash
sudo setcap 'cap_net_bind_service=+ep' AuthServer sudo setcap 'cap_net_bind_service=+ep' AuthServer
``` ```
and then go to `build/masterconfig.ini` and change `use_sudo_auth` to 0.
### Linux Service ### Linux Service
If you are running this on a linux based system, it will use your terminal to run the program interactively, preventing you using it for other tasks and requiring it to be open to run the server. If you are running this on a linux based system, it will use your terminal to run the program interactively, preventing you using it for other tasks and requiring it to be open to run the server.
@@ -307,8 +266,8 @@ systemctl stop darkflame.service
journalctl -xeu darkflame.service journalctl -xeu darkflame.service
``` ```
### First user or adding more users. ### First admin user
The first time you run `MasterServer`, you will be prompted to create an account. To create more accounts from the command line, `MasterServer -a` to get prompted to create an admin account. This method is only intended for the system administrator as a means to get started, do NOT use this method to create accounts for other users! Run `MasterServer -a` to get prompted to create an admin account. This method is only intended for the system administrator as a means to get started, do NOT use this method to create accounts for other users!
### Account management tool (Nexus Dashboard) ### Account management tool (Nexus Dashboard)
**If you are just using this server for yourself, you can skip setting up Nexus Dashboard** **If you are just using this server for yourself, you can skip setting up Nexus Dashboard**
@@ -324,22 +283,14 @@ While a character has a gmlevel of anything but `0`, some gameplay behavior will
Some changes to the client `boot.cfg` file are needed to play on your server. Some changes to the client `boot.cfg` file are needed to play on your server.
## Allowing a user to connect to your server ## Allowing a user to connect to your server
**ALL OF THESE CHANGES ARE REQUIRED. PLEASE FULLY READ THIS SECTION**
To connect to a server follow these steps: To connect to a server follow these steps:
* In the client directory, locate `boot.cfg` * In the client directory, locate `boot.cfg`
* Open `boot.cfg` in a text editor and locate the line `UGCUSE3DSERVICES=7:` * Open it in a text editor and locate where it says `AUTHSERVERIP=0:`
* Ensure the number after the 7 is a `0`
* Alternatively, remove the line with `UGCUSE3DSERVICES` altogether
* Next locate where it says `AUTHSERVERIP=0:`
* Replace the contents after to `:` and the following `,` with what you configured as the server's public facing IP. For example `AUTHSERVERIP=0:localhost` for locally hosted servers * Replace the contents after to `:` and the following `,` with what you configured as the server's public facing IP. For example `AUTHSERVERIP=0:localhost` for locally hosted servers
* Next locate the line `UGCUSE3DSERVICES=7:`
* Ensure the number after the 7 is a `0`
* Launch `legouniverse.exe`, through `wine` if on a Unix-like operating system * Launch `legouniverse.exe`, through `wine` if on a Unix-like operating system
* Note that if you are on WSL2, you will need to configure the public IP in the server and client to be the IP of the WSL2 instance and not localhost, which can be found by running `ifconfig` in the terminal. Windows defaults to WSL1, so this will not apply to most users. * Note that if you are on WSL2, you will need to configure the public IP in the server and client to be the IP of the WSL2 instance and not localhost, which can be found by running `ifconfig` in the terminal. Windows defaults to WSL1, so this will not apply to most users.
As an example, here is what the boot.cfg is required to contain for a server with the ip 12.34.56.78
```cfg
AUTHSERVERIP=0:12.34.56.78,
UGCUSE3DSERVICES=7:0
```
## Updating your server ## Updating your server
To update your server to the latest version navigate to your cloned directory To update your server to the latest version navigate to your cloned directory
@@ -356,10 +307,6 @@ Now follow the [build](#build-the-server) section for your system and your serve
## In-game commands ## In-game commands
* A list of all in-game commands can be found [here](./docs/Commands.md). * A list of all in-game commands can be found [here](./docs/Commands.md).
## Chat Web API
* The Chat server has an API that can be enabled via `chatconfig.ini`.
* You can view the OpenAPI doc for the API here [here](./docs/ChatWebAPI.yaml).
## Verifying your client files ## Verifying your client files
### LEGO® Universe 1.10.64 ### LEGO® Universe 1.10.64
@@ -424,7 +371,7 @@ at once. For that:
- Download the [.env.example](.env.example) file and place it next to `client` with the file name `.env` - Download the [.env.example](.env.example) file and place it next to `client` with the file name `.env`
- You may get warnings that this name starts with a dot, acknowledge those, this is intentional. Depending on your operating system, you may need to activate showing hidden files (e.g. Ctrl-H in Gnome on Linux) and/or file extensions ("File name extensions" in the "View" tab on Windows). - You may get warnings that this name starts with a dot, acknowledge those, this is intentional. Depending on your operating system, you may need to activate showing hidden files (e.g. Ctrl-H in Gnome on Linux) and/or file extensions ("File name extensions" in the "View" tab on Windows).
- Update the `ACCOUNT_MANAGER_SECRET` and `MARIADB_PASSWORD` with strong random passwords. - Update the `ACCOUNT_MANAGER_SECRET` and `MARIADB_PASSWORD` with strong random passwords.
- Use a password generator <https://gchq.github.io/CyberChef/#recipe=Pseudo-Random_Number_Generator(256,'Hex')> - Use a password generator like <https://keygen.io>
- Avoid `:` and `@` characters - Avoid `:` and `@` characters
- Once the database user is created, changing the password will not update it, so the server will just fail to connect. - Once the database user is created, changing the password will not update it, so the server will just fail to connect.
- Set `EXTERNAL_IP` to your LAN IP or public IP if you want to host the game for friends & family - Set `EXTERNAL_IP` to your LAN IP or public IP if you want to host the game for friends & family

View File

@@ -6,7 +6,8 @@ mkdir -p build
cd build cd build
# Run cmake to generate make files # Run cmake to generate make files
cmake -DCMAKE_BUILD_TYPE="Release" .. cmake ..
# To build utilizing multiple cores, append `-j` and the amount of cores to utilize, for example `cmake --build . --config Release -j8' # To build utilizing multiple cores, append `-j` and the amount of cores to utilize, for example `cmake --build . --config Release -j8'
cmake --build . --config Release $1 cmake --build . --config Release $1

View File

@@ -1,14 +0,0 @@
# Try and find a clang-16 install, falling back to a generic clang install otherwise
find_program(CLANG_C_COMPILER clang-16 | clang REQUIRED)
find_program(CLANG_CXX_COMPILER clang++-16 | clang++ REQUIRED)
# Debug messages
message(DEBUG "CLANG_C_COMPILER = ${CLANG_C_COMPILER}")
message(DEBUG "CLANG_CXX_COMPILER = ${CLANG_CXX_COMPILER}")
# Set compilers to clang (need to cache for VSCode tools to work correctly)
set(CMAKE_C_COMPILER ${CLANG_C_COMPILER} CACHE STRING "Set C compiler")
set(CMAKE_CXX_COMPILER ${CLANG_CXX_COMPILER} CACHE STRING "Set C++ compiler")
# Set linker to lld
add_link_options("-fuse-ld=lld")

View File

@@ -1,11 +0,0 @@
# Try and find a gcc/g++ install
find_program(GNU_C_COMPILER cc | gcc REQUIRED)
find_program(GNU_CXX_COMPILER c++ | g++ REQUIRED)
# Debug messages
message(DEBUG "GNU_C_COMPILER = ${GNU_C_COMPILER}")
message(DEBUG "GNU_CXX_COMPILER = ${GNU_CXX_COMPILER}")
# Set compilers to GNU (need to cache for VSCode tools to work correctly)
set(CMAKE_C_COMPILER ${GNU_C_COMPILER} CACHE STRING "Set C compiler")
set(CMAKE_CXX_COMPILER ${GNU_CXX_COMPILER} CACHE STRING "Set C++ compiler")

View File

@@ -21,12 +21,13 @@
//Auth includes: //Auth includes:
#include "AuthPackets.h" #include "AuthPackets.h"
#include "eConnectionType.h" #include "eConnectionType.h"
#include "MessageType/Server.h" #include "eServerMessageType.h"
#include "MessageType/Auth.h" #include "eAuthMessageType.h"
#include "Game.h" #include "Game.h"
#include "Server.h" #include "Server.h"
namespace Game { namespace Game {
Logger* logger = nullptr; Logger* logger = nullptr;
dServer* server = nullptr; dServer* server = nullptr;
@@ -59,7 +60,7 @@ int main(int argc, char** argv) {
try { try {
Database::Connect(); Database::Connect();
} catch (std::exception& ex) { } catch (sql::SQLException& ex) {
LOG("Got an error while connecting to the database: %s", ex.what()); LOG("Got an error while connecting to the database: %s", ex.what());
Database::Destroy("AuthServer"); Database::Destroy("AuthServer");
delete Game::server; delete Game::server;
@@ -70,15 +71,12 @@ int main(int argc, char** argv) {
//Find out the master's IP: //Find out the master's IP:
std::string masterIP; std::string masterIP;
uint32_t masterPort = 1500; uint32_t masterPort = 1500;
std::string masterPassword;
auto masterInfo = Database::Get()->GetMasterInfo(); auto masterInfo = Database::Get()->GetMasterInfo();
if (masterInfo) { if (masterInfo) {
masterIP = masterInfo->ip; masterIP = masterInfo->ip;
masterPort = masterInfo->port; masterPort = masterInfo->port;
masterPassword = masterInfo->password;
} }
LOG("Master is at %s:%d", masterIP.c_str(), masterPort); LOG("Master is at %s:%d", masterIP.c_str(), masterPort);
Game::randomEngine = std::mt19937(time(0)); Game::randomEngine = std::mt19937(time(0));
@@ -92,7 +90,7 @@ int main(int argc, char** argv) {
const auto externalIPString = Game::config->GetValue("external_ip"); const auto externalIPString = Game::config->GetValue("external_ip");
if (!externalIPString.empty()) ourIP = externalIPString; if (!externalIPString.empty()) ourIP = externalIPString;
Game::server = new dServer(ourIP, ourPort, 0, maxClients, false, true, Game::logger, masterIP, masterPort, ServerType::Auth, Game::config, &Game::lastSignal, masterPassword); Game::server = new dServer(ourIP, ourPort, 0, maxClients, false, true, Game::logger, masterIP, masterPort, ServerType::Auth, Game::config, &Game::lastSignal);
//Run it until server gets a kill message from Master: //Run it until server gets a kill message from Master:
auto t = std::chrono::high_resolution_clock::now(); auto t = std::chrono::high_resolution_clock::now();
@@ -104,7 +102,7 @@ int main(int argc, char** argv) {
uint32_t framesSinceLastSQLPing = 0; uint32_t framesSinceLastSQLPing = 0;
AuthPackets::LoadClaimCodes(); AuthPackets::LoadClaimCodes();
Game::logger->Flush(); // once immediately before main loop Game::logger->Flush(); // once immediately before main loop
while (!Game::ShouldShutdown()) { while (!Game::ShouldShutdown()) {
//Check if we're still connected to master: //Check if we're still connected to master:
@@ -168,11 +166,11 @@ void HandlePacket(Packet* packet) {
if (packet->data[0] == ID_USER_PACKET_ENUM) { if (packet->data[0] == ID_USER_PACKET_ENUM) {
if (static_cast<eConnectionType>(packet->data[1]) == eConnectionType::SERVER) { if (static_cast<eConnectionType>(packet->data[1]) == eConnectionType::SERVER) {
if (static_cast<MessageType::Server>(packet->data[3]) == MessageType::Server::VERSION_CONFIRM) { if (static_cast<eServerMessageType>(packet->data[3]) == eServerMessageType::VERSION_CONFIRM) {
AuthPackets::HandleHandshake(Game::server, packet); AuthPackets::HandleHandshake(Game::server, packet);
} }
} else if (static_cast<eConnectionType>(packet->data[1]) == eConnectionType::AUTH) { } else if (static_cast<eConnectionType>(packet->data[1]) == eConnectionType::AUTH) {
if (static_cast<MessageType::Auth>(packet->data[3]) == MessageType::Auth::LOGIN_REQUEST) { if (static_cast<eAuthMessageType>(packet->data[3]) == eAuthMessageType::LOGIN_REQUEST) {
AuthPackets::HandleLoginRequest(Game::server, packet); AuthPackets::HandleLoginRequest(Game::server, packet);
} }
} }

View File

@@ -1,4 +1,7 @@
add_executable(AuthServer "AuthServer.cpp") add_executable(AuthServer "AuthServer.cpp")
target_link_libraries(AuthServer ${COMMON_LIBRARIES} dServer) target_link_libraries(AuthServer ${COMMON_LIBRARIES} dServer)
target_include_directories(AuthServer PRIVATE ${PROJECT_SOURCE_DIR}/dServer) target_include_directories(AuthServer PRIVATE ${PROJECT_SOURCE_DIR}/dServer)
add_compile_definitions(AuthServer PRIVATE PROJECT_VERSION="\"${PROJECT_VERSION}\"") add_compile_definitions(AuthServer PRIVATE PROJECT_VERSION="\"${PROJECT_VERSION}\"")

View File

@@ -105,7 +105,7 @@ void dChatFilter::ExportWordlistToDCF(const std::string& filepath, bool allowLis
} }
} }
std::set<std::pair<uint8_t, uint8_t>> dChatFilter::IsSentenceOkay(const std::string& message, eGameMasterLevel gmLevel, bool allowList) { std::vector<std::pair<uint8_t, uint8_t>> dChatFilter::IsSentenceOkay(const std::string& message, eGameMasterLevel gmLevel, bool allowList) {
if (gmLevel > eGameMasterLevel::FORUM_MODERATOR) return { }; //If anything but a forum mod, return true. if (gmLevel > eGameMasterLevel::FORUM_MODERATOR) return { }; //If anything but a forum mod, return true.
if (message.empty()) return { }; if (message.empty()) return { };
if (!allowList && m_DeniedWords.empty()) return { { 0, message.length() } }; if (!allowList && m_DeniedWords.empty()) return { { 0, message.length() } };
@@ -114,7 +114,7 @@ std::set<std::pair<uint8_t, uint8_t>> dChatFilter::IsSentenceOkay(const std::str
std::string segment; std::string segment;
std::regex reg("(!*|\\?*|\\;*|\\.*|\\,*)"); std::regex reg("(!*|\\?*|\\;*|\\.*|\\,*)");
std::set<std::pair<uint8_t, uint8_t>> listOfBadSegments; std::vector<std::pair<uint8_t, uint8_t>> listOfBadSegments = std::vector<std::pair<uint8_t, uint8_t>>();
uint32_t position = 0; uint32_t position = 0;
@@ -127,17 +127,17 @@ std::set<std::pair<uint8_t, uint8_t>> dChatFilter::IsSentenceOkay(const std::str
size_t hash = CalculateHash(segment); size_t hash = CalculateHash(segment);
if (std::find(m_UserUnapprovedWordCache.begin(), m_UserUnapprovedWordCache.end(), hash) != m_UserUnapprovedWordCache.end() && allowList) { if (std::find(m_UserUnapprovedWordCache.begin(), m_UserUnapprovedWordCache.end(), hash) != m_UserUnapprovedWordCache.end() && allowList) {
listOfBadSegments.emplace(position, originalSegment.length()); listOfBadSegments.emplace_back(position, originalSegment.length());
} }
if (std::find(m_ApprovedWords.begin(), m_ApprovedWords.end(), hash) == m_ApprovedWords.end() && allowList) { if (std::find(m_ApprovedWords.begin(), m_ApprovedWords.end(), hash) == m_ApprovedWords.end() && allowList) {
m_UserUnapprovedWordCache.push_back(hash); m_UserUnapprovedWordCache.push_back(hash);
listOfBadSegments.emplace(position, originalSegment.length()); listOfBadSegments.emplace_back(position, originalSegment.length());
} }
if (std::find(m_DeniedWords.begin(), m_DeniedWords.end(), hash) != m_DeniedWords.end() && !allowList) { if (std::find(m_DeniedWords.begin(), m_DeniedWords.end(), hash) != m_DeniedWords.end() && !allowList) {
m_UserUnapprovedWordCache.push_back(hash); m_UserUnapprovedWordCache.push_back(hash);
listOfBadSegments.emplace(position, originalSegment.length()); listOfBadSegments.emplace_back(position, originalSegment.length());
} }
position += originalSegment.length() + 1; position += originalSegment.length() + 1;

View File

@@ -24,7 +24,7 @@ public:
void ReadWordlistPlaintext(const std::string& filepath, bool allowList); void ReadWordlistPlaintext(const std::string& filepath, bool allowList);
bool ReadWordlistDCF(const std::string& filepath, bool allowList); bool ReadWordlistDCF(const std::string& filepath, bool allowList);
void ExportWordlistToDCF(const std::string& filepath, bool allowList); void ExportWordlistToDCF(const std::string& filepath, bool allowList);
std::set<std::pair<uint8_t, uint8_t>> IsSentenceOkay(const std::string& message, eGameMasterLevel gmLevel, bool allowList = true); std::vector<std::pair<uint8_t, uint8_t>> IsSentenceOkay(const std::string& message, eGameMasterLevel gmLevel, bool allowList = true);
private: private:
bool m_DontGenerateDCF; bool m_DontGenerateDCF;

View File

@@ -1,19 +1,16 @@
set(DCHATSERVER_SOURCES set(DCHATSERVER_SOURCES
"ChatIgnoreList.cpp" "ChatIgnoreList.cpp"
"ChatPacketHandler.cpp" "ChatPacketHandler.cpp"
"ChatJSONUtils.cpp"
"ChatWeb.cpp"
"PlayerContainer.cpp" "PlayerContainer.cpp"
"TeamContainer.cpp"
) )
add_executable(ChatServer "ChatServer.cpp") add_executable(ChatServer "ChatServer.cpp")
target_include_directories(ChatServer PRIVATE "${PROJECT_SOURCE_DIR}/dChatFilter" "${PROJECT_SOURCE_DIR}/dWeb") target_include_directories(ChatServer PRIVATE "${PROJECT_SOURCE_DIR}/dChatFilter")
add_compile_definitions(ChatServer PRIVATE PROJECT_VERSION="\"${PROJECT_VERSION}\"") add_compile_definitions(ChatServer PRIVATE PROJECT_VERSION="\"${PROJECT_VERSION}\"")
add_library(dChatServer ${DCHATSERVER_SOURCES}) add_library(dChatServer ${DCHATSERVER_SOURCES})
target_include_directories(dChatServer PRIVATE "${PROJECT_SOURCE_DIR}/dServer" "${PROJECT_SOURCE_DIR}/dChatFilter") target_include_directories(dChatServer PRIVATE "${PROJECT_SOURCE_DIR}/dServer")
target_link_libraries(dChatServer ${COMMON_LIBRARIES} dChatFilter) target_link_libraries(dChatServer ${COMMON_LIBRARIES} dChatFilter)
target_link_libraries(ChatServer ${COMMON_LIBRARIES} dChatFilter dChatServer dServer mongoose dWeb) target_link_libraries(ChatServer ${COMMON_LIBRARIES} dChatFilter dChatServer dServer)

View File

@@ -1,6 +1,6 @@
#include "ChatIgnoreList.h" #include "ChatIgnoreList.h"
#include "PlayerContainer.h" #include "PlayerContainer.h"
#include "MessageType/Chat.h" #include "eChatMessageType.h"
#include "BitStreamUtils.h" #include "BitStreamUtils.h"
#include "Game.h" #include "Game.h"
#include "Logger.h" #include "Logger.h"
@@ -12,8 +12,8 @@
// not allowing teams, rejecting DMs, friends requets etc. // not allowing teams, rejecting DMs, friends requets etc.
// The only thing not auto-handled is instance activities force joining the team on the server. // The only thing not auto-handled is instance activities force joining the team on the server.
void WriteOutgoingReplyHeader(RakNet::BitStream& bitStream, const LWOOBJID& receivingPlayer, const MessageType::Client type) { void WriteOutgoingReplyHeader(RakNet::BitStream& bitStream, const LWOOBJID& receivingPlayer, const ChatIgnoreList::Response type) {
BitStreamUtils::WriteHeader(bitStream, eConnectionType::CHAT, MessageType::Chat::WORLD_ROUTE_PACKET); BitStreamUtils::WriteHeader(bitStream, eConnectionType::CHAT, eChatMessageType::WORLD_ROUTE_PACKET);
bitStream.Write(receivingPlayer); bitStream.Write(receivingPlayer);
//portion that will get routed: //portion that will get routed:
@@ -48,9 +48,9 @@ void ChatIgnoreList::GetIgnoreList(Packet* packet) {
} }
CBITSTREAM; CBITSTREAM;
WriteOutgoingReplyHeader(bitStream, receiver.playerID, MessageType::Client::GET_IGNORE_LIST_RESPONSE); WriteOutgoingReplyHeader(bitStream, receiver.playerID, ChatIgnoreList::Response::GET_IGNORE);
bitStream.Write<uint8_t>(false); // Is Free Trial, but we don't care about that bitStream.Write<uint8_t>(false); // Probably is Is Free Trial, but we don't care about that
bitStream.Write<uint16_t>(0); // literally spacing due to struct alignment bitStream.Write<uint16_t>(0); // literally spacing due to struct alignment
bitStream.Write<uint16_t>(receiver.ignoredPlayers.size()); bitStream.Write<uint16_t>(receiver.ignoredPlayers.size());
@@ -86,7 +86,7 @@ void ChatIgnoreList::AddIgnore(Packet* packet) {
std::string toIgnoreStr = toIgnoreName.GetAsString(); std::string toIgnoreStr = toIgnoreName.GetAsString();
CBITSTREAM; CBITSTREAM;
WriteOutgoingReplyHeader(bitStream, receiver.playerID, MessageType::Client::ADD_IGNORE_RESPONSE); WriteOutgoingReplyHeader(bitStream, receiver.playerID, ChatIgnoreList::Response::ADD_IGNORE);
// Check if the player exists // Check if the player exists
LWOOBJID ignoredPlayerId = LWOOBJID_EMPTY; LWOOBJID ignoredPlayerId = LWOOBJID_EMPTY;
@@ -161,7 +161,7 @@ void ChatIgnoreList::RemoveIgnore(Packet* packet) {
receiver.ignoredPlayers.erase(toRemove, receiver.ignoredPlayers.end()); receiver.ignoredPlayers.erase(toRemove, receiver.ignoredPlayers.end());
CBITSTREAM; CBITSTREAM;
WriteOutgoingReplyHeader(bitStream, receiver.playerID, MessageType::Client::REMOVE_IGNORE_RESPONSE); WriteOutgoingReplyHeader(bitStream, receiver.playerID, ChatIgnoreList::Response::REMOVE_IGNORE);
bitStream.Write<int8_t>(0); bitStream.Write<int8_t>(0);
LUWString playerNameSend(removedIgnoreStr, 33); LUWString playerNameSend(removedIgnoreStr, 33);

View File

@@ -5,16 +5,17 @@ struct Packet;
#include <cstdint> #include <cstdint>
/**
* @brief The ignore list allows players to ignore someone silently. Requests will generally be blocked by the client, but they should also be checked
* on the server as well so the sender can get a generic error code in response.
*
*/
namespace ChatIgnoreList { namespace ChatIgnoreList {
void GetIgnoreList(Packet* packet); void GetIgnoreList(Packet* packet);
void AddIgnore(Packet* packet); void AddIgnore(Packet* packet);
void RemoveIgnore(Packet* packet); void RemoveIgnore(Packet* packet);
enum class Response : uint8_t {
ADD_IGNORE = 32,
REMOVE_IGNORE = 33,
GET_IGNORE = 34,
};
enum class AddResponse : uint8_t { enum class AddResponse : uint8_t {
SUCCESS, SUCCESS,
ALREADY_IGNORED, ALREADY_IGNORED,

View File

@@ -1,49 +0,0 @@
#include "ChatJSONUtils.h"
#include "json.hpp"
using json = nlohmann::json;
void to_json(json& data, const PlayerData& playerData) {
data["id"] = playerData.playerID;
data["name"] = playerData.playerName;
data["gm_level"] = playerData.gmLevel;
data["muted"] = playerData.GetIsMuted();
auto& zoneID = data["zone_id"];
zoneID["map_id"] = playerData.zoneID.GetMapID();
zoneID["instance_id"] = playerData.zoneID.GetInstanceID();
zoneID["clone_id"] = playerData.zoneID.GetCloneID();
}
void to_json(json& data, const PlayerContainer& playerContainer) {
data = json::array();
for (auto& playerData : playerContainer.GetAllPlayers()) {
if (playerData.first == LWOOBJID_EMPTY) continue;
data.push_back(playerData.second);
}
}
void to_json(json& data, const TeamData& teamData) {
data["id"] = teamData.teamID;
data["loot_flag"] = teamData.lootFlag;
data["local"] = teamData.local;
auto& leader = Game::playerContainer.GetPlayerData(teamData.leaderID);
data["leader"] = leader.playerName;
auto& members = data["members"];
for (auto& member : teamData.memberIDs) {
auto& playerData = Game::playerContainer.GetPlayerData(member);
if (!playerData) continue;
members.push_back(playerData);
}
}
void TeamContainer::to_json(json& data, const TeamContainer::Data& teamContainer) {
for (auto& teamData : TeamContainer::GetTeams()) {
if (!teamData) continue;
data.push_back(*teamData);
}
}

View File

@@ -1,18 +0,0 @@
#ifndef __CHATJSONUTILS_H__
#define __CHATJSONUTILS_H__
#include "json_fwd.hpp"
#include "PlayerContainer.h"
#include "TeamContainer.h"
/* Remember, to_json needs to be in the same namespace as the class its located in */
void to_json(nlohmann::json& data, const PlayerData& playerData);
void to_json(nlohmann::json& data, const PlayerContainer& playerContainer);
void to_json(nlohmann::json& data, const TeamData& teamData);
namespace TeamContainer {
void to_json(nlohmann::json& data, const TeamContainer::Data& teamData);
};
#endif // !__CHATJSONUTILS_H__

View File

@@ -13,13 +13,12 @@
#include "dConfig.h" #include "dConfig.h"
#include "eObjectBits.h" #include "eObjectBits.h"
#include "eConnectionType.h" #include "eConnectionType.h"
#include "MessageType/Chat.h" #include "eChatMessageType.h"
#include "MessageType/Client.h" #include "eClientMessageType.h"
#include "MessageType/Game.h" #include "eGameMessageType.h"
#include "StringifiedEnum.h" #include "StringifiedEnum.h"
#include "eGameMasterLevel.h" #include "eGameMasterLevel.h"
#include "ChatPackets.h" #include "ChatPackets.h"
#include "TeamContainer.h"
void ChatPacketHandler::HandleFriendlistRequest(Packet* packet) { void ChatPacketHandler::HandleFriendlistRequest(Packet* packet) {
//Get from the packet which player we want to do something with: //Get from the packet which player we want to do something with:
@@ -50,7 +49,7 @@ void ChatPacketHandler::HandleFriendlistRequest(Packet* packet) {
fd.zoneID = fr.zoneID; fd.zoneID = fr.zoneID;
//Since this friend is online, we need to update them on the fact that we've just logged in: //Since this friend is online, we need to update them on the fact that we've just logged in:
if (player.isLogin) SendFriendUpdate(fr, player, 1, fd.isBestFriend); SendFriendUpdate(fr, player, 1, fd.isBestFriend);
} else { } else {
fd.isOnline = false; fd.isOnline = false;
fd.zoneID = LWOZONEID(); fd.zoneID = LWOZONEID();
@@ -61,11 +60,11 @@ void ChatPacketHandler::HandleFriendlistRequest(Packet* packet) {
//Now, we need to send the friendlist to the server they came from: //Now, we need to send the friendlist to the server they came from:
CBITSTREAM; CBITSTREAM;
BitStreamUtils::WriteHeader(bitStream, eConnectionType::CHAT, MessageType::Chat::WORLD_ROUTE_PACKET); BitStreamUtils::WriteHeader(bitStream, eConnectionType::CHAT, eChatMessageType::WORLD_ROUTE_PACKET);
bitStream.Write(playerID); bitStream.Write(playerID);
//portion that will get routed: //portion that will get routed:
BitStreamUtils::WriteHeader(bitStream, eConnectionType::CLIENT, MessageType::Client::GET_FRIENDS_LIST_RESPONSE); BitStreamUtils::WriteHeader(bitStream, eConnectionType::CLIENT, eClientMessageType::GET_FRIENDS_LIST_RESPONSE);
bitStream.Write<uint8_t>(0); bitStream.Write<uint8_t>(0);
bitStream.Write<uint16_t>(1); //Length of packet -- just writing one as it doesn't matter, client skips it. bitStream.Write<uint16_t>(1); //Length of packet -- just writing one as it doesn't matter, client skips it.
bitStream.Write<uint16_t>(player.friends.size()); bitStream.Write<uint16_t>(player.friends.size());
@@ -74,7 +73,7 @@ void ChatPacketHandler::HandleFriendlistRequest(Packet* packet) {
data.Serialize(bitStream); data.Serialize(bitStream);
} }
SystemAddress sysAddr = player.worldServerSysAddr; SystemAddress sysAddr = player.sysAddr;
SEND_PACKET; SEND_PACKET;
} }
@@ -104,8 +103,7 @@ void ChatPacketHandler::HandleFriendRequest(Packet* packet) {
return; return;
}; };
// Intentional copy auto& requestee = Game::playerContainer.GetPlayerDataMutable(playerName);
PlayerData requestee = Game::playerContainer.GetPlayerData(playerName);
// Check if player is online first // Check if player is online first
if (isBestFriendRequest && !requestee) { if (isBestFriendRequest && !requestee) {
@@ -123,7 +121,7 @@ void ChatPacketHandler::HandleFriendRequest(Packet* packet) {
requesteeFriendData.isOnline = false; requesteeFriendData.isOnline = false;
requesteeFriendData.zoneID = requestor.zoneID; requesteeFriendData.zoneID = requestor.zoneID;
requestee.friends.push_back(requesteeFriendData); requestee.friends.push_back(requesteeFriendData);
requestee.worldServerSysAddr = UNASSIGNED_SYSTEM_ADDRESS; requestee.sysAddr = UNASSIGNED_SYSTEM_ADDRESS;
break; break;
} }
} }
@@ -142,7 +140,7 @@ void ChatPacketHandler::HandleFriendRequest(Packet* packet) {
// Prevent GM friend spam // Prevent GM friend spam
// If the player we are trying to be friends with is not a civilian and we are a civilian, abort the process // If the player we are trying to be friends with is not a civilian and we are a civilian, abort the process
if (requestee.gmLevel > eGameMasterLevel::CIVILIAN && requestor.gmLevel == eGameMasterLevel::CIVILIAN) { if (requestee.gmLevel > eGameMasterLevel::CIVILIAN && requestor.gmLevel == eGameMasterLevel::CIVILIAN ) {
SendFriendResponse(requestor, requestee, eAddFriendResponseType::MYTHRAN); SendFriendResponse(requestor, requestee, eAddFriendResponseType::MYTHRAN);
return; return;
} }
@@ -190,29 +188,24 @@ void ChatPacketHandler::HandleFriendRequest(Packet* packet) {
Database::Get()->SetBestFriendStatus(requestorPlayerID, requestee.playerID, bestFriendStatus); Database::Get()->SetBestFriendStatus(requestorPlayerID, requestee.playerID, bestFriendStatus);
// Sent the best friend update here if the value is 3 // Sent the best friend update here if the value is 3
if (bestFriendStatus == 3U) { if (bestFriendStatus == 3U) {
if (requestee.worldServerSysAddr != UNASSIGNED_SYSTEM_ADDRESS) SendFriendResponse(requestee, requestor, eAddFriendResponseType::ACCEPTED, false, true); requestee.countOfBestFriends += 1;
if (requestor.worldServerSysAddr != UNASSIGNED_SYSTEM_ADDRESS) SendFriendResponse(requestor, requestee, eAddFriendResponseType::ACCEPTED, false, true); requestor.countOfBestFriends += 1;
if (requestee.sysAddr != UNASSIGNED_SYSTEM_ADDRESS) SendFriendResponse(requestee, requestor, eAddFriendResponseType::ACCEPTED, false, true);
if (requestor.sysAddr != UNASSIGNED_SYSTEM_ADDRESS) SendFriendResponse(requestor, requestee, eAddFriendResponseType::ACCEPTED, false, true);
for (auto& friendData : requestor.friends) { for (auto& friendData : requestor.friends) {
if (friendData.friendID == requestee.playerID) { if (friendData.friendID == requestee.playerID) {
friendData.isBestFriend = true; friendData.isBestFriend = true;
} }
} }
requestor.countOfBestFriends += 1; for (auto& friendData : requestee.friends) {
if (friendData.friendID == requestor.playerID) {
auto& toModify = Game::playerContainer.GetPlayerDataMutable(playerName); friendData.isBestFriend = true;
if (toModify) {
for (auto& friendData : toModify.friends) {
if (friendData.friendID == requestor.playerID) {
friendData.isBestFriend = true;
}
} }
toModify.countOfBestFriends += 1;
} }
} }
} }
} else { } else {
if (requestor.worldServerSysAddr != UNASSIGNED_SYSTEM_ADDRESS) SendFriendResponse(requestor, requestee, eAddFriendResponseType::WAITINGAPPROVAL, true, true); if (requestor.sysAddr != UNASSIGNED_SYSTEM_ADDRESS) SendFriendResponse(requestor, requestee, eAddFriendResponseType::WAITINGAPPROVAL, true, true);
} }
} else { } else {
auto maxFriends = Game::playerContainer.GetMaxNumberOfFriends(); auto maxFriends = Game::playerContainer.GetMaxNumberOfFriends();
@@ -375,17 +368,17 @@ void ChatPacketHandler::HandleWho(Packet* packet) {
bool online = player; bool online = player;
CBITSTREAM; CBITSTREAM;
BitStreamUtils::WriteHeader(bitStream, eConnectionType::CHAT, MessageType::Chat::WORLD_ROUTE_PACKET); BitStreamUtils::WriteHeader(bitStream, eConnectionType::CHAT, eChatMessageType::WORLD_ROUTE_PACKET);
bitStream.Write(request.requestor); bitStream.Write(request.requestor);
BitStreamUtils::WriteHeader(bitStream, eConnectionType::CLIENT, MessageType::Client::WHO_RESPONSE); BitStreamUtils::WriteHeader(bitStream, eConnectionType::CLIENT, eClientMessageType::WHO_RESPONSE);
bitStream.Write<uint8_t>(online); bitStream.Write<uint8_t>(online);
bitStream.Write(player.zoneID.GetMapID()); bitStream.Write(player.zoneID.GetMapID());
bitStream.Write(player.zoneID.GetInstanceID()); bitStream.Write(player.zoneID.GetInstanceID());
bitStream.Write(player.zoneID.GetCloneID()); bitStream.Write(player.zoneID.GetCloneID());
bitStream.Write(request.playerName); bitStream.Write(request.playerName);
SystemAddress sysAddr = sender.worldServerSysAddr; SystemAddress sysAddr = sender.sysAddr;
SEND_PACKET; SEND_PACKET;
} }
@@ -398,17 +391,17 @@ void ChatPacketHandler::HandleShowAll(Packet* packet) {
if (!sender) return; if (!sender) return;
CBITSTREAM; CBITSTREAM;
BitStreamUtils::WriteHeader(bitStream, eConnectionType::CHAT, MessageType::Chat::WORLD_ROUTE_PACKET); BitStreamUtils::WriteHeader(bitStream, eConnectionType::CHAT, eChatMessageType::WORLD_ROUTE_PACKET);
bitStream.Write(request.requestor); bitStream.Write(request.requestor);
BitStreamUtils::WriteHeader(bitStream, eConnectionType::CLIENT, MessageType::Client::SHOW_ALL_RESPONSE); BitStreamUtils::WriteHeader(bitStream, eConnectionType::CLIENT, eClientMessageType::SHOW_ALL_RESPONSE);
bitStream.Write<uint8_t>(!request.displayZoneData && !request.displayIndividualPlayers); bitStream.Write<uint8_t>(!request.displayZoneData && !request.displayIndividualPlayers);
bitStream.Write(Game::playerContainer.GetPlayerCount()); bitStream.Write(Game::playerContainer.GetPlayerCount());
bitStream.Write(Game::playerContainer.GetSimCount()); bitStream.Write(Game::playerContainer.GetSimCount());
bitStream.Write<uint8_t>(request.displayIndividualPlayers); bitStream.Write<uint8_t>(request.displayIndividualPlayers);
bitStream.Write<uint8_t>(request.displayZoneData); bitStream.Write<uint8_t>(request.displayZoneData);
if (request.displayZoneData || request.displayIndividualPlayers) { if (request.displayZoneData || request.displayIndividualPlayers){
for (auto& [playerID, playerData] : Game::playerContainer.GetAllPlayers()) { for (auto& [playerID, playerData ]: Game::playerContainer.GetAllPlayers()){
if (!playerData) continue; if (!playerData) continue;
bitStream.Write<uint8_t>(0); // structure packing bitStream.Write<uint8_t>(0); // structure packing
if (request.displayIndividualPlayers) bitStream.Write(LUWString(playerData.playerName)); if (request.displayIndividualPlayers) bitStream.Write(LUWString(playerData.playerName));
@@ -419,11 +412,11 @@ void ChatPacketHandler::HandleShowAll(Packet* packet) {
} }
} }
} }
SystemAddress sysAddr = sender.worldServerSysAddr; SystemAddress sysAddr = sender.sysAddr;
SEND_PACKET; SEND_PACKET;
} }
// the structure the client uses to send this packet is shared in many chat messages // the structure the client uses to send this packet is shared in many chat messages
// that are sent to the server. Because of this, there are large gaps of unused data in chat messages // that are sent to the server. Because of this, there are large gaps of unused data in chat messages
void ChatPacketHandler::HandleChatMessage(Packet* packet) { void ChatPacketHandler::HandleChatMessage(Packet* packet) {
CINSTREAM_SKIP_HEADER; CINSTREAM_SKIP_HEADER;
@@ -435,7 +428,7 @@ void ChatPacketHandler::HandleChatMessage(Packet* packet) {
eChatChannel channel; eChatChannel channel;
uint32_t size; uint32_t size;
inStream.IgnoreBytes(4); inStream.IgnoreBytes(4);
inStream.Read(channel); inStream.Read(channel);
inStream.Read(size); inStream.Read(size);
@@ -443,12 +436,12 @@ void ChatPacketHandler::HandleChatMessage(Packet* packet) {
LUWString message(size); LUWString message(size);
inStream.Read(message); inStream.Read(message);
LOG("Got a message from (%s) via [%s]: %s", sender.playerName.c_str(), StringifiedEnum::ToString(channel).data(), message.GetAsString().c_str()); LOG("Got a message from (%s) via [%s]: %s", sender.playerName.c_str(), StringifiedEnum::ToString(channel).data(), message.GetAsString().c_str());
switch (channel) { switch (channel) {
case eChatChannel::TEAM: { case eChatChannel::TEAM: {
auto* team = TeamContainer::GetTeam(playerID); auto* team = Game::playerContainer.GetTeam(playerID);
if (team == nullptr) return; if (team == nullptr) return;
for (const auto memberId : team->memberIDs) { for (const auto memberId : team->memberIDs) {
@@ -464,7 +457,7 @@ void ChatPacketHandler::HandleChatMessage(Packet* packet) {
} }
} }
// the structure the client uses to send this packet is shared in many chat messages // the structure the client uses to send this packet is shared in many chat messages
// that are sent to the server. Because of this, there are large gaps of unused data in chat messages // that are sent to the server. Because of this, there are large gaps of unused data in chat messages
void ChatPacketHandler::HandlePrivateChatMessage(Packet* packet) { void ChatPacketHandler::HandlePrivateChatMessage(Packet* packet) {
CINSTREAM_SKIP_HEADER; CINSTREAM_SKIP_HEADER;
@@ -491,7 +484,7 @@ void ChatPacketHandler::HandlePrivateChatMessage(Packet* packet) {
LUWString message(size); LUWString message(size);
inStream.Read(message); inStream.Read(message);
LOG("Got a message from (%s) via [%s]: %s to %s", sender.playerName.c_str(), StringifiedEnum::ToString(channel).data(), message.GetAsString().c_str(), receiverName.c_str()); LOG("Got a message from (%s) via [%s]: %s to %s", sender.playerName.c_str(), StringifiedEnum::ToString(channel).data(), message.GetAsString().c_str(), receiverName.c_str());
const auto& receiver = Game::playerContainer.GetPlayerData(receiverName); const auto& receiver = Game::playerContainer.GetPlayerData(receiverName);
@@ -520,34 +513,12 @@ void ChatPacketHandler::HandlePrivateChatMessage(Packet* packet) {
SendPrivateChatMessage(sender, receiver, sender, message, eChatChannel::GENERAL, eChatMessageResponseCode::NOTFRIENDS); SendPrivateChatMessage(sender, receiver, sender, message, eChatChannel::GENERAL, eChatMessageResponseCode::NOTFRIENDS);
} }
void ChatPacketHandler::OnAchievementNotify(RakNet::BitStream& bitstream, const SystemAddress& sysAddr) {
ChatPackets::AchievementNotify notify{};
notify.Deserialize(bitstream);
const auto& playerData = Game::playerContainer.GetPlayerData(notify.earnerName.GetAsString());
if (!playerData) return;
for (const auto& myFriend : playerData.friends) {
auto& friendData = Game::playerContainer.GetPlayerData(myFriend.friendID);
if (friendData) {
notify.targetPlayerName.string = GeneralUtils::ASCIIToUTF16(friendData.playerName);
LOG_DEBUG("Sending achievement notify to %s", notify.targetPlayerName.GetAsString().c_str());
RakNet::BitStream worldStream;
BitStreamUtils::WriteHeader(worldStream, eConnectionType::CHAT, MessageType::Chat::WORLD_ROUTE_PACKET);
worldStream.Write(friendData.playerID);
notify.WriteHeader(worldStream);
notify.Serialize(worldStream);
Game::server->Send(worldStream, friendData.worldServerSysAddr, false);
}
}
}
void ChatPacketHandler::SendPrivateChatMessage(const PlayerData& sender, const PlayerData& receiver, const PlayerData& routeTo, const LUWString& message, const eChatChannel channel, const eChatMessageResponseCode responseCode) { void ChatPacketHandler::SendPrivateChatMessage(const PlayerData& sender, const PlayerData& receiver, const PlayerData& routeTo, const LUWString& message, const eChatChannel channel, const eChatMessageResponseCode responseCode) {
CBITSTREAM; CBITSTREAM;
BitStreamUtils::WriteHeader(bitStream, eConnectionType::CHAT, MessageType::Chat::WORLD_ROUTE_PACKET); BitStreamUtils::WriteHeader(bitStream, eConnectionType::CHAT, eChatMessageType::WORLD_ROUTE_PACKET);
bitStream.Write(routeTo.playerID); bitStream.Write(routeTo.playerID);
BitStreamUtils::WriteHeader(bitStream, eConnectionType::CHAT, MessageType::Chat::PRIVATE_CHAT_MESSAGE); BitStreamUtils::WriteHeader(bitStream, eConnectionType::CHAT, eChatMessageType::PRIVATE_CHAT_MESSAGE);
bitStream.Write(sender.playerID); bitStream.Write(sender.playerID);
bitStream.Write(channel); bitStream.Write(channel);
bitStream.Write<uint32_t>(0); // not used bitStream.Write<uint32_t>(0); // not used
@@ -560,7 +531,387 @@ void ChatPacketHandler::SendPrivateChatMessage(const PlayerData& sender, const P
bitStream.Write(responseCode); bitStream.Write(responseCode);
bitStream.Write(message); bitStream.Write(message);
SystemAddress sysAddr = routeTo.worldServerSysAddr; SystemAddress sysAddr = routeTo.sysAddr;
SEND_PACKET;
}
void ChatPacketHandler::HandleTeamInvite(Packet* packet) {
CINSTREAM_SKIP_HEADER;
LWOOBJID playerID;
LUWString invitedPlayer;
inStream.Read(playerID);
inStream.IgnoreBytes(4);
inStream.Read(invitedPlayer);
const auto& player = Game::playerContainer.GetPlayerData(playerID);
if (!player) return;
auto* team = Game::playerContainer.GetTeam(playerID);
if (team == nullptr) {
team = Game::playerContainer.CreateTeam(playerID);
}
const auto& other = Game::playerContainer.GetPlayerData(invitedPlayer.GetAsString());
if (!other) return;
if (Game::playerContainer.GetTeam(other.playerID) != nullptr) {
return;
}
if (team->memberIDs.size() > 3) {
// no more teams greater than 4
LOG("Someone tried to invite a 5th player to a team");
return;
}
SendTeamInvite(other, player);
LOG("Got team invite: %llu -> %s", playerID, invitedPlayer.GetAsString().c_str());
}
void ChatPacketHandler::HandleTeamInviteResponse(Packet* packet) {
CINSTREAM_SKIP_HEADER;
LWOOBJID playerID = LWOOBJID_EMPTY;
inStream.Read(playerID);
uint32_t size = 0;
inStream.Read(size);
char declined = 0;
inStream.Read(declined);
LWOOBJID leaderID = LWOOBJID_EMPTY;
inStream.Read(leaderID);
LOG("Accepted invite: %llu -> %llu (%d)", playerID, leaderID, declined);
if (declined) {
return;
}
auto* team = Game::playerContainer.GetTeam(leaderID);
if (team == nullptr) {
LOG("Failed to find team for leader (%llu)", leaderID);
team = Game::playerContainer.GetTeam(playerID);
}
if (team == nullptr) {
LOG("Failed to find team for player (%llu)", playerID);
return;
}
Game::playerContainer.AddMember(team, playerID);
}
void ChatPacketHandler::HandleTeamLeave(Packet* packet) {
CINSTREAM_SKIP_HEADER;
LWOOBJID playerID = LWOOBJID_EMPTY;
inStream.Read(playerID);
uint32_t size = 0;
inStream.Read(size);
auto* team = Game::playerContainer.GetTeam(playerID);
LOG("(%llu) leaving team", playerID);
if (team != nullptr) {
Game::playerContainer.RemoveMember(team, playerID, false, false, true);
}
}
void ChatPacketHandler::HandleTeamKick(Packet* packet) {
CINSTREAM_SKIP_HEADER;
LWOOBJID playerID = LWOOBJID_EMPTY;
LUWString kickedPlayer;
inStream.Read(playerID);
inStream.IgnoreBytes(4);
inStream.Read(kickedPlayer);
LOG("(%llu) kicking (%s) from team", playerID, kickedPlayer.GetAsString().c_str());
const auto& kicked = Game::playerContainer.GetPlayerData(kickedPlayer.GetAsString());
LWOOBJID kickedId = LWOOBJID_EMPTY;
if (kicked) {
kickedId = kicked.playerID;
} else {
kickedId = Game::playerContainer.GetId(kickedPlayer.string);
}
if (kickedId == LWOOBJID_EMPTY) return;
auto* team = Game::playerContainer.GetTeam(playerID);
if (team != nullptr) {
if (team->leaderID != playerID || team->leaderID == kickedId) return;
Game::playerContainer.RemoveMember(team, kickedId, false, true, false);
}
}
void ChatPacketHandler::HandleTeamPromote(Packet* packet) {
CINSTREAM_SKIP_HEADER;
LWOOBJID playerID = LWOOBJID_EMPTY;
LUWString promotedPlayer;
inStream.Read(playerID);
inStream.IgnoreBytes(4);
inStream.Read(promotedPlayer);
LOG("(%llu) promoting (%s) to team leader", playerID, promotedPlayer.GetAsString().c_str());
const auto& promoted = Game::playerContainer.GetPlayerData(promotedPlayer.GetAsString());
if (!promoted) return;
auto* team = Game::playerContainer.GetTeam(playerID);
if (team != nullptr) {
if (team->leaderID != playerID) return;
Game::playerContainer.PromoteMember(team, promoted.playerID);
}
}
void ChatPacketHandler::HandleTeamLootOption(Packet* packet) {
CINSTREAM_SKIP_HEADER;
LWOOBJID playerID = LWOOBJID_EMPTY;
inStream.Read(playerID);
uint32_t size = 0;
inStream.Read(size);
char option;
inStream.Read(option);
auto* team = Game::playerContainer.GetTeam(playerID);
if (team != nullptr) {
if (team->leaderID != playerID) return;
team->lootFlag = option;
Game::playerContainer.TeamStatusUpdate(team);
Game::playerContainer.UpdateTeamsOnWorld(team, false);
}
}
void ChatPacketHandler::HandleTeamStatusRequest(Packet* packet) {
CINSTREAM_SKIP_HEADER;
LWOOBJID playerID = LWOOBJID_EMPTY;
inStream.Read(playerID);
auto* team = Game::playerContainer.GetTeam(playerID);
const auto& data = Game::playerContainer.GetPlayerData(playerID);
if (team != nullptr && data) {
if (team->local && data.zoneID.GetMapID() != team->zoneId.GetMapID() && data.zoneID.GetCloneID() != team->zoneId.GetCloneID()) {
Game::playerContainer.RemoveMember(team, playerID, false, false, true, true);
return;
}
if (team->memberIDs.size() <= 1 && !team->local) {
Game::playerContainer.DisbandTeam(team);
return;
}
if (!team->local) {
ChatPacketHandler::SendTeamSetLeader(data, team->leaderID);
} else {
ChatPacketHandler::SendTeamSetLeader(data, LWOOBJID_EMPTY);
}
Game::playerContainer.TeamStatusUpdate(team);
const auto leaderName = GeneralUtils::UTF8ToUTF16(data.playerName);
for (const auto memberId : team->memberIDs) {
const auto& otherMember = Game::playerContainer.GetPlayerData(memberId);
if (memberId == playerID) continue;
const auto memberName = Game::playerContainer.GetName(memberId);
if (otherMember) {
ChatPacketHandler::SendTeamSetOffWorldFlag(otherMember, data.playerID, data.zoneID);
}
ChatPacketHandler::SendTeamAddPlayer(data, false, team->local, false, memberId, memberName, otherMember ? otherMember.zoneID : LWOZONEID(0, 0, 0));
}
Game::playerContainer.UpdateTeamsOnWorld(team, false);
}
}
void ChatPacketHandler::SendTeamInvite(const PlayerData& receiver, const PlayerData& sender) {
CBITSTREAM;
BitStreamUtils::WriteHeader(bitStream, eConnectionType::CHAT, eChatMessageType::WORLD_ROUTE_PACKET);
bitStream.Write(receiver.playerID);
//portion that will get routed:
BitStreamUtils::WriteHeader(bitStream, eConnectionType::CLIENT, eClientMessageType::TEAM_INVITE);
bitStream.Write(LUWString(sender.playerName.c_str()));
bitStream.Write(sender.playerID);
SystemAddress sysAddr = receiver.sysAddr;
SEND_PACKET;
}
void ChatPacketHandler::SendTeamInviteConfirm(const PlayerData& receiver, bool bLeaderIsFreeTrial, LWOOBJID i64LeaderID, LWOZONEID i64LeaderZoneID, uint8_t ucLootFlag, uint8_t ucNumOfOtherPlayers, uint8_t ucResponseCode, std::u16string wsLeaderName) {
CBITSTREAM;
BitStreamUtils::WriteHeader(bitStream, eConnectionType::CHAT, eChatMessageType::WORLD_ROUTE_PACKET);
bitStream.Write(receiver.playerID);
//portion that will get routed:
CMSGHEADER;
bitStream.Write(receiver.playerID);
bitStream.Write(eGameMessageType::TEAM_INVITE_CONFIRM);
bitStream.Write(bLeaderIsFreeTrial);
bitStream.Write(i64LeaderID);
bitStream.Write(i64LeaderZoneID);
bitStream.Write<uint32_t>(0); // BinaryBuffe, no clue what's in here
bitStream.Write(ucLootFlag);
bitStream.Write(ucNumOfOtherPlayers);
bitStream.Write(ucResponseCode);
bitStream.Write<uint32_t>(wsLeaderName.size());
for (const auto character : wsLeaderName) {
bitStream.Write(character);
}
SystemAddress sysAddr = receiver.sysAddr;
SEND_PACKET;
}
void ChatPacketHandler::SendTeamStatus(const PlayerData& receiver, LWOOBJID i64LeaderID, LWOZONEID i64LeaderZoneID, uint8_t ucLootFlag, uint8_t ucNumOfOtherPlayers, std::u16string wsLeaderName) {
CBITSTREAM;
BitStreamUtils::WriteHeader(bitStream, eConnectionType::CHAT, eChatMessageType::WORLD_ROUTE_PACKET);
bitStream.Write(receiver.playerID);
//portion that will get routed:
CMSGHEADER;
bitStream.Write(receiver.playerID);
bitStream.Write(eGameMessageType::TEAM_GET_STATUS_RESPONSE);
bitStream.Write(i64LeaderID);
bitStream.Write(i64LeaderZoneID);
bitStream.Write<uint32_t>(0); // BinaryBuffe, no clue what's in here
bitStream.Write(ucLootFlag);
bitStream.Write(ucNumOfOtherPlayers);
bitStream.Write<uint32_t>(wsLeaderName.size());
for (const auto character : wsLeaderName) {
bitStream.Write(character);
}
SystemAddress sysAddr = receiver.sysAddr;
SEND_PACKET;
}
void ChatPacketHandler::SendTeamSetLeader(const PlayerData& receiver, LWOOBJID i64PlayerID) {
CBITSTREAM;
BitStreamUtils::WriteHeader(bitStream, eConnectionType::CHAT, eChatMessageType::WORLD_ROUTE_PACKET);
bitStream.Write(receiver.playerID);
//portion that will get routed:
CMSGHEADER;
bitStream.Write(receiver.playerID);
bitStream.Write(eGameMessageType::TEAM_SET_LEADER);
bitStream.Write(i64PlayerID);
SystemAddress sysAddr = receiver.sysAddr;
SEND_PACKET;
}
void ChatPacketHandler::SendTeamAddPlayer(const PlayerData& receiver, bool bIsFreeTrial, bool bLocal, bool bNoLootOnDeath, LWOOBJID i64PlayerID, std::u16string wsPlayerName, LWOZONEID zoneID) {
CBITSTREAM;
BitStreamUtils::WriteHeader(bitStream, eConnectionType::CHAT, eChatMessageType::WORLD_ROUTE_PACKET);
bitStream.Write(receiver.playerID);
//portion that will get routed:
CMSGHEADER;
bitStream.Write(receiver.playerID);
bitStream.Write(eGameMessageType::TEAM_ADD_PLAYER);
bitStream.Write(bIsFreeTrial);
bitStream.Write(bLocal);
bitStream.Write(bNoLootOnDeath);
bitStream.Write(i64PlayerID);
bitStream.Write<uint32_t>(wsPlayerName.size());
for (const auto character : wsPlayerName) {
bitStream.Write(character);
}
bitStream.Write1();
if (receiver.zoneID.GetCloneID() == zoneID.GetCloneID()) {
zoneID = LWOZONEID(zoneID.GetMapID(), zoneID.GetInstanceID(), 0);
}
bitStream.Write(zoneID);
SystemAddress sysAddr = receiver.sysAddr;
SEND_PACKET;
}
void ChatPacketHandler::SendTeamRemovePlayer(const PlayerData& receiver, bool bDisband, bool bIsKicked, bool bIsLeaving, bool bLocal, LWOOBJID i64LeaderID, LWOOBJID i64PlayerID, std::u16string wsPlayerName) {
CBITSTREAM;
BitStreamUtils::WriteHeader(bitStream, eConnectionType::CHAT, eChatMessageType::WORLD_ROUTE_PACKET);
bitStream.Write(receiver.playerID);
//portion that will get routed:
CMSGHEADER;
bitStream.Write(receiver.playerID);
bitStream.Write(eGameMessageType::TEAM_REMOVE_PLAYER);
bitStream.Write(bDisband);
bitStream.Write(bIsKicked);
bitStream.Write(bIsLeaving);
bitStream.Write(bLocal);
bitStream.Write(i64LeaderID);
bitStream.Write(i64PlayerID);
bitStream.Write<uint32_t>(wsPlayerName.size());
for (const auto character : wsPlayerName) {
bitStream.Write(character);
}
SystemAddress sysAddr = receiver.sysAddr;
SEND_PACKET;
}
void ChatPacketHandler::SendTeamSetOffWorldFlag(const PlayerData& receiver, LWOOBJID i64PlayerID, LWOZONEID zoneID) {
CBITSTREAM;
BitStreamUtils::WriteHeader(bitStream, eConnectionType::CHAT, eChatMessageType::WORLD_ROUTE_PACKET);
bitStream.Write(receiver.playerID);
//portion that will get routed:
CMSGHEADER;
bitStream.Write(receiver.playerID);
bitStream.Write(eGameMessageType::TEAM_SET_OFF_WORLD_FLAG);
bitStream.Write(i64PlayerID);
if (receiver.zoneID.GetCloneID() == zoneID.GetCloneID()) {
zoneID = LWOZONEID(zoneID.GetMapID(), zoneID.GetInstanceID(), 0);
}
bitStream.Write(zoneID);
SystemAddress sysAddr = receiver.sysAddr;
SEND_PACKET; SEND_PACKET;
} }
@@ -579,11 +930,11 @@ void ChatPacketHandler::SendFriendUpdate(const PlayerData& friendData, const Pla
[bool] - is FTP*/ [bool] - is FTP*/
CBITSTREAM; CBITSTREAM;
BitStreamUtils::WriteHeader(bitStream, eConnectionType::CHAT, MessageType::Chat::WORLD_ROUTE_PACKET); BitStreamUtils::WriteHeader(bitStream, eConnectionType::CHAT, eChatMessageType::WORLD_ROUTE_PACKET);
bitStream.Write(friendData.playerID); bitStream.Write(friendData.playerID);
//portion that will get routed: //portion that will get routed:
BitStreamUtils::WriteHeader(bitStream, eConnectionType::CLIENT, MessageType::Client::UPDATE_FRIEND_NOTIFY); BitStreamUtils::WriteHeader(bitStream, eConnectionType::CLIENT, eClientMessageType::UPDATE_FRIEND_NOTIFY);
bitStream.Write<uint8_t>(notifyType); bitStream.Write<uint8_t>(notifyType);
std::string playerName = playerData.playerName.c_str(); std::string playerName = playerData.playerName.c_str();
@@ -602,7 +953,7 @@ void ChatPacketHandler::SendFriendUpdate(const PlayerData& friendData, const Pla
bitStream.Write<uint8_t>(isBestFriend); //isBFF bitStream.Write<uint8_t>(isBestFriend); //isBFF
bitStream.Write<uint8_t>(0); //isFTP bitStream.Write<uint8_t>(0); //isFTP
SystemAddress sysAddr = friendData.worldServerSysAddr; SystemAddress sysAddr = friendData.sysAddr;
SEND_PACKET; SEND_PACKET;
} }
@@ -616,28 +967,28 @@ void ChatPacketHandler::SendFriendRequest(const PlayerData& receiver, const Play
} }
CBITSTREAM; CBITSTREAM;
BitStreamUtils::WriteHeader(bitStream, eConnectionType::CHAT, MessageType::Chat::WORLD_ROUTE_PACKET); BitStreamUtils::WriteHeader(bitStream, eConnectionType::CHAT, eChatMessageType::WORLD_ROUTE_PACKET);
bitStream.Write(receiver.playerID); bitStream.Write(receiver.playerID);
//portion that will get routed: //portion that will get routed:
BitStreamUtils::WriteHeader(bitStream, eConnectionType::CLIENT, MessageType::Client::ADD_FRIEND_REQUEST); BitStreamUtils::WriteHeader(bitStream, eConnectionType::CLIENT, eClientMessageType::ADD_FRIEND_REQUEST);
bitStream.Write(LUWString(sender.playerName)); bitStream.Write(LUWString(sender.playerName));
bitStream.Write<uint8_t>(0); // This is a BFF flag however this is unused in live and does not have an implementation client side. bitStream.Write<uint8_t>(0); // This is a BFF flag however this is unused in live and does not have an implementation client side.
SystemAddress sysAddr = receiver.worldServerSysAddr; SystemAddress sysAddr = receiver.sysAddr;
SEND_PACKET; SEND_PACKET;
} }
void ChatPacketHandler::SendFriendResponse(const PlayerData& receiver, const PlayerData& sender, eAddFriendResponseType responseCode, uint8_t isBestFriendsAlready, uint8_t isBestFriendRequest) { void ChatPacketHandler::SendFriendResponse(const PlayerData& receiver, const PlayerData& sender, eAddFriendResponseType responseCode, uint8_t isBestFriendsAlready, uint8_t isBestFriendRequest) {
CBITSTREAM; CBITSTREAM;
BitStreamUtils::WriteHeader(bitStream, eConnectionType::CHAT, MessageType::Chat::WORLD_ROUTE_PACKET); BitStreamUtils::WriteHeader(bitStream, eConnectionType::CHAT, eChatMessageType::WORLD_ROUTE_PACKET);
bitStream.Write(receiver.playerID); bitStream.Write(receiver.playerID);
// Portion that will get routed: // Portion that will get routed:
BitStreamUtils::WriteHeader(bitStream, eConnectionType::CLIENT, MessageType::Client::ADD_FRIEND_RESPONSE); BitStreamUtils::WriteHeader(bitStream, eConnectionType::CLIENT, eClientMessageType::ADD_FRIEND_RESPONSE);
bitStream.Write(responseCode); bitStream.Write(responseCode);
// For all requests besides accepted, write a flag that says whether or not we are already best friends with the receiver. // For all requests besides accepted, write a flag that says whether or not we are already best friends with the receiver.
bitStream.Write<uint8_t>(responseCode != eAddFriendResponseType::ACCEPTED ? isBestFriendsAlready : sender.worldServerSysAddr != UNASSIGNED_SYSTEM_ADDRESS); bitStream.Write<uint8_t>(responseCode != eAddFriendResponseType::ACCEPTED ? isBestFriendsAlready : sender.sysAddr != UNASSIGNED_SYSTEM_ADDRESS);
// Then write the player name // Then write the player name
bitStream.Write(LUWString(sender.playerName)); bitStream.Write(LUWString(sender.playerName));
// Then if this is an acceptance code, write the following extra info. // Then if this is an acceptance code, write the following extra info.
@@ -647,20 +998,20 @@ void ChatPacketHandler::SendFriendResponse(const PlayerData& receiver, const Pla
bitStream.Write(isBestFriendRequest); //isBFF bitStream.Write(isBestFriendRequest); //isBFF
bitStream.Write<uint8_t>(0); //isFTP bitStream.Write<uint8_t>(0); //isFTP
} }
SystemAddress sysAddr = receiver.worldServerSysAddr; SystemAddress sysAddr = receiver.sysAddr;
SEND_PACKET; SEND_PACKET;
} }
void ChatPacketHandler::SendRemoveFriend(const PlayerData& receiver, std::string& personToRemove, bool isSuccessful) { void ChatPacketHandler::SendRemoveFriend(const PlayerData& receiver, std::string& personToRemove, bool isSuccessful) {
CBITSTREAM; CBITSTREAM;
BitStreamUtils::WriteHeader(bitStream, eConnectionType::CHAT, MessageType::Chat::WORLD_ROUTE_PACKET); BitStreamUtils::WriteHeader(bitStream, eConnectionType::CHAT, eChatMessageType::WORLD_ROUTE_PACKET);
bitStream.Write(receiver.playerID); bitStream.Write(receiver.playerID);
//portion that will get routed: //portion that will get routed:
BitStreamUtils::WriteHeader(bitStream, eConnectionType::CLIENT, MessageType::Client::REMOVE_FRIEND_RESPONSE); BitStreamUtils::WriteHeader(bitStream, eConnectionType::CLIENT, eClientMessageType::REMOVE_FRIEND_RESPONSE);
bitStream.Write<uint8_t>(isSuccessful); //isOnline bitStream.Write<uint8_t>(isSuccessful); //isOnline
bitStream.Write(LUWString(personToRemove)); bitStream.Write(LUWString(personToRemove));
SystemAddress sysAddr = receiver.worldServerSysAddr; SystemAddress sysAddr = receiver.sysAddr;
SEND_PACKET; SEND_PACKET;
} }

View File

@@ -35,13 +35,13 @@ enum class eChatChannel : uint8_t {
enum class eChatMessageResponseCode : uint8_t { enum class eChatMessageResponseCode : uint8_t {
SENT = 0, SENT = 0,
NOTONLINE, NOTONLINE,
GENERALERROR, GENERALERROR,
RECEIVEDNEWWHISPER, RECEIVEDNEWWHISPER,
NOTFRIENDS, NOTFRIENDS,
SENDERFREETRIAL, SENDERFREETRIAL,
RECEIVERFREETRIAL, RECEIVERFREETRIAL,
}; };
namespace ChatPacketHandler { namespace ChatPacketHandler {
@@ -52,14 +52,30 @@ namespace ChatPacketHandler {
void HandleGMLevelUpdate(Packet* packet); void HandleGMLevelUpdate(Packet* packet);
void HandleWho(Packet* packet); void HandleWho(Packet* packet);
void HandleShowAll(Packet* packet); void HandleShowAll(Packet* packet);
void HandleChatMessage(Packet* packet); void HandleChatMessage(Packet* packet);
void HandlePrivateChatMessage(Packet* packet); void HandlePrivateChatMessage(Packet* packet);
void SendPrivateChatMessage(const PlayerData& sender, const PlayerData& receiver, const PlayerData& routeTo, const LUWString& message, const eChatChannel channel, const eChatMessageResponseCode responseCode);
void OnAchievementNotify(RakNet::BitStream& bitstream, const SystemAddress& sysAddr); void HandleTeamInvite(Packet* packet);
void HandleTeamInviteResponse(Packet* packet);
void HandleTeamLeave(Packet* packet);
void HandleTeamKick(Packet* packet);
void HandleTeamPromote(Packet* packet);
void HandleTeamLootOption(Packet* packet);
void HandleTeamStatusRequest(Packet* packet);
void SendTeamInvite(const PlayerData& receiver, const PlayerData& sender);
void SendTeamInviteConfirm(const PlayerData& receiver, bool bLeaderIsFreeTrial, LWOOBJID i64LeaderID, LWOZONEID i64LeaderZoneID, uint8_t ucLootFlag, uint8_t ucNumOfOtherPlayers, uint8_t ucResponseCode, std::u16string wsLeaderName);
void SendTeamStatus(const PlayerData& receiver, LWOOBJID i64LeaderID, LWOZONEID i64LeaderZoneID, uint8_t ucLootFlag, uint8_t ucNumOfOtherPlayers, std::u16string wsLeaderName);
void SendTeamSetLeader(const PlayerData& receiver, LWOOBJID i64PlayerID);
void SendTeamAddPlayer(const PlayerData& receiver, bool bIsFreeTrial, bool bLocal, bool bNoLootOnDeath, LWOOBJID i64PlayerID, std::u16string wsPlayerName, LWOZONEID zoneID);
void SendTeamRemovePlayer(const PlayerData& receiver, bool bDisband, bool bIsKicked, bool bIsLeaving, bool bLocal, LWOOBJID i64LeaderID, LWOOBJID i64PlayerID, std::u16string wsPlayerName);
void SendTeamSetOffWorldFlag(const PlayerData& receiver, LWOOBJID i64PlayerID, LWOZONEID zoneID);
//FriendData is the player we're SENDING this stuff to. Player is the friend that changed state. //FriendData is the player we're SENDING this stuff to. Player is the friend that changed state.
void SendFriendUpdate(const PlayerData& friendData, const PlayerData& playerData, uint8_t notifyType, uint8_t isBestFriend); void SendFriendUpdate(const PlayerData& friendData, const PlayerData& playerData, uint8_t notifyType, uint8_t isBestFriend);
void SendPrivateChatMessage(const PlayerData& sender, const PlayerData& receiver, const PlayerData& routeTo, const LUWString& message, const eChatChannel channel, const eChatMessageResponseCode responseCode);
void SendFriendRequest(const PlayerData& receiver, const PlayerData& sender); void SendFriendRequest(const PlayerData& receiver, const PlayerData& sender);
void SendFriendResponse(const PlayerData& receiver, const PlayerData& sender, eAddFriendResponseType responseCode, uint8_t isBestFriendsAlready = 0U, uint8_t isBestFriendRequest = 0U); void SendFriendResponse(const PlayerData& receiver, const PlayerData& sender, eAddFriendResponseType responseCode, uint8_t isBestFriendsAlready = 0U, uint8_t isBestFriendRequest = 0U);
void SendRemoveFriend(const PlayerData& receiver, std::string& personToRemove, bool isSuccessful); void SendRemoveFriend(const PlayerData& receiver, std::string& personToRemove, bool isSuccessful);

View File

@@ -16,11 +16,10 @@
#include "eConnectionType.h" #include "eConnectionType.h"
#include "PlayerContainer.h" #include "PlayerContainer.h"
#include "ChatPacketHandler.h" #include "ChatPacketHandler.h"
#include "MessageType/Chat.h" #include "eChatMessageType.h"
#include "MessageType/World.h" #include "eWorldMessageType.h"
#include "ChatIgnoreList.h" #include "ChatIgnoreList.h"
#include "StringifiedEnum.h" #include "StringifiedEnum.h"
#include "TeamContainer.h"
#include "Game.h" #include "Game.h"
#include "Server.h" #include "Server.h"
@@ -29,8 +28,6 @@
#include "RakNetDefines.h" #include "RakNetDefines.h"
#include "MessageIdentifiers.h" #include "MessageIdentifiers.h"
#include "ChatWeb.h"
namespace Game { namespace Game {
Logger* logger = nullptr; Logger* logger = nullptr;
dServer* server = nullptr; dServer* server = nullptr;
@@ -77,44 +74,28 @@ int main(int argc, char** argv) {
Game::assetManager = new AssetManager(clientPath); Game::assetManager = new AssetManager(clientPath);
} catch (std::runtime_error& ex) { } catch (std::runtime_error& ex) {
LOG("Got an error while setting up assets: %s", ex.what()); LOG("Got an error while setting up assets: %s", ex.what());
delete Game::logger;
delete Game::config;
return EXIT_FAILURE; return EXIT_FAILURE;
} }
//Connect to the MySQL Database //Connect to the MySQL Database
try { try {
Database::Connect(); Database::Connect();
} catch (std::exception& ex) { } catch (sql::SQLException& ex) {
LOG("Got an error while connecting to the database: %s", ex.what()); LOG("Got an error while connecting to the database: %s", ex.what());
Database::Destroy("ChatServer"); Database::Destroy("ChatServer");
delete Game::server;
delete Game::logger; delete Game::logger;
delete Game::config;
return EXIT_FAILURE; return EXIT_FAILURE;
} }
// setup the chat api web server
const uint32_t web_server_port = GeneralUtils::TryParse<uint32_t>(Game::config->GetValue("web_server_port")).value_or(2005);
if (Game::config->GetValue("web_server_enabled") == "1" && !Game::web.Startup("localhost", web_server_port)) {
// if we want the web server and it fails to start, exit
LOG("Failed to start web server, shutting down.");
Database::Destroy("ChatServer");
delete Game::logger;
delete Game::config;
return EXIT_FAILURE;
}
if (Game::web.IsEnabled()) ChatWeb::RegisterRoutes();
//Find out the master's IP: //Find out the master's IP:
std::string masterIP; std::string masterIP;
uint32_t masterPort = 1000; uint32_t masterPort = 1000;
std::string masterPassword;
auto masterInfo = Database::Get()->GetMasterInfo(); auto masterInfo = Database::Get()->GetMasterInfo();
if (masterInfo) { if (masterInfo) {
masterIP = masterInfo->ip; masterIP = masterInfo->ip;
masterPort = masterInfo->port; masterPort = masterInfo->port;
masterPassword = masterInfo->password;
} }
//It's safe to pass 'localhost' here, as the IP is only used as the external IP. //It's safe to pass 'localhost' here, as the IP is only used as the external IP.
std::string ourIP = "localhost"; std::string ourIP = "localhost";
@@ -123,11 +104,11 @@ int main(int argc, char** argv) {
const auto externalIPString = Game::config->GetValue("external_ip"); const auto externalIPString = Game::config->GetValue("external_ip");
if (!externalIPString.empty()) ourIP = externalIPString; if (!externalIPString.empty()) ourIP = externalIPString;
Game::server = new dServer(ourIP, ourPort, 0, maxClients, false, true, Game::logger, masterIP, masterPort, ServerType::Chat, Game::config, &Game::lastSignal, masterPassword); Game::server = new dServer(ourIP, ourPort, 0, maxClients, false, true, Game::logger, masterIP, masterPort, ServerType::Chat, Game::config, &Game::lastSignal);
const bool dontGenerateDCF = GeneralUtils::TryParse<bool>(Game::config->GetValue("dont_generate_dcf")).value_or(false); const bool dontGenerateDCF = GeneralUtils::TryParse<bool>(Game::config->GetValue("dont_generate_dcf")).value_or(false);
Game::chatFilter = new dChatFilter(Game::assetManager->GetResPath().string() + "/chatplus_en_us", dontGenerateDCF); Game::chatFilter = new dChatFilter(Game::assetManager->GetResPath().string() + "/chatplus_en_us", dontGenerateDCF);
Game::randomEngine = std::mt19937(time(0)); Game::randomEngine = std::mt19937(time(0));
Game::playerContainer.Initialize(); Game::playerContainer.Initialize();
@@ -141,8 +122,6 @@ int main(int argc, char** argv) {
uint32_t framesSinceMasterDisconnect = 0; uint32_t framesSinceMasterDisconnect = 0;
uint32_t framesSinceLastSQLPing = 0; uint32_t framesSinceLastSQLPing = 0;
auto lastTime = std::chrono::high_resolution_clock::now();
Game::logger->Flush(); // once immediately before main loop Game::logger->Flush(); // once immediately before main loop
while (!Game::ShouldShutdown()) { while (!Game::ShouldShutdown()) {
//Check if we're still connected to master: //Check if we're still connected to master:
@@ -153,11 +132,7 @@ int main(int argc, char** argv) {
break; //Exit our loop, shut down. break; //Exit our loop, shut down.
} else framesSinceMasterDisconnect = 0; } else framesSinceMasterDisconnect = 0;
const auto currentTime = std::chrono::high_resolution_clock::now(); //In world we'd update our other systems here.
const float deltaTime = std::chrono::duration<float>(currentTime - lastTime).count();
lastTime = currentTime;
Game::playerContainer.Update(deltaTime);
//Check for packets here: //Check for packets here:
Game::server->ReceiveFromMaster(); //ReceiveFromMaster also handles the master packets if needed. Game::server->ReceiveFromMaster(); //ReceiveFromMaster also handles the master packets if needed.
@@ -168,9 +143,6 @@ int main(int argc, char** argv) {
packet = nullptr; packet = nullptr;
} }
// Check and handle web requests:
if (Game::web.IsEnabled()) Game::web.ReceiveRequests();
//Push our log every 30s: //Push our log every 30s:
if (framesSinceLastFlush >= logFlushTime) { if (framesSinceLastFlush >= logFlushTime) {
Game::logger->Flush(); Game::logger->Flush();
@@ -196,8 +168,7 @@ int main(int argc, char** argv) {
t += std::chrono::milliseconds(chatFrameDelta); //Chat can run at a lower "fps" t += std::chrono::milliseconds(chatFrameDelta); //Chat can run at a lower "fps"
std::this_thread::sleep_until(t); std::this_thread::sleep_until(t);
} }
Game::playerContainer.Shutdown();
TeamContainer::Shutdown();
//Delete our objects here: //Delete our objects here:
Database::Destroy("ChatServer"); Database::Destroy("ChatServer");
delete Game::server; delete Game::server;
@@ -208,7 +179,6 @@ int main(int argc, char** argv) {
} }
void HandlePacket(Packet* packet) { void HandlePacket(Packet* packet) {
if (packet->length < 1) return;
if (packet->data[0] == ID_DISCONNECTION_NOTIFICATION || packet->data[0] == ID_CONNECTION_LOST) { if (packet->data[0] == ID_DISCONNECTION_NOTIFICATION || packet->data[0] == ID_CONNECTION_LOST) {
LOG("A server has disconnected, erasing their connected players from the list."); LOG("A server has disconnected, erasing their connected players from the list.");
} else if (packet->data[0] == ID_NEW_INCOMING_CONNECTION) { } else if (packet->data[0] == ID_NEW_INCOMING_CONNECTION) {
@@ -219,162 +189,157 @@ void HandlePacket(Packet* packet) {
inStream.SetReadOffset(BYTES_TO_BITS(1)); inStream.SetReadOffset(BYTES_TO_BITS(1));
eConnectionType connection; eConnectionType connection;
MessageType::Chat chatMessageID; eChatMessageType chatMessageID;
inStream.Read(connection); inStream.Read(connection);
if (connection != eConnectionType::CHAT) return; if (connection != eConnectionType::CHAT) return;
inStream.Read(chatMessageID); inStream.Read(chatMessageID);
// Our packing byte wasnt there? Probably a false packet
if (inStream.GetNumberOfUnreadBits() < 8) return;
inStream.IgnoreBytes(1);
switch (chatMessageID) { switch (chatMessageID) {
case MessageType::Chat::GM_MUTE: case eChatMessageType::GM_MUTE:
Game::playerContainer.MuteUpdate(packet); Game::playerContainer.MuteUpdate(packet);
break; break;
case MessageType::Chat::CREATE_TEAM: case eChatMessageType::CREATE_TEAM:
TeamContainer::CreateTeamServer(packet); Game::playerContainer.CreateTeamServer(packet);
break; break;
case MessageType::Chat::GET_FRIENDS_LIST: case eChatMessageType::GET_FRIENDS_LIST:
ChatPacketHandler::HandleFriendlistRequest(packet); ChatPacketHandler::HandleFriendlistRequest(packet);
break; break;
case MessageType::Chat::GET_IGNORE_LIST: case eChatMessageType::GET_IGNORE_LIST:
ChatIgnoreList::GetIgnoreList(packet); ChatIgnoreList::GetIgnoreList(packet);
break; break;
case MessageType::Chat::ADD_IGNORE: case eChatMessageType::ADD_IGNORE:
ChatIgnoreList::AddIgnore(packet); ChatIgnoreList::AddIgnore(packet);
break; break;
case MessageType::Chat::REMOVE_IGNORE: case eChatMessageType::REMOVE_IGNORE:
ChatIgnoreList::RemoveIgnore(packet); ChatIgnoreList::RemoveIgnore(packet);
break; break;
case MessageType::Chat::TEAM_GET_STATUS: case eChatMessageType::TEAM_GET_STATUS:
TeamContainer::HandleTeamStatusRequest(packet); ChatPacketHandler::HandleTeamStatusRequest(packet);
break; break;
case MessageType::Chat::ADD_FRIEND_REQUEST: case eChatMessageType::ADD_FRIEND_REQUEST:
//this involves someone sending the initial request, the response is below, response as in from the other player. //this involves someone sending the initial request, the response is below, response as in from the other player.
//We basically just check to see if this player is online or not and route the packet. //We basically just check to see if this player is online or not and route the packet.
ChatPacketHandler::HandleFriendRequest(packet); ChatPacketHandler::HandleFriendRequest(packet);
break; break;
case MessageType::Chat::ADD_FRIEND_RESPONSE: case eChatMessageType::ADD_FRIEND_RESPONSE:
//This isn't the response a server sent, rather it is a player's response to a received request. //This isn't the response a server sent, rather it is a player's response to a received request.
//Here, we'll actually have to add them to eachother's friend lists depending on the response code. //Here, we'll actually have to add them to eachother's friend lists depending on the response code.
ChatPacketHandler::HandleFriendResponse(packet); ChatPacketHandler::HandleFriendResponse(packet);
break; break;
case MessageType::Chat::REMOVE_FRIEND: case eChatMessageType::REMOVE_FRIEND:
ChatPacketHandler::HandleRemoveFriend(packet); ChatPacketHandler::HandleRemoveFriend(packet);
break; break;
case MessageType::Chat::GENERAL_CHAT_MESSAGE: case eChatMessageType::GENERAL_CHAT_MESSAGE:
ChatPacketHandler::HandleChatMessage(packet); ChatPacketHandler::HandleChatMessage(packet);
break; break;
case MessageType::Chat::PRIVATE_CHAT_MESSAGE: case eChatMessageType::PRIVATE_CHAT_MESSAGE:
//This message is supposed to be echo'd to both the sender and the receiver //This message is supposed to be echo'd to both the sender and the receiver
//BUT: they have to have different responseCodes, so we'll do some of the ol hacky wacky to fix that right up. //BUT: they have to have different responseCodes, so we'll do some of the ol hacky wacky to fix that right up.
ChatPacketHandler::HandlePrivateChatMessage(packet); ChatPacketHandler::HandlePrivateChatMessage(packet);
break; break;
case MessageType::Chat::TEAM_INVITE: case eChatMessageType::TEAM_INVITE:
TeamContainer::HandleTeamInvite(packet); ChatPacketHandler::HandleTeamInvite(packet);
break; break;
case MessageType::Chat::TEAM_INVITE_RESPONSE: case eChatMessageType::TEAM_INVITE_RESPONSE:
TeamContainer::HandleTeamInviteResponse(packet); ChatPacketHandler::HandleTeamInviteResponse(packet);
break; break;
case MessageType::Chat::TEAM_LEAVE: case eChatMessageType::TEAM_LEAVE:
TeamContainer::HandleTeamLeave(packet); ChatPacketHandler::HandleTeamLeave(packet);
break; break;
case MessageType::Chat::TEAM_SET_LEADER: case eChatMessageType::TEAM_SET_LEADER:
TeamContainer::HandleTeamPromote(packet); ChatPacketHandler::HandleTeamPromote(packet);
break; break;
case MessageType::Chat::TEAM_KICK: case eChatMessageType::TEAM_KICK:
TeamContainer::HandleTeamKick(packet); ChatPacketHandler::HandleTeamKick(packet);
break; break;
case MessageType::Chat::TEAM_SET_LOOT: case eChatMessageType::TEAM_SET_LOOT:
TeamContainer::HandleTeamLootOption(packet); ChatPacketHandler::HandleTeamLootOption(packet);
break; break;
case MessageType::Chat::GMLEVEL_UPDATE: case eChatMessageType::GMLEVEL_UPDATE:
ChatPacketHandler::HandleGMLevelUpdate(packet); ChatPacketHandler::HandleGMLevelUpdate(packet);
break; break;
case MessageType::Chat::LOGIN_SESSION_NOTIFY: case eChatMessageType::LOGIN_SESSION_NOTIFY:
Game::playerContainer.InsertPlayer(packet); Game::playerContainer.InsertPlayer(packet);
break; break;
case MessageType::Chat::GM_ANNOUNCE: case eChatMessageType::GM_ANNOUNCE:{
// we just forward this packet to every connected server // we just forward this packet to every connected server
inStream.ResetReadPointer(); inStream.ResetReadPointer();
Game::server->Send(inStream, packet->systemAddress, true); // send to everyone except origin Game::server->Send(inStream, packet->systemAddress, true); // send to everyone except origin
break; }
case MessageType::Chat::UNEXPECTED_DISCONNECT: break;
Game::playerContainer.ScheduleRemovePlayer(packet); case eChatMessageType::UNEXPECTED_DISCONNECT:
break; Game::playerContainer.RemovePlayer(packet);
case MessageType::Chat::WHO: break;
ChatPacketHandler::HandleWho(packet); case eChatMessageType::WHO:
break; ChatPacketHandler::HandleWho(packet);
case MessageType::Chat::SHOW_ALL: break;
ChatPacketHandler::HandleShowAll(packet); case eChatMessageType::SHOW_ALL:
break; ChatPacketHandler::HandleShowAll(packet);
case MessageType::Chat::ACHIEVEMENT_NOTIFY: break;
ChatPacketHandler::OnAchievementNotify(inStream, packet->systemAddress); case eChatMessageType::USER_CHANNEL_CHAT_MESSAGE:
break; case eChatMessageType::WORLD_DISCONNECT_REQUEST:
case MessageType::Chat::USER_CHANNEL_CHAT_MESSAGE: case eChatMessageType::WORLD_PROXIMITY_RESPONSE:
case MessageType::Chat::WORLD_DISCONNECT_REQUEST: case eChatMessageType::WORLD_PARCEL_RESPONSE:
case MessageType::Chat::WORLD_PROXIMITY_RESPONSE: case eChatMessageType::TEAM_MISSED_INVITE_CHECK:
case MessageType::Chat::WORLD_PARCEL_RESPONSE: case eChatMessageType::GUILD_CREATE:
case MessageType::Chat::TEAM_MISSED_INVITE_CHECK: case eChatMessageType::GUILD_INVITE:
case MessageType::Chat::GUILD_CREATE: case eChatMessageType::GUILD_INVITE_RESPONSE:
case MessageType::Chat::GUILD_INVITE: case eChatMessageType::GUILD_LEAVE:
case MessageType::Chat::GUILD_INVITE_RESPONSE: case eChatMessageType::GUILD_KICK:
case MessageType::Chat::GUILD_LEAVE: case eChatMessageType::GUILD_GET_STATUS:
case MessageType::Chat::GUILD_KICK: case eChatMessageType::GUILD_GET_ALL:
case MessageType::Chat::GUILD_GET_STATUS: case eChatMessageType::BLUEPRINT_MODERATED:
case MessageType::Chat::GUILD_GET_ALL: case eChatMessageType::BLUEPRINT_MODEL_READY:
case MessageType::Chat::BLUEPRINT_MODERATED: case eChatMessageType::PROPERTY_READY_FOR_APPROVAL:
case MessageType::Chat::BLUEPRINT_MODEL_READY: case eChatMessageType::PROPERTY_MODERATION_CHANGED:
case MessageType::Chat::PROPERTY_READY_FOR_APPROVAL: case eChatMessageType::PROPERTY_BUILDMODE_CHANGED:
case MessageType::Chat::PROPERTY_MODERATION_CHANGED: case eChatMessageType::PROPERTY_BUILDMODE_CHANGED_REPORT:
case MessageType::Chat::PROPERTY_BUILDMODE_CHANGED: case eChatMessageType::MAIL:
case MessageType::Chat::PROPERTY_BUILDMODE_CHANGED_REPORT: case eChatMessageType::WORLD_INSTANCE_LOCATION_REQUEST:
case MessageType::Chat::MAIL: case eChatMessageType::REPUTATION_UPDATE:
case MessageType::Chat::WORLD_INSTANCE_LOCATION_REQUEST: case eChatMessageType::SEND_CANNED_TEXT:
case MessageType::Chat::REPUTATION_UPDATE: case eChatMessageType::CHARACTER_NAME_CHANGE_REQUEST:
case MessageType::Chat::SEND_CANNED_TEXT: case eChatMessageType::CSR_REQUEST:
case MessageType::Chat::CHARACTER_NAME_CHANGE_REQUEST: case eChatMessageType::CSR_REPLY:
case MessageType::Chat::CSR_REQUEST: case eChatMessageType::GM_KICK:
case MessageType::Chat::CSR_REPLY: case eChatMessageType::WORLD_ROUTE_PACKET:
case MessageType::Chat::GM_KICK: case eChatMessageType::GET_ZONE_POPULATIONS:
case MessageType::Chat::WORLD_ROUTE_PACKET: case eChatMessageType::REQUEST_MINIMUM_CHAT_MODE:
case MessageType::Chat::GET_ZONE_POPULATIONS: case eChatMessageType::MATCH_REQUEST:
case MessageType::Chat::REQUEST_MINIMUM_CHAT_MODE: case eChatMessageType::UGCMANIFEST_REPORT_MISSING_FILE:
case MessageType::Chat::MATCH_REQUEST: case eChatMessageType::UGCMANIFEST_REPORT_DONE_FILE:
case MessageType::Chat::UGCMANIFEST_REPORT_MISSING_FILE: case eChatMessageType::UGCMANIFEST_REPORT_DONE_BLUEPRINT:
case MessageType::Chat::UGCMANIFEST_REPORT_DONE_FILE: case eChatMessageType::UGCC_REQUEST:
case MessageType::Chat::UGCMANIFEST_REPORT_DONE_BLUEPRINT: case eChatMessageType::WORLD_PLAYERS_PET_MODERATED_ACKNOWLEDGE:
case MessageType::Chat::UGCC_REQUEST: case eChatMessageType::ACHIEVEMENT_NOTIFY:
case MessageType::Chat::WORLD_PLAYERS_PET_MODERATED_ACKNOWLEDGE: case eChatMessageType::GM_CLOSE_PRIVATE_CHAT_WINDOW:
case MessageType::Chat::GM_CLOSE_PRIVATE_CHAT_WINDOW: case eChatMessageType::PLAYER_READY:
case MessageType::Chat::PLAYER_READY: case eChatMessageType::GET_DONATION_TOTAL:
case MessageType::Chat::GET_DONATION_TOTAL: case eChatMessageType::UPDATE_DONATION:
case MessageType::Chat::UPDATE_DONATION: case eChatMessageType::PRG_CSR_COMMAND:
case MessageType::Chat::PRG_CSR_COMMAND: case eChatMessageType::HEARTBEAT_REQUEST_FROM_WORLD:
case MessageType::Chat::HEARTBEAT_REQUEST_FROM_WORLD: case eChatMessageType::UPDATE_FREE_TRIAL_STATUS:
case MessageType::Chat::UPDATE_FREE_TRIAL_STATUS: LOG("Unhandled CHAT Message id: %s (%i)", StringifiedEnum::ToString(chatMessageID).data(), chatMessageID);
LOG("Unhandled CHAT Message id: %s (%i)", StringifiedEnum::ToString(chatMessageID).data(), chatMessageID); break;
break; default:
default: LOG("Unknown CHAT Message id: %i", chatMessageID);
LOG("Unknown CHAT Message id: %i", chatMessageID);
} }
} }

View File

@@ -1,133 +0,0 @@
#include "ChatWeb.h"
#include "Logger.h"
#include "Game.h"
#include "json.hpp"
#include "dCommonVars.h"
#include "MessageType/Chat.h"
#include "dServer.h"
#include "dConfig.h"
#include "PlayerContainer.h"
#include "GeneralUtils.h"
#include "eHTTPMethod.h"
#include "magic_enum.hpp"
#include "ChatPackets.h"
#include "StringifiedEnum.h"
#include "Database.h"
#include "ChatJSONUtils.h"
#include "JSONUtils.h"
#include "eGameMasterLevel.h"
#include "dChatFilter.h"
#include "TeamContainer.h"
using json = nlohmann::json;
void HandleHTTPPlayersRequest(HTTPReply& reply, std::string body) {
const json data = Game::playerContainer;
reply.status = data.empty() ? eHTTPStatusCode::NO_CONTENT : eHTTPStatusCode::OK;
reply.message = data.empty() ? "{\"error\":\"No Players Online\"}" : data.dump();
}
void HandleHTTPTeamsRequest(HTTPReply& reply, std::string body) {
const json data = TeamContainer::GetTeamContainer();
reply.status = data.empty() ? eHTTPStatusCode::NO_CONTENT : eHTTPStatusCode::OK;
reply.message = data.empty() ? "{\"error\":\"No Teams Online\"}" : data.dump();
}
void HandleHTTPAnnounceRequest(HTTPReply& reply, std::string body) {
auto data = GeneralUtils::TryParse<json>(body);
if (!data) {
reply.status = eHTTPStatusCode::BAD_REQUEST;
reply.message = "{\"error\":\"Invalid JSON\"}";
return;
}
const auto& good_data = data.value();
auto check = JSONUtils::CheckRequiredData(good_data, { "title", "message" });
if (!check.empty()) {
reply.status = eHTTPStatusCode::BAD_REQUEST;
reply.message = check;
} else {
ChatPackets::Announcement announcement;
announcement.title = good_data["title"];
announcement.message = good_data["message"];
announcement.Broadcast();
reply.status = eHTTPStatusCode::OK;
reply.message = "{\"status\":\"Announcement Sent\"}";
}
}
void HandleWSChat(mg_connection* connection, json data) {
auto check = JSONUtils::CheckRequiredData(data, { "user", "message", "gmlevel", "zone" });
if (!check.empty()) {
LOG_DEBUG("Received invalid websocket message: %s", check.c_str());
} else {
const auto user = data["user"].get<std::string>();
const auto message = data["message"].get<std::string>();
const auto gmlevel = GeneralUtils::TryParse<eGameMasterLevel>(data["gmlevel"].get<std::string>()).value_or(eGameMasterLevel::CIVILIAN);
const auto zone = data["zone"].get<uint32_t>();
const auto filter_check = Game::chatFilter->IsSentenceOkay(message, gmlevel);
if (!filter_check.empty()) {
LOG_DEBUG("Chat message \"%s\" from %s was not allowed", message.c_str(), user.c_str());
data["error"] = "Chat message blocked by filter";
data["filtered"] = json::array();
for (const auto& [start, len] : filter_check) {
data["filtered"].push_back(message.substr(start, len));
}
mg_ws_send(connection, data.dump().c_str(), data.dump().size(), WEBSOCKET_OP_TEXT);
return;
}
LOG("%s: %s", user.c_str(), message.c_str());
// TODO: Implement chat message handling from websocket message
}
}
namespace ChatWeb {
void RegisterRoutes() {
// REST API v1 routes
std::string v1_route = "/api/v1/";
Game::web.RegisterHTTPRoute({
.path = v1_route + "players",
.method = eHTTPMethod::GET,
.handle = HandleHTTPPlayersRequest
});
Game::web.RegisterHTTPRoute({
.path = v1_route + "teams",
.method = eHTTPMethod::GET,
.handle = HandleHTTPTeamsRequest
});
Game::web.RegisterHTTPRoute({
.path = v1_route + "announce",
.method = eHTTPMethod::POST,
.handle = HandleHTTPAnnounceRequest
});
// WebSocket Events Handlers
// Game::web.RegisterWSEvent({
// .name = "chat",
// .handle = HandleWSChat
// });
// WebSocket subscriptions
Game::web.RegisterWSSubscription("player");
}
void SendWSPlayerUpdate(const PlayerData& player, eActivityType activityType) {
json data;
data["player_data"] = player;
data["update_type"] = magic_enum::enum_name(activityType);
Game::web.SendWSMessage("player", data);
}
}

View File

@@ -1,19 +0,0 @@
#ifndef __CHATWEB_H__
#define __CHATWEB_H__
#include <string>
#include <functional>
#include "Web.h"
#include "PlayerContainer.h"
#include "IActivityLog.h"
#include "ChatPacketHandler.h"
namespace ChatWeb {
void RegisterRoutes();
void SendWSPlayerUpdate(const PlayerData& player, eActivityType activityType);
};
#endif // __CHATWEB_H__

View File

@@ -11,9 +11,7 @@
#include "eConnectionType.h" #include "eConnectionType.h"
#include "ChatPackets.h" #include "ChatPackets.h"
#include "dConfig.h" #include "dConfig.h"
#include "MessageType/Chat.h" #include "eChatMessageType.h"
#include "ChatWeb.h"
#include "TeamContainer.h"
void PlayerContainer::Initialize() { void PlayerContainer::Initialize() {
m_MaxNumberOfBestFriends = m_MaxNumberOfBestFriends =
@@ -34,61 +32,35 @@ void PlayerContainer::InsertPlayer(Packet* packet) {
return; return;
} }
auto isLogin = !m_Players.contains(playerId);
auto& data = m_Players[playerId]; auto& data = m_Players[playerId];
data = PlayerData();
data.isLogin = isLogin;
data.playerID = playerId; data.playerID = playerId;
uint32_t len; uint32_t len;
if (!inStream.Read<uint32_t>(len)) return; inStream.Read<uint32_t>(len);
if (len > 33) { for (int i = 0; i < len; i++) {
LOG("Received a really long player name, probably a fake packet %i.", len); char character; inStream.Read<char>(character);
return; data.playerName += character;
} }
data.playerName.resize(len); inStream.Read(data.zoneID);
inStream.ReadAlignedBytes(reinterpret_cast<unsigned char*>(data.playerName.data()), len); inStream.Read(data.muteExpire);
inStream.Read(data.gmLevel);
if (!inStream.Read(data.zoneID)) return; data.sysAddr = packet->systemAddress;
if (!inStream.Read(data.muteExpire)) return;
if (!inStream.Read(data.gmLevel)) return;
data.worldServerSysAddr = packet->systemAddress;
m_Names[data.playerID] = GeneralUtils::UTF8ToUTF16(data.playerName); m_Names[data.playerID] = GeneralUtils::UTF8ToUTF16(data.playerName);
m_PlayerCount++; m_PlayerCount++;
LOG("Added user: %s (%llu), zone: %i", data.playerName.c_str(), data.playerID, data.zoneID.GetMapID()); LOG("Added user: %s (%llu), zone: %i", data.playerName.c_str(), data.playerID, data.zoneID.GetMapID());
ChatWeb::SendWSPlayerUpdate(data, isLogin ? eActivityType::PlayerLoggedIn : eActivityType::PlayerChangedZone);
Database::Get()->UpdateActivityLog(data.playerID, isLogin ? eActivityType::PlayerLoggedIn : eActivityType::PlayerChangedZone, data.zoneID.GetMapID()); Database::Get()->UpdateActivityLog(data.playerID, eActivityType::PlayerLoggedIn, data.zoneID.GetMapID());
m_PlayersToRemove.erase(playerId);
} }
void PlayerContainer::ScheduleRemovePlayer(Packet* packet) { void PlayerContainer::RemovePlayer(Packet* packet) {
CINSTREAM_SKIP_HEADER; CINSTREAM_SKIP_HEADER;
LWOOBJID playerID{ LWOOBJID_EMPTY }; LWOOBJID playerID;
inStream.Read(playerID); inStream.Read(playerID);
constexpr float updatePlayerOnLogoutTime = 20.0f;
if (playerID != LWOOBJID_EMPTY) m_PlayersToRemove.insert_or_assign(playerID, updatePlayerOnLogoutTime);
}
void PlayerContainer::Update(const float deltaTime) {
for (auto it = m_PlayersToRemove.begin(); it != m_PlayersToRemove.end();) {
auto& [id, time] = *it;
time -= deltaTime;
if (time <= 0.0f) {
RemovePlayer(id);
it = m_PlayersToRemove.erase(it);
} else {
++it;
}
}
}
void PlayerContainer::RemovePlayer(const LWOOBJID playerID) {
//Before they get kicked, we need to also send a message to their friends saying that they disconnected. //Before they get kicked, we need to also send a message to their friends saying that they disconnected.
const auto& player = GetPlayerData(playerID); const auto& player = GetPlayerData(playerID);
@@ -102,7 +74,7 @@ void PlayerContainer::RemovePlayer(const LWOOBJID playerID) {
if (fd) ChatPacketHandler::SendFriendUpdate(fd, player, 0, fr.isBestFriend); if (fd) ChatPacketHandler::SendFriendUpdate(fd, player, 0, fr.isBestFriend);
} }
auto* team = TeamContainer::GetTeam(playerID); auto* team = GetTeam(playerID);
if (team != nullptr) { if (team != nullptr) {
const auto memberName = GeneralUtils::UTF8ToUTF16(player.playerName); const auto memberName = GeneralUtils::UTF8ToUTF16(player.playerName);
@@ -112,12 +84,10 @@ void PlayerContainer::RemovePlayer(const LWOOBJID playerID) {
if (!otherMember) continue; if (!otherMember) continue;
TeamContainer::SendTeamSetOffWorldFlag(otherMember, playerID, { 0, 0, 0 }); ChatPacketHandler::SendTeamSetOffWorldFlag(otherMember, playerID, { 0, 0, 0 });
} }
} }
ChatWeb::SendWSPlayerUpdate(player, eActivityType::PlayerLoggedOut);
m_PlayerCount--; m_PlayerCount--;
LOG("Removed user: %llu", playerID); LOG("Removed user: %llu", playerID);
m_Players.erase(playerID); m_Players.erase(playerID);
@@ -145,9 +115,39 @@ void PlayerContainer::MuteUpdate(Packet* packet) {
BroadcastMuteUpdate(playerID, expire); BroadcastMuteUpdate(playerID, expire);
} }
void PlayerContainer::CreateTeamServer(Packet* packet) {
CINSTREAM_SKIP_HEADER;
LWOOBJID playerID;
inStream.Read(playerID);
size_t membersSize = 0;
inStream.Read(membersSize);
std::vector<LWOOBJID> members;
members.reserve(membersSize);
for (size_t i = 0; i < membersSize; i++) {
LWOOBJID member;
inStream.Read(member);
members.push_back(member);
}
LWOZONEID zoneId;
inStream.Read(zoneId);
auto* team = CreateLocalTeam(members);
if (team != nullptr) {
team->zoneId = zoneId;
}
UpdateTeamsOnWorld(team, false);
}
void PlayerContainer::BroadcastMuteUpdate(LWOOBJID player, time_t time) { void PlayerContainer::BroadcastMuteUpdate(LWOOBJID player, time_t time) {
CBITSTREAM; CBITSTREAM;
BitStreamUtils::WriteHeader(bitStream, eConnectionType::CHAT, MessageType::Chat::GM_MUTE); BitStreamUtils::WriteHeader(bitStream, eConnectionType::CHAT, eChatMessageType::GM_MUTE);
bitStream.Write(player); bitStream.Write(player);
bitStream.Write(time); bitStream.Write(time);
@@ -155,6 +155,221 @@ void PlayerContainer::BroadcastMuteUpdate(LWOOBJID player, time_t time) {
Game::server->Send(bitStream, UNASSIGNED_SYSTEM_ADDRESS, true); Game::server->Send(bitStream, UNASSIGNED_SYSTEM_ADDRESS, true);
} }
TeamData* PlayerContainer::CreateLocalTeam(std::vector<LWOOBJID> members) {
if (members.empty()) {
return nullptr;
}
TeamData* newTeam = nullptr;
for (const auto member : members) {
auto* team = GetTeam(member);
if (team != nullptr) {
RemoveMember(team, member, false, false, true);
}
if (newTeam == nullptr) {
newTeam = CreateTeam(member, true);
} else {
AddMember(newTeam, member);
}
}
newTeam->lootFlag = 1;
TeamStatusUpdate(newTeam);
return newTeam;
}
TeamData* PlayerContainer::CreateTeam(LWOOBJID leader, bool local) {
auto* team = new TeamData();
team->teamID = ++m_TeamIDCounter;
team->leaderID = leader;
team->local = local;
mTeams.push_back(team);
AddMember(team, leader);
return team;
}
TeamData* PlayerContainer::GetTeam(LWOOBJID playerID) {
for (auto* team : mTeams) {
if (std::find(team->memberIDs.begin(), team->memberIDs.end(), playerID) == team->memberIDs.end()) continue;
return team;
}
return nullptr;
}
void PlayerContainer::AddMember(TeamData* team, LWOOBJID playerID) {
if (team->memberIDs.size() >= 4) {
LOG("Tried to add player to team that already had 4 players");
const auto& player = GetPlayerData(playerID);
if (!player) return;
ChatPackets::SendSystemMessage(player.sysAddr, u"The teams is full! You have not been added to a team!");
return;
}
const auto index = std::find(team->memberIDs.begin(), team->memberIDs.end(), playerID);
if (index != team->memberIDs.end()) return;
team->memberIDs.push_back(playerID);
const auto& leader = GetPlayerData(team->leaderID);
const auto& member = GetPlayerData(playerID);
if (!leader || !member) return;
const auto leaderName = GeneralUtils::UTF8ToUTF16(leader.playerName);
const auto memberName = GeneralUtils::UTF8ToUTF16(member.playerName);
ChatPacketHandler::SendTeamInviteConfirm(member, false, leader.playerID, leader.zoneID, team->lootFlag, 0, 0, leaderName);
if (!team->local) {
ChatPacketHandler::SendTeamSetLeader(member, leader.playerID);
} else {
ChatPacketHandler::SendTeamSetLeader(member, LWOOBJID_EMPTY);
}
UpdateTeamsOnWorld(team, false);
for (const auto memberId : team->memberIDs) {
const auto& otherMember = GetPlayerData(memberId);
if (otherMember == member) continue;
const auto otherMemberName = GetName(memberId);
ChatPacketHandler::SendTeamAddPlayer(member, false, team->local, false, memberId, otherMemberName, otherMember ? otherMember.zoneID : LWOZONEID(0, 0, 0));
if (otherMember) {
ChatPacketHandler::SendTeamAddPlayer(otherMember, false, team->local, false, member.playerID, memberName, member.zoneID);
}
}
}
void PlayerContainer::RemoveMember(TeamData* team, LWOOBJID playerID, bool disband, bool kicked, bool leaving, bool silent) {
const auto index = std::find(team->memberIDs.begin(), team->memberIDs.end(), playerID);
if (index == team->memberIDs.end()) return;
const auto& member = GetPlayerData(playerID);
if (member && !silent) {
ChatPacketHandler::SendTeamSetLeader(member, LWOOBJID_EMPTY);
}
const auto memberName = GetName(playerID);
for (const auto memberId : team->memberIDs) {
if (silent && memberId == playerID) {
continue;
}
const auto& otherMember = GetPlayerData(memberId);
if (!otherMember) continue;
ChatPacketHandler::SendTeamRemovePlayer(otherMember, disband, kicked, leaving, false, team->leaderID, playerID, memberName);
}
team->memberIDs.erase(index);
UpdateTeamsOnWorld(team, false);
if (team->memberIDs.size() <= 1) {
DisbandTeam(team);
} else {
if (playerID == team->leaderID) {
PromoteMember(team, team->memberIDs[0]);
}
}
}
void PlayerContainer::PromoteMember(TeamData* team, LWOOBJID newLeader) {
team->leaderID = newLeader;
for (const auto memberId : team->memberIDs) {
const auto& otherMember = GetPlayerData(memberId);
if (!otherMember) continue;
ChatPacketHandler::SendTeamSetLeader(otherMember, newLeader);
}
}
void PlayerContainer::DisbandTeam(TeamData* team) {
const auto index = std::find(mTeams.begin(), mTeams.end(), team);
if (index == mTeams.end()) return;
for (const auto memberId : team->memberIDs) {
const auto& otherMember = GetPlayerData(memberId);
if (!otherMember) continue;
const auto memberName = GeneralUtils::UTF8ToUTF16(otherMember.playerName);
ChatPacketHandler::SendTeamSetLeader(otherMember, LWOOBJID_EMPTY);
ChatPacketHandler::SendTeamRemovePlayer(otherMember, true, false, false, team->local, team->leaderID, otherMember.playerID, memberName);
}
UpdateTeamsOnWorld(team, true);
mTeams.erase(index);
delete team;
}
void PlayerContainer::TeamStatusUpdate(TeamData* team) {
const auto index = std::find(mTeams.begin(), mTeams.end(), team);
if (index == mTeams.end()) return;
const auto& leader = GetPlayerData(team->leaderID);
if (!leader) return;
const auto leaderName = GeneralUtils::UTF8ToUTF16(leader.playerName);
for (const auto memberId : team->memberIDs) {
const auto& otherMember = GetPlayerData(memberId);
if (!otherMember) continue;
if (!team->local) {
ChatPacketHandler::SendTeamStatus(otherMember, team->leaderID, leader.zoneID, team->lootFlag, 0, leaderName);
}
}
UpdateTeamsOnWorld(team, false);
}
void PlayerContainer::UpdateTeamsOnWorld(TeamData* team, bool deleteTeam) {
CBITSTREAM;
BitStreamUtils::WriteHeader(bitStream, eConnectionType::CHAT, eChatMessageType::TEAM_GET_STATUS);
bitStream.Write(team->teamID);
bitStream.Write(deleteTeam);
if (!deleteTeam) {
bitStream.Write(team->lootFlag);
bitStream.Write<char>(team->memberIDs.size());
for (const auto memberID : team->memberIDs) {
bitStream.Write(memberID);
}
}
Game::server->Send(bitStream, UNASSIGNED_SYSTEM_ADDRESS, true);
}
std::u16string PlayerContainer::GetName(LWOOBJID playerID) { std::u16string PlayerContainer::GetName(LWOOBJID playerID) {
const auto iter = m_Names.find(playerID); const auto iter = m_Names.find(playerID);
@@ -195,12 +410,3 @@ const PlayerData& PlayerContainer::GetPlayerData(const LWOOBJID& playerID) {
const PlayerData& PlayerContainer::GetPlayerData(const std::string& playerName) { const PlayerData& PlayerContainer::GetPlayerData(const std::string& playerName) {
return GetPlayerDataMutable(playerName); return GetPlayerDataMutable(playerName);
} }
void PlayerContainer::Shutdown() {
m_Players.erase(LWOOBJID_EMPTY);
while (!m_Players.empty()) {
const auto& [id, playerData] = *m_Players.begin();
Database::Get()->UpdateActivityLog(id, eActivityType::PlayerLoggedOut, playerData.zoneID.GetMapID());
m_Players.erase(m_Players.begin());
}
}

View File

@@ -9,8 +9,6 @@
enum class eGameMasterLevel : uint8_t; enum class eGameMasterLevel : uint8_t;
struct TeamData;
struct IgnoreData { struct IgnoreData {
IgnoreData(const std::string& name, const LWOOBJID& id) : playerName{ name }, playerId{ id } {} IgnoreData(const std::string& name, const LWOOBJID& id) : playerName{ name }, playerId{ id } {}
inline bool operator==(const std::string& other) const noexcept { inline bool operator==(const std::string& other) const noexcept {
@@ -38,7 +36,7 @@ struct PlayerData {
return muteExpire == 1 || muteExpire > time(NULL); return muteExpire == 1 || muteExpire > time(NULL);
} }
SystemAddress worldServerSysAddr{}; SystemAddress sysAddr{};
LWOZONEID zoneID{}; LWOZONEID zoneID{};
LWOOBJID playerID = LWOOBJID_EMPTY; LWOOBJID playerID = LWOOBJID_EMPTY;
time_t muteExpire = 0; time_t muteExpire = 0;
@@ -48,10 +46,8 @@ struct PlayerData {
std::vector<IgnoreData> ignoredPlayers; std::vector<IgnoreData> ignoredPlayers;
eGameMasterLevel gmLevel = static_cast<eGameMasterLevel>(0); // CIVILLIAN eGameMasterLevel gmLevel = static_cast<eGameMasterLevel>(0); // CIVILLIAN
bool isFTP = false; bool isFTP = false;
bool isLogin = false;
}; };
struct TeamData { struct TeamData {
TeamData(); TeamData();
LWOOBJID teamID = LWOOBJID_EMPTY; // Internal use LWOOBJID teamID = LWOOBJID_EMPTY; // Internal use
@@ -66,31 +62,38 @@ class PlayerContainer {
public: public:
void Initialize(); void Initialize();
void InsertPlayer(Packet* packet); void InsertPlayer(Packet* packet);
void ScheduleRemovePlayer(Packet* packet); void RemovePlayer(Packet* packet);
void RemovePlayer(const LWOOBJID playerID);
void MuteUpdate(Packet* packet); void MuteUpdate(Packet* packet);
void CreateTeamServer(Packet* packet);
void BroadcastMuteUpdate(LWOOBJID player, time_t time); void BroadcastMuteUpdate(LWOOBJID player, time_t time);
void Shutdown();
const PlayerData& GetPlayerData(const LWOOBJID& playerID); const PlayerData& GetPlayerData(const LWOOBJID& playerID);
const PlayerData& GetPlayerData(const std::string& playerName); const PlayerData& GetPlayerData(const std::string& playerName);
PlayerData& GetPlayerDataMutable(const LWOOBJID& playerID); PlayerData& GetPlayerDataMutable(const LWOOBJID& playerID);
PlayerData& GetPlayerDataMutable(const std::string& playerName); PlayerData& GetPlayerDataMutable(const std::string& playerName);
std::u16string GetName(LWOOBJID playerID);
LWOOBJID GetId(const std::u16string& playerName);
void Update(const float deltaTime);
uint32_t GetPlayerCount() { return m_PlayerCount; }; uint32_t GetPlayerCount() { return m_PlayerCount; };
uint32_t GetSimCount() { return m_SimCount; }; uint32_t GetSimCount() { return m_SimCount; };
const std::map<LWOOBJID, PlayerData>& GetAllPlayers() const { return m_Players; }; const std::map<LWOOBJID, PlayerData>& GetAllPlayers() { return m_Players; };
TeamData* CreateLocalTeam(std::vector<LWOOBJID> members);
TeamData* CreateTeam(LWOOBJID leader, bool local = false);
TeamData* GetTeam(LWOOBJID playerID);
void AddMember(TeamData* team, LWOOBJID playerID);
void RemoveMember(TeamData* team, LWOOBJID playerID, bool disband, bool kicked, bool leaving, bool silent = false);
void PromoteMember(TeamData* team, LWOOBJID newLeader);
void DisbandTeam(TeamData* team);
void TeamStatusUpdate(TeamData* team);
void UpdateTeamsOnWorld(TeamData* team, bool deleteTeam);
std::u16string GetName(LWOOBJID playerID);
LWOOBJID GetId(const std::u16string& playerName);
uint32_t GetMaxNumberOfBestFriends() { return m_MaxNumberOfBestFriends; } uint32_t GetMaxNumberOfBestFriends() { return m_MaxNumberOfBestFriends; }
uint32_t GetMaxNumberOfFriends() { return m_MaxNumberOfFriends; } uint32_t GetMaxNumberOfFriends() { return m_MaxNumberOfFriends; }
bool PlayerBeingRemoved(const LWOOBJID playerID) { return m_PlayersToRemove.contains(playerID); }
private: private:
LWOOBJID m_TeamIDCounter = 0;
std::map<LWOOBJID, PlayerData> m_Players; std::map<LWOOBJID, PlayerData> m_Players;
std::vector<TeamData*> mTeams;
std::unordered_map<LWOOBJID, std::u16string> m_Names; std::unordered_map<LWOOBJID, std::u16string> m_Names;
std::map<LWOOBJID, float> m_PlayersToRemove;
uint32_t m_MaxNumberOfBestFriends = 5; uint32_t m_MaxNumberOfBestFriends = 5;
uint32_t m_MaxNumberOfFriends = 50; uint32_t m_MaxNumberOfFriends = 50;
uint32_t m_PlayerCount = 0; uint32_t m_PlayerCount = 0;

View File

@@ -1,669 +0,0 @@
#include "TeamContainer.h"
#include "ChatPackets.h"
#include "MessageType/Chat.h"
#include "MessageType/Game.h"
#include "ChatPacketHandler.h"
#include "PlayerContainer.h"
namespace {
TeamContainer::Data g_TeamContainer{};
LWOOBJID g_TeamIDCounter = 0;
}
const TeamContainer::Data& TeamContainer::GetTeamContainer() {
return g_TeamContainer;
}
std::vector<TeamData*>& TeamContainer::GetTeamsMut() {
return g_TeamContainer.mTeams;
}
const std::vector<TeamData*>& TeamContainer::GetTeams() {
return GetTeamsMut();
}
void TeamContainer::Shutdown() {
for (auto* team : g_TeamContainer.mTeams) if (team) delete team;
}
void TeamContainer::HandleTeamInvite(Packet* packet) {
CINSTREAM_SKIP_HEADER;
LWOOBJID playerID;
LUWString invitedPlayer;
inStream.Read(playerID);
inStream.IgnoreBytes(4);
inStream.Read(invitedPlayer);
const auto& player = Game::playerContainer.GetPlayerData(playerID);
if (!player) return;
auto* team = GetTeam(playerID);
if (team == nullptr) {
team = CreateTeam(playerID);
}
const auto& other = Game::playerContainer.GetPlayerData(invitedPlayer.GetAsString());
if (!other) return;
if (GetTeam(other.playerID) != nullptr) {
return;
}
if (team->memberIDs.size() > 3) {
// no more teams greater than 4
LOG("Someone tried to invite a 5th player to a team");
return;
}
SendTeamInvite(other, player);
LOG("Got team invite: %llu -> %s", playerID, invitedPlayer.GetAsString().c_str());
bool failed = false;
for (const auto& ignore : other.ignoredPlayers) {
if (ignore.playerId == player.playerID) {
failed = true;
break;
}
}
ChatPackets::TeamInviteInitialResponse response{};
response.inviteFailedToSend = failed;
response.playerName = invitedPlayer.string;
ChatPackets::SendRoutedMsg(response, playerID, player.worldServerSysAddr);
}
void TeamContainer::HandleTeamInviteResponse(Packet* packet) {
CINSTREAM_SKIP_HEADER;
LWOOBJID playerID = LWOOBJID_EMPTY;
inStream.Read(playerID);
uint32_t size = 0;
inStream.Read(size);
char declined = 0;
inStream.Read(declined);
LWOOBJID leaderID = LWOOBJID_EMPTY;
inStream.Read(leaderID);
LOG("Invite reponse received: %llu -> %llu (%d)", playerID, leaderID, declined);
if (declined) {
return;
}
auto* team = GetTeam(leaderID);
if (team == nullptr) {
LOG("Failed to find team for leader (%llu)", leaderID);
team = GetTeam(playerID);
}
if (team == nullptr) {
LOG("Failed to find team for player (%llu)", playerID);
return;
}
AddMember(team, playerID);
}
void TeamContainer::HandleTeamLeave(Packet* packet) {
CINSTREAM_SKIP_HEADER;
LWOOBJID playerID = LWOOBJID_EMPTY;
inStream.Read(playerID);
uint32_t size = 0;
inStream.Read(size);
auto* team = GetTeam(playerID);
LOG("(%llu) leaving team", playerID);
if (team != nullptr) {
RemoveMember(team, playerID, false, false, true);
}
}
void TeamContainer::HandleTeamKick(Packet* packet) {
CINSTREAM_SKIP_HEADER;
LWOOBJID playerID = LWOOBJID_EMPTY;
LUWString kickedPlayer;
inStream.Read(playerID);
inStream.IgnoreBytes(4);
inStream.Read(kickedPlayer);
LOG("(%llu) kicking (%s) from team", playerID, kickedPlayer.GetAsString().c_str());
const auto& kicked = Game::playerContainer.GetPlayerData(kickedPlayer.GetAsString());
LWOOBJID kickedId = LWOOBJID_EMPTY;
if (kicked) {
kickedId = kicked.playerID;
} else {
kickedId = Game::playerContainer.GetId(kickedPlayer.string);
}
if (kickedId == LWOOBJID_EMPTY) return;
auto* team = GetTeam(playerID);
if (team != nullptr) {
if (team->leaderID != playerID || team->leaderID == kickedId) return;
RemoveMember(team, kickedId, false, true, false);
}
}
void TeamContainer::HandleTeamPromote(Packet* packet) {
CINSTREAM_SKIP_HEADER;
LWOOBJID playerID = LWOOBJID_EMPTY;
LUWString promotedPlayer;
inStream.Read(playerID);
inStream.IgnoreBytes(4);
inStream.Read(promotedPlayer);
LOG("(%llu) promoting (%s) to team leader", playerID, promotedPlayer.GetAsString().c_str());
const auto& promoted = Game::playerContainer.GetPlayerData(promotedPlayer.GetAsString());
if (!promoted) return;
auto* team = GetTeam(playerID);
if (team != nullptr) {
if (team->leaderID != playerID) return;
PromoteMember(team, promoted.playerID);
}
}
void TeamContainer::HandleTeamLootOption(Packet* packet) {
CINSTREAM_SKIP_HEADER;
LWOOBJID playerID = LWOOBJID_EMPTY;
inStream.Read(playerID);
uint32_t size = 0;
inStream.Read(size);
char option;
inStream.Read(option);
auto* team = GetTeam(playerID);
if (team != nullptr) {
if (team->leaderID != playerID) return;
team->lootFlag = option;
TeamStatusUpdate(team);
UpdateTeamsOnWorld(team, false);
}
}
void TeamContainer::HandleTeamStatusRequest(Packet* packet) {
CINSTREAM_SKIP_HEADER;
LWOOBJID playerID = LWOOBJID_EMPTY;
inStream.Read(playerID);
auto* team = GetTeam(playerID);
const auto& data = Game::playerContainer.GetPlayerData(playerID);
if (team != nullptr && data) {
LOG_DEBUG("Player %llu is requesting team status", playerID);
if (team->local && data.zoneID.GetMapID() != team->zoneId.GetMapID() && data.zoneID.GetCloneID() != team->zoneId.GetCloneID()) {
RemoveMember(team, playerID, false, false, false, true);
return;
}
if (team->memberIDs.size() <= 1 && !team->local) {
DisbandTeam(team, LWOOBJID_EMPTY, u"");
return;
}
if (!team->local) {
SendTeamSetLeader(data, team->leaderID);
} else {
SendTeamSetLeader(data, LWOOBJID_EMPTY);
}
TeamStatusUpdate(team);
const auto leaderName = GeneralUtils::UTF8ToUTF16(data.playerName);
for (const auto memberId : team->memberIDs) {
const auto& otherMember = Game::playerContainer.GetPlayerData(memberId);
if (memberId == playerID) continue;
const auto memberName = Game::playerContainer.GetName(memberId);
if (otherMember) {
SendTeamSetOffWorldFlag(otherMember, data.playerID, data.zoneID);
}
SendTeamAddPlayer(data, false, team->local, false, memberId, memberName, otherMember ? otherMember.zoneID : LWOZONEID(0, 0, 0));
}
UpdateTeamsOnWorld(team, false);
}
}
void TeamContainer::SendTeamInvite(const PlayerData& receiver, const PlayerData& sender) {
CBITSTREAM;
BitStreamUtils::WriteHeader(bitStream, eConnectionType::CHAT, MessageType::Chat::WORLD_ROUTE_PACKET);
bitStream.Write(receiver.playerID);
//portion that will get routed:
BitStreamUtils::WriteHeader(bitStream, eConnectionType::CLIENT, MessageType::Client::TEAM_INVITE);
bitStream.Write(LUWString(sender.playerName.c_str()));
bitStream.Write(sender.playerID);
SystemAddress sysAddr = receiver.worldServerSysAddr;
SEND_PACKET;
}
void TeamContainer::SendTeamInviteConfirm(const PlayerData& receiver, bool bLeaderIsFreeTrial, LWOOBJID i64LeaderID, LWOZONEID i64LeaderZoneID, uint8_t ucLootFlag, uint8_t ucNumOfOtherPlayers, uint8_t ucResponseCode, std::u16string wsLeaderName) {
CBITSTREAM;
BitStreamUtils::WriteHeader(bitStream, eConnectionType::CHAT, MessageType::Chat::WORLD_ROUTE_PACKET);
bitStream.Write(receiver.playerID);
//portion that will get routed:
CMSGHEADER;
bitStream.Write(receiver.playerID);
bitStream.Write(MessageType::Game::TEAM_INVITE_CONFIRM);
bitStream.Write(bLeaderIsFreeTrial);
bitStream.Write(i64LeaderID);
bitStream.Write(i64LeaderZoneID);
bitStream.Write<uint32_t>(0); // BinaryBuffe, no clue what's in here
bitStream.Write(ucLootFlag);
bitStream.Write(ucNumOfOtherPlayers);
bitStream.Write(ucResponseCode);
bitStream.Write<uint32_t>(wsLeaderName.size());
for (const auto character : wsLeaderName) {
bitStream.Write(character);
}
SystemAddress sysAddr = receiver.worldServerSysAddr;
SEND_PACKET;
}
void TeamContainer::SendTeamStatus(const PlayerData& receiver, LWOOBJID i64LeaderID, LWOZONEID i64LeaderZoneID, uint8_t ucLootFlag, uint8_t ucNumOfOtherPlayers, std::u16string wsLeaderName) {
CBITSTREAM;
BitStreamUtils::WriteHeader(bitStream, eConnectionType::CHAT, MessageType::Chat::WORLD_ROUTE_PACKET);
bitStream.Write(receiver.playerID);
//portion that will get routed:
CMSGHEADER;
bitStream.Write(receiver.playerID);
bitStream.Write(MessageType::Game::TEAM_GET_STATUS_RESPONSE);
bitStream.Write(i64LeaderID);
bitStream.Write(i64LeaderZoneID);
bitStream.Write<uint32_t>(0); // BinaryBuffe, no clue what's in here
bitStream.Write(ucLootFlag);
bitStream.Write(ucNumOfOtherPlayers);
bitStream.Write<uint32_t>(wsLeaderName.size());
for (const auto character : wsLeaderName) {
bitStream.Write(character);
}
SystemAddress sysAddr = receiver.worldServerSysAddr;
SEND_PACKET;
}
void TeamContainer::SendTeamSetLeader(const PlayerData& receiver, LWOOBJID i64PlayerID) {
CBITSTREAM;
BitStreamUtils::WriteHeader(bitStream, eConnectionType::CHAT, MessageType::Chat::WORLD_ROUTE_PACKET);
bitStream.Write(receiver.playerID);
//portion that will get routed:
CMSGHEADER;
bitStream.Write(receiver.playerID);
bitStream.Write(MessageType::Game::TEAM_SET_LEADER);
bitStream.Write(i64PlayerID);
SystemAddress sysAddr = receiver.worldServerSysAddr;
SEND_PACKET;
}
void TeamContainer::SendTeamAddPlayer(const PlayerData& receiver, bool bIsFreeTrial, bool bLocal, bool bNoLootOnDeath, LWOOBJID i64PlayerID, std::u16string wsPlayerName, LWOZONEID zoneID) {
CBITSTREAM;
BitStreamUtils::WriteHeader(bitStream, eConnectionType::CHAT, MessageType::Chat::WORLD_ROUTE_PACKET);
bitStream.Write(receiver.playerID);
//portion that will get routed:
CMSGHEADER;
bitStream.Write(receiver.playerID);
bitStream.Write(MessageType::Game::TEAM_ADD_PLAYER);
bitStream.Write(bIsFreeTrial);
bitStream.Write(bLocal);
bitStream.Write(bNoLootOnDeath);
bitStream.Write(i64PlayerID);
bitStream.Write<uint32_t>(wsPlayerName.size());
for (const auto character : wsPlayerName) {
bitStream.Write(character);
}
bitStream.Write1();
if (receiver.zoneID.GetCloneID() == zoneID.GetCloneID()) {
zoneID = LWOZONEID(zoneID.GetMapID(), zoneID.GetInstanceID(), 0);
}
bitStream.Write(zoneID);
SystemAddress sysAddr = receiver.worldServerSysAddr;
SEND_PACKET;
}
void TeamContainer::SendTeamRemovePlayer(const PlayerData& receiver, bool bDisband, bool bIsKicked, bool bIsLeaving, bool bLocal, LWOOBJID i64LeaderID, LWOOBJID i64PlayerID, std::u16string wsPlayerName) {
CBITSTREAM;
BitStreamUtils::WriteHeader(bitStream, eConnectionType::CHAT, MessageType::Chat::WORLD_ROUTE_PACKET);
bitStream.Write(receiver.playerID);
//portion that will get routed:
CMSGHEADER;
bitStream.Write(receiver.playerID);
bitStream.Write(MessageType::Game::TEAM_REMOVE_PLAYER);
bitStream.Write(bDisband);
bitStream.Write(bIsKicked);
bitStream.Write(bIsLeaving);
bitStream.Write(bLocal);
bitStream.Write(i64LeaderID);
bitStream.Write(i64PlayerID);
bitStream.Write<uint32_t>(wsPlayerName.size());
for (const auto character : wsPlayerName) {
bitStream.Write(character);
}
SystemAddress sysAddr = receiver.worldServerSysAddr;
SEND_PACKET;
}
void TeamContainer::SendTeamSetOffWorldFlag(const PlayerData& receiver, LWOOBJID i64PlayerID, LWOZONEID zoneID) {
CBITSTREAM;
BitStreamUtils::WriteHeader(bitStream, eConnectionType::CHAT, MessageType::Chat::WORLD_ROUTE_PACKET);
bitStream.Write(receiver.playerID);
//portion that will get routed:
CMSGHEADER;
bitStream.Write(receiver.playerID);
bitStream.Write(MessageType::Game::TEAM_SET_OFF_WORLD_FLAG);
bitStream.Write(i64PlayerID);
if (receiver.zoneID.GetCloneID() == zoneID.GetCloneID()) {
zoneID = LWOZONEID(zoneID.GetMapID(), zoneID.GetInstanceID(), 0);
}
bitStream.Write(zoneID);
SystemAddress sysAddr = receiver.worldServerSysAddr;
SEND_PACKET;
}
void TeamContainer::CreateTeamServer(Packet* packet) {
CINSTREAM_SKIP_HEADER;
LWOOBJID playerID;
inStream.Read(playerID);
size_t membersSize = 0;
inStream.Read(membersSize);
if (membersSize >= 4) {
LOG("Tried to create a team with more than 4 players");
return;
}
std::vector<LWOOBJID> members;
members.reserve(membersSize);
for (size_t i = 0; i < membersSize; i++) {
LWOOBJID member;
inStream.Read(member);
members.push_back(member);
}
LWOZONEID zoneId;
inStream.Read(zoneId);
auto* team = CreateLocalTeam(members);
if (team != nullptr) {
team->zoneId = zoneId;
UpdateTeamsOnWorld(team, false);
}
}
TeamData* TeamContainer::CreateLocalTeam(std::vector<LWOOBJID> members) {
if (members.empty()) {
return nullptr;
}
TeamData* newTeam = nullptr;
for (const auto member : members) {
auto* team = GetTeam(member);
if (team != nullptr) {
RemoveMember(team, member, false, false, true);
}
if (newTeam == nullptr) {
newTeam = CreateTeam(member, true);
} else {
AddMember(newTeam, member);
}
}
newTeam->lootFlag = 1;
TeamStatusUpdate(newTeam);
return newTeam;
}
TeamData* TeamContainer::CreateTeam(LWOOBJID leader, bool local) {
auto* team = new TeamData();
team->teamID = ++g_TeamIDCounter;
team->leaderID = leader;
team->local = local;
GetTeamsMut().push_back(team);
AddMember(team, leader);
return team;
}
TeamData* TeamContainer::GetTeam(LWOOBJID playerID) {
for (auto* team : GetTeams()) {
if (std::find(team->memberIDs.begin(), team->memberIDs.end(), playerID) == team->memberIDs.end()) continue;
return team;
}
return nullptr;
}
void TeamContainer::AddMember(TeamData* team, LWOOBJID playerID) {
if (team->memberIDs.size() >= 4) {
LOG("Tried to add player to team that already had 4 players");
const auto& player = Game::playerContainer.GetPlayerData(playerID);
if (!player) return;
ChatPackets::SendSystemMessage(player.worldServerSysAddr, u"The teams is full! You have not been added to a team!");
return;
}
const auto index = std::find(team->memberIDs.begin(), team->memberIDs.end(), playerID);
if (index != team->memberIDs.end()) return;
team->memberIDs.push_back(playerID);
const auto& leader = Game::playerContainer.GetPlayerData(team->leaderID);
const auto& member = Game::playerContainer.GetPlayerData(playerID);
if (!leader || !member) return;
const auto leaderName = GeneralUtils::UTF8ToUTF16(leader.playerName);
const auto memberName = GeneralUtils::UTF8ToUTF16(member.playerName);
SendTeamInviteConfirm(member, false, leader.playerID, leader.zoneID, team->lootFlag, 0, 0, leaderName);
if (!team->local) {
SendTeamSetLeader(member, leader.playerID);
} else {
SendTeamSetLeader(member, LWOOBJID_EMPTY);
}
UpdateTeamsOnWorld(team, false);
for (const auto memberId : team->memberIDs) {
const auto& otherMember = Game::playerContainer.GetPlayerData(memberId);
if (otherMember == member) continue;
const auto otherMemberName = Game::playerContainer.GetName(memberId);
SendTeamAddPlayer(member, false, team->local, false, memberId, otherMemberName, otherMember ? otherMember.zoneID : LWOZONEID(0, 0, 0));
if (otherMember) {
SendTeamAddPlayer(otherMember, false, team->local, false, member.playerID, memberName, member.zoneID);
}
}
}
void TeamContainer::RemoveMember(TeamData* team, LWOOBJID causingPlayerID, bool disband, bool kicked, bool leaving, bool silent) {
LOG_DEBUG("Player %llu is leaving team %i", causingPlayerID, team->teamID);
const auto index = std::ranges::find(team->memberIDs, causingPlayerID);
if (index == team->memberIDs.end()) return;
team->memberIDs.erase(index);
const auto& member = Game::playerContainer.GetPlayerData(causingPlayerID);
const auto causingMemberName = Game::playerContainer.GetName(causingPlayerID);
if (member && !silent) {
SendTeamRemovePlayer(member, disband, kicked, leaving, team->local, LWOOBJID_EMPTY, causingPlayerID, causingMemberName);
}
if (team->memberIDs.size() <= 1) {
DisbandTeam(team, causingPlayerID, causingMemberName);
} else /* team has enough members to be a team still */ {
team->leaderID = (causingPlayerID == team->leaderID) ? team->memberIDs[0] : team->leaderID;
for (const auto memberId : team->memberIDs) {
if (silent && memberId == causingPlayerID) {
continue;
}
const auto& otherMember = Game::playerContainer.GetPlayerData(memberId);
if (!otherMember) continue;
SendTeamRemovePlayer(otherMember, disband, kicked, leaving, team->local, team->leaderID, causingPlayerID, causingMemberName);
}
UpdateTeamsOnWorld(team, false);
}
}
void TeamContainer::PromoteMember(TeamData* team, LWOOBJID newLeader) {
team->leaderID = newLeader;
for (const auto memberId : team->memberIDs) {
const auto& otherMember = Game::playerContainer.GetPlayerData(memberId);
if (!otherMember) continue;
SendTeamSetLeader(otherMember, newLeader);
}
}
void TeamContainer::DisbandTeam(TeamData* team, const LWOOBJID causingPlayerID, const std::u16string& causingPlayerName) {
const auto index = std::ranges::find(GetTeams(), team);
if (index == GetTeams().end()) return;
LOG_DEBUG("Disbanding team %i", (*index)->teamID);
for (const auto memberId : team->memberIDs) {
const auto& otherMember = Game::playerContainer.GetPlayerData(memberId);
if (!otherMember) continue;
SendTeamSetLeader(otherMember, LWOOBJID_EMPTY);
SendTeamRemovePlayer(otherMember, true, false, false, team->local, team->leaderID, causingPlayerID, causingPlayerName);
}
UpdateTeamsOnWorld(team, true);
GetTeamsMut().erase(index);
delete team;
}
void TeamContainer::TeamStatusUpdate(TeamData* team) {
const auto index = std::find(GetTeams().begin(), GetTeams().end(), team);
if (index == GetTeams().end()) return;
const auto& leader = Game::playerContainer.GetPlayerData(team->leaderID);
if (!leader) return;
const auto leaderName = GeneralUtils::UTF8ToUTF16(leader.playerName);
for (const auto memberId : team->memberIDs) {
const auto& otherMember = Game::playerContainer.GetPlayerData(memberId);
if (!otherMember) continue;
if (!team->local) {
SendTeamStatus(otherMember, team->leaderID, leader.zoneID, team->lootFlag, 0, leaderName);
}
}
UpdateTeamsOnWorld(team, false);
}
void TeamContainer::UpdateTeamsOnWorld(TeamData* team, bool deleteTeam) {
CBITSTREAM;
BitStreamUtils::WriteHeader(bitStream, eConnectionType::CHAT, MessageType::Chat::TEAM_GET_STATUS);
bitStream.Write(team->teamID);
bitStream.Write(deleteTeam);
if (!deleteTeam) {
bitStream.Write(team->lootFlag);
bitStream.Write<char>(team->memberIDs.size());
for (const auto memberID : team->memberIDs) {
bitStream.Write(memberID);
}
}
Game::server->Send(bitStream, UNASSIGNED_SYSTEM_ADDRESS, true);
}

View File

@@ -1,59 +0,0 @@
// Darkflame Universe
// Copyright 2025
#ifndef TEAMCONTAINER_H
#define TEAMCONTAINER_H
#include <cstdint>
#include <string>
#include <vector>
#include "dCommonVars.h"
struct Packet;
struct PlayerData;
struct TeamData;
namespace TeamContainer {
struct Data {
std::vector<TeamData*> mTeams;
};
void Shutdown();
void HandleTeamInvite(Packet* packet);
void HandleTeamInviteResponse(Packet* packet);
void HandleTeamLeave(Packet* packet);
void HandleTeamKick(Packet* packet);
void HandleTeamPromote(Packet* packet);
void HandleTeamLootOption(Packet* packet);
void HandleTeamStatusRequest(Packet* packet);
void SendTeamInvite(const PlayerData& receiver, const PlayerData& sender);
void SendTeamInviteConfirm(const PlayerData& receiver, bool bLeaderIsFreeTrial, LWOOBJID i64LeaderID, LWOZONEID i64LeaderZoneID, uint8_t ucLootFlag, uint8_t ucNumOfOtherPlayers, uint8_t ucResponseCode, std::u16string wsLeaderName);
void SendTeamStatus(const PlayerData& receiver, LWOOBJID i64LeaderID, LWOZONEID i64LeaderZoneID, uint8_t ucLootFlag, uint8_t ucNumOfOtherPlayers, std::u16string wsLeaderName);
void SendTeamSetLeader(const PlayerData& receiver, LWOOBJID i64PlayerID);
void SendTeamAddPlayer(const PlayerData& receiver, bool bIsFreeTrial, bool bLocal, bool bNoLootOnDeath, LWOOBJID i64PlayerID, std::u16string wsPlayerName, LWOZONEID zoneID);
/* Sends a message to the provided `receiver` with information about the updated team. If `i64LeaderID` is not LWOOBJID_EMPTY, the client will update the leader to that new playerID. */
void SendTeamRemovePlayer(const PlayerData& receiver, bool bDisband, bool bIsKicked, bool bIsLeaving, bool bLocal, LWOOBJID i64LeaderID, LWOOBJID i64PlayerID, std::u16string wsPlayerName);
void SendTeamSetOffWorldFlag(const PlayerData& receiver, LWOOBJID i64PlayerID, LWOZONEID zoneID);
void CreateTeamServer(Packet* packet);
TeamData* CreateLocalTeam(std::vector<LWOOBJID> members);
TeamData* CreateTeam(LWOOBJID leader, bool local = false);
TeamData* GetTeam(LWOOBJID playerID);
void AddMember(TeamData* team, LWOOBJID playerID);
void RemoveMember(TeamData* team, LWOOBJID playerID, bool disband, bool kicked, bool leaving, bool silent = false);
void PromoteMember(TeamData* team, LWOOBJID newLeader);
void DisbandTeam(TeamData* team, const LWOOBJID causingPlayerID, const std::u16string& causingPlayerName);
void TeamStatusUpdate(TeamData* team);
void UpdateTeamsOnWorld(TeamData* team, bool deleteTeam);
const TeamContainer::Data& GetTeamContainer();
std::vector<TeamData*>& GetTeamsMut();
const std::vector<TeamData*>& GetTeams();
};
#endif //!TEAMCONTAINER_H

View File

@@ -9,54 +9,73 @@
* AMF3 Deserializer written by EmosewaMC * AMF3 Deserializer written by EmosewaMC
*/ */
std::unique_ptr<AMFBaseValue> AMFDeserialize::Read(RakNet::BitStream& inStream) { AMFBaseValue* AMFDeserialize::Read(RakNet::BitStream& inStream) {
AMFBaseValue* returnValue = nullptr;
// Read in the value type from the bitStream // Read in the value type from the bitStream
eAmf marker; eAmf marker;
inStream.Read(marker); inStream.Read(marker);
// Based on the typing, create the value associated with that and return the base value class // Based on the typing, create the value associated with that and return the base value class
switch (marker) { switch (marker) {
case eAmf::Undefined: case eAmf::Undefined: {
return std::make_unique<AMFBaseValue>(); returnValue = new AMFBaseValue();
case eAmf::Null: break;
return std::make_unique<AMFNullValue>(); }
case eAmf::False:
return std::make_unique<AMFBoolValue>(false); case eAmf::Null: {
case eAmf::True: returnValue = new AMFNullValue();
return std::make_unique<AMFBoolValue>(true); break;
case eAmf::Integer: }
return ReadAmfInteger(inStream);
case eAmf::Double: case eAmf::False: {
return ReadAmfDouble(inStream); returnValue = new AMFBoolValue(false);
case eAmf::String: break;
return ReadAmfString(inStream); }
case eAmf::Array:
return ReadAmfArray(inStream); case eAmf::True: {
returnValue = new AMFBoolValue(true);
break;
}
case eAmf::Integer: {
returnValue = ReadAmfInteger(inStream);
break;
}
case eAmf::Double: {
returnValue = ReadAmfDouble(inStream);
break;
}
case eAmf::String: {
returnValue = ReadAmfString(inStream);
break;
}
case eAmf::Array: {
returnValue = ReadAmfArray(inStream);
break;
}
// These values are unimplemented in the live client and will remain unimplemented // These values are unimplemented in the live client and will remain unimplemented
// unless someone modifies the client to allow serializing of these values. // unless someone modifies the client to allow serializing of these values.
case eAmf::XMLDoc: case eAmf::XMLDoc:
[[fallthrough]];
case eAmf::Date: case eAmf::Date:
[[fallthrough]];
case eAmf::Object: case eAmf::Object:
[[fallthrough]];
case eAmf::XML: case eAmf::XML:
[[fallthrough]];
case eAmf::ByteArray: case eAmf::ByteArray:
[[fallthrough]];
case eAmf::VectorInt: case eAmf::VectorInt:
[[fallthrough]];
case eAmf::VectorUInt: case eAmf::VectorUInt:
[[fallthrough]];
case eAmf::VectorDouble: case eAmf::VectorDouble:
[[fallthrough]];
case eAmf::VectorObject: case eAmf::VectorObject:
[[fallthrough]]; case eAmf::Dictionary: {
case eAmf::Dictionary:
throw marker; throw marker;
break;
}
default: default:
throw std::invalid_argument("Invalid AMF3 marker" + std::to_string(static_cast<int32_t>(marker))); throw std::invalid_argument("Invalid AMF3 marker" + std::to_string(static_cast<int32_t>(marker)));
break;
} }
return returnValue;
} }
uint32_t AMFDeserialize::ReadU29(RakNet::BitStream& inStream) { uint32_t AMFDeserialize::ReadU29(RakNet::BitStream& inStream) {
@@ -99,14 +118,14 @@ const std::string AMFDeserialize::ReadString(RakNet::BitStream& inStream) {
} }
} }
std::unique_ptr<AMFDoubleValue> AMFDeserialize::ReadAmfDouble(RakNet::BitStream& inStream) { AMFBaseValue* AMFDeserialize::ReadAmfDouble(RakNet::BitStream& inStream) {
double value; double value;
inStream.Read<double>(value); inStream.Read<double>(value);
return std::make_unique<AMFDoubleValue>(value); return new AMFDoubleValue(value);
} }
std::unique_ptr<AMFArrayValue> AMFDeserialize::ReadAmfArray(RakNet::BitStream& inStream) { AMFBaseValue* AMFDeserialize::ReadAmfArray(RakNet::BitStream& inStream) {
auto arrayValue = std::make_unique<AMFArrayValue>(); auto arrayValue = new AMFArrayValue();
// Read size of dense array // Read size of dense array
const auto sizeOfDenseArray = (ReadU29(inStream) >> 1); const auto sizeOfDenseArray = (ReadU29(inStream) >> 1);
@@ -124,10 +143,10 @@ std::unique_ptr<AMFArrayValue> AMFDeserialize::ReadAmfArray(RakNet::BitStream& i
return arrayValue; return arrayValue;
} }
std::unique_ptr<AMFStringValue> AMFDeserialize::ReadAmfString(RakNet::BitStream& inStream) { AMFBaseValue* AMFDeserialize::ReadAmfString(RakNet::BitStream& inStream) {
return std::make_unique<AMFStringValue>(ReadString(inStream)); return new AMFStringValue(ReadString(inStream));
} }
std::unique_ptr<AMFIntValue> AMFDeserialize::ReadAmfInteger(RakNet::BitStream& inStream) { AMFBaseValue* AMFDeserialize::ReadAmfInteger(RakNet::BitStream& inStream) {
return std::make_unique<AMFIntValue>(ReadU29(inStream)); // NOTE: NARROWING CONVERSION FROM UINT TO INT. IS THIS INTENDED? return new AMFIntValue(ReadU29(inStream));
} }

View File

@@ -1,12 +1,12 @@
#pragma once #pragma once
#include "Amf3.h"
#include "BitStream.h" #include "BitStream.h"
#include <memory>
#include <vector> #include <vector>
#include <string> #include <string>
class AMFBaseValue;
class AMFDeserialize { class AMFDeserialize {
public: public:
/** /**
@@ -15,7 +15,7 @@ public:
* @param inStream inStream to read value from. * @param inStream inStream to read value from.
* @return Returns an AMFValue with all the information from the bitStream in it. * @return Returns an AMFValue with all the information from the bitStream in it.
*/ */
std::unique_ptr<AMFBaseValue> Read(RakNet::BitStream& inStream); AMFBaseValue* Read(RakNet::BitStream& inStream);
private: private:
/** /**
* @brief Private method to read a U29 integer from a bitstream * @brief Private method to read a U29 integer from a bitstream
@@ -39,7 +39,7 @@ private:
* @param inStream bitStream to read data from * @param inStream bitStream to read data from
* @return Double value represented as an AMFValue * @return Double value represented as an AMFValue
*/ */
static std::unique_ptr<AMFDoubleValue> ReadAmfDouble(RakNet::BitStream& inStream); AMFBaseValue* ReadAmfDouble(RakNet::BitStream& inStream);
/** /**
* @brief Read an AMFArray from a bitStream * @brief Read an AMFArray from a bitStream
@@ -47,7 +47,7 @@ private:
* @param inStream bitStream to read data from * @param inStream bitStream to read data from
* @return Array value represented as an AMFValue * @return Array value represented as an AMFValue
*/ */
std::unique_ptr<AMFArrayValue> ReadAmfArray(RakNet::BitStream& inStream); AMFBaseValue* ReadAmfArray(RakNet::BitStream& inStream);
/** /**
* @brief Read an AMFString from a bitStream * @brief Read an AMFString from a bitStream
@@ -55,7 +55,7 @@ private:
* @param inStream bitStream to read data from * @param inStream bitStream to read data from
* @return String value represented as an AMFValue * @return String value represented as an AMFValue
*/ */
std::unique_ptr<AMFStringValue> ReadAmfString(RakNet::BitStream& inStream); AMFBaseValue* ReadAmfString(RakNet::BitStream& inStream);
/** /**
* @brief Read an AMFInteger from a bitStream * @brief Read an AMFInteger from a bitStream
@@ -63,7 +63,7 @@ private:
* @param inStream bitStream to read data from * @param inStream bitStream to read data from
* @return Integer value represented as an AMFValue * @return Integer value represented as an AMFValue
*/ */
static std::unique_ptr<AMFIntValue> ReadAmfInteger(RakNet::BitStream& inStream); AMFBaseValue* ReadAmfInteger(RakNet::BitStream& inStream);
/** /**
* List of strings read so far saved to be read by reference. * List of strings read so far saved to be read by reference.

View File

@@ -5,7 +5,6 @@
#include "Logger.h" #include "Logger.h"
#include "Game.h" #include "Game.h"
#include <type_traits>
#include <unordered_map> #include <unordered_map>
#include <vector> #include <vector>
@@ -40,7 +39,6 @@ public:
// AMFValue template class instantiations // AMFValue template class instantiations
template <typename ValueType> template <typename ValueType>
class AMFValue : public AMFBaseValue { class AMFValue : public AMFBaseValue {
static_assert(!std::is_same_v<ValueType, std::string_view>, "AMFValue cannot be instantiated with std::string_view");
public: public:
AMFValue() = default; AMFValue() = default;
AMFValue(const ValueType value) : m_Data{ value } {} AMFValue(const ValueType value) : m_Data{ value } {}
@@ -53,15 +51,6 @@ public:
void SetValue(const ValueType value) { m_Data = value; } void SetValue(const ValueType value) { m_Data = value; }
AMFValue<ValueType>& operator=(const AMFValue<ValueType>& other) {
return operator=(other.m_Data);
}
AMFValue<ValueType>& operator=(const ValueType& other) {
m_Data = other;
return *this;
}
protected: protected:
ValueType m_Data; ValueType m_Data;
}; };
@@ -116,14 +105,27 @@ using AMFDoubleValue = AMFValue<double>;
* and are not to be deleted by a caller. * and are not to be deleted by a caller.
*/ */
class AMFArrayValue : public AMFBaseValue { class AMFArrayValue : public AMFBaseValue {
using AMFAssociative = using AMFAssociative = std::unordered_map<std::string, AMFBaseValue*>;
std::unordered_map<std::string, std::unique_ptr<AMFBaseValue>, GeneralUtils::transparent_string_hash, std::equal_to<>>; using AMFDense = std::vector<AMFBaseValue*>;
using AMFDense = std::vector<std::unique_ptr<AMFBaseValue>>;
public: public:
[[nodiscard]] constexpr eAmf GetValueType() const noexcept override { return eAmf::Array; } [[nodiscard]] constexpr eAmf GetValueType() const noexcept override { return eAmf::Array; }
~AMFArrayValue() override {
for (const auto* valueToDelete : GetDense()) {
if (valueToDelete) {
delete valueToDelete;
valueToDelete = nullptr;
}
}
for (auto valueToDelete : GetAssociative()) {
if (valueToDelete.second) {
delete valueToDelete.second;
valueToDelete.second = nullptr;
}
}
}
/** /**
* Returns the Associative portion of the object * Returns the Associative portion of the object
*/ */
@@ -149,32 +151,30 @@ public:
* or nullptr if a key existed and was not the same type * or nullptr if a key existed and was not the same type
*/ */
template <typename ValueType> template <typename ValueType>
[[maybe_unused]] std::pair<AMFValue<ValueType>*, bool> Insert(const std::string_view key, const ValueType value) { [[maybe_unused]] std::pair<AMFValue<ValueType>*, bool> Insert(const std::string& key, const ValueType value) {
const auto element = m_Associative.find(key); const auto element = m_Associative.find(key);
AMFValue<ValueType>* val = nullptr; AMFValue<ValueType>* val = nullptr;
bool found = true; bool found = true;
if (element == m_Associative.cend()) { if (element == m_Associative.cend()) {
auto newVal = std::make_unique<AMFValue<ValueType>>(value); val = new AMFValue<ValueType>(value);
val = newVal.get(); m_Associative.emplace(key, val);
m_Associative.emplace(key, std::move(newVal));
} else { } else {
val = dynamic_cast<AMFValue<ValueType>*>(element->second.get()); val = dynamic_cast<AMFValue<ValueType>*>(element->second);
found = false; found = false;
} }
return std::make_pair(val, found); return std::make_pair(val, found);
} }
// Associates an array with a string key // Associates an array with a string key
[[maybe_unused]] std::pair<AMFBaseValue*, bool> Insert(const std::string_view key) { [[maybe_unused]] std::pair<AMFBaseValue*, bool> Insert(const std::string& key) {
const auto element = m_Associative.find(key); const auto element = m_Associative.find(key);
AMFArrayValue* val = nullptr; AMFArrayValue* val = nullptr;
bool found = true; bool found = true;
if (element == m_Associative.cend()) { if (element == m_Associative.cend()) {
auto newVal = std::make_unique<AMFArrayValue>(); val = new AMFArrayValue();
val = newVal.get(); m_Associative.emplace(key, val);
m_Associative.emplace(key, std::move(newVal));
} else { } else {
val = dynamic_cast<AMFArrayValue*>(element->second.get()); val = dynamic_cast<AMFArrayValue*>(element->second);
found = false; found = false;
} }
return std::make_pair(val, found); return std::make_pair(val, found);
@@ -182,13 +182,15 @@ public:
// Associates an array with an integer key // Associates an array with an integer key
[[maybe_unused]] std::pair<AMFBaseValue*, bool> Insert(const size_t index) { [[maybe_unused]] std::pair<AMFBaseValue*, bool> Insert(const size_t index) {
AMFArrayValue* val = nullptr;
bool inserted = false; bool inserted = false;
if (index >= m_Dense.size()) { if (index >= m_Dense.size()) {
m_Dense.resize(index + 1); m_Dense.resize(index + 1);
m_Dense.at(index) = std::make_unique<AMFArrayValue>(); val = new AMFArrayValue();
m_Dense.at(index) = val;
inserted = true; inserted = true;
} }
return std::make_pair(dynamic_cast<AMFArrayValue*>(m_Dense.at(index).get()), inserted); return std::make_pair(dynamic_cast<AMFArrayValue*>(m_Dense.at(index)), inserted);
} }
/** /**
@@ -203,13 +205,15 @@ public:
*/ */
template <typename ValueType> template <typename ValueType>
[[maybe_unused]] std::pair<AMFValue<ValueType>*, bool> Insert(const size_t index, const ValueType value) { [[maybe_unused]] std::pair<AMFValue<ValueType>*, bool> Insert(const size_t index, const ValueType value) {
AMFValue<ValueType>* val = nullptr;
bool inserted = false; bool inserted = false;
if (index >= m_Dense.size()) { if (index >= m_Dense.size()) {
m_Dense.resize(index + 1); m_Dense.resize(index + 1);
m_Dense.at(index) = std::make_unique<AMFValue<ValueType>>(value); val = new AMFValue<ValueType>(value);
m_Dense.at(index) = val;
inserted = true; inserted = true;
} }
return std::make_pair(dynamic_cast<AMFValue<ValueType>*>(m_Dense.at(index).get()), inserted); return std::make_pair(dynamic_cast<AMFValue<ValueType>*>(m_Dense.at(index)), inserted);
} }
/** /**
@@ -221,17 +225,14 @@ public:
* @param key The key to associate with the value * @param key The key to associate with the value
* @param value The value to insert * @param value The value to insert
*/ */
template<typename AmfType> void Insert(const std::string& key, AMFBaseValue* const value) {
AmfType& Insert(const std::string_view key, std::unique_ptr<AmfType> value) {
const auto element = m_Associative.find(key); const auto element = m_Associative.find(key);
auto& toReturn = *value;
if (element != m_Associative.cend() && element->second) { if (element != m_Associative.cend() && element->second) {
element->second = std::move(value); delete element->second;
element->second = value;
} else { } else {
m_Associative.emplace(key, std::move(value)); m_Associative.emplace(key, value);
} }
return toReturn;
} }
/** /**
@@ -243,15 +244,14 @@ public:
* @param key The key to associate with the value * @param key The key to associate with the value
* @param value The value to insert * @param value The value to insert
*/ */
template<typename AmfType> void Insert(const size_t index, AMFBaseValue* const value) {
AmfType& Insert(const size_t index, std::unique_ptr<AmfType> value) { if (index < m_Dense.size()) {
auto& toReturn = *value; const AMFDense::const_iterator itr = m_Dense.cbegin() + index;
if (index >= m_Dense.size()) { if (*itr) delete m_Dense.at(index);
} else {
m_Dense.resize(index + 1); m_Dense.resize(index + 1);
} }
m_Dense.at(index) = value;
m_Dense.at(index) = std::move(value);
return toReturn;
} }
/** /**
@@ -276,9 +276,10 @@ public:
* *
* @param key The key to remove from the associative portion * @param key The key to remove from the associative portion
*/ */
void Remove(const std::string& key) { void Remove(const std::string& key, const bool deleteValue = true) {
const AMFAssociative::const_iterator it = m_Associative.find(key); const AMFAssociative::const_iterator it = m_Associative.find(key);
if (it != m_Associative.cend()) { if (it != m_Associative.cend()) {
if (deleteValue) delete it->second;
m_Associative.erase(it); m_Associative.erase(it);
} }
} }
@@ -289,6 +290,7 @@ public:
void Remove(const size_t index) { void Remove(const size_t index) {
if (!m_Dense.empty() && index < m_Dense.size()) { if (!m_Dense.empty() && index < m_Dense.size()) {
const auto itr = m_Dense.cbegin() + index; const auto itr = m_Dense.cbegin() + index;
if (*itr) delete (*itr);
m_Dense.erase(itr); m_Dense.erase(itr);
} }
} }
@@ -297,16 +299,16 @@ public:
if (!m_Dense.empty()) Remove(m_Dense.size() - 1); if (!m_Dense.empty()) Remove(m_Dense.size() - 1);
} }
[[nodiscard]] AMFArrayValue* GetArray(const std::string_view key) const { [[nodiscard]] AMFArrayValue* GetArray(const std::string& key) const {
const AMFAssociative::const_iterator it = m_Associative.find(key); const AMFAssociative::const_iterator it = m_Associative.find(key);
return it != m_Associative.cend() ? dynamic_cast<AMFArrayValue*>(it->second.get()) : nullptr; return it != m_Associative.cend() ? dynamic_cast<AMFArrayValue*>(it->second) : nullptr;
} }
[[nodiscard]] AMFArrayValue* GetArray(const size_t index) const { [[nodiscard]] AMFArrayValue* GetArray(const size_t index) const {
return index < m_Dense.size() ? dynamic_cast<AMFArrayValue*>(m_Dense.at(index).get()) : nullptr; return index < m_Dense.size() ? dynamic_cast<AMFArrayValue*>(m_Dense.at(index)) : nullptr;
} }
[[maybe_unused]] inline AMFArrayValue* InsertArray(const std::string_view key) { [[maybe_unused]] inline AMFArrayValue* InsertArray(const std::string& key) {
return static_cast<AMFArrayValue*>(Insert(key).first); return static_cast<AMFArrayValue*>(Insert(key).first);
} }
@@ -328,17 +330,17 @@ public:
* @return The AMFValue * @return The AMFValue
*/ */
template <typename AmfType> template <typename AmfType>
[[nodiscard]] AMFValue<AmfType>* Get(const std::string_view key) const { [[nodiscard]] AMFValue<AmfType>* Get(const std::string& key) const {
const AMFAssociative::const_iterator it = m_Associative.find(key); const AMFAssociative::const_iterator it = m_Associative.find(key);
return it != m_Associative.cend() ? return it != m_Associative.cend() ?
dynamic_cast<AMFValue<AmfType>*>(it->second.get()) : dynamic_cast<AMFValue<AmfType>*>(it->second) :
nullptr; nullptr;
} }
// Get from the array but dont cast it // Get from the array but dont cast it
[[nodiscard]] AMFBaseValue* Get(const std::string_view key) const { [[nodiscard]] AMFBaseValue* Get(const std::string& key) const {
const AMFAssociative::const_iterator it = m_Associative.find(key); const AMFAssociative::const_iterator it = m_Associative.find(key);
return it != m_Associative.cend() ? it->second.get() : nullptr; return it != m_Associative.cend() ? it->second : nullptr;
} }
/** /**
@@ -353,25 +355,13 @@ public:
template <typename AmfType> template <typename AmfType>
[[nodiscard]] AMFValue<AmfType>* Get(const size_t index) const { [[nodiscard]] AMFValue<AmfType>* Get(const size_t index) const {
return index < m_Dense.size() ? return index < m_Dense.size() ?
dynamic_cast<AMFValue<AmfType>*>(m_Dense.at(index).get()) : dynamic_cast<AMFValue<AmfType>*>(m_Dense.at(index)) :
nullptr; nullptr;
} }
// Get from the dense but dont cast it // Get from the dense but dont cast it
[[nodiscard]] AMFBaseValue* Get(const size_t index) const { [[nodiscard]] AMFBaseValue* Get(const size_t index) const {
return index < m_Dense.size() ? m_Dense.at(index).get() : nullptr; return index < m_Dense.size() ? m_Dense.at(index) : nullptr;
}
void Reset() {
m_Associative.clear();
m_Dense.clear();
}
template<typename AmfType = AMFArrayValue>
AmfType& PushDebug(const std::string_view name) {
auto* value = PushArray();
value->Insert("name", name.data());
return value->Insert<AmfType>("value", std::make_unique<AmfType>());
} }
private: private:

View File

@@ -2,25 +2,16 @@
#include <string> #include <string>
//For reading null-terminated strings //For reading null-terminated strings
template<typename StringType> std::string BinaryIO::ReadString(std::istream& instream) {
StringType ReadString(std::istream& instream) { std::string toReturn;
StringType toReturn{}; char buffer;
typename StringType::value_type buffer{};
BinaryIO::BinaryRead(instream, buffer); BinaryIO::BinaryRead(instream, buffer);
while (buffer != 0x00) { while (buffer != 0x00) {
toReturn += buffer; toReturn += buffer;
BinaryIO::BinaryRead(instream, buffer); BinaryRead(instream, buffer);
} }
return toReturn; return toReturn;
} }
std::string BinaryIO::ReadString(std::istream& instream) {
return ::ReadString<std::string>(instream);
}
std::u8string BinaryIO::ReadU8String(std::istream& instream) {
return ::ReadString<std::u8string>(instream);
}

View File

@@ -65,8 +65,6 @@ namespace BinaryIO {
std::string ReadString(std::istream& instream); std::string ReadString(std::istream& instream);
std::u8string ReadU8String(std::istream& instream);
inline bool DoesFileExist(const std::string& name) { inline bool DoesFileExist(const std::string& name) {
std::ifstream f(name.c_str()); std::ifstream f(name.c_str());
return f.good(); return f.good();

View File

@@ -8,7 +8,6 @@
#include "Database.h" #include "Database.h"
#include "Game.h" #include "Game.h"
#include "Sd0.h"
#include "ZCompression.h" #include "ZCompression.h"
#include "Logger.h" #include "Logger.h"
@@ -45,10 +44,10 @@ uint32_t BrickByBrickFix::TruncateBrokenBrickByBrickXml() {
} }
// Ignore the valgrind warning about uninitialized values. These are discarded later when we know the actual uncompressed size. // Ignore the valgrind warning about uninitialized values. These are discarded later when we know the actual uncompressed size.
std::unique_ptr<uint8_t[]> uncompressedChunk(new uint8_t[Sd0::MAX_UNCOMPRESSED_CHUNK_SIZE]); std::unique_ptr<uint8_t[]> uncompressedChunk(new uint8_t[ZCompression::MAX_SD0_CHUNK_SIZE]);
int32_t err{}; int32_t err{};
int32_t actualUncompressedSize = ZCompression::Decompress( int32_t actualUncompressedSize = ZCompression::Decompress(
compressedChunk.get(), chunkSize, uncompressedChunk.get(), Sd0::MAX_UNCOMPRESSED_CHUNK_SIZE, err); compressedChunk.get(), chunkSize, uncompressedChunk.get(), ZCompression::MAX_SD0_CHUNK_SIZE, err);
if (actualUncompressedSize != -1) { if (actualUncompressedSize != -1) {
uint32_t previousSize = completeUncompressedModel.size(); uint32_t previousSize = completeUncompressedModel.size();
@@ -118,13 +117,13 @@ uint32_t BrickByBrickFix::UpdateBrickByBrickModelsToSd0() {
} }
std::string outputString(sd0ConvertedModel.get(), oldLxfmlSizeWithHeader); std::string outputString(sd0ConvertedModel.get(), oldLxfmlSizeWithHeader);
std::stringstream outputStringStream(outputString); std::istringstream outputStringStream(outputString);
try { try {
Database::Get()->UpdateUgcModelData(model.id, outputStringStream); Database::Get()->UpdateUgcModelData(model.id, outputStringStream);
LOG("Updated model %i to sd0", model.id); LOG("Updated model %i to sd0", model.id);
updatedModels++; updatedModels++;
} catch (std::exception& exception) { } catch (sql::SQLException exception) {
LOG("Failed to update model %i. This model should be inspected manually to see why." LOG("Failed to update model %i. This model should be inspected manually to see why."
"The database error is %s", model.id, exception.what()); "The database error is %s", model.id, exception.what());
} }

View File

@@ -16,11 +16,6 @@ set(DCOMMON_SOURCES
"BrickByBrickFix.cpp" "BrickByBrickFix.cpp"
"BinaryPathFinder.cpp" "BinaryPathFinder.cpp"
"FdbToSqlite.cpp" "FdbToSqlite.cpp"
"JSONUtils.cpp"
"TinyXmlUtils.cpp"
"Sd0.cpp"
"Lxfml.cpp"
"LxfmlBugged.cpp"
) )
# Workaround for compiler bug where the optimized code could result in a memcpy of 0 bytes, even though that isnt possible. # Workaround for compiler bug where the optimized code could result in a memcpy of 0 bytes, even though that isnt possible.
@@ -42,6 +37,7 @@ target_include_directories(dCommon
"${PROJECT_SOURCE_DIR}/dDatabase/GameDatabase" "${PROJECT_SOURCE_DIR}/dDatabase/GameDatabase"
"${PROJECT_SOURCE_DIR}/dDatabase/GameDatabase/ITables" "${PROJECT_SOURCE_DIR}/dDatabase/GameDatabase/ITables"
"${PROJECT_SOURCE_DIR}/dDatabase/CDClientDatabase" "${PROJECT_SOURCE_DIR}/dDatabase/CDClientDatabase"
"${PROJECT_SOURCE_DIR}/thirdparty/mariadb-connector-cpp/include"
) )
if (UNIX) if (UNIX)

View File

@@ -201,7 +201,7 @@ void OnTerminate() {
} }
void MakeBacktrace() { void MakeBacktrace() {
struct sigaction sigact{}; struct sigaction sigact;
sigact.sa_sigaction = CritErrHdlr; sigact.sa_sigaction = CritErrHdlr;
sigact.sa_flags = SA_RESTART | SA_SIGINFO; sigact.sa_flags = SA_RESTART | SA_SIGINFO;

View File

@@ -4,7 +4,7 @@
#include <assert.h> #include <assert.h>
#ifdef _DEBUG #ifdef _DEBUG
# define DluAssert(expression) do { assert(expression); } while(0) # define DluAssert(expression) assert(expression)
#else #else
# define DluAssert(expression) # define DluAssert(expression)
#endif #endif

View File

@@ -65,14 +65,13 @@ int64_t FdbToSqlite::Convert::ReadInt64(std::istream& cdClientBuffer) {
return value; return value;
} }
// cdclient is encoded in latin1
std::string FdbToSqlite::Convert::ReadString(std::istream& cdClientBuffer) { std::string FdbToSqlite::Convert::ReadString(std::istream& cdClientBuffer) {
int32_t prevPosition = SeekPointer(cdClientBuffer); int32_t prevPosition = SeekPointer(cdClientBuffer);
const auto readString = BinaryIO::ReadU8String(cdClientBuffer); auto readString = BinaryIO::ReadString(cdClientBuffer);
cdClientBuffer.seekg(prevPosition); cdClientBuffer.seekg(prevPosition);
return GeneralUtils::Latin1ToUTF8(readString); return readString;
} }
int32_t FdbToSqlite::Convert::SeekPointer(std::istream& cdClientBuffer) { int32_t FdbToSqlite::Convert::SeekPointer(std::istream& cdClientBuffer) {

View File

@@ -7,10 +7,6 @@
#include <filesystem> #include <filesystem>
#include <map> #include <map>
#include "json.hpp"
using json = nlohmann::json;
template <typename T> template <typename T>
static inline size_t MinSize(const size_t size, const std::basic_string_view<T> string) { static inline size_t MinSize(const size_t size, const std::basic_string_view<T> string) {
if (size == SIZE_MAX || size > string.size()) { if (size == SIZE_MAX || size > string.size()) {
@@ -171,15 +167,6 @@ std::u16string GeneralUtils::ASCIIToUTF16(const std::string_view string, const s
return ret; return ret;
} }
std::string GeneralUtils::Latin1ToUTF8(const std::u8string_view string, const size_t size) {
std::string toReturn{};
for (const auto u : string) {
PushUTF8CodePoint(toReturn, u);
}
return toReturn;
}
//! Converts a (potentially-ill-formed) UTF-16 string to UTF-8 //! Converts a (potentially-ill-formed) UTF-16 string to UTF-8
//! See: <http://simonsapin.github.io/wtf-8/#decoding-ill-formed-utf-16> //! See: <http://simonsapin.github.io/wtf-8/#decoding-ill-formed-utf-16>
std::string GeneralUtils::UTF16ToWTF8(const std::u16string_view string, const size_t size) { std::string GeneralUtils::UTF16ToWTF8(const std::u16string_view string, const size_t size) {
@@ -188,9 +175,9 @@ std::string GeneralUtils::UTF16ToWTF8(const std::u16string_view string, const si
ret.reserve(newSize); ret.reserve(newSize);
for (size_t i = 0; i < newSize; ++i) { for (size_t i = 0; i < newSize; ++i) {
const auto u = string[i]; const char16_t u = string[i];
if (IsLeadSurrogate(u) && (i + 1) < newSize) { if (IsLeadSurrogate(u) && (i + 1) < newSize) {
const auto next = string[i + 1]; const char16_t next = string[i + 1];
if (IsTrailSurrogate(next)) { if (IsTrailSurrogate(next)) {
i += 1; i += 1;
const char32_t cp = 0x10000 const char32_t cp = 0x10000
@@ -304,12 +291,11 @@ std::u16string GeneralUtils::ReadWString(RakNet::BitStream& inStream) {
std::vector<std::string> GeneralUtils::GetSqlFileNamesFromFolder(const std::string_view folder) { std::vector<std::string> GeneralUtils::GetSqlFileNamesFromFolder(const std::string_view folder) {
// Because we dont know how large the initial number before the first _ is we need to make it a map like so. // Because we dont know how large the initial number before the first _ is we need to make it a map like so.
std::map<uint32_t, std::string> filenames{}; std::map<uint32_t, std::string> filenames{};
for (const auto& t : std::filesystem::directory_iterator(folder)) { for (const auto& t : std::filesystem::directory_iterator(folder)) {
if (t.is_directory() || t.is_symlink()) continue; auto filename = t.path().filename().string();
auto filename = t.path().filename().string(); const auto index = std::stoi(GeneralUtils::SplitString(filename, '_').at(0));
const auto index = std::stoi(GeneralUtils::SplitString(filename, '_').at(0)); filenames.emplace(index, std::move(filename));
filenames.emplace(index, std::move(filename));
} }
// Now sort the map by the oldest migration. // Now sort the map by the oldest migration.
@@ -331,17 +317,6 @@ std::vector<std::string> GeneralUtils::GetSqlFileNamesFromFolder(const std::stri
return sortedFiles; return sortedFiles;
} }
template<>
[[nodiscard]] std::optional<json> GeneralUtils::TryParse(std::string_view str) {
try {
return json::parse(str);
} catch (const std::exception& e) {
return std::nullopt;
} catch (...) {
return std::nullopt;
}
}
#if !(__GNUC__ >= 11 || _MSC_VER >= 1924) #if !(__GNUC__ >= 11 || _MSC_VER >= 1924)
// MacOS floating-point parse function specializations // MacOS floating-point parse function specializations

View File

@@ -51,14 +51,6 @@ namespace GeneralUtils {
bool _NextUTF8Char(std::string_view& slice, uint32_t& out); bool _NextUTF8Char(std::string_view& slice, uint32_t& out);
} }
//! Converts a Latin1 string to a UTF-8 string
/*!
\param string The string to convert
\param size A size to trim the string to. Default is SIZE_MAX (No trimming)
\return An UTF-8 representation of the string
*/
std::string Latin1ToUTF8(const std::u8string_view string, const size_t size = SIZE_MAX);
//! Converts a UTF-16 string to a UTF-8 string //! Converts a UTF-16 string to a UTF-8 string
/*! /*!
\param string The string to convert \param string The string to convert
@@ -137,29 +129,6 @@ namespace GeneralUtils {
std::vector<std::string> GetSqlFileNamesFromFolder(const std::string_view folder); std::vector<std::string> GetSqlFileNamesFromFolder(const std::string_view folder);
/**
* Transparent string hasher - used to allow string_view key lookups for maps storing std::string keys
* https://www.reddit.com/r/cpp_questions/comments/12xw3sn/find_stdstring_view_in_unordered_map_with/jhki225/
* https://godbolt.org/z/789xv8Eeq
*/
template <typename... Bases>
struct overload : Bases... {
using is_transparent = void;
using Bases::operator() ... ;
};
struct char_pointer_hash {
auto operator()(const char* const ptr) const noexcept {
return std::hash<std::string_view>{}(ptr);
}
};
using transparent_string_hash = overload<
std::hash<std::string>,
std::hash<std::string_view>,
char_pointer_hash
>;
// Concept constraining to enum types // Concept constraining to enum types
template <typename T> template <typename T>
concept Enum = std::is_enum_v<T>; concept Enum = std::is_enum_v<T>;
@@ -201,10 +170,6 @@ namespace GeneralUtils {
return isParsed ? static_cast<T>(result) : std::optional<T>{}; return isParsed ? static_cast<T>(result) : std::optional<T>{};
} }
template<typename T>
requires(!Numeric<T>)
[[nodiscard]] std::optional<T> TryParse(std::string_view str);
#if !(__GNUC__ >= 11 || _MSC_VER >= 1924) #if !(__GNUC__ >= 11 || _MSC_VER >= 1924)
// MacOS floating-point parse helper function specializations // MacOS floating-point parse helper function specializations

View File

@@ -1,92 +0,0 @@
#ifndef __IMPLEMENTATION_H__
#define __IMPLEMENTATION_H__
#include <functional>
#include <optional>
/**
* @brief A way to defer the implementation of an action.
*
* @tparam R The result of the action.
* @tparam T The types of the arguments that the implementation requires.
*/
template <typename R, typename... T>
class Implementation {
public:
typedef std::function<std::optional<R>(T...)> ImplementationFunction;
/**
* @brief Sets the implementation of the action.
*
* @param implementation The implementation of the action.
*/
void SetImplementation(const ImplementationFunction& implementation) {
this->implementation = implementation;
}
/**
* @brief Clears the implementation of the action.
*/
void ClearImplementation() {
implementation.reset();
}
/**
* @brief Checks if the implementation is set.
*
* @return true If the implementation is set.
* @return false If the implementation is not set.
*/
bool IsSet() const {
return implementation.has_value();
}
/**
* @brief Executes the implementation if it is set.
*
* @param args The arguments to pass to the implementation.
* @return std::optional<R> The optional result of the implementation. If the result is not set, it indicates that the default action should be taken.
*/
std::optional<R> Execute(T... args) const {
return IsSet() ? implementation.value()(args...) : std::nullopt;
}
/**
* @brief Exectues the implementation if it is set, otherwise returns a default value.
*
* @param args The arguments to pass to the implementation.
* @param defaultValue The default value to return if the implementation is not set or should not be deferred.
*/
R ExecuteWithDefault(T... args, const R& defaultValue) const {
return Execute(args...).value_or(defaultValue);
}
/**
* = operator overload.
*/
Implementation& operator=(const Implementation& other) {
implementation = other.implementation;
return *this;
}
/**
* = operator overload.
*/
Implementation& operator=(const ImplementationFunction& implementation) {
this->implementation = implementation;
return *this;
}
/**
* () operator overload.
*/
std::optional<R> operator()(T... args) {
return !IsSet() ? std::nullopt : implementation(args...);
}
private:
std::optional<ImplementationFunction> implementation;
};
#endif //!__IMPLEMENTATION_H__

View File

@@ -1,17 +0,0 @@
#include "JSONUtils.h"
#include "json.hpp"
using json = nlohmann::json;
std::string JSONUtils::CheckRequiredData(const json& data, const std::vector<std::string>& requiredData) {
json check;
check["error"] = json::array();
for (const auto& required : requiredData) {
if (!data.contains(required)) {
check["error"].push_back("Missing Parameter: " + required);
} else if (data[required] == "") {
check["error"].push_back("Empty Parameter: " + required);
}
}
return check["error"].empty() ? "" : check.dump();
}

View File

@@ -1,11 +0,0 @@
#ifndef _JSONUTILS_H_
#define _JSONUTILS_H_
#include "json_fwd.hpp"
namespace JSONUtils {
// check required fields in json data
std::string CheckRequiredData(const nlohmann::json& data, const std::vector<std::string>& requiredData);
}
#endif // _JSONUTILS_H_

View File

@@ -83,12 +83,6 @@ public:
this->value = value; this->value = value;
} }
//! Initializer
LDFData(const std::string& key, const T& value) {
this->key = GeneralUtils::ASCIIToUTF16(key);
this->value = value;
}
//! Destructor //! Destructor
~LDFData(void) override {} ~LDFData(void) override {}

View File

@@ -29,8 +29,8 @@ constexpr const char* GetFileNameFromAbsolutePath(const char* path) {
// they will not be valid constexpr and will be evaluated at runtime instead of compile time! // they will not be valid constexpr and will be evaluated at runtime instead of compile time!
// The full string is still stored in the binary, however the offset of the filename in the absolute paths // The full string is still stored in the binary, however the offset of the filename in the absolute paths
// is used in the instruction instead of the start of the absolute path. // is used in the instruction instead of the start of the absolute path.
#define LOG(message, ...) do { auto str_ = FILENAME_AND_LINE; Game::logger->Log(str_, message, ##__VA_ARGS__); } while(0) #define LOG(message, ...) do { auto str = FILENAME_AND_LINE; Game::logger->Log(str, message, ##__VA_ARGS__); } while(0)
#define LOG_DEBUG(message, ...) do { auto str_ = FILENAME_AND_LINE; Game::logger->LogDebug(str_, message, ##__VA_ARGS__); } while(0) #define LOG_DEBUG(message, ...) do { auto str = FILENAME_AND_LINE; Game::logger->LogDebug(str, message, ##__VA_ARGS__); } while(0)
// Writer class for writing data to files. // Writer class for writing data to files.
class Writer { class Writer {

View File

@@ -1,119 +0,0 @@
#include "Lxfml.h"
#include "GeneralUtils.h"
#include "StringifiedEnum.h"
#include "TinyXmlUtils.h"
#include <ranges>
Lxfml::Result Lxfml::NormalizePosition(const std::string_view data) {
Result toReturn;
tinyxml2::XMLDocument doc;
const auto err = doc.Parse(data.data());
if (err != tinyxml2::XML_SUCCESS) {
LOG("Failed to parse xml %s.", StringifiedEnum::ToString(err).data());
return toReturn;
}
TinyXmlUtils::DocumentReader reader(doc);
std::map<std::string/* refID */, std::string> transformations;
auto lxfml = reader["LXFML"];
if (!lxfml) {
LOG("Failed to find LXFML element.");
return toReturn;
}
// First get all the positions of bricks
for (const auto& brick : lxfml["Bricks"]) {
const auto* part = brick.FirstChildElement("Part");
while (part) {
const auto* bone = part->FirstChildElement("Bone");
if (bone) {
auto* transformation = bone->Attribute("transformation");
if (transformation) {
auto* refID = bone->Attribute("refID");
if (refID) transformations[refID] = transformation;
}
}
part = part->NextSiblingElement("Part");
}
}
// These points are well out of bounds for an actual player
NiPoint3 lowest{ 10'000.0f, 10'000.0f, 10'000.0f };
NiPoint3 highest{ -10'000.0f, -10'000.0f, -10'000.0f };
// Calculate the lowest and highest points on the entire model
for (const auto& transformation : transformations | std::views::values) {
auto split = GeneralUtils::SplitString(transformation, ',');
if (split.size() < 12) {
LOG("Not enough in the split?");
continue;
}
auto x = GeneralUtils::TryParse<float>(split[9]).value();
auto y = GeneralUtils::TryParse<float>(split[10]).value();
auto z = GeneralUtils::TryParse<float>(split[11]).value();
if (x < lowest.x) lowest.x = x;
if (y < lowest.y) lowest.y = y;
if (z < lowest.z) lowest.z = z;
if (highest.x < x) highest.x = x;
if (highest.y < y) highest.y = y;
if (highest.z < z) highest.z = z;
}
auto delta = (highest - lowest) / 2.0f;
auto newRootPos = lowest + delta;
// Clamp the Y to the lowest point on the model
newRootPos.y = lowest.y;
// newRootPos = newRootPos - NiPoint3{-0.4f, 0, 0.4};
const auto posToReturn = newRootPos;
// Adjust all positions to account for the new origin
for (auto& transformation : transformations | std::views::values) {
auto split = GeneralUtils::SplitString(transformation, ',');
if (split.size() < 12) {
LOG("Not enough in the split?");
continue;
}
auto x = GeneralUtils::TryParse<float>(split[9]).value() - newRootPos.x;
auto y = GeneralUtils::TryParse<float>(split[10]).value() - newRootPos.y;
auto z = GeneralUtils::TryParse<float>(split[11]).value() - newRootPos.z;
std::stringstream stream;
for (int i = 0; i < 9; i++) {
stream << split[i];
stream << ',';
}
stream << x << ',' << y << ',' << z;
transformation = stream.str();
}
// Finally write the new transformation back into the lxfml
for (auto& brick : lxfml["Bricks"]) {
auto* part = brick.FirstChildElement("Part");
while (part) {
auto* bone = part->FirstChildElement("Bone");
if (bone) {
auto* transformation = bone->Attribute("transformation");
if (transformation) {
auto* refID = bone->Attribute("refID");
if (refID) {
bone->SetAttribute("transformation", transformations[refID].c_str());
}
}
}
part = part->NextSiblingElement("Part");
}
}
tinyxml2::XMLPrinter printer;
doc.Print(&printer);
toReturn.lxfml = printer.CStr();
toReturn.center = posToReturn;
return toReturn;
}

View File

@@ -1,27 +0,0 @@
// Darkflame Universe
// Copyright 2025
#ifndef LXFML_H
#define LXFML_H
#include <string>
#include <string_view>
#include "NiPoint3.h"
namespace Lxfml {
struct Result {
std::string lxfml;
NiPoint3 center;
};
// Normalizes a LXFML model to be positioned relative to its local 0, 0, 0 rather than a game worlds 0, 0, 0.
// Returns a struct of its new center and the updated LXFML containing these edits.
[[nodiscard]] Result NormalizePosition(const std::string_view data);
// these are only for the migrations due to a bug in one of the implementations.
[[nodiscard]] Result NormalizePositionOnlyFirstPart(const std::string_view data);
[[nodiscard]] Result NormalizePositionAfterFirstPart(const std::string_view data, const NiPoint3& position);
};
#endif //!LXFML_H

View File

@@ -1,215 +0,0 @@
#include "Lxfml.h"
#include "GeneralUtils.h"
#include "StringifiedEnum.h"
#include "TinyXmlUtils.h"
#include <ranges>
// this file should not be touched
Lxfml::Result Lxfml::NormalizePositionOnlyFirstPart(const std::string_view data) {
// this is bugged
return;
Result toReturn;
tinyxml2::XMLDocument doc;
const auto err = doc.Parse(data.data());
if (err != tinyxml2::XML_SUCCESS) {
LOG("Failed to parse xml %s.", StringifiedEnum::ToString(err).data());
return toReturn;
}
TinyXmlUtils::DocumentReader reader(doc);
std::map<std::string/* refID */, std::string> transformations;
auto lxfml = reader["LXFML"];
if (!lxfml) {
LOG("Failed to find LXFML element.");
return toReturn;
}
// First get all the positions of bricks
for (const auto& brick : lxfml["Bricks"]) {
const auto* part = brick.FirstChildElement("Part");
if (part) {
const auto* bone = part->FirstChildElement("Bone");
if (bone) {
auto* transformation = bone->Attribute("transformation");
if (transformation) {
auto* refID = bone->Attribute("refID");
if (refID) transformations[refID] = transformation;
}
}
}
}
// These points are well out of bounds for an actual player
NiPoint3 lowest{ 10'000.0f, 10'000.0f, 10'000.0f };
NiPoint3 highest{ -10'000.0f, -10'000.0f, -10'000.0f };
// Calculate the lowest and highest points on the entire model
for (const auto& transformation : transformations | std::views::values) {
auto split = GeneralUtils::SplitString(transformation, ',');
if (split.size() < 12) {
LOG("Not enough in the split?");
continue;
}
auto x = GeneralUtils::TryParse<float>(split[9]).value();
auto y = GeneralUtils::TryParse<float>(split[10]).value();
auto z = GeneralUtils::TryParse<float>(split[11]).value();
if (x < lowest.x) lowest.x = x;
if (y < lowest.y) lowest.y = y;
if (z < lowest.z) lowest.z = z;
if (highest.x < x) highest.x = x;
if (highest.y < y) highest.y = y;
if (highest.z < z) highest.z = z;
}
auto delta = (highest - lowest) / 2.0f;
auto newRootPos = lowest + delta;
// Clamp the Y to the lowest point on the model
newRootPos.y = lowest.y;
// Adjust all positions to account for the new origin
for (auto& transformation : transformations | std::views::values) {
auto split = GeneralUtils::SplitString(transformation, ',');
if (split.size() < 12) {
LOG("Not enough in the split?");
continue;
}
auto x = GeneralUtils::TryParse<float>(split[9]).value() - newRootPos.x;
auto y = GeneralUtils::TryParse<float>(split[10]).value() - newRootPos.y;
auto z = GeneralUtils::TryParse<float>(split[11]).value() - newRootPos.z;
std::stringstream stream;
for (int i = 0; i < 9; i++) {
stream << split[i];
stream << ',';
}
stream << x << ',' << y << ',' << z;
transformation = stream.str();
}
// Finally write the new transformation back into the lxfml
for (auto& brick : lxfml["Bricks"]) {
auto* part = brick.FirstChildElement("Part");
if (part) {
auto* bone = part->FirstChildElement("Bone");
if (bone) {
auto* transformation = bone->Attribute("transformation");
if (transformation) {
auto* refID = bone->Attribute("refID");
if (refID) {
bone->SetAttribute("transformation", transformations[refID].c_str());
}
}
}
}
}
tinyxml2::XMLPrinter printer;
doc.Print(&printer);
toReturn.lxfml = printer.CStr();
toReturn.center = newRootPos;
return toReturn;
}
Lxfml::Result Lxfml::NormalizePositionAfterFirstPart(const std::string_view data, const NiPoint3& position) {
// this is bugged
return;
Result toReturn;
tinyxml2::XMLDocument doc;
const auto err = doc.Parse(data.data());
if (err != tinyxml2::XML_SUCCESS) {
LOG("Failed to parse xml %s.", StringifiedEnum::ToString(err).data());
return toReturn;
}
TinyXmlUtils::DocumentReader reader(doc);
std::map<std::string/* refID */, std::string> transformations;
auto lxfml = reader["LXFML"];
if (!lxfml) {
LOG("Failed to find LXFML element.");
return toReturn;
}
// First get all the positions of bricks
for (const auto& brick : lxfml["Bricks"]) {
const auto* part = brick.FirstChildElement("Part");
bool firstPart = true;
while (part) {
if (firstPart) {
firstPart = false;
} else {
LOG("Found extra bricks");
const auto* bone = part->FirstChildElement("Bone");
if (bone) {
auto* transformation = bone->Attribute("transformation");
if (transformation) {
auto* refID = bone->Attribute("refID");
if (refID) transformations[refID] = transformation;
}
}
}
part = part->NextSiblingElement("Part");
}
}
auto newRootPos = position;
// Adjust all positions to account for the new origin
for (auto& transformation : transformations | std::views::values) {
auto split = GeneralUtils::SplitString(transformation, ',');
if (split.size() < 12) {
LOG("Not enough in the split?");
continue;
}
auto x = GeneralUtils::TryParse<float>(split[9]).value() - newRootPos.x;
auto y = GeneralUtils::TryParse<float>(split[10]).value() - newRootPos.y;
auto z = GeneralUtils::TryParse<float>(split[11]).value() - newRootPos.z;
std::stringstream stream;
for (int i = 0; i < 9; i++) {
stream << split[i];
stream << ',';
}
stream << x << ',' << y << ',' << z;
transformation = stream.str();
}
// Finally write the new transformation back into the lxfml
for (auto& brick : lxfml["Bricks"]) {
auto* part = brick.FirstChildElement("Part");
bool firstPart = true;
while (part) {
if (firstPart) {
firstPart = false;
} else {
auto* bone = part->FirstChildElement("Bone");
if (bone) {
auto* transformation = bone->Attribute("transformation");
if (transformation) {
auto* refID = bone->Attribute("refID");
if (refID) {
bone->SetAttribute("transformation", transformations[refID].c_str());
}
}
}
}
part = part->NextSiblingElement("Part");
}
}
tinyxml2::XMLPrinter printer;
doc.Print(&printer);
toReturn.lxfml = printer.CStr();
toReturn.center = newRootPos;
return toReturn;
}

View File

@@ -1,73 +0,0 @@
#ifndef __OBSERVABLE_H__
#define __OBSERVABLE_H__
#include <vector>
#include <functional>
/**
* @brief An event which can be observed by multiple observers.
*
* @tparam T The types of the arguments to be passed to the observers.
*/
template <typename... T>
class Observable {
public:
typedef std::function<void(T...)> Observer;
/**
* @brief Adds an observer to the event.
*
* @param observer The observer to add.
*/
void AddObserver(const Observer& observer) {
observers.push_back(observer);
}
/**
* @brief Removes an observer from the event.
*
* @param observer The observer to remove.
*/
void RemoveObserver(const Observer& observer) {
observers.erase(std::remove(observers.begin(), observers.end(), observer), observers.end());
}
/**
* @brief Notifies all observers of the event.
*
* @param args The arguments to pass to the observers.
*/
void Notify(T... args) {
for (const auto& observer : observers) {
observer(args...);
}
}
/**
* += operator overload.
*/
Observable& operator+=(const Observer& observer) {
AddObserver(observer);
return *this;
}
/**
* -= operator overload.
*/
Observable& operator-=(const Observer& observer) {
RemoveObserver(observer);
return *this;
}
/**
* () operator overload.
*/
void operator()(T... args) {
Notify(args...);
}
private:
std::vector<Observer> observers;
};
#endif //!__OBSERVABLE_H__

View File

@@ -1,150 +0,0 @@
#include "Sd0.h"
#include <array>
#include <ranges>
#include "BinaryIO.h"
#include "Game.h"
#include "Logger.h"
#include "ZCompression.h"
// Insert header if on first buffer
void WriteHeader(Sd0::BinaryBuffer& chunk) {
chunk.push_back(Sd0::SD0_HEADER[0]);
chunk.push_back(Sd0::SD0_HEADER[1]);
chunk.push_back(Sd0::SD0_HEADER[2]);
chunk.push_back(Sd0::SD0_HEADER[3]);
chunk.push_back(Sd0::SD0_HEADER[4]);
}
// Write the size of the buffer to a chunk
void WriteSize(Sd0::BinaryBuffer& chunk, uint32_t chunkSize) {
for (int i = 0; i < 4; i++) {
char toPush = chunkSize & 0xff;
chunkSize = chunkSize >> 8;
chunk.push_back(toPush);
}
}
int32_t GetDataOffset(bool firstBuffer) {
return firstBuffer ? 9 : 4;
}
Sd0::Sd0(std::istream& buffer) {
char header[5]{};
// Check if this is an sd0 buffer. It's possible we may be handed a zlib buffer directly due to old code so check for that too.
if (!BinaryIO::BinaryRead(buffer, header) || memcmp(header, SD0_HEADER, sizeof(header)) != 0) {
LOG("Failed to read SD0 header %i %i %i %i %i %i %i", buffer.good(), buffer.tellg(), header[0], header[1], header[2], header[3], header[4]);
LOG_DEBUG("This may be a zlib buffer directly? Trying again assuming its a zlib buffer.");
auto& firstChunk = m_Chunks.emplace_back();
WriteHeader(firstChunk);
buffer.seekg(0, std::ios::end);
uint32_t bufferSize = buffer.tellg();
buffer.seekg(0, std::ios::beg);
WriteSize(firstChunk, bufferSize);
firstChunk.resize(firstChunk.size() + bufferSize);
auto* dataStart = reinterpret_cast<char*>(firstChunk.data() + GetDataOffset(true));
if (!buffer.read(dataStart, bufferSize)) {
m_Chunks.pop_back();
LOG("Failed to read %u bytes from chunk %i", bufferSize, m_Chunks.size() - 1);
}
return;
}
while (buffer && buffer.peek() != std::istream::traits_type::eof()) {
uint32_t chunkSize{};
if (!BinaryIO::BinaryRead(buffer, chunkSize)) {
LOG("Failed to read chunk size from stream %lld %zu", buffer.tellg(), m_Chunks.size());
break;
}
auto& chunk = m_Chunks.emplace_back();
bool firstBuffer = m_Chunks.size() == 1;
auto dataOffset = GetDataOffset(firstBuffer);
// Insert header if on first buffer
if (firstBuffer) {
WriteHeader(chunk);
}
WriteSize(chunk, chunkSize);
chunk.resize(chunkSize + dataOffset);
auto* dataStart = reinterpret_cast<char*>(chunk.data() + dataOffset);
if (!buffer.read(dataStart, chunkSize)) {
m_Chunks.pop_back();
LOG("Failed to read %u bytes from chunk %i", chunkSize, m_Chunks.size() - 1);
break;
}
}
}
void Sd0::FromData(const uint8_t* data, size_t bufferSize) {
const auto originalBufferSize = bufferSize;
if (bufferSize == 0) return;
m_Chunks.clear();
while (bufferSize > 0) {
const auto numToCopy = std::min(MAX_UNCOMPRESSED_CHUNK_SIZE, bufferSize);
const auto* startOffset = data + originalBufferSize - bufferSize;
bufferSize -= numToCopy;
std::array<uint8_t, MAX_UNCOMPRESSED_CHUNK_SIZE> compressedChunk;
const auto compressedSize = ZCompression::Compress(
startOffset, numToCopy,
compressedChunk.data(), compressedChunk.size());
auto& chunk = m_Chunks.emplace_back();
bool firstBuffer = m_Chunks.size() == 1;
auto dataOffset = GetDataOffset(firstBuffer);
if (firstBuffer) {
WriteHeader(chunk);
}
WriteSize(chunk, compressedSize);
chunk.resize(compressedSize + dataOffset);
memcpy(chunk.data() + dataOffset, compressedChunk.data(), compressedSize);
}
}
std::string Sd0::GetAsStringUncompressed() const {
std::string toReturn;
bool first = true;
uint32_t totalSize{};
for (const auto& chunk : m_Chunks) {
auto dataOffset = GetDataOffset(first);
first = false;
const auto chunkSize = chunk.size();
auto oldSize = toReturn.size();
toReturn.resize(oldSize + MAX_UNCOMPRESSED_CHUNK_SIZE);
int32_t error{};
const auto uncompressedSize = ZCompression::Decompress(
chunk.data() + dataOffset, chunkSize - dataOffset,
reinterpret_cast<uint8_t*>(toReturn.data()) + oldSize, MAX_UNCOMPRESSED_CHUNK_SIZE,
error);
totalSize += uncompressedSize;
}
toReturn.resize(totalSize);
return toReturn;
}
std::stringstream Sd0::GetAsStream() const {
std::stringstream toReturn;
for (const auto& chunk : m_Chunks) {
toReturn.write(reinterpret_cast<const char*>(chunk.data()), chunk.size());
}
return toReturn;
}
const std::vector<Sd0::BinaryBuffer>& Sd0::GetAsVector() const {
return m_Chunks;
}

View File

@@ -1,42 +0,0 @@
// Darkflame Universe
// Copyright 2025
#ifndef SD0_H
#define SD0_H
#include <fstream>
#include <vector>
// Sd0 is comprised of multiple zlib compressed buffers stored in a row.
// The format starts with a SD0 header (see SD0_HEADER) followed by the size of a zlib buffer, and then the zlib buffer itself.
// This repeats until end of file
class Sd0 {
public:
using BinaryBuffer = std::vector<uint8_t>;
static inline const char* SD0_HEADER = "sd0\x01\xff";
/**
* @brief Max size of an inflated sd0 zlib chunk
*/
static constexpr inline size_t MAX_UNCOMPRESSED_CHUNK_SIZE = 1024 * 256;
// Read the input buffer into an internal chunk stream to be used later
Sd0(std::istream& buffer);
// Uncompresses the entire Sd0 buffer and returns it as a string
[[nodiscard]] std::string GetAsStringUncompressed() const;
// Gets the Sd0 buffer as a stream in its raw compressed form
[[nodiscard]] std::stringstream GetAsStream() const;
// Gets the Sd0 buffer as a vector in its raw compressed form
[[nodiscard]] const std::vector<BinaryBuffer>& GetAsVector() const;
// Compress data into a Sd0 buffer
void FromData(const uint8_t* data, size_t bufferSize);
private:
std::vector<BinaryBuffer> m_Chunks{};
};
#endif //!SD0_H

View File

@@ -1,37 +0,0 @@
#include "TinyXmlUtils.h"
#include <tinyxml2.h>
using namespace TinyXmlUtils;
Element DocumentReader::operator[](const std::string_view elem) const {
return Element(m_Doc.FirstChildElement(elem.empty() ? nullptr : elem.data()), elem);
}
Element::Element(tinyxml2::XMLElement* xmlElem, const std::string_view elem) :
m_IteratedName{ elem },
m_Elem{ xmlElem } {
}
Element Element::operator[](const std::string_view elem) const {
const auto* usedElem = elem.empty() ? nullptr : elem.data();
auto* toReturn = m_Elem ? m_Elem->FirstChildElement(usedElem) : nullptr;
return Element(toReturn, m_IteratedName);
}
ElementIterator Element::begin() {
return ElementIterator(m_Elem ? m_Elem->FirstChildElement() : nullptr);
}
ElementIterator Element::end() {
return ElementIterator(nullptr);
}
ElementIterator::ElementIterator(tinyxml2::XMLElement* elem) :
m_CurElem{ elem } {
}
ElementIterator& ElementIterator::operator++() {
if (m_CurElem) m_CurElem = m_CurElem->NextSiblingElement();
return *this;
}

View File

@@ -1,66 +0,0 @@
// Darkflame Universe
// Copyright 2025
#ifndef TINYXMLUTILS_H
#define TINYXMLUTILS_H
#include <string>
#include "DluAssert.h"
#include <tinyxml2.h>
namespace TinyXmlUtils {
// See cstdlib for iterator technicalities
struct ElementIterator {
ElementIterator(tinyxml2::XMLElement* elem);
ElementIterator& operator++();
[[nodiscard]] tinyxml2::XMLElement* operator->() { DluAssert(m_CurElem); return m_CurElem; }
[[nodiscard]] tinyxml2::XMLElement& operator*() { DluAssert(m_CurElem); return *m_CurElem; }
bool operator==(const ElementIterator& other) const { return other.m_CurElem == m_CurElem; }
private:
tinyxml2::XMLElement* m_CurElem{ nullptr };
};
// Wrapper class to act as an iterator over xml elements.
// All the normal rules that apply to Iterators in the std library apply here.
class Element {
public:
Element(tinyxml2::XMLElement* xmlElem, const std::string_view elem);
// The first child element of this element.
[[nodiscard]] ElementIterator begin();
// Always returns an ElementIterator which points to nullptr.
// TinyXml2 return NULL when you've reached the last child element so
// you can't do any funny one past end logic here.
[[nodiscard]] ElementIterator end();
// Get a child element
[[nodiscard]] Element operator[](const std::string_view elem) const;
[[nodiscard]] Element operator[](const char* elem) const { return operator[](std::string_view(elem)); };
// Whether or not data exists for this element
operator bool() const { return m_Elem != nullptr; }
[[nodiscard]] const tinyxml2::XMLElement* operator->() const { return m_Elem; }
private:
const char* GetElementName() const { return m_IteratedName.empty() ? nullptr : m_IteratedName.c_str(); }
const std::string m_IteratedName;
tinyxml2::XMLElement* m_Elem;
};
class DocumentReader {
public:
DocumentReader(tinyxml2::XMLDocument& doc) : m_Doc{ doc } {}
[[nodiscard]] Element operator[](const std::string_view elem) const;
private:
tinyxml2::XMLDocument& m_Doc;
};
};
#endif //!TINYXMLUTILS_H

View File

@@ -8,5 +8,11 @@ namespace ZCompression {
int32_t Compress(const uint8_t* abSrc, int32_t nLenSrc, uint8_t* abDst, int32_t nLenDst); int32_t Compress(const uint8_t* abSrc, int32_t nLenSrc, uint8_t* abDst, int32_t nLenDst);
int32_t Decompress(const uint8_t* abSrc, int32_t nLenSrc, uint8_t* abDst, int32_t nLenDst, int32_t& nErr); int32_t Decompress(const uint8_t* abSrc, int32_t nLenSrc, uint8_t* abDst, int32_t nLenDst, int32_t& nErr);
/**
* @brief Max size of an inflated sd0 zlib chunk
*
*/
constexpr uint32_t MAX_SD0_CHUNK_SIZE = 1024 * 256;
} }

View File

@@ -6,9 +6,6 @@
#include "zlib.h" #include "zlib.h"
constexpr uint32_t CRC32_INIT = 0xFFFFFFFF;
constexpr auto NULL_TERMINATOR = std::string_view{"\0\0\0", 4};
AssetManager::AssetManager(const std::filesystem::path& path) { AssetManager::AssetManager(const std::filesystem::path& path) {
if (!std::filesystem::is_directory(path)) { if (!std::filesystem::is_directory(path)) {
throw std::runtime_error("Attempted to load asset bundle (" + path.string() + ") however it is not a valid directory."); throw std::runtime_error("Attempted to load asset bundle (" + path.string() + ") however it is not a valid directory.");
@@ -21,20 +18,12 @@ AssetManager::AssetManager(const std::filesystem::path& path) {
m_RootPath = m_Path; m_RootPath = m_Path;
m_ResPath = (m_Path / "client" / "res"); m_ResPath = (m_Path / "client" / "res");
} else if (std::filesystem::exists(m_Path / "res" / "pack")) { } else if (std::filesystem::exists(m_Path / ".." / "versions") && std::filesystem::exists(m_Path / "res")) {
if (!std::filesystem::exists(m_Path / ".." / "versions")) {
throw std::runtime_error("No \"versions\" directory found in the parent directories of \"res\" - packed asset bundle cannot be loaded.");
}
m_AssetBundleType = eAssetBundleType::Packed; m_AssetBundleType = eAssetBundleType::Packed;
m_RootPath = (m_Path / ".."); m_RootPath = (m_Path / "..");
m_ResPath = (m_Path / "res"); m_ResPath = (m_Path / "res");
} else if (std::filesystem::exists(m_Path / "pack")) { } else if (std::filesystem::exists(m_Path / "pack") && std::filesystem::exists(m_Path / ".." / ".." / "versions")) {
if (!std::filesystem::exists(m_Path / ".." / ".." / "versions")) {
throw std::runtime_error("No \"versions\" directory found in the parent directories of \"res\" - packed asset bundle cannot be loaded.");
}
m_AssetBundleType = eAssetBundleType::Packed; m_AssetBundleType = eAssetBundleType::Packed;
m_RootPath = (m_Path / ".." / ".."); m_RootPath = (m_Path / ".." / "..");
@@ -59,7 +48,6 @@ AssetManager::AssetManager(const std::filesystem::path& path) {
break; break;
} }
case eAssetBundleType::None: case eAssetBundleType::None:
[[fallthrough]];
case eAssetBundleType::Unpacked: { case eAssetBundleType::Unpacked: {
break; break;
} }
@@ -67,10 +55,19 @@ AssetManager::AssetManager(const std::filesystem::path& path) {
} }
void AssetManager::LoadPackIndex() { void AssetManager::LoadPackIndex() {
m_PackIndex = PackIndex(m_RootPath); m_PackIndex = new PackIndex(m_RootPath);
} }
bool AssetManager::HasFile(std::string fixedName) const { std::filesystem::path AssetManager::GetResPath() {
return m_ResPath;
}
eAssetBundleType AssetManager::GetAssetBundleType() {
return m_AssetBundleType;
}
bool AssetManager::HasFile(const char* name) {
auto fixedName = std::string(name);
std::transform(fixedName.begin(), fixedName.end(), fixedName.begin(), [](uint8_t c) { return std::tolower(c); }); std::transform(fixedName.begin(), fixedName.end(), fixedName.begin(), [](uint8_t c) { return std::tolower(c); });
// Special case for unpacked client have BrickModels in upper case // Special case for unpacked client have BrickModels in upper case
@@ -84,7 +81,8 @@ bool AssetManager::HasFile(std::string fixedName) const {
std::replace(fixedName.begin(), fixedName.end(), '/', '\\'); std::replace(fixedName.begin(), fixedName.end(), '/', '\\');
if (fixedName.rfind("client\\res\\", 0) != 0) fixedName = "client\\res\\" + fixedName; if (fixedName.rfind("client\\res\\", 0) != 0) fixedName = "client\\res\\" + fixedName;
const auto crc = crc32b(crc32b(CRC32_INIT, fixedName), NULL_TERMINATOR); uint32_t crc = crc32b(0xFFFFFFFF, reinterpret_cast<uint8_t*>(const_cast<char*>(fixedName.c_str())), fixedName.size());
crc = crc32b(crc, reinterpret_cast<Bytef*>(const_cast<char*>("\0\0\0\0")), 4);
for (const auto& item : this->m_PackIndex->GetPackFileIndices()) { for (const auto& item : this->m_PackIndex->GetPackFileIndices()) {
if (item.m_Crc == crc) { if (item.m_Crc == crc) {
@@ -95,7 +93,8 @@ bool AssetManager::HasFile(std::string fixedName) const {
return false; return false;
} }
bool AssetManager::GetFile(std::string fixedName, char** data, uint32_t* len) const { bool AssetManager::GetFile(const char* name, char** data, uint32_t* len) {
auto fixedName = std::string(name);
std::transform(fixedName.begin(), fixedName.end(), fixedName.begin(), [](uint8_t c) { return std::tolower(c); }); std::transform(fixedName.begin(), fixedName.end(), fixedName.begin(), [](uint8_t c) { return std::tolower(c); });
std::replace(fixedName.begin(), fixedName.end(), '\\', '/'); // On the off chance someone has the wrong slashes, force forward slashes std::replace(fixedName.begin(), fixedName.end(), '\\', '/'); // On the off chance someone has the wrong slashes, force forward slashes
@@ -130,7 +129,8 @@ bool AssetManager::GetFile(std::string fixedName, char** data, uint32_t* len) co
fixedName = "client\\res\\" + fixedName; fixedName = "client\\res\\" + fixedName;
} }
int32_t packIndex = -1; int32_t packIndex = -1;
auto crc = crc32b(crc32b(CRC32_INIT, fixedName), NULL_TERMINATOR); uint32_t crc = crc32b(0xFFFFFFFF, reinterpret_cast<uint8_t*>(const_cast<char*>(fixedName.c_str())), fixedName.size());
crc = crc32b(crc, reinterpret_cast<Bytef*>(const_cast<char*>("\0\0\0\0")), 4);
for (const auto& item : this->m_PackIndex->GetPackFileIndices()) { for (const auto& item : this->m_PackIndex->GetPackFileIndices()) {
if (item.m_Crc == crc) { if (item.m_Crc == crc) {
@@ -144,13 +144,15 @@ bool AssetManager::GetFile(std::string fixedName, char** data, uint32_t* len) co
return false; return false;
} }
const auto& pack = this->m_PackIndex->GetPacks().at(packIndex); auto packs = this->m_PackIndex->GetPacks();
const bool success = pack.ReadFileFromPack(crc, data, len); auto* pack = packs.at(packIndex);
bool success = pack->ReadFileFromPack(crc, data, len);
return success; return success;
} }
AssetStream AssetManager::GetFile(const char* name) const { AssetStream AssetManager::GetFile(const char* name) {
char* buf; uint32_t len; char* buf; uint32_t len;
bool success = this->GetFile(name, &buf, &len); bool success = this->GetFile(name, &buf, &len);
@@ -158,15 +160,23 @@ AssetStream AssetManager::GetFile(const char* name) const {
return AssetStream(buf, len, success); return AssetStream(buf, len, success);
} }
uint32_t AssetManager::crc32b(uint32_t crc, const std::string_view message) { uint32_t AssetManager::crc32b(uint32_t base, uint8_t* message, size_t l) {
for (const auto byte : message) { size_t i, j;
uint32_t crc, msb;
crc = base;
for (i = 0; i < l; i++) {
// xor next byte to upper bits of crc // xor next byte to upper bits of crc
crc ^= (static_cast<uint32_t>(std::bit_cast<uint8_t>(byte)) << 24); crc ^= (static_cast<unsigned int>(message[i]) << 24);
for (size_t _ = 0; _ < 8; _++) { // Do eight times. for (j = 0; j < 8; j++) { // Do eight times.
const uint32_t msb = crc >> 31; msb = crc >> 31;
crc <<= 1; crc <<= 1;
crc ^= (0 - msb) & 0x04C11DB7; crc ^= (0 - msb) & 0x04C11DB7;
} }
} }
return crc; // don't complement crc on output return crc; // don't complement crc on output
} }
AssetManager::~AssetManager() {
delete m_PackIndex;
}

View File

@@ -61,32 +61,23 @@ struct AssetStream : std::istream {
class AssetManager { class AssetManager {
public: public:
AssetManager(const std::filesystem::path& path); AssetManager(const std::filesystem::path& path);
~AssetManager();
[[nodiscard]] std::filesystem::path GetResPath();
const std::filesystem::path& GetResPath() const { eAssetBundleType GetAssetBundleType();
return m_ResPath;
}
[[nodiscard]]
eAssetBundleType GetAssetBundleType() const {
return m_AssetBundleType;
}
[[nodiscard]] bool HasFile(const char* name);
bool HasFile(std::string name) const; bool GetFile(const char* name, char** data, uint32_t* len);
AssetStream GetFile(const char* name);
[[nodiscard]]
bool GetFile(std::string name, char** data, uint32_t* len) const;
[[nodiscard]]
AssetStream GetFile(const char* name) const;
private: private:
void LoadPackIndex(); void LoadPackIndex();
// Modified crc algorithm (mpeg2) // Modified crc algorithm (mpeg2)
// Reference: https://stackoverflow.com/questions/54339800/how-to-modify-crc-32-to-crc-32-mpeg-2 // Reference: https://stackoverflow.com/questions/54339800/how-to-modify-crc-32-to-crc-32-mpeg-2
static inline uint32_t crc32b(uint32_t crc, std::string_view message); inline uint32_t crc32b(uint32_t base, uint8_t* message, size_t l);
bool m_SuccessfullyLoaded;
std::filesystem::path m_Path; std::filesystem::path m_Path;
std::filesystem::path m_RootPath; std::filesystem::path m_RootPath;
@@ -94,5 +85,5 @@ private:
eAssetBundleType m_AssetBundleType = eAssetBundleType::None; eAssetBundleType m_AssetBundleType = eAssetBundleType::None;
std::optional<PackIndex> m_PackIndex; PackIndex* m_PackIndex;
}; };

View File

@@ -1,7 +1,6 @@
#include "Pack.h" #include "Pack.h"
#include "BinaryIO.h" #include "BinaryIO.h"
#include "Sd0.h"
#include "ZCompression.h" #include "ZCompression.h"
Pack::Pack(const std::filesystem::path& filePath) { Pack::Pack(const std::filesystem::path& filePath) {
@@ -22,20 +21,19 @@ Pack::Pack(const std::filesystem::path& filePath) {
m_FileStream.seekg(recordCountPos, std::ios::beg); m_FileStream.seekg(recordCountPos, std::ios::beg);
uint32_t recordCount = 0; BinaryIO::BinaryRead<uint32_t>(m_FileStream, m_RecordCount);
BinaryIO::BinaryRead<uint32_t>(m_FileStream, recordCount);
m_Records.reserve(recordCount); for (int i = 0; i < m_RecordCount; i++) {
std::generate_n(std::back_inserter(m_Records), recordCount, [&] {
PackRecord record; PackRecord record;
BinaryIO::BinaryRead<PackRecord>(m_FileStream, record); BinaryIO::BinaryRead<PackRecord>(m_FileStream, record);
return record;
}); m_Records.push_back(record);
}
m_FileStream.close(); m_FileStream.close();
} }
bool Pack::HasFile(const uint32_t crc) const { bool Pack::HasFile(uint32_t crc) {
for (const auto& record : m_Records) { for (const auto& record : m_Records) {
if (record.m_Crc == crc) { if (record.m_Crc == crc) {
return true; return true;
@@ -45,7 +43,7 @@ bool Pack::HasFile(const uint32_t crc) const {
return false; return false;
} }
bool Pack::ReadFileFromPack(const uint32_t crc, char** data, uint32_t* len) const { bool Pack::ReadFileFromPack(uint32_t crc, char** data, uint32_t* len) {
// Time for some wacky C file reading for speed reasons // Time for some wacky C file reading for speed reasons
PackRecord pkRecord{}; PackRecord pkRecord{};
@@ -107,7 +105,7 @@ bool Pack::ReadFileFromPack(const uint32_t crc, char** data, uint32_t* len) cons
pos += size; // Move pointer position the amount of bytes read to the right pos += size; // Move pointer position the amount of bytes read to the right
int32_t err; int32_t err;
currentReadPos += ZCompression::Decompress(reinterpret_cast<uint8_t*>(chunk), size, reinterpret_cast<uint8_t*>(decompressedData + currentReadPos), Sd0::MAX_UNCOMPRESSED_CHUNK_SIZE, err); currentReadPos += ZCompression::Decompress(reinterpret_cast<uint8_t*>(chunk), size, reinterpret_cast<uint8_t*>(decompressedData + currentReadPos), ZCompression::MAX_SD0_CHUNK_SIZE, err);
free(chunk); free(chunk);
} }

View File

@@ -24,17 +24,16 @@ struct PackRecord {
class Pack { class Pack {
public: public:
Pack(const std::filesystem::path& filePath); Pack(const std::filesystem::path& filePath);
~Pack() = default;
[[nodiscard]] bool HasFile(uint32_t crc);
bool HasFile(uint32_t crc) const; bool ReadFileFromPack(uint32_t crc, char** data, uint32_t* len);
[[nodiscard]]
bool ReadFileFromPack(uint32_t crc, char** data, uint32_t* len) const;
private: private:
std::ifstream m_FileStream; std::ifstream m_FileStream;
std::filesystem::path m_FilePath; std::filesystem::path m_FilePath;
char m_Version[7]; char m_Version[7];
uint32_t m_RecordCount;
std::vector<PackRecord> m_Records; std::vector<PackRecord> m_Records;
}; };

View File

@@ -6,32 +6,38 @@
PackIndex::PackIndex(const std::filesystem::path& filePath) { PackIndex::PackIndex(const std::filesystem::path& filePath) {
m_FileStream = std::ifstream(filePath / "versions" / "primary.pki", std::ios::in | std::ios::binary); m_FileStream = std::ifstream(filePath / "versions" / "primary.pki", std::ios::in | std::ios::binary);
uint32_t packPathCount = 0;
BinaryIO::BinaryRead<uint32_t>(m_FileStream, m_Version); BinaryIO::BinaryRead<uint32_t>(m_FileStream, m_Version);
BinaryIO::BinaryRead<uint32_t>(m_FileStream, packPathCount); BinaryIO::BinaryRead<uint32_t>(m_FileStream, m_PackPathCount);
m_PackPaths.resize(packPathCount); m_PackPaths.resize(m_PackPathCount);
for (auto& item : m_PackPaths) { for (auto& item : m_PackPaths) {
BinaryIO::ReadString<uint32_t>(m_FileStream, item, BinaryIO::ReadType::String); BinaryIO::ReadString<uint32_t>(m_FileStream, item, BinaryIO::ReadType::String);
} }
uint32_t packFileIndexCount = 0; BinaryIO::BinaryRead<uint32_t>(m_FileStream, m_PackFileIndexCount);
BinaryIO::BinaryRead<uint32_t>(m_FileStream, packFileIndexCount);
m_PackFileIndices.reserve(packFileIndexCount); for (int i = 0; i < m_PackFileIndexCount; i++) {
std::generate_n(std::back_inserter(m_PackFileIndices), packFileIndexCount, [&] {
PackFileIndex packFileIndex; PackFileIndex packFileIndex;
BinaryIO::BinaryRead<PackFileIndex>(m_FileStream, packFileIndex); BinaryIO::BinaryRead<PackFileIndex>(m_FileStream, packFileIndex);
return packFileIndex;
}); m_PackFileIndices.push_back(packFileIndex);
}
LOG("Loaded pack catalog with %i pack files and %i files", m_PackPaths.size(), m_PackFileIndices.size()); LOG("Loaded pack catalog with %i pack files and %i files", m_PackPaths.size(), m_PackFileIndices.size());
m_Packs.reserve(m_PackPaths.size());
for (auto& item : m_PackPaths) { for (auto& item : m_PackPaths) {
std::replace(item.begin(), item.end(), '\\', '/'); std::replace(item.begin(), item.end(), '\\', '/');
m_Packs.emplace_back(filePath / item);
auto* pack = new Pack(filePath / item);
m_Packs.push_back(pack);
} }
m_FileStream.close(); m_FileStream.close();
} }
PackIndex::~PackIndex() {
for (const auto* item : m_Packs) {
delete item;
}
}

View File

@@ -21,23 +21,20 @@ struct PackFileIndex {
class PackIndex { class PackIndex {
public: public:
PackIndex(const std::filesystem::path& filePath); PackIndex(const std::filesystem::path& filePath);
~PackIndex();
[[nodiscard]] const std::vector<std::string>& GetPackPaths() { return m_PackPaths; }
const std::vector<std::string>& GetPackPaths() const { return m_PackPaths; } const std::vector<PackFileIndex>& GetPackFileIndices() { return m_PackFileIndices; }
const std::vector<Pack*>& GetPacks() { return m_Packs; }
[[nodiscard]]
const std::vector<PackFileIndex>& GetPackFileIndices() const { return m_PackFileIndices; }
[[nodiscard]]
const std::vector<Pack>& GetPacks() const { return m_Packs; }
private: private:
std::ifstream m_FileStream; std::ifstream m_FileStream;
uint32_t m_Version; uint32_t m_Version;
uint32_t m_PackPathCount;
std::vector<std::string> m_PackPaths; std::vector<std::string> m_PackPaths;
uint32_t m_PackFileIndexCount;
std::vector<PackFileIndex> m_PackFileIndices; std::vector<PackFileIndex> m_PackFileIndices;
std::vector<Pack> m_Packs; std::vector<Pack*> m_Packs;
}; };

View File

@@ -1,7 +1,6 @@
#include "dConfig.h" #include "dConfig.h"
#include <sstream> #include <sstream>
#include <algorithm>
#include "BinaryPathFinder.h" #include "BinaryPathFinder.h"
#include "GeneralUtils.h" #include "GeneralUtils.h"

View File

@@ -1,13 +0,0 @@
#pragma once
#include <cstdint>
namespace MessageType {
enum class Auth : uint32_t {
LOGIN_REQUEST = 0,
LOGOUT_REQUEST,
CREATE_NEW_ACCOUNT_REQUEST,
LEGOINTERFACE_AUTH_RESPONSE,
SESSIONKEY_RECEIVED_CONFIRM,
RUNTIME_CONFIG
};
}

View File

@@ -1,78 +0,0 @@
#pragma once
#include <cstdint>
namespace MessageType {
//! The Internal Chat Packet Identifiers
enum class Chat : uint32_t {
LOGIN_SESSION_NOTIFY = 0,
GENERAL_CHAT_MESSAGE,
PRIVATE_CHAT_MESSAGE,
USER_CHANNEL_CHAT_MESSAGE,
WORLD_DISCONNECT_REQUEST,
WORLD_PROXIMITY_RESPONSE,
WORLD_PARCEL_RESPONSE,
ADD_FRIEND_REQUEST,
ADD_FRIEND_RESPONSE,
REMOVE_FRIEND,
GET_FRIENDS_LIST,
ADD_IGNORE,
REMOVE_IGNORE,
GET_IGNORE_LIST,
TEAM_MISSED_INVITE_CHECK,
TEAM_INVITE,
TEAM_INVITE_RESPONSE,
TEAM_KICK,
TEAM_LEAVE,
TEAM_SET_LOOT,
TEAM_SET_LEADER,
TEAM_GET_STATUS,
GUILD_CREATE,
GUILD_INVITE,
GUILD_INVITE_RESPONSE,
GUILD_LEAVE,
GUILD_KICK,
GUILD_GET_STATUS,
GUILD_GET_ALL,
SHOW_ALL,
BLUEPRINT_MODERATED,
BLUEPRINT_MODEL_READY,
PROPERTY_READY_FOR_APPROVAL,
PROPERTY_MODERATION_CHANGED,
PROPERTY_BUILDMODE_CHANGED,
PROPERTY_BUILDMODE_CHANGED_REPORT,
MAIL,
WORLD_INSTANCE_LOCATION_REQUEST,
REPUTATION_UPDATE,
SEND_CANNED_TEXT,
GMLEVEL_UPDATE,
CHARACTER_NAME_CHANGE_REQUEST,
CSR_REQUEST,
CSR_REPLY,
GM_KICK,
GM_ANNOUNCE,
GM_MUTE,
ACTIVITY_UPDATE,
WORLD_ROUTE_PACKET,
GET_ZONE_POPULATIONS,
REQUEST_MINIMUM_CHAT_MODE,
REQUEST_MINIMUM_CHAT_MODE_PRIVATE,
MATCH_REQUEST,
UGCMANIFEST_REPORT_MISSING_FILE,
UGCMANIFEST_REPORT_DONE_FILE,
UGCMANIFEST_REPORT_DONE_BLUEPRINT,
UGCC_REQUEST,
WHO,
WORLD_PLAYERS_PET_MODERATED_ACKNOWLEDGE,
ACHIEVEMENT_NOTIFY,
GM_CLOSE_PRIVATE_CHAT_WINDOW,
UNEXPECTED_DISCONNECT,
PLAYER_READY,
GET_DONATION_TOTAL,
UPDATE_DONATION,
PRG_CSR_COMMAND,
HEARTBEAT_REQUEST_FROM_WORLD,
UPDATE_FREE_TRIAL_STATUS,
// CUSTOM DLU MESSAGE ID FOR INTERNAL USE
CREATE_TEAM,
};
}

View File

@@ -1,74 +0,0 @@
#pragma once
#include <cstdint>
namespace MessageType {
enum class Client : uint32_t {
LOGIN_RESPONSE = 0,
LOGOUT_RESPONSE,
LOAD_STATIC_ZONE,
CREATE_OBJECT,
CREATE_CHARACTER,
CREATE_CHARACTER_EXTENDED,
CHARACTER_LIST_RESPONSE,
CHARACTER_CREATE_RESPONSE,
CHARACTER_RENAME_RESPONSE,
CHAT_CONNECT_RESPONSE,
AUTH_ACCOUNT_CREATE_RESPONSE,
DELETE_CHARACTER_RESPONSE,
GAME_MSG,
CONNECT_CHAT,
TRANSFER_TO_WORLD,
IMPENDING_RELOAD_NOTIFY,
MAKE_GM_RESPONSE,
HTTP_MONITOR_INFO_RESPONSE,
SLASH_PUSH_MAP_RESPONSE,
SLASH_PULL_MAP_RESPONSE,
SLASH_LOCK_MAP_RESPONSE,
BLUEPRINT_SAVE_RESPONSE,
BLUEPRINT_LUP_SAVE_RESPONSE,
BLUEPRINT_LOAD_RESPONSE_ITEMID,
BLUEPRINT_GET_ALL_DATA_RESPONSE,
MODEL_INSTANTIATE_RESPONSE,
DEBUG_OUTPUT,
ADD_FRIEND_REQUEST,
ADD_FRIEND_RESPONSE,
REMOVE_FRIEND_RESPONSE,
GET_FRIENDS_LIST_RESPONSE,
UPDATE_FRIEND_NOTIFY,
ADD_IGNORE_RESPONSE,
REMOVE_IGNORE_RESPONSE,
GET_IGNORE_LIST_RESPONSE,
TEAM_INVITE,
TEAM_INVITE_INITIAL_RESPONSE,
GUILD_CREATE_RESPONSE,
GUILD_GET_STATUS_RESPONSE,
GUILD_INVITE,
GUILD_INVITE_INITIAL_RESPONSE,
GUILD_INVITE_FINAL_RESPONSE,
GUILD_INVITE_CONFIRM,
GUILD_ADD_PLAYER,
GUILD_REMOVE_PLAYER,
GUILD_LOGIN_LOGOUT,
GUILD_RANK_CHANGE,
GUILD_DATA,
GUILD_STATUS,
MAIL,
DB_PROXY_RESULT,
SHOW_ALL_RESPONSE,
WHO_RESPONSE,
SEND_CANNED_TEXT,
UPDATE_CHARACTER_NAME,
SET_NETWORK_SIMULATOR,
INVALID_CHAT_MESSAGE,
MINIMUM_CHAT_MODE_RESPONSE,
MINIMUM_CHAT_MODE_RESPONSE_PRIVATE,
CHAT_MODERATION_STRING,
UGC_MANIFEST_RESPONSE,
IN_LOGIN_QUEUE,
SERVER_STATES,
GM_CLOSE_TARGET_CHAT_WINDOW,
GENERAL_TEXT_FOR_LOCALIZATION,
UPDATE_FREE_TRIAL_STATUS,
UGC_DOWNLOAD_FAILED = 120
};
}

File diff suppressed because it is too large Load Diff

View File

@@ -1,34 +0,0 @@
#pragma once
#include <cstdint>
namespace MessageType {
enum class Master : uint32_t {
REQUEST_PERSISTENT_ID = 1,
REQUEST_PERSISTENT_ID_RESPONSE,
REQUEST_ZONE_TRANSFER,
REQUEST_ZONE_TRANSFER_RESPONSE,
SERVER_INFO,
REQUEST_SESSION_KEY,
SET_SESSION_KEY,
SESSION_KEY_RESPONSE,
PLAYER_ADDED,
PLAYER_REMOVED,
CREATE_PRIVATE_ZONE,
REQUEST_PRIVATE_ZONE,
WORLD_READY,
PREP_ZONE,
SHUTDOWN,
SHUTDOWN_RESPONSE,
SHUTDOWN_IMMEDIATE,
SHUTDOWN_UNIVERSE,
AFFIRM_TRANSFER_REQUEST,
AFFIRM_TRANSFER_RESPONSE,
NEW_SESSION_ALERT
};
}

View File

@@ -1,11 +0,0 @@
#pragma once
#include <cstdint>
namespace MessageType {
//! The Internal Server Packet Identifiers
enum class Server : uint32_t {
VERSION_CONFIRM = 0,
DISCONNECT_NOTIFY,
GENERAL_NOTIFY
};
}

View File

@@ -1,49 +0,0 @@
#pragma once
#include <cstdint>
#include "magic_enum.hpp"
namespace MessageType {
enum class World : uint32_t {
VALIDATION = 1, // Session info
CHARACTER_LIST_REQUEST,
CHARACTER_CREATE_REQUEST,
LOGIN_REQUEST, // Character selected
GAME_MSG,
CHARACTER_DELETE_REQUEST,
CHARACTER_RENAME_REQUEST,
HAPPY_FLOWER_MODE_NOTIFY,
SLASH_RELOAD_MAP, // Reload map cmp
SLASH_PUSH_MAP_REQUEST, // Push map req cmd
SLASH_PUSH_MAP, // Push map cmd
SLASH_PULL_MAP, // Pull map cmd
LOCK_MAP_REQUEST,
GENERAL_CHAT_MESSAGE, // General chat message
HTTP_MONITOR_INFO_REQUEST,
SLASH_DEBUG_SCRIPTS, // Debug scripts cmd
MODELS_CLEAR,
EXHIBIT_INSERT_MODEL,
LEVEL_LOAD_COMPLETE, // Character data request
TMP_GUILD_CREATE,
ROUTE_PACKET, // Social?
POSITION_UPDATE,
MAIL,
WORD_CHECK, // AllowList word check
STRING_CHECK, // AllowList string check
GET_PLAYERS_IN_ZONE,
REQUEST_UGC_MANIFEST_INFO,
BLUEPRINT_GET_ALL_DATA_REQUEST,
CANCEL_MAP_QUEUE,
HANDLE_FUNNESS,
FAKE_PRG_CSR_MESSAGE,
REQUEST_FREE_TRIAL_REFRESH,
GM_SET_FREE_TRIAL_STATUS,
UI_HELP_TOP_5 = 91
};
}
template <>
struct magic_enum::customize::enum_range<MessageType::World> {
static constexpr int min = 0;
static constexpr int max = 91;
};

View File

@@ -3,14 +3,13 @@
#ifndef __DCOMMONVARS__H__ #ifndef __DCOMMONVARS__H__
#define __DCOMMONVARS__H__ #define __DCOMMONVARS__H__
#include <compare>
#include <cstdint> #include <cstdint>
#include <set>
#include <string> #include <string>
#include <set>
#include "BitStream.h" #include "BitStream.h"
#include "BitStreamUtils.h"
#include "MessageType/Client.h"
#include "eConnectionType.h" #include "eConnectionType.h"
#include "eClientMessageType.h"
#include "BitStreamUtils.h"
#pragma warning (disable:4251) //Disables SQL warnings #pragma warning (disable:4251) //Disables SQL warnings
@@ -34,7 +33,7 @@ constexpr uint32_t lowFrameDelta = FRAMES_TO_MS(lowFramerate);
#define CBITSTREAM RakNet::BitStream bitStream; #define CBITSTREAM RakNet::BitStream bitStream;
#define CINSTREAM RakNet::BitStream inStream(packet->data, packet->length, false); #define CINSTREAM RakNet::BitStream inStream(packet->data, packet->length, false);
#define CINSTREAM_SKIP_HEADER CINSTREAM if (inStream.GetNumberOfUnreadBits() >= BYTES_TO_BITS(HEADER_SIZE)) inStream.IgnoreBytes(HEADER_SIZE); else inStream.IgnoreBits(inStream.GetNumberOfUnreadBits()); #define CINSTREAM_SKIP_HEADER CINSTREAM if (inStream.GetNumberOfUnreadBits() >= BYTES_TO_BITS(HEADER_SIZE)) inStream.IgnoreBytes(HEADER_SIZE); else inStream.IgnoreBits(inStream.GetNumberOfUnreadBits());
#define CMSGHEADER BitStreamUtils::WriteHeader(bitStream, eConnectionType::CLIENT, MessageType::Client::GAME_MSG); #define CMSGHEADER BitStreamUtils::WriteHeader(bitStream, eConnectionType::CLIENT, eClientMessageType::GAME_MSG);
#define SEND_PACKET Game::server->Send(bitStream, sysAddr, false); #define SEND_PACKET Game::server->Send(bitStream, sysAddr, false);
#define SEND_PACKET_BROADCAST Game::server->Send(bitStream, UNASSIGNED_SYSTEM_ADDRESS, true); #define SEND_PACKET_BROADCAST Game::server->Send(bitStream, UNASSIGNED_SYSTEM_ADDRESS, true);
@@ -99,8 +98,6 @@ public:
constexpr LWOZONEID() noexcept = default; constexpr LWOZONEID() noexcept = default;
constexpr LWOZONEID(const LWOMAPID& mapID, const LWOINSTANCEID& instanceID, const LWOCLONEID& cloneID) noexcept { m_MapID = mapID; m_InstanceID = instanceID; m_CloneID = cloneID; } constexpr LWOZONEID(const LWOMAPID& mapID, const LWOINSTANCEID& instanceID, const LWOCLONEID& cloneID) noexcept { m_MapID = mapID; m_InstanceID = instanceID; m_CloneID = cloneID; }
constexpr LWOZONEID(const LWOZONEID& replacement) noexcept { *this = replacement; } constexpr LWOZONEID(const LWOZONEID& replacement) noexcept { *this = replacement; }
constexpr bool operator==(const LWOZONEID&) const = default;
constexpr auto operator<=>(const LWOZONEID&) const = default;
private: private:
LWOMAPID m_MapID = LWOMAPID_INVALID; //1000 for VE, 1100 for AG, etc... LWOMAPID m_MapID = LWOMAPID_INVALID; //1000 for VE, 1100 for AG, etc...

View File

@@ -0,0 +1,15 @@
#ifndef __EAUTHMESSAGETYPE__H__
#define __EAUTHMESSAGETYPE__H__
#include <cstdint>
enum class eAuthMessageType : uint32_t {
LOGIN_REQUEST = 0,
LOGOUT_REQUEST,
CREATE_NEW_ACCOUNT_REQUEST,
LEGOINTERFACE_AUTH_RESPONSE,
SESSIONKEY_RECEIVED_CONFIRM,
RUNTIME_CONFIG
};
#endif //!__EAUTHMESSAGETYPE__H__

View File

@@ -15,10 +15,7 @@ enum class eCharacterVersion : uint32_t {
// Fixes vault size value // Fixes vault size value
VAULT_SIZE, VAULT_SIZE,
// Fixes speed base value in level component // Fixes speed base value in level component
SPEED_BASE, UP_TO_DATE, // will become SPEED_BASE
// Fixes nexus force explorer missions
NJ_JAYMISSIONS,
UP_TO_DATE, // will become NEXUS_FORCE_EXPLORER
}; };
#endif //!__ECHARACTERVERSION__H__ #endif //!__ECHARACTERVERSION__H__

View File

@@ -0,0 +1,80 @@
#ifndef __ECHATMESSAGETYPE__H__
#define __ECHATMESSAGETYPE__H__
#include <cstdint>
//! The Internal Chat Packet Identifiers
enum class eChatMessageType :uint32_t {
LOGIN_SESSION_NOTIFY = 0,
GENERAL_CHAT_MESSAGE,
PRIVATE_CHAT_MESSAGE,
USER_CHANNEL_CHAT_MESSAGE,
WORLD_DISCONNECT_REQUEST,
WORLD_PROXIMITY_RESPONSE,
WORLD_PARCEL_RESPONSE,
ADD_FRIEND_REQUEST,
ADD_FRIEND_RESPONSE,
REMOVE_FRIEND,
GET_FRIENDS_LIST,
ADD_IGNORE,
REMOVE_IGNORE,
GET_IGNORE_LIST,
TEAM_MISSED_INVITE_CHECK,
TEAM_INVITE,
TEAM_INVITE_RESPONSE,
TEAM_KICK,
TEAM_LEAVE,
TEAM_SET_LOOT,
TEAM_SET_LEADER,
TEAM_GET_STATUS,
GUILD_CREATE,
GUILD_INVITE,
GUILD_INVITE_RESPONSE,
GUILD_LEAVE,
GUILD_KICK,
GUILD_GET_STATUS,
GUILD_GET_ALL,
SHOW_ALL,
BLUEPRINT_MODERATED,
BLUEPRINT_MODEL_READY,
PROPERTY_READY_FOR_APPROVAL,
PROPERTY_MODERATION_CHANGED,
PROPERTY_BUILDMODE_CHANGED,
PROPERTY_BUILDMODE_CHANGED_REPORT,
MAIL,
WORLD_INSTANCE_LOCATION_REQUEST,
REPUTATION_UPDATE,
SEND_CANNED_TEXT,
GMLEVEL_UPDATE,
CHARACTER_NAME_CHANGE_REQUEST,
CSR_REQUEST,
CSR_REPLY,
GM_KICK,
GM_ANNOUNCE,
GM_MUTE,
ACTIVITY_UPDATE,
WORLD_ROUTE_PACKET,
GET_ZONE_POPULATIONS,
REQUEST_MINIMUM_CHAT_MODE,
REQUEST_MINIMUM_CHAT_MODE_PRIVATE,
MATCH_REQUEST,
UGCMANIFEST_REPORT_MISSING_FILE,
UGCMANIFEST_REPORT_DONE_FILE,
UGCMANIFEST_REPORT_DONE_BLUEPRINT,
UGCC_REQUEST,
WHO,
WORLD_PLAYERS_PET_MODERATED_ACKNOWLEDGE,
ACHIEVEMENT_NOTIFY,
GM_CLOSE_PRIVATE_CHAT_WINDOW,
UNEXPECTED_DISCONNECT,
PLAYER_READY,
GET_DONATION_TOTAL,
UPDATE_DONATION,
PRG_CSR_COMMAND,
HEARTBEAT_REQUEST_FROM_WORLD,
UPDATE_FREE_TRIAL_STATUS,
// CUSTOM DLU MESSAGE ID FOR INTERNAL USE
CREATE_TEAM,
};
#endif //!__ECHATMESSAGETYPE__H__

View File

@@ -0,0 +1,76 @@
#ifndef __ECLIENTMESSAGETYPE__H__
#define __ECLIENTMESSAGETYPE__H__
#include <cstdint>
enum class eClientMessageType : uint32_t {
LOGIN_RESPONSE = 0,
LOGOUT_RESPONSE,
LOAD_STATIC_ZONE,
CREATE_OBJECT,
CREATE_CHARACTER,
CREATE_CHARACTER_EXTENDED,
CHARACTER_LIST_RESPONSE,
CHARACTER_CREATE_RESPONSE,
CHARACTER_RENAME_RESPONSE,
CHAT_CONNECT_RESPONSE,
AUTH_ACCOUNT_CREATE_RESPONSE,
DELETE_CHARACTER_RESPONSE,
GAME_MSG,
CONNECT_CHAT,
TRANSFER_TO_WORLD,
IMPENDING_RELOAD_NOTIFY,
MAKE_GM_RESPONSE,
HTTP_MONITOR_INFO_RESPONSE,
SLASH_PUSH_MAP_RESPONSE,
SLASH_PULL_MAP_RESPONSE,
SLASH_LOCK_MAP_RESPONSE,
BLUEPRINT_SAVE_RESPONSE,
BLUEPRINT_LUP_SAVE_RESPONSE,
BLUEPRINT_LOAD_RESPONSE_ITEMID,
BLUEPRINT_GET_ALL_DATA_RESPONSE,
MODEL_INSTANTIATE_RESPONSE,
DEBUG_OUTPUT,
ADD_FRIEND_REQUEST,
ADD_FRIEND_RESPONSE,
REMOVE_FRIEND_RESPONSE,
GET_FRIENDS_LIST_RESPONSE,
UPDATE_FRIEND_NOTIFY,
ADD_IGNORE_RESPONSE,
REMOVE_IGNORE_RESPONSE,
GET_IGNORE_LIST_RESPONSE,
TEAM_INVITE,
TEAM_INVITE_INITIAL_RESPONSE,
GUILD_CREATE_RESPONSE,
GUILD_GET_STATUS_RESPONSE,
GUILD_INVITE,
GUILD_INVITE_INITIAL_RESPONSE,
GUILD_INVITE_FINAL_RESPONSE,
GUILD_INVITE_CONFIRM,
GUILD_ADD_PLAYER,
GUILD_REMOVE_PLAYER,
GUILD_LOGIN_LOGOUT,
GUILD_RANK_CHANGE,
GUILD_DATA,
GUILD_STATUS,
MAIL,
DB_PROXY_RESULT,
SHOW_ALL_RESPONSE,
WHO_RESPONSE,
SEND_CANNED_TEXT,
UPDATE_CHARACTER_NAME,
SET_NETWORK_SIMULATOR,
INVALID_CHAT_MESSAGE,
MINIMUM_CHAT_MODE_RESPONSE,
MINIMUM_CHAT_MODE_RESPONSE_PRIVATE,
CHAT_MODERATION_STRING,
UGC_MANIFEST_RESPONSE,
IN_LOGIN_QUEUE,
SERVER_STATES,
GM_CLOSE_TARGET_CHAT_WINDOW,
GENERAL_TEXT_FOR_LOCALIZATION,
UPDATE_FREE_TRIAL_STATUS,
UGC_DOWNLOAD_FAILED = 120
};
#endif //!__ECLIENTMESSAGETYPE__H__

View File

@@ -7,8 +7,7 @@ enum class eConnectionType : uint16_t {
CHAT, CHAT,
WORLD = 4, WORLD = 4,
CLIENT, CLIENT,
MASTER, MASTER
UNKNOWN
}; };
#endif //!__ECONNECTIONTYPE__H__ #endif //!__ECONNECTIONTYPE__H__

File diff suppressed because it is too large Load Diff

View File

@@ -1,28 +0,0 @@
#ifndef __EHTTPMETHODS__H__
#define __EHTTPMETHODS__H__
#include "dPlatforms.h"
#ifdef DARKFLAME_PLATFORM_WIN32
#pragma push_macro("DELETE")
#undef DELETE
#endif
enum class eHTTPMethod {
GET,
POST,
PUT,
DELETE,
HEAD,
CONNECT,
OPTIONS,
TRACE,
PATCH,
INVALID
};
#ifdef DARKFLAME_PLATFORM_WIN32
#pragma pop_macro("DELETE")
#endif
#endif // __EHTTPMETHODS__H__

View File

@@ -1,72 +0,0 @@
#ifndef __EHTTPSTATUSCODE__H__
#define __EHTTPSTATUSCODE__H__
#include <cstdint>
// verbose list of http codes
// https://developer.mozilla.org/en-US/docs/Web/HTTP/Status
enum class eHTTPStatusCode : uint32_t {
CONTINUE = 100,
SWITCHING_PROTOCOLS = 101,
PROCESSING = 102,
EARLY_HINTS = 103,
OK = 200,
CREATED = 201,
ACCEPTED = 202,
NON_AUTHORITATIVE_INFORMATION = 203,
NO_CONTENT = 204,
RESET_CONTENT = 205,
PARTIAL_CONTENT = 206,
MULTI_STATUS = 207,
ALREADY_REPORTED = 208,
IM_USED = 226,
MULTIPLE_CHOICES = 300,
MOVED_PERMANENTLY = 301,
FOUND = 302,
SEE_OTHER = 303,
NOT_MODIFIED = 304,
USE_PROXY = 305,
SWITCH_PROXY = 306,
TEMPORARY_REDIRECT = 307,
PERMANENT_REDIRECT = 308,
BAD_REQUEST = 400,
UNAUTHORIZED = 401,
PAYMENT_REQUIRED = 402,
FORBIDDEN = 403,
NOT_FOUND = 404,
METHOD_NOT_ALLOWED = 405,
NOT_ACCEPTABLE = 406,
PROXY_AUTHENTICATION_REQUIRED = 407,
REQUEST_TIMEOUT = 408,
CONFLICT = 409,
GONE = 410,
LENGTH_REQUIRED = 411,
PRECONDITION_FAILED = 412,
PAYLOAD_TOO_LARGE = 413,
URI_TOO_LONG = 414,
UNSUPPORTED_MEDIA_TYPE = 415,
RANGE_NOT_SATISFIABLE = 416,
EXPECTATION_FAILED = 417,
I_AM_A_TEAPOT = 418,
MISDIRECTED_REQUEST = 421,
UNPROCESSABLE_ENTITY = 422,
LOCKED = 423,
FAILED_DEPENDENCY = 424,
UPGRADE_REQUIRED = 426,
PRECONDITION_REQUIRED = 428,
TOO_MANY_REQUESTS = 429,
REQUEST_HEADER_FIELDS_TOO_LARGE = 431,
UNAVAILABLE_FOR_LEGAL_REASONS = 451,
INTERNAL_SERVER_ERROR = 500,
NOT_IMPLEMENTED = 501,
BAD_GATEWAY = 502,
SERVICE_UNAVAILABLE = 503,
GATEWAY_TIMEOUT = 504,
HTTP_VERSION_NOT_SUPPORTED = 505,
VARIANT_ALSO_NEGOTIATES = 506,
INSUFFICIENT_STORAGE = 507,
LOOP_DETECTED = 508,
NOT_EXTENDED = 510,
NETWORK_AUTHENTICATION_REQUIRED = 511
};
#endif // !__EHTTPSTATUSCODE__H__

View File

@@ -4,9 +4,6 @@
#define __EINVENTORYTYPE__H__ #define __EINVENTORYTYPE__H__
#include <cstdint> #include <cstdint>
#include "magic_enum.hpp"
static const uint8_t NUMBER_OF_INVENTORIES = 17; static const uint8_t NUMBER_OF_INVENTORIES = 17;
/** /**
* Represents the different types of inventories an entity may have * Represents the different types of inventories an entity may have
@@ -28,8 +25,7 @@ enum eInventoryType : uint32_t {
DONATION, DONATION,
VAULT_MODELS, VAULT_MODELS,
ITEM_SETS, //internal, technically this is BankBehaviors. ITEM_SETS, //internal, technically this is BankBehaviors.
INVALID, // made up, for internal use!!!, Technically this called the ALL inventory. INVALID // made up, for internal use!!!, Technically this called the ALL inventory.
ALL, // Use this to search all inventories instead of a specific one.
}; };
class InventoryType { class InventoryType {
@@ -60,10 +56,4 @@ public:
}; };
}; };
template <>
struct magic_enum::customize::enum_range<eInventoryType> {
static constexpr int min = 0;
static constexpr int max = 16;
};
#endif //!__EINVENTORYTYPE__H__ #endif //!__EINVENTORYTYPE__H__

View File

@@ -0,0 +1,36 @@
#ifndef __EMASTERMESSAGETYPE__H__
#define __EMASTERMESSAGETYPE__H__
#include <cstdint>
enum class eMasterMessageType : uint32_t {
REQUEST_PERSISTENT_ID = 1,
REQUEST_PERSISTENT_ID_RESPONSE,
REQUEST_ZONE_TRANSFER,
REQUEST_ZONE_TRANSFER_RESPONSE,
SERVER_INFO,
REQUEST_SESSION_KEY,
SET_SESSION_KEY,
SESSION_KEY_RESPONSE,
PLAYER_ADDED,
PLAYER_REMOVED,
CREATE_PRIVATE_ZONE,
REQUEST_PRIVATE_ZONE,
WORLD_READY,
PREP_ZONE,
SHUTDOWN,
SHUTDOWN_RESPONSE,
SHUTDOWN_IMMEDIATE,
SHUTDOWN_UNIVERSE,
AFFIRM_TRANSFER_REQUEST,
AFFIRM_TRANSFER_RESPONSE,
NEW_SESSION_ALERT
};
#endif //!__EMASTERMESSAGETYPE__H__

View File

@@ -1,11 +0,0 @@
#ifndef EPROPERTYSORTTYPE_H
#define EPROPERTYSORTTYPE_H
enum ePropertySortType : int32_t {
SORT_TYPE_FRIENDS = 0,
SORT_TYPE_REPUTATION = 1,
SORT_TYPE_RECENT = 3,
SORT_TYPE_FEATURED = 5
};
#endif //!EPROPERTYSORTTYPE_H

View File

@@ -1,21 +0,0 @@
#ifndef __EREPONSEMOVEITEMBETWEENINVENTORYTYPECODE__H__
#define __EREPONSEMOVEITEMBETWEENINVENTORYTYPECODE__H__
#include <cstdint>
enum class eReponseMoveItemBetweenInventoryTypeCode : int32_t {
SUCCESS,
FAIL_GENERIC,
FAIL_INV_FULL,
FAIL_ITEM_NOT_FOUND,
FAIL_CANT_MOVE_TO_THAT_INV_TYPE,
FAIL_NOT_NEAR_BANK,
FAIL_CANT_SWAP_ITEMS,
FAIL_SOURCE_TYPE,
FAIL_WRONG_DEST_TYPE,
FAIL_SWAP_DEST_TYPE,
FAIL_CANT_MOVE_THINKING_HAT,
FAIL_DISMOUNT_BEFORE_MOVING
};
#endif //!__EREPONSEMOVEITEMBETWEENINVENTORYTYPECODE__H__

View File

@@ -0,0 +1,12 @@
#ifndef __ESERVERMESSAGETYPE__H__
#define __ESERVERMESSAGETYPE__H__
#include <cstdint>
//! The Internal Server Packet Identifiers
enum class eServerMessageType : uint32_t {
VERSION_CONFIRM = 0,
DISCONNECT_NOTIFY,
GENERAL_NOTIFY
};
#endif //!__ESERVERMESSAGETYPE__H__

View File

@@ -0,0 +1,51 @@
#ifndef __EWORLDMESSAGETYPE__H__
#define __EWORLDMESSAGETYPE__H__
#include <cstdint>
#include "magic_enum.hpp"
enum class eWorldMessageType : uint32_t {
VALIDATION = 1, // Session info
CHARACTER_LIST_REQUEST,
CHARACTER_CREATE_REQUEST,
LOGIN_REQUEST, // Character selected
GAME_MSG,
CHARACTER_DELETE_REQUEST,
CHARACTER_RENAME_REQUEST,
HAPPY_FLOWER_MODE_NOTIFY,
SLASH_RELOAD_MAP, // Reload map cmp
SLASH_PUSH_MAP_REQUEST, // Push map req cmd
SLASH_PUSH_MAP, // Push map cmd
SLASH_PULL_MAP, // Pull map cmd
LOCK_MAP_REQUEST,
GENERAL_CHAT_MESSAGE, // General chat message
HTTP_MONITOR_INFO_REQUEST,
SLASH_DEBUG_SCRIPTS, // Debug scripts cmd
MODELS_CLEAR,
EXHIBIT_INSERT_MODEL,
LEVEL_LOAD_COMPLETE, // Character data request
TMP_GUILD_CREATE,
ROUTE_PACKET, // Social?
POSITION_UPDATE,
MAIL,
WORD_CHECK, // AllowList word check
STRING_CHECK, // AllowList string check
GET_PLAYERS_IN_ZONE,
REQUEST_UGC_MANIFEST_INFO,
BLUEPRINT_GET_ALL_DATA_REQUEST,
CANCEL_MAP_QUEUE,
HANDLE_FUNNESS,
FAKE_PRG_CSR_MESSAGE,
REQUEST_FREE_TRIAL_REFRESH,
GM_SET_FREE_TRIAL_STATUS,
UI_HELP_TOP_5 = 91
};
template <>
struct magic_enum::customize::enum_range<eWorldMessageType> {
static constexpr int min = 0;
static constexpr int max = 91;
};
#endif //!__EWORLDMESSAGETYPE__H__

View File

@@ -102,6 +102,7 @@ DEFINE_TABLE_STORAGE(CDScriptComponentTable);
DEFINE_TABLE_STORAGE(CDSkillBehaviorTable); DEFINE_TABLE_STORAGE(CDSkillBehaviorTable);
DEFINE_TABLE_STORAGE(CDTamingBuildPuzzleTable); DEFINE_TABLE_STORAGE(CDTamingBuildPuzzleTable);
DEFINE_TABLE_STORAGE(CDVendorComponentTable); DEFINE_TABLE_STORAGE(CDVendorComponentTable);
DEFINE_TABLE_STORAGE(CDZoneTableTable);
void CDClientManager::LoadValuesFromDatabase() { void CDClientManager::LoadValuesFromDatabase() {
if (!CDClientDatabase::isConnected) { if (!CDClientDatabase::isConnected) {
@@ -148,7 +149,7 @@ void CDClientManager::LoadValuesFromDatabase() {
CDSkillBehaviorTable::Instance().LoadValuesFromDatabase(); CDSkillBehaviorTable::Instance().LoadValuesFromDatabase();
CDTamingBuildPuzzleTable::Instance().LoadValuesFromDatabase(); CDTamingBuildPuzzleTable::Instance().LoadValuesFromDatabase();
CDVendorComponentTable::Instance().LoadValuesFromDatabase(); CDVendorComponentTable::Instance().LoadValuesFromDatabase();
CDZoneTableTable::LoadValuesFromDatabase(); CDZoneTableTable::Instance().LoadValuesFromDatabase();
} }
void CDClientManager::LoadValuesFromDefaults() { void CDClientManager::LoadValuesFromDefaults() {

View File

@@ -1,38 +0,0 @@
#include "CDPlayerFlagsTable.h"
#include "CDClientDatabase.h"
namespace CDPlayerFlagsTable {
Table entries;
void ReadEntry(CppSQLite3Query& table) {
Entry entry;
entry.sessionOnly = table.getIntField("SessionOnly") == 1;
entry.onlySetByServer = table.getIntField("OnlySetByServer") == 1;
entry.sessionZoneOnly = table.getIntField("SessionZoneOnly") == 1;
entries[table.getIntField("id")] = entry;
}
void LoadValuesFromDatabase() {
auto table = CDClientDatabase::ExecuteQuery("SELECT * FROM PlayerFlags;");
if (!table.eof()) {
do {
ReadEntry(table);
} while (!table.nextRow());
}
}
const std::optional<Entry> GetEntry(const FlagId flagId) {
if (!entries.contains(flagId)) {
auto table = CDClientDatabase::CreatePreppedStmt("SELECT * FROM PlayerFlags WHERE id = ?;");
table.bind(1, static_cast<int>(flagId));
auto result = table.execQuery();
if (!result.eof()) {
ReadEntry(result);
}
}
return entries[flagId];
}
}

View File

@@ -1,21 +0,0 @@
#ifndef CDPLAYERFLAGSTABLE_H
#define CDPLAYERFLAGSTABLE_H
#include <map>
#include <optional>
namespace CDPlayerFlagsTable {
struct Entry {
bool sessionOnly{};
bool onlySetByServer{};
bool sessionZoneOnly{};
};
using FlagId = uint32_t;
using Table = std::map<FlagId, std::optional<Entry>>;
void LoadValuesFromDatabase();
const std::optional<Entry> GetEntry(const FlagId flagId);
};
#endif //!CDPLAYERFLAGSTABLE_H

View File

@@ -1,53 +1,67 @@
#include "CDZoneTableTable.h" #include "CDZoneTableTable.h"
namespace CDZoneTableTable { void CDZoneTableTable::LoadValuesFromDatabase() {
Table entries;
void LoadValuesFromDatabase() { // First, get the size of the table
// Get the data from the database uint32_t size = 0;
auto tableData = CDClientDatabase::ExecuteQuery("SELECT * FROM ZoneTable"); auto tableSize = CDClientDatabase::ExecuteQuery("SELECT COUNT(*) FROM ZoneTable");
while (!tableData.eof()) { while (!tableSize.eof()) {
CDZoneTable entry; size = tableSize.getIntField(0, 0);
entry.zoneID = tableData.getIntField("zoneID", -1);
entry.locStatus = tableData.getIntField("locStatus", -1);
entry.zoneName = tableData.getStringField("zoneName", "");
entry.scriptID = tableData.getIntField("scriptID", -1);
entry.ghostdistance_min = tableData.getFloatField("ghostdistance_min", -1.0f);
entry.ghostdistance = tableData.getFloatField("ghostdistance", -1.0f);
entry.population_soft_cap = tableData.getIntField("population_soft_cap", -1);
entry.population_hard_cap = tableData.getIntField("population_hard_cap", -1);
UNUSED(entry.DisplayDescription = tableData.getStringField("DisplayDescription", ""));
UNUSED(entry.mapFolder = tableData.getStringField("mapFolder", ""));
entry.smashableMinDistance = tableData.getFloatField("smashableMinDistance", -1.0f);
entry.smashableMaxDistance = tableData.getFloatField("smashableMaxDistance", -1.0f);
UNUSED(entry.mixerProgram = tableData.getStringField("mixerProgram", ""));
UNUSED(entry.clientPhysicsFramerate = tableData.getStringField("clientPhysicsFramerate", ""));
entry.serverPhysicsFramerate = tableData.getStringField("serverPhysicsFramerate", "");
entry.zoneControlTemplate = tableData.getIntField("zoneControlTemplate", -1);
entry.widthInChunks = tableData.getIntField("widthInChunks", -1);
entry.heightInChunks = tableData.getIntField("heightInChunks", -1);
entry.petsAllowed = tableData.getIntField("petsAllowed", -1) == 1 ? true : false;
entry.localize = tableData.getIntField("localize", -1) == 1 ? true : false;
entry.fZoneWeight = tableData.getFloatField("fZoneWeight", -1.0f);
UNUSED(entry.thumbnail = tableData.getStringField("thumbnail", ""));
entry.PlayerLoseCoinsOnDeath = tableData.getIntField("PlayerLoseCoinsOnDeath", -1) == 1 ? true : false;
entry.disableSaveLoc = tableData.getIntField("disableSaveLoc", -1) == 1 ? true : false;
entry.teamRadius = tableData.getFloatField("teamRadius", -1.0f);
UNUSED(entry.gate_version = tableData.getStringField("gate_version", ""));
entry.mountsAllowed = tableData.getIntField("mountsAllowed", -1) == 1 ? true : false;
entries[entry.zoneID] = entry; tableSize.nextRow();
tableData.nextRow();
}
} }
//! Queries the table with a zoneID to find. tableSize.finalize();
const CDZoneTable* Query(uint32_t zoneID) {
const auto& iter = entries.find(zoneID);
if (iter != entries.end()) {
return &iter->second;
}
return nullptr; // Now get the data
auto tableData = CDClientDatabase::ExecuteQuery("SELECT * FROM ZoneTable");
auto& entries = GetEntriesMutable();
while (!tableData.eof()) {
CDZoneTable entry;
entry.zoneID = tableData.getIntField("zoneID", -1);
entry.locStatus = tableData.getIntField("locStatus", -1);
entry.zoneName = tableData.getStringField("zoneName", "");
entry.scriptID = tableData.getIntField("scriptID", -1);
entry.ghostdistance_min = tableData.getFloatField("ghostdistance_min", -1.0f);
entry.ghostdistance = tableData.getFloatField("ghostdistance", -1.0f);
entry.population_soft_cap = tableData.getIntField("population_soft_cap", -1);
entry.population_hard_cap = tableData.getIntField("population_hard_cap", -1);
UNUSED(entry.DisplayDescription = tableData.getStringField("DisplayDescription", ""));
UNUSED(entry.mapFolder = tableData.getStringField("mapFolder", ""));
entry.smashableMinDistance = tableData.getFloatField("smashableMinDistance", -1.0f);
entry.smashableMaxDistance = tableData.getFloatField("smashableMaxDistance", -1.0f);
UNUSED(entry.mixerProgram = tableData.getStringField("mixerProgram", ""));
UNUSED(entry.clientPhysicsFramerate = tableData.getStringField("clientPhysicsFramerate", ""));
entry.serverPhysicsFramerate = tableData.getStringField("serverPhysicsFramerate", "");
entry.zoneControlTemplate = tableData.getIntField("zoneControlTemplate", -1);
entry.widthInChunks = tableData.getIntField("widthInChunks", -1);
entry.heightInChunks = tableData.getIntField("heightInChunks", -1);
entry.petsAllowed = tableData.getIntField("petsAllowed", -1) == 1 ? true : false;
entry.localize = tableData.getIntField("localize", -1) == 1 ? true : false;
entry.fZoneWeight = tableData.getFloatField("fZoneWeight", -1.0f);
UNUSED(entry.thumbnail = tableData.getStringField("thumbnail", ""));
entry.PlayerLoseCoinsOnDeath = tableData.getIntField("PlayerLoseCoinsOnDeath", -1) == 1 ? true : false;
entry.disableSaveLoc = tableData.getIntField("disableSaveLoc", -1) == 1 ? true : false;
entry.teamRadius = tableData.getFloatField("teamRadius", -1.0f);
UNUSED(entry.gate_version = tableData.getStringField("gate_version", ""));
entry.mountsAllowed = tableData.getIntField("mountsAllowed", -1) == 1 ? true : false;
entries.insert(std::make_pair(entry.zoneID, entry));
tableData.nextRow();
} }
tableData.finalize();
} }
//! Queries the table with a zoneID to find.
const CDZoneTable* CDZoneTableTable::Query(uint32_t zoneID) {
auto& m_Entries = GetEntries();
const auto& iter = m_Entries.find(zoneID);
if (iter != m_Entries.end()) {
return &iter->second;
}
return nullptr;
}

View File

@@ -33,8 +33,8 @@ struct CDZoneTable {
bool mountsAllowed; //!< Whether or not mounts are allowed bool mountsAllowed; //!< Whether or not mounts are allowed
}; };
namespace CDZoneTableTable { class CDZoneTableTable : public CDTable<CDZoneTableTable, std::map<uint32_t, CDZoneTable>> {
using Table = std::map<uint32_t, CDZoneTable>; public:
void LoadValuesFromDatabase(); void LoadValuesFromDatabase();
// Queries the table with a zoneID to find. // Queries the table with a zoneID to find.

View File

@@ -25,7 +25,6 @@ set(DDATABASE_CDCLIENTDATABASE_CDCLIENTTABLES_SOURCES "CDActivitiesTable.cpp"
"CDObjectsTable.cpp" "CDObjectsTable.cpp"
"CDPetComponentTable.cpp" "CDPetComponentTable.cpp"
"CDPackageComponentTable.cpp" "CDPackageComponentTable.cpp"
"CDPlayerFlagsTable.cpp"
"CDPhysicsComponentTable.cpp" "CDPhysicsComponentTable.cpp"
"CDPropertyEntranceComponentTable.cpp" "CDPropertyEntranceComponentTable.cpp"
"CDPropertyTemplateTable.cpp" "CDPropertyTemplateTable.cpp"

View File

@@ -1,13 +1,7 @@
add_subdirectory(CDClientDatabase) add_subdirectory(CDClientDatabase)
add_subdirectory(GameDatabase) add_subdirectory(GameDatabase)
add_library(dDatabase STATIC "MigrationRunner.cpp" "ModelNormalizeMigration.cpp") add_library(dDatabase STATIC "MigrationRunner.cpp")
add_custom_target(conncpp_dylib
${CMAKE_COMMAND} -E copy $<TARGET_FILE:MariaDB::ConnCpp> ${PROJECT_BINARY_DIR})
add_dependencies(dDatabase conncpp_dylib)
target_include_directories(dDatabase PUBLIC ".") target_include_directories(dDatabase PUBLIC ".")
target_link_libraries(dDatabase target_link_libraries(dDatabase
PUBLIC dDatabaseCDClient dDatabaseGame) PUBLIC dDatabaseCDClient dDatabaseGame)

View File

@@ -8,28 +8,15 @@ foreach(file ${DDATABSE_DATABSES_MYSQL_SOURCES})
set(DDATABASE_GAMEDATABASE_SOURCES ${DDATABASE_GAMEDATABASE_SOURCES} "MySQL/${file}") set(DDATABASE_GAMEDATABASE_SOURCES ${DDATABASE_GAMEDATABASE_SOURCES} "MySQL/${file}")
endforeach() endforeach()
add_subdirectory(SQLite)
foreach(file ${DDATABSE_DATABSES_SQLITE_SOURCES})
set(DDATABASE_GAMEDATABASE_SOURCES ${DDATABASE_GAMEDATABASE_SOURCES} "SQLite/${file}")
endforeach()
add_subdirectory(TestSQL)
foreach(file ${DDATABSE_DATABSES_TEST_SQL_SOURCES})
set(DDATABASE_GAMEDATABASE_SOURCES ${DDATABASE_GAMEDATABASE_SOURCES} "TestSQL/${file}")
endforeach()
add_library(dDatabaseGame STATIC ${DDATABASE_GAMEDATABASE_SOURCES}) add_library(dDatabaseGame STATIC ${DDATABASE_GAMEDATABASE_SOURCES})
target_include_directories(dDatabaseGame PUBLIC "." target_include_directories(dDatabaseGame PUBLIC "."
"ITables" PRIVATE "MySQL" "SQLite" "TestSQL" "ITables" PRIVATE "MySQL"
"${PROJECT_SOURCE_DIR}/dCommon" "${PROJECT_SOURCE_DIR}/dCommon"
"${PROJECT_SOURCE_DIR}/dCommon/dEnums" "${PROJECT_SOURCE_DIR}/dCommon/dEnums"
) )
target_link_libraries(dDatabaseGame target_link_libraries(dDatabaseGame
INTERFACE dCommon PUBLIC MariaDB::ConnCpp
PRIVATE sqlite3 MariaDB::ConnCpp) INTERFACE dCommon)
# Glob together all headers that need to be precompiled # Glob together all headers that need to be precompiled
file( file(

View File

@@ -2,12 +2,8 @@
#include "Game.h" #include "Game.h"
#include "dConfig.h" #include "dConfig.h"
#include "Logger.h" #include "Logger.h"
#include "DluAssert.h"
#include "SQLiteDatabase.h"
#include "MySQLDatabase.h" #include "MySQLDatabase.h"
#include "DluAssert.h"
#include <ranges>
#pragma warning (disable:4251) //Disables SQL warnings #pragma warning (disable:4251) //Disables SQL warnings
@@ -15,33 +11,13 @@ namespace {
GameDatabase* database = nullptr; GameDatabase* database = nullptr;
} }
std::string Database::GetMigrationFolder() {
const std::set<std::string> validMysqlTypes = { "mysql", "mariadb", "maria" };
auto databaseType = Game::config->GetValue("database_type");
std::ranges::transform(databaseType, databaseType.begin(), ::tolower);
if (databaseType == "sqlite") return "sqlite";
else if (validMysqlTypes.contains(databaseType)) return "mysql";
else {
LOG("No database specified, using MySQL");
return "mysql";
}
}
void Database::Connect() { void Database::Connect() {
if (database) { if (database) {
LOG("Tried to connect to database when it's already connected!"); LOG("Tried to connect to database when it's already connected!");
return; return;
} }
const auto databaseType = GetMigrationFolder(); database = new MySQLDatabase();
if (databaseType == "sqlite") database = new SQLiteDatabase();
else if (databaseType == "mysql") database = new MySQLDatabase();
else {
LOG("Invalid database type specified in config, using MySQL");
database = new MySQLDatabase();
}
database->Connect(); database->Connect();
} }
@@ -62,8 +38,3 @@ void Database::Destroy(std::string source) {
LOG("Trying to destroy database when it's not connected!"); LOG("Trying to destroy database when it's not connected!");
} }
} }
void Database::_setDatabase(GameDatabase* const db) {
if (database) delete database;
database = db;
}

View File

@@ -1,6 +1,7 @@
#pragma once #pragma once
#include <string> #include <string>
#include <conncpp.hpp>
#include "GameDatabase.h" #include "GameDatabase.h"
@@ -8,10 +9,4 @@ namespace Database {
void Connect(); void Connect();
GameDatabase* Get(); GameDatabase* Get();
void Destroy(std::string source = ""); void Destroy(std::string source = "");
// Used for assigning a test database as the handler for database logic.
// Do not use in production code.
void _setDatabase(GameDatabase* const db);
std::string GetMigrationFolder();
}; };

View File

@@ -23,11 +23,14 @@
#include "IActivityLog.h" #include "IActivityLog.h"
#include "IIgnoreList.h" #include "IIgnoreList.h"
#include "IAccountsRewardCodes.h" #include "IAccountsRewardCodes.h"
#include "IBehaviors.h"
#include "IUgcModularBuild.h" namespace sql {
class Statement;
class PreparedStatement;
};
#ifdef _DEBUG #ifdef _DEBUG
# define DLU_SQL_TRY_CATCH_RETHROW(x) do { try { x; } catch (std::exception& ex) { LOG("SQL Error: %s", ex.what()); throw; } } while(0) # define DLU_SQL_TRY_CATCH_RETHROW(x) do { try { x; } catch (sql::SQLException& ex) { LOG("SQL Error: %s", ex.what()); throw; } } while(0)
#else #else
# define DLU_SQL_TRY_CATCH_RETHROW(x) x # define DLU_SQL_TRY_CATCH_RETHROW(x) x
#endif // _DEBUG #endif // _DEBUG
@@ -37,14 +40,14 @@ class GameDatabase :
public IMail, public ICommandLog, public IPlayerCheatDetections, public IBugReports, public IMail, public ICommandLog, public IPlayerCheatDetections, public IBugReports,
public IPropertyContents, public IProperty, public IPetNames, public ICharXml, public IPropertyContents, public IProperty, public IPetNames, public ICharXml,
public IMigrationHistory, public IUgc, public IFriends, public ICharInfo, public IMigrationHistory, public IUgc, public IFriends, public ICharInfo,
public IAccounts, public IActivityLog, public IAccountsRewardCodes, public IIgnoreList, public IAccounts, public IActivityLog, public IAccountsRewardCodes, public IIgnoreList {
public IBehaviors, public IUgcModularBuild {
public: public:
virtual ~GameDatabase() = default; virtual ~GameDatabase() = default;
// TODO: These should be made private. // TODO: These should be made private.
virtual void Connect() = 0; virtual void Connect() = 0;
virtual void Destroy(std::string source = "") = 0; virtual void Destroy(std::string source = "") = 0;
virtual void ExecuteCustomQuery(const std::string_view query) = 0; virtual void ExecuteCustomQuery(const std::string_view query) = 0;
virtual sql::PreparedStatement* CreatePreppedStmt(const std::string& query) = 0;
virtual void Commit() = 0; virtual void Commit() = 0;
virtual bool GetAutoCommit() = 0; virtual bool GetAutoCommit() = 0;
virtual void SetAutoCommit(bool value) = 0; virtual void SetAutoCommit(bool value) = 0;

View File

@@ -33,11 +33,6 @@ public:
// Add a new account to the database. // Add a new account to the database.
virtual void InsertNewAccount(const std::string_view username, const std::string_view bcryptpassword) = 0; virtual void InsertNewAccount(const std::string_view username, const std::string_view bcryptpassword) = 0;
// Update the GameMaster level of an account.
virtual void UpdateAccountGmLevel(const uint32_t accountId, const eGameMasterLevel gmLevel) = 0;
virtual uint32_t GetAccountCount() = 0;
}; };
#endif //!__IACCOUNTS__H__ #endif //!__IACCOUNTS__H__

Some files were not shown because too many files have changed in this diff Show More