Compare commits

...

117 Commits

Author SHA1 Message Date
David Markowitz
2804dc3ec2 fix: make include paths consistent (#1390)
* fix: bad header includes

tests pass

* fix-up more include paths
2024-01-05 06:33:52 -06:00
David Markowitz
870b56fe02 chore: cleanup objectIdManager overloading and classes (#1391)
* objectIdManager fixes

* Remove debug log
2024-01-05 06:31:22 -06:00
Gie "Max" Vanommeslaeghe
66ac5a1b7a Merge pull request #1388 from DarkflameUniverse/fix--make-all-settings-have-sane-defaults-where-possible
fix: don't crash if some configs values aren't present
2024-01-05 11:34:50 +01:00
321d354e96 fix: don't crash if some configs aren't present
remove not starting if ini's aren't present since everything can run from env vars now
2024-01-04 18:21:03 -06:00
72b69c7899 fix: dont crash if resServer doesn't exist (#1386)
and try to make it as well
2024-01-03 22:18:27 -06:00
Daniel Seiler
d283bbd1c4 fix: split apart big Cmake libraries (#1381)
* fix: split apart big Cmake libraries

* fix: formatting

* fix: newline

* fix: add quotes
2024-01-03 18:41:46 -06:00
David Markowitz
10baa98f00 Update build-and-push-docker.yml (#1385)
change name to be caps like the other
2024-01-03 14:41:35 +01:00
David Markowitz
c7c84c21ef feat: partially functioning property behavior ui (no saving or loading yet) (#1384)
* Add addstrip handling

add SendBehaviorBlocksToClient serialization
add id generation and auto updating
add behaviorlisttoclient serialization

* fix crash

happened if you added state 0 and 6 and nothing in between

* Section off code

Use proper encapsulation to hide code away and only let specific objects do certain jobs.

* Organize serialization

Section off into operational chunks
Write data at the level most appropriate

* Remove and simplify BlockDefinitions

Remove pointer usage for BlockDefinitions and move to optional.

* ControlBehaviors: Add addaction handling

* re-organization

remove const from return value
change to int from uint
use generic methods to reduce code clutter

* add strip ui position handling

* add split strip functionality

* fix issues

fix an issue where if you were on an empty state, the server would allow you to remain on that state
fix an issue where the ui would not open on the previously opened state
fix an issue where deleting strips in order caused the wrong strips to be deleted

* update how you remove behaviors from models

* Add remove actions and rename

* migrate actions

* update action and rearrange strip

* merge strips

* add and move to inventory

* Remove dead code

* simplify code

* nits and move finish MoveToInventory

constify serialize

further include path fixes

use const, comments

fix amf message

Update ModelComponent.cpp

replace operator subscript with at

* Update ModelComponent.cpp

* Update MigrateActionsMessage.h

* const

* Move to separate translation units

* include amf3

its precompiled, but just in case
2024-01-03 07:34:38 -06:00
David Markowitz
09fb1dfff9 Update docker-compose.yml (#1383) 2024-01-03 06:00:49 -06:00
9116317834 fix: improve our docker config (#1373)
* greatly simplify docker

* back to gcc since I can't the libs to load properly

* fix typo
add set -e to entrypoint
better copy of entrypoint.sh and use proper entrypoint

* use debian instead of gcc for runtime
comment and organize it a bit
drop gcc to 12 since we are using debian 12 as well

* explicitly include mariadb libs

* Make the server not crash in the case we are using only env-vars
make the dockerfile have configs in the expected location incase of bypassing entrypoint.sh

* remove unneede var from example, since it's in the container now

* coments to dockerfile

* Revert master server changes

* Resolve conflicting port options between chat, master, and world
move chat_server_port to shared since it's used by world and chat

* Don't error if file does not exists when updating a config option
move update before and use bin dir var
2024-01-03 02:36:17 -06:00
David Markowitz
a84ca1f00d crash and log fix (#1382) 2024-01-03 06:52:11 +00:00
David Markowitz
bb79528c0e fix: nullptr access for logger in master (#1380)
* fix nullptr access for logger

* fix nullptr access for logger

fix no save on crash

* Update MasterServer.cpp
2024-01-02 18:28:17 -06:00
David Markowitz
b1134b340f Add config update function (#1379)
Update CMakeLists.txt

FINALLY

dont ignore cmake module directory

move to separate file

very cool feature
tested that this still works

Co-authored-by: Aaron Kimbrell <aronwk.aaron@gmail.com>
2024-01-02 18:25:57 -06:00
David Markowitz
1941679d27 feat: Bump standard to 20 (#1376)
* Bump to 20

* fix warnings and errors

* thanks RakNet
2024-01-02 07:53:00 +00:00
Daniel Seiler
85672e060a fix: signal handling (#1375)
* fix: signal handling

* fix: flush WorldServer logger before main loop

* fix: consolidate signal code
2024-01-01 21:50:00 -06:00
Daniel Seiler
18feea5fed fix: optional party phrases (#1377)
* fix: optional party phrases

Don't return early if there are no party phrases

* Update VanityUtilities.cpp
2024-01-01 17:08:38 -06:00
David Markowitz
e54faa3820 chore: organize build flags (#1371)
* chore: organize build flags

* Remove ambiguous include path

Don't be default incluyde bcrypt so you need to specify the folder.  Allows pre-processor to find the correct file.

* Revert settings

* working

f
2023-12-31 00:26:49 -06:00
David Markowitz
4ecb6ae30e fix: joining lobby twice (#1374) 2023-12-31 00:14:58 -06:00
Gie "Max" Vanommeslaeghe
c708246f73 Merge pull request #1247 from maxdelayer/main
Fix edge case where leaderboard viewing would cause unhandled exception
2023-12-31 00:43:37 +01:00
Gie "Max" Vanommeslaeghe
295ba628c2 Merge pull request #1372 from DarkflameUniverse/feature/update-connector
fix: bump connector version
2023-12-31 00:32:52 +01:00
Xiphoseer
d8f74f008f fix: bump connector version 2023-12-31 00:22:39 +01:00
Daniel Seiler
42a71bbeab feat: add DLU_CONFIG_DIR env var (#1370)
* feat: add DLU_CONFIG_DIR env var

* fix: PascalCase
2023-12-30 07:07:49 -06:00
Daniel Seiler
98d2f25af2 feat: get & print std::current_exception (#1366) 2023-12-30 08:04:26 +01:00
Daniel Seiler
dd9d94f75f feat: allow env var override for game config (#1367) 2023-12-30 08:04:09 +01:00
Daniel Seiler
f08df25085 feat: split out system() calls from the rest of MasterServer (#1368) 2023-12-30 08:00:43 +01:00
1bdce8384f fix: LUP Exhibit component type and use delta compression on serialization (#1365)
* fix: component type and use delta compression on serialization

* sorry jett, emo made me do it
2023-12-28 22:24:45 -06:00
15954413ae chore: rename rebuild to quickbuild (#1364)
* rename rebuild to quickbuild

* fix includes
2023-12-28 22:24:30 -06:00
fddf99946f chore: make scripted activity derived from activity (#1363)
* chore: make scripted activity derived from activity
this paves the way for other component being properly derived from the activity component

* use a dirty flag instead

* address feedback
2023-12-28 19:11:19 -06:00
jadebenn
ef6f2f133e chore: Eradicate C-style casts and further clean up some code (#1361)
* cast and code cleanup

* cast cleanup

* bug fixes and improvements

* no getBoolField method exists

* fixes

* unbroke sg cannon scoring

* removing comments

* Remove the c-style cast warning I added from CMakeLists now that they're gone (it triggers on 3rd party dependencies and slows down compilation)

* (Hopefully) fix MacOS compilation error

* partially-implemented feedback

* more updates to account for feedback

* change bool default

---------

Co-authored-by: jadebenn <jonahebenn@yahoo.com>
2023-12-27 22:18:20 -06:00
David Markowitz
46ac039a3b fix: mission offering (#1359)
fixes an issue where NPCs would offer the incorrect missions which caused odd blocks.  Consolidated logic for mission offering and removed redundant code.
2023-12-26 15:45:10 -08:00
David Markowitz
ffa2f9986c AssetManager: fix deleting no file (#1358) 2023-12-25 08:48:57 -06:00
Jett
d2d67384a7 fix: update trigger loading code after typo (#1357) 2023-12-24 21:45:27 +01:00
David Markowitz
dbf37c2d2b fix: more than 1 zone control object causing issues with scripted minigames (#1355)
* fix: more than 1 zone control object

Fixes an issue when players cannot load into Avant Gardens Survival and other script based minigames with more than 1 zone control object in the level files.  (why are there 26 zone control objects in BoNS and AGS)

* Update Level.cpp
2023-12-24 11:17:04 -06:00
David Markowitz
81dc4e2216 fix: remove cancelOnLogout handling (#1354) 2023-12-24 08:01:47 -06:00
jadebenn
6de224a2fa chore: miscellaneous code, formatting, and syntax cleanup (#1351)
* miscellaneous code, formatting, and syntax cleanup

* update

* update again

* updated to account for feedback
2023-12-23 18:11:00 -06:00
David Markowitz
5e9355b1ff fix: shooting gallery bugs (#1347)
fix: reset some more vars

Co-authored-by: Aaron Kimbre <aronwk.aaron@gmail.com>
2023-12-23 17:50:14 +00:00
David Markowitz
6b9798595e fix: leavezone command (#1345)
Players do not need access to leave Return to the Venture Explorer (rttve) via a slash command.  They can leave through the usual zone method.

Tested that you cannot leavezone in rttve
2023-12-23 11:28:32 -06:00
David Markowitz
e58218cfbc chore: Speed up and cleanup level and zone loading; Add safer asset buffer reading (#1314)
* Remove std::couts littered throughout the base

* working

End of optimizations for now

going faster

* Remove extraneous compare function

std::less<LWOSCENEID> already does this in a map.

* gaming

* Update Zone.cpp

* dlu is moving to bitbucket again

* Update Level.cpp

---------

Co-authored-by: Jettford <mrjettbradford@gmail.com>
2023-12-23 11:24:16 -06:00
jadebenn
fcf4d6c6fa feat: Improve console output to show packet enum names (magic_enum) (#1344)
* add enum stringification functionality from third party source

* squashed commit

* Macros: Add test and improve speed

Space macros out
utilize cache locality
ensure no lost functionality

* moved stringify code to dCommon

* Rename #defines in stringify enum tests

* Revert "moved stringify code to dCommon"

This reverts commit 33fa5f8d2f.

* improve macro functionality

change function handle

formatting and function definition tweaks

* typo fixes

* moved code to dCommon/dEnums and tests to dCommonTests/dEnumsTests

* initial magic_enums alternate implementation of enum stringification

* deleted unused tests

* reverted compile flag oopsy and fixed output types

* fixed testing suite

* test formatting improvement

* formatting again :(

* added gm string to "aborting gm!" message

* Push my suggestion for CI tests.

* updated magic enum test

* fix test variable type

* added gm test

* making sure magic_enum is on a release branch

* tidying up console outputs

* re-implemented enum array access for performance

* now it is bugged :(

* nvm, working

* helping out the snowflake compilers

* changed return type too

* optimization too

* formatting too I guess because why not

* being even more painfully specific

* Update WorldServer.cpp to match emo's feedback

* Update MagicEnumTests.cpp to use srand(time(NULL))

* Update eGameMessageType.h - formatting

* Trying to fix the crash but can't actually compile the code to check on my own rn

* Update WorldServer.cpp - third try at this

* Update MagicEnumTests.cpp - use better macro definitions

* Update MagicEnumTests.cpp - c string comparison fix

* addressing all but the cmake feedback

* fixed cmake to the best of my very limited ability

* added tests to verify magic enum arrays are pre-sorted

* updated

---------

Co-authored-by: David Markowitz <EmosewaMC@gmail.com>
Co-authored-by: Jettford <mrjettbradford@gmail.com>
2023-12-23 10:51:59 -06:00
David Markowitz
c1e8546d48 fix: add Nexus Tower missing scripts (#1349)
add final missing scripts for nt

also fix the turnin for the breadcrumb missions not showing the completion window.

Fix another missing script

Add another script

fix include guards

Fix dirt clouds not appearing on mission accept
2023-12-22 23:53:21 -08:00
jadebenn
c07c909a57 fix: Character access after deletion (#1348)
* assorted skill debug plus crash fix

* removed uncleaned debug
2023-12-21 22:12:52 -06:00
42ffd00478 feat: implement handling vendorStatusRequest (#1346)
this fixes the crafting menu on johny umami not being there on the initial internaction
2023-12-20 22:25:21 -06:00
jadebenn
799269c79e fix: Using 'leave-zone' in Return to the Venture Explorer allows players to return to the original Venture Explorer map (#1341)
* fixed VE zone bug and added config option to renable it

* add zero back to LWOMAPID initialization

* typo

* removed config option to enable bug
2023-12-20 15:12:01 -06:00
jadebenn
e2391665b9 imagination costs that equal your capacity no longer abort qbs (#1338) 2023-12-16 19:35:16 -06:00
David Markowitz
24c2361248 fix: achievements counting twice when accepted (#1337)
Tested that new achievements progress exactly once
Tested that already accepted achievements progress as expeected
Tested that tiered achievements only count the progress to the current tier and not the next one as well

Update MissionComponent.cpp
2023-12-16 12:27:14 -06:00
David Markowitz
511672c5cb fix: general crashes (#1336)
* Fix crashes

fix crash with chat filter
fix ldf_config being empty in database on windows debug

* WorldServer: Fix further crashes on windows

address multi threaded signal handling on worldservers
Remove iterator invalidation code in zone manager
2023-12-16 12:27:06 -06:00
Gie "Max" Vanommeslaeghe
c0b969e3f0 Merge pull request #1333 from DarkflameUniverse/chore-rename-zonecontrol-to-minigamecontrol
chore: rename zonecontrolcomp to minigamecontrolcomp
2023-12-06 14:39:21 +01:00
1f399a7277 chore: rename zonecontrolcomp to minigamecontrolcomp 2023-12-04 20:59:08 -06:00
3d85f6639e chore: rename VehiclePhysics to HavokVehiclePhysics (#1331)
since that is it's true name
and there's another VehiclePhysics component that is simply named that
2023-12-04 09:20:41 -06:00
David Markowitz
731b828c12 properties: fix visiting multiple owned by same person (#1325) 2023-12-01 10:13:01 -06:00
David Markowitz
eca87c7257 fix: Buff FX not playing and general BuffComponent improvements (#1296)
* fix: Buff FX not playing

Fixes an issue where buff effects would not play at all.

Tested that frakjaw and maelstrom dagger now play their respective effects when you are effected by them

fix: buffs general improvements

add new arguments

* Remove duplicated code

* fix times and remove buff
2023-12-01 10:12:48 -06:00
David Markowitz
0b9dbaedbf Add HailStorms guide to README (#1329) 2023-11-30 16:11:45 -08:00
7de07a7722 fix: use defaults when getting variables for jetpack behavior (#1328)
Tested that the hover jetpack now works and that the normal jetpack still works
2023-11-30 02:21:18 -08:00
David Markowitz
62a1e135c3 Fix backwards name check (#1327)
Untested.  Fixes not being able to rename a character at character create
2023-11-29 00:25:06 -08:00
Gie "Max" Vanommeslaeghe
de0b560a8c Merge pull request #1326 from DarkflameUniverse/use-more-tacarc-vars
fix: tacarc not using offset or checking upper/lower bounds
2023-11-28 22:44:00 +01:00
e8a0f50ec9 use std::abs 2023-11-27 22:18:27 -06:00
b3564cb9ea fix: tacarc not using offset or checking upper/lower bounds 2023-11-27 22:16:31 -06:00
jadebenn
3df3552467 fix: respawn rotation (#1323)
* fixed respawn rotation

* even though the condensed calls work, splitting em
2023-11-26 14:30:39 -08:00
David Markowitz
a1f8ab763d Entity: Fix death behavior of 0 (#1319) 2023-11-25 22:50:28 -06:00
David Markowitz
0c32be01ba fix: nexus tower smashable (#1320) 2023-11-25 22:50:20 -06:00
David Markowitz
8a15906885 fix: Racing end of race leaderboards now work (#1317)
* Fix UB in remote input info

Yes i should have made this first
no this wouldnt have happened with rust

* fix end of race leaderboard

Tested that with two players, both players see the others time at the end of the race and all other metrics are shown correctly.

Technically the outBitStream->Write(static_cast<uint16_t>(m_RacingPlayers.size())); should only be written once but how we do it now it is written as we load players in and this is the cheap option compared to the number of bits we are supposed to waste at the end of races
2023-11-24 01:46:26 -08:00
Gie "Max" Vanommeslaeghe
af70f871cb Merge pull request #1307 from DarkflameUniverse/985
feat: Implement ignore list
2023-11-24 00:38:03 +01:00
David Markowitz
fd20baaf09 Fix UB in remote input info (#1316)
Yes i should have made this first
no this wouldnt have happened with rust
2023-11-23 05:48:06 -06:00
jadebenn
0217f88c44 pet emote fix (#1315) 2023-11-22 08:06:09 -06:00
David Markowitz
198b3371c5 chore: Update old character create code (#1291)
* create new character fixes

Don't use persistentIds, guarantee ids are unique by using do while to generate the id

* Update queries to actually use prep stmt

* Update UserManager.cpp

* Update UserManager.cpp
2023-11-21 18:14:30 -08:00
David Markowitz
4e5facd0c5 Merge branch 'main' into 985 2023-11-21 18:11:46 -08:00
David Markowitz
c931f5d456 Merge branch 'main' into 985 2023-11-21 18:10:45 -08:00
df83f0d847 feat: Reward codes (#1308)
* feat: reward codes
this is for giving rewards across characters as the did in live.
Tested that the default config works
Tested that all claim codes work
Tested that saving and loading claim codes work
Tested that mail sends correctly

* newlines

* include array

* delete cascade

* newline

* address feedback
2023-11-21 18:05:15 -08:00
David Markowitz
9c5388c70e feat: Add ability to toggle skipping of cinematics (#1312)
* Cinematics: Add ability to toggle skipping them

* Add docs

* Move sections
2023-11-21 18:04:44 -08:00
David Markowitz
1a199151da Remove std::couts littered throughout the base (#1313) 2023-11-21 18:04:23 -08:00
David Markowitz
98dc291b57 fix gating (#1306) 2023-11-19 12:01:34 -06:00
David Markowitz
1001e41528 simplify code path 2023-11-18 20:33:30 -08:00
David Markowitz
59bf91b14f fix up enum names
Update ChatIgnoreList.cpp

Add extra check for size on adding

Cant happen without hacks, but worth checking anyways

Update ChatIgnoreList.cpp
2023-11-18 20:21:13 -08:00
David Markowitz
50ad2a29ec Merge branch 'lol' into 985 2023-11-18 19:16:56 -08:00
David Markowitz
81b1b73f04 fix gating 2023-11-18 19:15:57 -08:00
David Markowitz
12ea2dfb2e Works for online players now 2023-11-18 18:37:06 -08:00
David Markowitz
d193fe61be Database working for ignores 2023-11-18 18:05:26 -08:00
David Markowitz
511407c8ea Removing ignore works now 2023-11-18 17:33:52 -08:00
David Markowitz
df3515f474 IgnoreList: Add and Populate fully working 2023-11-18 03:55:12 -08:00
8e84cafdfa feat: add configurable feature and versions (#1298)
* feat: add configurable feature and versions
to allow for easily swithing it out to enable features in the client for funsies
tested that this doesn't break anything and added test

* cleanup
2023-11-18 03:33:23 -06:00
David Markowitz
57e3a4f4ef fix: general issues with dismantling (#1304) 2023-11-18 01:15:47 -06:00
David Markowitz
98822d400f fix: ChatServer crash on startup (#1303) 2023-11-18 01:15:31 -06:00
David Markowitz
7f623d358c refactor: Database abstraction and organization of files (#1274)
* Database: Convert to proper namespace

* Database: Use base class and getter

* Database: Move files around

* Database: Add property Management query

Database: Move over user queries

Tested at gm 0 that pre-approved names are pre-approved, unapproved need moderator approval
deleting characters deletes the selcted one
refreshing the character page shows the last character you logged in as
tested all my characters show up when i login
tested that you can delete all 4 characters and the correct character is selected each time
tested renaming, approving names as gm0

Database: Add ugc model getter

Hey it works, look I got around the mariadb issue.

Database: Add queries

Database: consolidate name query

Database: Add friends list query

Update name of approved names query

Documentation

Database: Add name check

Database: Add BFF Query

Database: Move BFF Setter

Database: Move new friend query

Database: Add remove friend queries

Database: Add activity log

Database: Add ugc & prop content removal

Database: Add model update

Database: Add migration queries

Database: Add character and xml queries

Database: Add user queries

Untested, but compiling code

Need to test that new character names are properly assigned in the following scenarios
gm 0 and pre-approved name
gm 0 and unapproved name
gm 9 and pre-approved name
gm 9 and unapproved name

Database: constify function arguments

Database: Add pet queries

* Database: Move property model queries

Untested.  Need to test
placing a new model
moving existing one
removing ugc model
placing ugc model
moving ugc model(?)
changing privacy option variously
change description and name
approve property
can properly travel to property

* Property: Move stale reference deletion

* Database: Move performance update query

* Database: Add bug report query

* Database: Add cheat detection query

* Database: Add mail send query

* Untested code

need to test mailing from slash command, from all users of SendMail, getting bbb of a property and sending messages to bffs

* Update CDComponentsRegistryTable.h

Database: Rename and add further comments

Datavbase: Add comments

Add some comments

Build: Fix PCH directories

Database: Fix time

thanks apple

Database: Fix compiler warnings

Overload destructor
Define specialty for time_t
Use string instead of string_view for temp empty string

Update CDTable.h

Property: Update queries to use mapId

Database: Reorganize

Reorganize into CDClient folder and GameDatabase folder for clearer meanings and file structure

Folders: Rename to GameDatabase

MySQL: Remove MySQL Specifier from table

Database: Move Tables to Interfaces

Database: Reorder functions in header

Database: Simplify property queries

Database: Remove unused queries

Remove extra query definitions as well

Database: Consolidate User getters

Database: Comment logs

Update MySQLDatabase.cpp

Database: Use generic code

Playkey: Fix bad optional access

Database: Move stuff around

WorldServer: Update queries

Ugc reduced by many scopes
use new queries
very fast
tested that ugc still loads

Database: Add auth queries

I tested that only the correct password can sign into an account.
Tested that disabled playkeys do not allow the user to play the game

Database: Add donation query

Database: add objectId queries

Database: Add master queries

Database: Fix mis-named function

Database: Add slash command queries

Mail: Fix itemId type

CharFilter: Use new query

ObjectID: Remove duplicate code

SlashCommand: Update query with function

Database: Add mail queries

Ugc: Fix issues with saving models

Resolve large scope blocks as well

* Database: Add debug try catch rethrow macro

* General fixes

* fix play key not working

* Further fixes

---------

Co-authored-by: Aaron Kimbre <aronwk.aaron@gmail.com>
2023-11-17 18:47:18 -06:00
b68823b4cb fix: properly check friend list limits (#1300)
* fix: properly check friend list limits
added a config for friend list limit for the brave that want to mod the client to sanely go over 50
moved the best friend limit config to chatconfig.ini where it should be
cleanup loading these configs options a bit

Tested that the BFF limit works and that the new friend limit works as well

* fix typo

* fix member variable naming
2023-11-17 18:44:48 -06:00
jadebenn
7bbe5ffc39 Fix falling from Spider Cave not smashing you (#1302) 2023-11-17 18:43:01 -06:00
David Markowitz
0ddd20e2b5 fix: ugc Save rocket and car modular assembly data to database (#1279)
* Ugc: Use persistent Id for rocekts and cars

* Remove comment

* Ugc: Save rocket and car IDs to the database

* Correct names

* Database: formatting
2023-11-15 19:32:30 -06:00
David Markowitz
f59ca8b1da fix: Ugc model pickup (#1275)
* Ugc: Make it so we dont bin the model

Users must dismantle the model as opposed to accidentally picking it up in model mode

* Fix editing model in brick mode

* PropEntrance: Remove debug log
2023-11-15 19:32:17 -06:00
David Markowitz
a44f998bd1 Navmesh: Update Avant Gardens (#1288) 2023-11-15 19:30:59 -06:00
78d8c57752 feat: make the help menu top 5 issues work and configurable (#1293)
* feat: make make the help menu questions work

* address feedback

* typo

* update defaults

* fix and address feedback

* newline
2023-11-15 19:30:46 -06:00
David Markowitz
8b270ca97a fix: Dismantling basically being O(n!) (#1295) 2023-11-15 19:29:53 -06:00
David Markowitz
59303a232e Slash commands: Update compares (#1270)
use case insensitive compare
use character name for kill instead of account name

Use correct Id for muting when a player is found
2023-11-15 19:29:00 -06:00
8cd5bf7b8d feat: implement consume item behavior (#1098)
* feature: implement consume item behavior

* Cleanup

* tested with skill 456 and fixed some things

* remove logs
2023-11-14 19:38:52 -06:00
8a9883c224 feat: use more zoneTable options (#1273)
* feat: use more zoneTable options

Allow setting framrate for the zone
Allow setting if pets are allowed in the zone
Allow setting if mounts are allowed in a zone
Allow disabling saving location to a zone

* address feedback
2023-11-14 07:02:17 -06:00
Gie "Max" Vanommeslaeghe
79ff6e7ee4 Merge pull request #1292 from DarkflameUniverse/fix-dismantling
fix: Models leak
2023-11-14 13:38:32 +01:00
Gie "Max" Vanommeslaeghe
4167d98667 Merge pull request #1272 from DarkflameUniverse/vanity-reload-and-spawners
feat: Vanity reload and vanity spawners
2023-11-14 13:34:14 +01:00
David Markowitz
23c5d13151 Models: Fix leak 2023-11-13 22:51:45 -08:00
jadebenn
68e5737b74 Fix: Pets can no longer dig treasure without completing Bella Pepper's "Lost Tags" mission (#1287)
* Added mission check to pet digs

* Little formatting change

* More formatting clean-up
2023-11-13 02:41:27 -08:00
jadebenn
411dce7457 Adding damage cooldown/"invincibility frames" as in Live (#1276)
* Added cooldown handling

* Made most of the logs hidden outside of debug mode

* removed weird submodule

* kill this phantom submodule

* updated to reflect reviewed feedback

* Added IsCooldownImmune() method to DestroyableComponent

* friggin typo

* Implemented non-pending changes and added cooldown immunity functions to DestroyableComponentTests

* add trailing linebreak

* another typo :(

* flipped cooldown test order (not leaving immune)

* Clean up comment and add DestroyableComponent test
2023-11-12 05:53:03 -06:00
175b354e68 address feedback 2023-11-10 22:01:25 -06:00
4c3949e5d8 remove logs 2023-11-09 21:29:29 -06:00
f727e3951c consolidate code 2023-11-09 21:28:52 -06:00
4867136133 fix spawners and cleanup 2023-11-09 18:59:43 -06:00
6e07798023 try to kill spawners, they don't exist? 2023-11-09 18:44:51 -06:00
3dc7b6ef7f add spawner handeling and reload to deleted exsiting entities
spawned don't get deleted yet
2023-11-09 18:33:39 -06:00
Nathan Ogden
2c9a98313a chore: Notes for running as system service (#1252)
* Note for running as system service
Note for running as system service

* Additional detailing of linux service.

* Added darkflame.service file

changed readme to reference new file
2023-11-08 19:15:46 -08:00
David Markowitz
d1dc9f5403 fix: add Onedrive log message (#1269)
* fix: Onedrive log message

* Update README.md
2023-11-08 17:56:59 -08:00
David Markowitz
52b5994b98 Ugc: Add subkey for rockets and cars (#1266)
Tested that, if a user has followed the guide and turned UGCUSE3DSERVICES from 1 to 0, the do not get booted to login for having a rocket with a subkey in their inventory.

Add bouncer logic

Ugc: Use random Id
2023-11-08 12:18:02 -06:00
Gie "Max" Vanommeslaeghe
4c10faa852 Merge pull request #1265 from DarkflameUniverse/ugc_patch
fix: Ugc Remove async and second id usage
2023-11-07 19:09:37 +01:00
Gie "Max" Vanommeslaeghe
070d4a1fa8 Merge pull request #1267 from DarkflameUniverse/dummyfix
fix: Nexus Tower Combat Challenge exploding dummy
2023-11-07 19:03:12 +01:00
David Markowitz
6795dd189c CombatChallenge: Fix exploding dummy 2023-11-06 14:49:53 -08:00
David Markowitz
f40fce7711 Ugc: Remove async and second id usage 2023-11-06 01:41:28 -08:00
David Markowitz
797abb176a ChatFilter: Fix incorrect segment highlighting (#1255) 2023-11-05 17:19:26 -06:00
David Markowitz
0f9e951162 Database: Use null for accounts (#1253) 2023-11-05 01:00:31 -08:00
David Markowitz
cea0b98edf WorldServer: Fix crash from deleting last char (#1254)
Revert "WorldServer: Fix crash from deleting last char"

This reverts commit d9adafa1fef0ac88ed2b3b8ca6b97c2421a603e2.

Update WorldServer.cpp
2023-11-05 01:00:19 -08:00
David Markowitz
1e2d5605eb fix: Navmesh updates to Frostburgh and Forbidden Valley (#1251)
* Navmesh: Add Frostburgh mesh

* Navmesh: Add Frostburgh mesh

Also edit Forbidden Valley mesh
2023-11-04 10:42:39 -05:00
David Markowitz
32cf111810 Script: Fix incorrect kill method (#1248) 2023-11-04 10:42:28 -05:00
Max
a19bead268 Fix edge case where leaderboard viewing would cause unhandled exception 2023-10-29 22:40:06 -04:00
571 changed files with 10113 additions and 7810 deletions

View File

@@ -3,8 +3,8 @@ Dockerfile
*.md
logo.png
versions.txt
build.sh
docker-compose.yml
.env
docker/__pycache__
.env.example
.env.example
build

View File

@@ -0,0 +1,56 @@
name: CI
on:
push:
branches:
- "main"
tags:
- "v*.*.*"
pull_request:
branches:
- "main"
env:
REGISTRY: ghcr.io
IMAGE_NAME: ${{ github.repository }}
jobs:
build-and-push-image:
runs-on: ubuntu-latest
permissions:
contents: read
packages: write
steps:
- name: Checkout repository
uses: actions/checkout@v4
with:
submodules: recursive
- name: Log in to the Container registry
uses: docker/login-action@v3
with:
registry: ${{ env.REGISTRY }}
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Extract metadata (tags, labels) for Docker
id: meta
uses: docker/metadata-action@v5
with:
images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
# generate Docker tags based on the following events/attributes
tags: |
type=ref,event=pr
type=raw,value=latest,enable=${{ github.ref == format('refs/heads/{0}', 'main') }}
type=semver,pattern={{version}}
type=semver,pattern={{major}}.{{minor}}
type=semver,pattern={{major}}
- name: Build and push Docker image
uses: docker/build-push-action@v5
with:
context: .
push: ${{ github.event_name != 'pull_request' }}
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}

1
.gitignore vendored
View File

@@ -122,3 +122,4 @@ docker/__pycache__
docker-compose.override.yml
!*Test.bin
!cmake/*

6
.gitmodules vendored
View File

@@ -14,6 +14,6 @@
path = thirdparty/mariadb-connector-cpp
url = https://github.com/mariadb-corporation/mariadb-connector-cpp.git
ignore = dirty
[submodule "thirdparty/AccountManager"]
path = thirdparty/AccountManager
url = https://github.com/DarkflameUniverse/AccountManager
[submodule "thirdparty/magic_enum"]
path = thirdparty/magic_enum
url = https://github.com/Neargye/magic_enum.git

View File

@@ -2,7 +2,8 @@ cmake_minimum_required(VERSION 3.18)
project(Darkflame)
include(CTest)
set (CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD 20)
set(CMAKE_MODULE_PATH "${CMAKE_SOURCE_DIR}/cmake")
# Read variables from file
FILE(READ "${CMAKE_SOURCE_DIR}/CMakeVariables.txt" variables)
@@ -14,30 +15,26 @@ string(REPLACE "\n" ";" variables ${variables})
foreach(variable ${variables})
# If the string contains a #, skip it
if(NOT "${variable}" MATCHES "#")
# Split the variable into name and value
string(REPLACE "=" ";" variable ${variable})
# Check that the length of the variable is 2 (name and value)
list(LENGTH variable length)
if(${length} EQUAL 2)
if(${length} EQUAL 2)
list(GET variable 0 variable_name)
list(GET variable 1 variable_value)
# Set the variable
set(${variable_name} ${variable_value})
# Add compiler definition
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -D${variable_name}=${variable_value}")
message(STATUS "Variable: ${variable_name} = ${variable_value}")
endif()
endif()
endforeach()
# Set the version
set(PROJECT_VERSION "${PROJECT_VERSION_MAJOR}.${PROJECT_VERSION_MINOR}.${PROJECT_VERSION_PATCH}")
set(PROJECT_VERSION "\"${PROJECT_VERSION_MAJOR}.${PROJECT_VERSION_MINOR}.${PROJECT_VERSION_PATCH}\"")
# Echo the version
message(STATUS "Version: ${PROJECT_VERSION}")
@@ -53,19 +50,22 @@ set(RECASTNAVIGATION_EXAMPLES OFF CACHE BOOL "" FORCE)
# Disabled misleading indentation as DL_LinkedList from RakNet has a weird indent.
# Disabled no-register
# Disabled unknown pragmas because Linux doesn't understand Windows pragmas.
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -DPROJECT_VERSION=${PROJECT_VERSION}")
if(UNIX)
if(APPLE)
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=gnu++17 -O2 -Wuninitialized -D_GLIBCXX_USE_CXX11_ABI=0 -D_GLIBCXX_USE_CXX17_ABI=0 -fPIC")
else()
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=gnu++17 -O2 -Wuninitialized -D_GLIBCXX_USE_CXX11_ABI=0 -D_GLIBCXX_USE_CXX17_ABI=0 -static-libgcc -fPIC -lstdc++fs")
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=gnu++17 -O2 -Wuninitialized -fPIC")
add_compile_definitions(_GLIBCXX_USE_CXX11_ABI=0 _GLIBCXX_USE_CXX17_ABI=0)
if(NOT APPLE)
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -static-libgcc -lstdc++fs")
endif()
if (__dynamic AND CMAKE_CXX_COMPILER_ID STREQUAL "GNU")
if(${DYNAMIC} AND CMAKE_CXX_COMPILER_ID STREQUAL "GNU")
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -rdynamic")
endif()
if (__ggdb)
if(${GGDB})
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -ggdb")
endif()
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -std=c99 -O2 -fPIC")
elseif(MSVC)
# Skip warning for invalid conversion from size_t to uint32_t for all targets below for now
@@ -98,47 +98,64 @@ make_directory(${CMAKE_BINARY_DIR}/logs)
# Copy resource files on first build
set(RESOURCE_FILES "sharedconfig.ini" "authconfig.ini" "chatconfig.ini" "worldconfig.ini" "masterconfig.ini" "blacklist.dcf")
message(STATUS "Checking resource file integrity")
foreach (resource_file ${RESOURCE_FILES})
include(Utils)
UpdateConfigOption(${PROJECT_BINARY_DIR}/authconfig.ini "port" "auth_server_port")
UpdateConfigOption(${PROJECT_BINARY_DIR}/chatconfig.ini "port" "chat_server_port")
UpdateConfigOption(${PROJECT_BINARY_DIR}/masterconfig.ini "port" "master_server_port")
foreach(resource_file ${RESOURCE_FILES})
set(file_size 0)
if (EXISTS ${PROJECT_BINARY_DIR}/${resource_file})
if(EXISTS ${PROJECT_BINARY_DIR}/${resource_file})
file(SIZE ${PROJECT_BINARY_DIR}/${resource_file} file_size)
endif()
if (${file_size} EQUAL 0)
if(${file_size} EQUAL 0)
configure_file(
${CMAKE_SOURCE_DIR}/resources/${resource_file} ${PROJECT_BINARY_DIR}/${resource_file}
COPYONLY
)
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")
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})
set(parsed_current_file_contents "")
# Remove comment lines so they do not interfere with the variable parsing
foreach (line ${current_file_contents})
foreach(line ${current_file_contents})
string(FIND ${line} "#" is_comment)
if (NOT ${is_comment} EQUAL 0)
if(NOT ${is_comment} EQUAL 0)
string(APPEND parsed_current_file_contents ${line})
endif()
endforeach()
file(READ ${CMAKE_SOURCE_DIR}/resources/${resource_file} depot_file_contents)
string(REPLACE "\\\n" "" depot_file_contents ${depot_file_contents})
string(REPLACE "\n" ";" depot_file_contents ${depot_file_contents})
set(line_to_add "")
foreach (line ${depot_file_contents})
foreach(line ${depot_file_contents})
string(FIND ${line} "#" is_comment)
if (NOT ${is_comment} EQUAL 0)
if(NOT ${is_comment} EQUAL 0)
string(REPLACE "=" ";" line_split ${line})
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})
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 ${PROJECT_BINARY_DIR}/${resource_file} "\n" ${line_to_append})
endforeach()
file(APPEND ${PROJECT_BINARY_DIR}/${resource_file} "\n")
endif()
set(line_to_add "")
else()
set(line_to_add ${line_to_add} ${line})
@@ -146,24 +163,23 @@ foreach (resource_file ${RESOURCE_FILES})
endforeach()
endif()
endforeach()
message(STATUS "Resource file integrity check complete")
# if navmeshes directory does not exist, create it
if (NOT EXISTS ${PROJECT_BINARY_DIR}/navmeshes)
if(NOT EXISTS ${PROJECT_BINARY_DIR}/navmeshes)
file(MAKE_DIRECTORY ${PROJECT_BINARY_DIR}/navmeshes)
endif()
# Copy navmesh data on first build and extract it
configure_file(
${CMAKE_SOURCE_DIR}/resources/navmeshes.zip ${PROJECT_BINARY_DIR}/navmeshes.zip
COPYONLY
)
configure_file(${CMAKE_SOURCE_DIR}/resources/navmeshes.zip ${PROJECT_BINARY_DIR}/navmeshes.zip COPYONLY)
file(ARCHIVE_EXTRACT INPUT ${PROJECT_BINARY_DIR}/navmeshes.zip DESTINATION ${PROJECT_BINARY_DIR}/navmeshes)
file(REMOVE ${PROJECT_BINARY_DIR}/navmeshes.zip)
# Copy vanity files on first build
set(VANITY_FILES "CREDITS.md" "INFO.md" "TESTAMENT.md" "NPC.xml")
foreach(file ${VANITY_FILES})
configure_file("${CMAKE_SOURCE_DIR}/vanity/${file}" "${CMAKE_BINARY_DIR}/vanity/${file}" COPYONLY)
endforeach()
@@ -171,26 +187,18 @@ endforeach()
# Move our migrations for MasterServer to run
file(MAKE_DIRECTORY ${PROJECT_BINARY_DIR}/migrations/dlu/)
file(GLOB SQL_FILES ${CMAKE_SOURCE_DIR}/migrations/dlu/*.sql)
foreach(file ${SQL_FILES})
get_filename_component(file ${file} NAME)
if (NOT EXISTS ${PROJECT_BINARY_DIR}/migrations/dlu/${file})
configure_file(
${CMAKE_SOURCE_DIR}/migrations/dlu/${file} ${PROJECT_BINARY_DIR}/migrations/dlu/${file}
COPYONLY
)
endif()
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)
if (NOT EXISTS ${PROJECT_BINARY_DIR}/migrations/cdserver/${file})
configure_file(
${CMAKE_SOURCE_DIR}/migrations/cdserver/${file} ${PROJECT_BINARY_DIR}/migrations/cdserver/${file}
COPYONLY
)
endif()
configure_file(${CMAKE_SOURCE_DIR}/migrations/cdserver/${file} ${PROJECT_BINARY_DIR}/migrations/cdserver/${file})
endforeach()
# Create our list of include directories
@@ -198,7 +206,9 @@ set(INCLUDED_DIRECTORIES
"dCommon"
"dCommon/dClient"
"dCommon/dEnums"
"dChatFilter"
"dGame"
"dGame/dBehaviors"
"dGame/dComponents"
@@ -209,116 +219,54 @@ set(INCLUDED_DIRECTORIES
"dGame/dPropertyBehaviors"
"dGame/dPropertyBehaviors/ControlBehaviorMessages"
"dGame/dUtilities"
"dPhysics"
"dNavigation"
"dNavigation/dTerrain"
"dZoneManager"
"dDatabase"
"dDatabase/Tables"
"dNet"
"dScripts"
"dScripts/02_server"
"dScripts/ai"
"dScripts/client"
"dScripts/EquipmentScripts"
"dScripts/EquipmentTriggers"
"dScripts/zone"
"dScripts/02_server/DLU"
"dScripts/02_server/Enemy"
"dScripts/02_server/Equipment"
"dScripts/02_server/Map"
"dScripts/02_server/Minigame"
"dScripts/02_server/Objects"
"dScripts/02_server/Pets"
"dScripts/02_server/Enemy/AG"
"dScripts/02_server/Enemy/AM"
"dScripts/02_server/Enemy/FV"
"dScripts/02_server/Enemy/General"
"dScripts/02_server/Enemy/Survival"
"dScripts/02_server/Enemy/VE"
"dScripts/02_server/Enemy/Waves"
"dScripts/02_server/Map/AG"
"dScripts/02_server/Map/AG_Spider_Queen"
"dScripts/02_server/Map/AM"
"dScripts/02_server/Map/FV"
"dScripts/02_server/Map/General"
"dScripts/02_server/Map/GF"
"dScripts/02_server/Map/njhub"
"dScripts/02_server/Map/NS"
"dScripts/02_server/Map/NT"
"dScripts/02_server/Map/PR"
"dScripts/02_server/Map/Property"
"dScripts/02_server/Map/SS"
"dScripts/02_server/Map/VE"
"dScripts/02_server/Map/FV/Racing"
"dScripts/02_server/Map/General/Ninjago"
"dScripts/02_server/Map/njhub/boss_instance"
"dScripts/02_server/Map/NS/Waves"
"dScripts/02_server/Map/Property/AG_Med"
"dScripts/02_server/Map/Property/AG_Small"
"dScripts/02_server/Map/Property/NS_Med"
"dScripts/02_server/Minigame/General"
"dScripts/ai/ACT"
"dScripts/ai/AG"
"dScripts/ai/FV"
"dScripts/ai/GENERAL"
"dScripts/ai/GF"
"dScripts/ai/MINIGAME"
"dScripts/ai/NP"
"dScripts/ai/NS"
"dScripts/ai/PETS"
"dScripts/ai/PROPERTY"
"dScripts/ai/RACING"
"dScripts/ai/SPEC"
"dScripts/ai/WILD"
"dScripts/ai/ACT/FootRace"
"dScripts/ai/MINIGAME/SG_GF"
"dScripts/ai/MINIGAME/SG_GF/SERVER"
"dScripts/ai/NS/NS_PP_01"
"dScripts/ai/NS/WH"
"dScripts/ai/PROPERTY/AG"
"dScripts/ai/RACING/OBJECTS"
"dScripts/client/ai"
"dScripts/client/ai/PR"
"dScripts/zone/AG"
"dScripts/zone/LUPs"
"dScripts/zone/PROPERTY"
"dScripts/zone/PROPERTY/FV"
"dScripts/zone/PROPERTY/GF"
"dScripts/zone/PROPERTY/NS"
"dZoneManager"
"dDatabase"
"dDatabase/CDClientDatabase"
"dDatabase/CDClientDatabase/CDClientTables"
"dDatabase/GameDatabase"
"dDatabase/GameDatabase/ITables"
"dDatabase/GameDatabase/MySQL"
"dDatabase/GameDatabase/MySQL/Tables"
"dNet"
"thirdparty/magic_enum/include/magic_enum"
"thirdparty/raknet/Source"
"thirdparty/tinyxml2"
"thirdparty/recastnavigation"
"thirdparty/SQLite"
"thirdparty/cpplinq"
"thirdparty/cpp-httplib"
"tests"
"tests/dCommonTests"
"tests/dGameTests"
"tests/dGameTests/dComponentsTests"
)
)
# Add system specfic includes for Apple, Windows and Other Unix OS' (including Linux)
if (APPLE)
if(APPLE)
include_directories("/usr/local/include/")
endif()
if (WIN32)
set(INCLUDED_DIRECTORIES ${INCLUDED_DIRECTORIES} "thirdparty/libbcrypt/include")
elseif (UNIX)
set(INCLUDED_DIRECTORIES ${INCLUDED_DIRECTORIES} "thirdparty/libbcrypt")
set(INCLUDED_DIRECTORIES ${INCLUDED_DIRECTORIES} "thirdparty/libbcrypt/include/bcrypt")
endif()
# Add binary directory as an include directory
include_directories(${PROJECT_BINARY_DIR})
# Actually include the directories from our list
foreach (dir ${INCLUDED_DIRECTORIES})
foreach(dir ${INCLUDED_DIRECTORIES})
include_directories(${PROJECT_SOURCE_DIR}/${dir})
endforeach()
if(NOT WIN32)
include_directories("${PROJECT_SOURCE_DIR}/thirdparty/libbcrypt/include/bcrypt")
endif()
include_directories("${PROJECT_SOURCE_DIR}/thirdparty/libbcrypt/include")
# Add linking directories:
link_directories(${PROJECT_BINARY_DIR})
@@ -329,8 +277,9 @@ add_subdirectory(thirdparty)
file(
GLOB HEADERS_DDATABASE
LIST_DIRECTORIES false
${PROJECT_SOURCE_DIR}/dDatabase/*.h
${PROJECT_SOURCE_DIR}/dDatabase/Tables/*.h
${PROJECT_SOURCE_DIR}/dDatabase/CDClientDatabase/*.h
${PROJECT_SOURCE_DIR}/dDatabase/CDClientDatabase/CDClientTables/*.h
${PROJECT_SOURCE_DIR}/dDatabase/GameDatabase/ITables/*.h
${PROJECT_SOURCE_DIR}/thirdparty/SQLite/*.h
)
@@ -367,13 +316,13 @@ add_subdirectory(dNavigation)
add_subdirectory(dPhysics)
# Create a list of common libraries shared between all binaries
set(COMMON_LIBRARIES "dCommon" "dDatabase" "dNet" "raknet" "mariadbConnCpp")
set(COMMON_LIBRARIES "dCommon" "dDatabase" "dNet" "raknet" "mariadbConnCpp" "magic_enum")
# Add platform specific common libraries
if (UNIX)
if(UNIX)
set(COMMON_LIBRARIES ${COMMON_LIBRARIES} "dl" "pthread")
if (NOT APPLE AND __include_backtrace__)
if(NOT APPLE AND ${INCLUDE_BACKTRACE})
set(COMMON_LIBRARIES ${COMMON_LIBRARIES} "backtrace")
endif()
endif()
@@ -384,12 +333,6 @@ add_subdirectory(dAuthServer)
add_subdirectory(dChatServer)
add_subdirectory(dMasterServer) # Add MasterServer last so it can rely on the other binaries
# Add our precompiled headers
target_precompile_headers(
dGame PRIVATE
${HEADERS_DGAME}
)
target_precompile_headers(
dZoneManager PRIVATE
${HEADERS_DZONEMANAGER}
@@ -411,6 +354,6 @@ target_precompile_headers(
"$<$<COMPILE_LANGUAGE:CXX>:${PROJECT_SOURCE_DIR}/thirdparty/tinyxml2/tinyxml2.h>"
)
if (${__enable_testing__} MATCHES "1")
if(${ENABLE_TESTING})
add_subdirectory(tests)
endif()

View File

@@ -1,22 +1,32 @@
PROJECT_VERSION_MAJOR=1
PROJECT_VERSION_MINOR=1
PROJECT_VERSION_PATCH=1
# LICENSE
LICENSE=AGPL-3.0
# Debugging
# Set __dynamic to 1 to enable the -rdynamic flag for the linker, yielding some symbols in crashlogs.
__dynamic=1
# Set __ggdb to 1 to enable the -ggdb flag for the linker, including more debug info.
# __ggdb=1
# Set __include_backtrace__ to 1 to includes the backtrace library for better crashlogs.
# __include_backtrace__=1
# Set __compile_backtrace__ to 1 to compile the backtrace library instead of using system libraries.
# __compile_backtrace__=1
# Set DYNAMIC to 1 to enable the -rdynamic flag for the linker, yielding some symbols in crashlogs.
DYNAMIC=1
# Set GGDB to 1 to enable the -ggdb flag for the linker, including more debug info.
# Do note, changing this will re-build the whole server
GGDB=0
# Set INCLUDE_BACKTRACE to 1 to includes the backtrace library for better crashlogs.
# Do note, changing this will re-build the whole server
INCLUDE_BACKTRACE=0
# Set COMPILE_BACKTRACE to 1 to compile the backtrace library instead of using system libraries.
# Do note, changing this will re-build the whole server
COMPILE_BACKTRACE=0
# Set to the number of jobs (make -j equivalent) to compile the mariadbconn files with.
__maria_db_connector_compile_jobs__=1
MARIADB_CONNECTOR_COMPILE_JOBS=1
# When set to 1 and uncommented, compiling and linking testing folders and libraries will be done.
__enable_testing__=1
ENABLE_TESTING=1
# The path to OpenSSL. Change this if your OpenSSL install path is different than the default.
OPENSSL_ROOT_DIR=/usr/local/opt/openssl@3/
# Uncomment the below line to cache the entire CDClient into memory
# CDCLIENT_CACHE_ALL=1
# Whether or not to cache the entire CDClient Database into memory instead of lazy loading.
# 0 means to lazy load, all other values mean load the entire database.
CDCLIENT_CACHE_ALL=0

View File

@@ -1,46 +0,0 @@
# Run the Darkflame Server inside Docker
## What you need
- [Docker](https://docs.docker.com/get-docker/) (Docker Desktop or on Linux normal Docker)
- [Docker Compose](https://docs.docker.com/compose/install/) (Included in Docker Desktop)
- LEGO® Universe Client. Check the main [README](./README.md) for details on this.
## Run server inside Docker
1. Copy `.env.example` and save it as `.env` inside the root directory of this repository
2. Edit the `.env` file and add your path to the root directory of your LEGO® Universe Client after `CLIENT_PATH=`
3. Update other values in the `.env` file as needed (be sure to update passwords!)
4. Run `docker compose up -d --build`
5. Run `docker compose exec darkflame /app/MasterServer -a` and setup your admin account
6. Follow the directions [here](https://github.com/DarkflameUniverse/AccountManager) to setup regular user accounts. The server will be accessible at: `http://<EXTERNAL_IP>:5000`
7. Now you can see the output of the server with `docker compose logs -f --tail 100` or `docker compose logs -f --tail 100`. This can help you understand issues and there you can also see when the server finishes it's startup.
8. You're ready to connect your client!
**NOTE #1**: If you're running an older version of Docker, you may need to use the command `docker-compose` instead of `docker compose`.
**NOTE #2**: To stop the server simply run `docker compose down` and to restart it just run `docker compose up -d` again. No need to run all the steps above every time.
**NOTE #3**: Docker buildkit needs to be enabled. https://docs.docker.com/develop/develop-images/build_enhancements/#to-enable-buildkit-builds
**NOTE #4**: Make sure to run the following in the repo root directory after cloning so submodules are also downloaded.
```
git submodule update --init --recursive
```
**NOTE #5**: If DarkflameSetup fails due to not having cdclient.fdb, rename CDClient.fdb (in the same folder) to cdclient.fdb
## Disable brickbuildfix
If you don't need the http server running on port 80 do this:
1. Create a file with the name `docker-compose.override.yml` in the root of the repository
2. Paste this content:
```yml
services:
brickbuildfix:
profiles:
- donotstart
```
3. Now run `docker compose up -d`

View File

@@ -1,58 +0,0 @@
# Installation under Windows
## First Run
1. Navigate to the [Docker download page](https://www.docker.com/products/docker-desktop) and download docker.
![Docker Download Page](docker/images/Docker_Download_Page.png)
2. Once the file has finished downloading, run it and proceed through the installation. Make sure, "Install required Windows components for WSL 2" is checked.
![Docker Desktop Installer Configuration](docker/images/Docker_Desktop_Installer_Configuration.png)
3. If necessary, restart your computer.
4. After the restart, Docker Desktop will automatically open. If it does not, simply start it like any other program.
5. If a window "WSL 2 Installation is incomplete." pops up, follow the link and click "WSL2 Linux kernel update package for x64 machines". Run the downloaded file and once that finishes, click "Restart" in the Docker Desktop window.
![WSL 2 download](docker/images/WSL_2_download.png)
6. Wait until Docker Desktop has started. You may skip the tutorial.
7. You may want to disable "Open Docker Dashboard at startup" in _Settings_ -> _General_
![Disable Dashboard Autostart](docker/images/DD_General_Settings.png)
8. Install [Git for Windows](https://git-scm.com/download/win). During the installation, simply confirming the defaults is sufficient.
9. In the folder you wish to save the Server, right click and select "Git Bash Here".
10. Type `git clone --recursive https://github.com/DarkflameUniverse/DarkflameServer`
11. Once the command has completed (you can see you path again and can enter commands), close the window.
12. Inside the downloaded folder, copy `.env.example` and name the copy `.env`
13. Open `.env` with Notepad by right-clicking it and selecting _Open With_ -> _More apps_ -> _Notepad_.
14. Change the text after `CLIENT_PATH=` to the location of your client. This folder must contain either a folder `client` or `legouniverse.exe`.
> If you need the extra performance, place the client files in `\\wsl$\<your linux OS>\...` to avoid working across file systems, see [Docker Best Practices](https://docs.docker.com/desktop/windows/wsl/#best-practices) and [WSL documentation](https://docs.microsoft.com/en-us/windows/wsl/filesystems#file-storage-and-performance-across-file-systems).
15. Optionally, you can change the number after `BUILD_THREADS=` to the number of cores / threads your processor has. If your computer crashes while building, you can try to reduce this value.
16. After `ACCOUNT_MANAGER_SECRET=` paste a "SHA 256-bit Key" from https://keygen.io/
17. If you are not only hosting a local server, change the value after `EXTERNAL_IP=` to the external IP address of your computer.
18. Change the two values `SECRET_VALUE_CHANGE_ME` to passwords only you know. Save and close the file.
19. In the extracted folder hit Shift+Right Click and select "Open PowerShell window here".
![Open PowerShell](docker/images/Open_Powershell.png)
17. In the new window, paste (with right click) or type `docker compose up -d --build` and confirm with enter.
18. Once you see the blinking cursor and the path again, setup has finished and the server is already running.
![setup done](docker/images/setup_finished.png)
19. Create an admin account by pasting `docker compose exec darkflame /app/MasterServer -a` and following the prompts.
![admin account creation](docker/images/Account_Creation.png)
20. You can now login with these credentials at `http://your_ip:5000` (replace your_ip with your external IP). There you can create your account for playing as well as generate keys for other people to join; use these at `http://your_ip:5000/activate`
## Normal Use
1. In Docker Desktop you should now see an entry `darkflameserver-main` and when you click on it all containers but `DarkflameSetup` should eventually be green. That means the server is running.
![server running](docker/images/Docker_Compose_Finished.png)
2. For troubleshooting, you can check the logs of the various parts by clicking their entry.
3. You can start and stop the server with the corresponding buttons. Once all containers are grey, the server has shut down, and when all containers but `DarkflameSetup` are green, the server is running. Note that starting and stopping takes some time, please be patient.
![start stop buttons](docker/images/DD_Server_Startstop.png)

51
Dockerfile Normal file
View File

@@ -0,0 +1,51 @@
FROM gcc:12 as build
WORKDIR /app
RUN set -ex; \
apt-get update; \
apt-get install -y cmake
COPY . /app/
COPY --chmod=0500 ./build.sh /app/
RUN sed -i 's/MARIADB_CONNECTOR_COMPILE_JOBS__=.*/MARIADB_CONNECTOR_COMPILE_JOBS__=2/' /app/CMakeVariables.txt
RUN ./build.sh
FROM debian:12 as runtime
WORKDIR /app
RUN --mount=type=cache,id=build-apt-cache,target=/var/cache/apt \
apt update && \
apt install -y libssl3 libcurl4 && \
rm -rf /var/lib/apt/lists/*
# Grab libraries and load them
COPY --from=build /app/build/mariadbcpp/src/mariadb_connector_cpp-build/libmariadbcpp.so /usr/local/lib/
COPY --from=build /app/build/mariadbcpp/src/mariadb_connector_cpp-build/libmariadb/libmariadb/libmariadb.so.3 /usr/local/lib
RUN ldconfig
# Server bins
COPY --from=build /app/build/*Server /app/
# Necessary suplimentary files
COPY --from=build /app/build/*.ini /app/configs/
COPY --from=build /app/build/vanity/*.* /app/vanity/*
COPY --from=build /app/build/navmeshes /app/navmeshes
COPY --from=build /app/build/migrations /app/migrations
COPY --from=build /app/build/*.dcf /app/
# backup of config and vanity files to copy to the host incase
# of a mount clobbering the copy from above
COPY --from=build /app/build/*.ini /app/default-configs/
COPY --from=build /app/build/vanity/*.* /app/default-vanity/*
# needed as the container runs with the root user
# and therefore sudo doesn't exist
ENV USE_SUDO_AUTH=0
ENV DLU_CONFIG_DIR=/app/configs/
COPY --chmod=0500 ./entrypoint.sh /app/
ENTRYPOINT [ "/app/entrypoint.sh" ]

View File

@@ -23,6 +23,9 @@ We do not recommend hosting public servers. Darkflame Universe is intended for s
### 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.
## Step by step walkthrough for a single-player server
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.
## Steps to setup server
* [Clone this repository](#clone-the-repository)
* [Install dependencies](#install-dependencies)
@@ -34,6 +37,7 @@ Darkflame Universe is a server emulator and does not distribute any LEGO® Unive
* [Verify your setup](#verify-your-setup)
* [Running the server](#running-the-server)
* [User Guide](#user-guide)
* [Docker](#docker)
## Clone the repository
If you are on Windows, you will need to download and install git from [here](https://git-scm.com/download/win)
@@ -224,6 +228,44 @@ sudo setcap 'cap_net_bind_service=+ep' AuthServer
```
and then go to `build/masterconfig.ini` and change `use_sudo_auth` to 0.
### 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.
_Note: You could use screen or tmux instead for virtual terminals_
To run the server non-interactively, we can use a systemctl service by copying the following file:
```shell
cp ./systemd.example /etc/systemd/system/darkflame.service
```
Make sure to edit the file in `/etc/systemd/system/darkflame.service` and change the:
- `User` and `Group` to the user that runs the darkflame server.
- `ExecPath` to the full file path of the server executable.
To register, enable and start the service use the following commands:
- Reload the systemd manager configuration to make it aware of the new service file:
```shell
systemctl daemon-reload
```
- Start the service:
```shell
systemctl start darkflame.service
```
- Enable OR disable the service to start on boot using:
```shell
systemctl enable darkflame.service
systemctl disable darkflame.service
```
- Verify that the service is running without errors:
```shell
systemctl status darkflame.service
```
- You can also restart, stop, or check the logs of the service using journalctl
```shell
systemctl restart darkflame.service
systemctl stop darkflame.service
journalctl -xeu darkflame.service
```
### First admin user
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!
@@ -285,6 +327,7 @@ Below are known good SHA256 checksums of the client:
* `0d862f71eedcadc4494c4358261669721b40b2131101cbd6ef476c5a6ec6775b` (unpacked client, includes extra locales, rar compressed)
If the returned hash matches one of the lines above then you can continue with setting up the server. If you are using a fully downloaded and complete client from live, then it will work, but the hash above may not match. Otherwise you must obtain a full install of LEGO® Universe 1.10.64.
You must also make absolutely sure your LEGO Universe client is not in a Windows OneDrive. DLU is not and will not support a client being stored in a OneDrive, so ensure you have moved the client outside of that location.
### Darkflame Universe Client
Darkflame Universe clients identify themselves using a higher version number than the regular live clients out there.
@@ -305,6 +348,42 @@ certutil -hashfile <file> SHA1
Known good *SHA1* checksum of the Darkflame Universe client:
- `91498e09b83ce69f46baf9e521d48f23fe502985` (packed client, zip compressed)
# Docker
## Standalone
For standalone deployment, you can use the image provided via Github's Container Repository:
`ghcr.io/darkflameuniverse/darkflameserver`
This assumes that you have a database deployed to your host or in another docker container.
A basic deployment of this contianer would look like:
```sh
# example docker contianer deployment
docker run -it \
-v /path/to/configs/:/app/configs \
-v /path/to/logs/:/app/logs \
-v /path/to/dumps/:/app/dumps \
-v /path/to/res:/app/res:ro \
-v /path/to/resServer:/app/resServer \
-e DUMP_FOLDER=/app/dumps \
-p 1001:1001/udp \
-p 2005:2005/udp \
-p 3000-3300:3000-3300/udp \
ghcr.io/darkflameuniverse/darkflameserver:latest
```
You will need to replace the `/path/to/`'s to reflect the paths on your host.
Any config option in the `.ini`'s can be overridden with environmental variables: Ex: `log_to_console=1` from `shared_config.ini` would be overidden like `-e LOG_TO_CONSOLE=0`
## Compose
See the [compose](docker-compose.yml) file in the root of the repo.
This compose file is for a full deployment: MariaDB, DarkflameServer, and Nexus Dashboard.
All of the environmental options are listed in the compose file so the can be pass through, or you can edit the compose file to suit your specific needs
# Development Documentation
This is a Work in Progress, but below are some quick links to documentaion for systems and structs in the server
[Networked message structs](https://lcdruniverse.org/lu_packets/lu_packets/index.html)

51
cmake/Utils.cmake Normal file
View File

@@ -0,0 +1,51 @@
# Parses a config file for a specific option and appends the new option if it does not exist
# If the new option does exist, this function will do nothing.
# file_name: The name of the file to parse
# old_option_name: The name of the option to find
# new_option_name: The name of the option to add
function(UpdateConfigOption file_name old_option_name new_option_name)
string(APPEND old_option_name "=")
string(APPEND new_option_name "=")
message(STATUS "Checking " ${file_name} " for " ${old_option_name} " and adding " ${new_option_name} " if it does not exist")
if(NOT EXISTS ${file_name})
message(STATUS ${file_name} " does not exist. Doing nothing")
return()
endif()
file(READ ${file_name} 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 "")
# Remove comment lines so they do not interfere with the variable parsing
foreach(line ${current_file_contents})
string(FIND ${line} "#" is_comment)
if(NOT ${is_comment} EQUAL 0)
string(APPEND parsed_current_file_contents ${line})
endif()
endforeach()
set(found_new_option -1)
set(found_old_option -1)
set(current_value -1)
foreach(line ${current_file_contents})
string(FIND ${line} ${old_option_name} old_option_in_file)
if(${old_option_in_file} EQUAL 0)
set(found_old_option 1)
set(current_value ${line})
endif()
string(FIND ${line} ${new_option_name} found_new_option_in_file)
if(${found_new_option_in_file} EQUAL 0)
set(found_new_option 1)
endif()
endforeach(line ${current_file_contents})
if(${found_old_option} EQUAL 1 AND NOT ${found_new_option} EQUAL 1)
string(REPLACE ${old_option_name} ${new_option_name} current_value ${current_value})
file(APPEND ${file_name} "\n" ${current_value})
endif()
endfunction()

View File

@@ -1,6 +1,7 @@
#include <iostream>
#include <string>
#include <ctime>
#include <csignal>
#include <chrono>
#include <thread>
@@ -15,7 +16,7 @@
//RakNet includes:
#include "RakNetDefines.h"
#include <MessageIdentifiers.h>
#include "MessageIdentifiers.h"
//Auth includes:
#include "AuthPackets.h"
@@ -28,7 +29,7 @@ namespace Game {
Logger* logger = nullptr;
dServer* server = nullptr;
dConfig* config = nullptr;
bool shouldShutdown = false;
Game::signal_t lastSignal = 0;
std::mt19937 randomEngine;
}
@@ -42,27 +43,24 @@ int main(int argc, char** argv) {
Diagnostics::SetProcessFileName(argv[0]);
Diagnostics::Initialize();
std::signal(SIGINT, Game::OnSignal);
std::signal(SIGTERM, Game::OnSignal);
//Create all the objects we need to run our service:
Game::logger = SetupLogger();
if (!Game::logger) return EXIT_FAILURE;
//Read our config:
Game::config = new dConfig((BinaryPathFinder::GetBinaryDir() / "authconfig.ini").string());
Game::config = new dConfig("authconfig.ini");
Game::logger->SetLogToConsole(Game::config->GetValue("log_to_console") != "0");
Game::logger->SetLogDebugStatements(Game::config->GetValue("log_debug_statements") == "1");
LOG("Starting Auth server...");
LOG("Version: %i.%i", PROJECT_VERSION_MAJOR, PROJECT_VERSION_MINOR);
LOG("Version: %s", PROJECT_VERSION);
LOG("Compiled on: %s", __TIMESTAMP__);
//Connect to the MySQL Database
std::string mysql_host = Game::config->GetValue("mysql_host");
std::string mysql_database = Game::config->GetValue("mysql_database");
std::string mysql_username = Game::config->GetValue("mysql_username");
std::string mysql_password = Game::config->GetValue("mysql_password");
try {
Database::Connect(mysql_host, mysql_database, mysql_username, mysql_password);
Database::Connect();
} catch (sql::SQLException& ex) {
LOG("Got an error while connecting to the database: %s", ex.what());
Database::Destroy("AuthServer");
@@ -74,25 +72,26 @@ int main(int argc, char** argv) {
//Find out the master's IP:
std::string masterIP;
uint32_t masterPort = 1500;
sql::PreparedStatement* stmt = Database::CreatePreppedStmt("SELECT ip, port FROM servers WHERE name='master';");
auto res = stmt->executeQuery();
while (res->next()) {
masterIP = res->getString(1).c_str();
masterPort = res->getInt(2);
}
delete res;
delete stmt;
auto masterInfo = Database::Get()->GetMasterInfo();
if (masterInfo) {
masterIP = masterInfo->ip;
masterPort = masterInfo->port;
}
LOG("Master is at %s:%d", masterIP.c_str(), masterPort);
Game::randomEngine = std::mt19937(time(0));
//It's safe to pass 'localhost' here, as the IP is only used as the external IP.
uint32_t maxClients = 50;
uint32_t maxClients = 999;
uint32_t ourPort = 1001; //LU client is hardcoded to use this for auth port, so I'm making it the default.
if (Game::config->GetValue("max_clients") != "") maxClients = std::stoi(Game::config->GetValue("max_clients"));
if (Game::config->GetValue("port") != "") ourPort = std::atoi(Game::config->GetValue("port").c_str());
std::string ourIP = "localhost";
GeneralUtils::TryParse(Game::config->GetValue("max_clients"), maxClients);
GeneralUtils::TryParse(Game::config->GetValue("auth_server_port"), ourPort);
const auto externalIPString = Game::config->GetValue("external_ip");
if (!externalIPString.empty()) ourIP = externalIPString;
Game::server = new dServer(Game::config->GetValue("external_ip"), ourPort, 0, maxClients, false, true, Game::logger, masterIP, masterPort, ServerType::Auth, Game::config, &Game::shouldShutdown);
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:
auto t = std::chrono::high_resolution_clock::now();
@@ -103,13 +102,18 @@ int main(int argc, char** argv) {
uint32_t framesSinceMasterDisconnect = 0;
uint32_t framesSinceLastSQLPing = 0;
while (!Game::shouldShutdown) {
AuthPackets::LoadClaimCodes();
Game::logger->Flush(); // once immediately before main loop
while (!Game::ShouldShutdown()) {
//Check if we're still connected to master:
if (!Game::server->GetIsConnectedToMaster()) {
framesSinceMasterDisconnect++;
if (framesSinceMasterDisconnect >= authFramerate)
if (framesSinceMasterDisconnect >= authFramerate) {
LOG("No connection to master!");
break; //Exit our loop, shut down.
}
} else framesSinceMasterDisconnect = 0;
//In world we'd update our other systems here.
@@ -134,16 +138,12 @@ int main(int argc, char** argv) {
//Find out the master's IP for absolutely no reason:
std::string masterIP;
uint32_t masterPort;
sql::PreparedStatement* stmt = Database::CreatePreppedStmt("SELECT ip, port FROM servers WHERE name='master';");
auto res = stmt->executeQuery();
while (res->next()) {
masterIP = res->getString(1).c_str();
masterPort = res->getInt(2);
auto masterInfo = Database::Get()->GetMasterInfo();
if (masterInfo) {
masterIP = masterInfo->ip;
masterPort = masterInfo->port;
}
delete res;
delete stmt;
framesSinceLastSQLPing = 0;
} else framesSinceLastSQLPing++;
@@ -152,6 +152,7 @@ int main(int argc, char** argv) {
std::this_thread::sleep_until(t);
}
LOG("Exited Main Loop! (signal %d)", Game::lastSignal);
//Delete our objects here:
Database::Destroy("AuthServer");
delete Game::server;

View File

@@ -1,2 +1,3 @@
add_executable(AuthServer "AuthServer.cpp")
target_link_libraries(AuthServer ${COMMON_LIBRARIES})
add_compile_definitions(AuthServer PRIVATE PROJECT_VERSION="\"${PROJECT_VERSION}\"")

View File

@@ -32,15 +32,11 @@ dChatFilter::dChatFilter(const std::string& filepath, bool dontGenerateDCF) {
}
//Read player names that are ok as well:
auto stmt = Database::CreatePreppedStmt("select name from charinfo;");
auto res = stmt->executeQuery();
while (res->next()) {
std::string line = res->getString(1).c_str();
std::transform(line.begin(), line.end(), line.begin(), ::tolower); //Transform to lowercase
m_ApprovedWords.push_back(CalculateHash(line));
auto approvedNames = Database::Get()->GetApprovedCharacterNames();
for (auto& name : approvedNames) {
std::transform(name.begin(), name.end(), name.begin(), ::tolower); //Transform to lowercase
m_ApprovedWords.push_back(CalculateHash(name));
}
delete res;
delete stmt;
}
dChatFilter::~dChatFilter() {
@@ -144,7 +140,7 @@ std::vector<std::pair<uint8_t, uint8_t>> dChatFilter::IsSentenceOkay(const std::
listOfBadSegments.emplace_back(position, originalSegment.length());
}
position += segment.length() + 1;
position += originalSegment.length() + 1;
}
return listOfBadSegments;

View File

@@ -1,10 +1,12 @@
set(DCHATSERVER_SOURCES
"ChatIgnoreList.cpp"
"ChatPacketHandler.cpp"
"PlayerContainer.cpp"
)
add_executable(ChatServer "ChatServer.cpp")
add_library(dChatServer ${DCHATSERVER_SOURCES})
add_compile_definitions(ChatServer PRIVATE PROJECT_VERSION="\"${PROJECT_VERSION}\"")
target_link_libraries(dChatServer ${COMMON_LIBRARIES} dChatFilter)
target_link_libraries(ChatServer ${COMMON_LIBRARIES} dChatFilter dChatServer)

View File

@@ -0,0 +1,173 @@
#include "ChatIgnoreList.h"
#include "PlayerContainer.h"
#include "eChatInternalMessageType.h"
#include "BitStreamUtils.h"
#include "PacketUtils.h"
#include "Game.h"
#include "Logger.h"
#include "eObjectBits.h"
#include "Database.h"
// A note to future readers, The client handles all the actual ignoring logic:
// not allowing teams, rejecting DMs, friends requets etc.
// 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 ChatIgnoreList::Response type) {
BitStreamUtils::WriteHeader(bitStream, eConnectionType::CHAT_INTERNAL, eChatInternalMessageType::ROUTE_TO_PLAYER);
bitStream.Write(receivingPlayer);
//portion that will get routed:
BitStreamUtils::WriteHeader(bitStream, eConnectionType::CLIENT, type);
}
void ChatIgnoreList::GetIgnoreList(Packet* packet) {
CINSTREAM_SKIP_HEADER;
LWOOBJID playerId;
inStream.Read(playerId);
auto* receiver = Game::playerContainer.GetPlayerData(playerId);
if (!receiver) {
LOG("Tried to get ignore list, but player %llu not found in container", playerId);
return;
}
if (!receiver->ignoredPlayers.empty()) {
LOG_DEBUG("Player %llu already has an ignore list", playerId);
return;
}
auto ignoreList = Database::Get()->GetIgnoreList(static_cast<uint32_t>(playerId));
if (ignoreList.empty()) {
LOG_DEBUG("Player %llu has no ignores", playerId);
return;
}
for (auto& ignoredPlayer : ignoreList) {
receiver->ignoredPlayers.push_back(IgnoreData{ ignoredPlayer.id, ignoredPlayer.name });
GeneralUtils::SetBit(receiver->ignoredPlayers.back().playerId, eObjectBits::CHARACTER);
GeneralUtils::SetBit(receiver->ignoredPlayers.back().playerId, eObjectBits::PERSISTENT);
}
CBITSTREAM;
WriteOutgoingReplyHeader(bitStream, receiver->playerID, ChatIgnoreList::Response::GET_IGNORE);
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>(receiver->ignoredPlayers.size());
for (const auto& ignoredPlayer : receiver->ignoredPlayers) {
bitStream.Write(ignoredPlayer.playerId);
bitStream.Write(LUWString(ignoredPlayer.playerName, 36));
}
Game::server->Send(&bitStream, packet->systemAddress, false);
}
void ChatIgnoreList::AddIgnore(Packet* packet) {
CINSTREAM_SKIP_HEADER;
LWOOBJID playerId;
inStream.Read(playerId);
auto* receiver = Game::playerContainer.GetPlayerData(playerId);
if (!receiver) {
LOG("Tried to get ignore list, but player %llu not found in container", playerId);
return;
}
constexpr int32_t MAX_IGNORES = 32;
if (receiver->ignoredPlayers.size() > MAX_IGNORES) {
LOG_DEBUG("Player %llu has too many ignores", playerId);
return;
}
inStream.IgnoreBytes(4); // ignore some garbage zeros idk
LUWString toIgnoreName(33);
inStream.Read(toIgnoreName);
std::string toIgnoreStr = toIgnoreName.GetAsString();
CBITSTREAM;
WriteOutgoingReplyHeader(bitStream, receiver->playerID, ChatIgnoreList::Response::ADD_IGNORE);
// Check if the player exists
LWOOBJID ignoredPlayerId = LWOOBJID_EMPTY;
if (toIgnoreStr == receiver->playerName || toIgnoreStr.find("[GM]") == 0) {
LOG_DEBUG("Player %llu tried to ignore themselves", playerId);
bitStream.Write(ChatIgnoreList::AddResponse::GENERAL_ERROR);
} else if (std::count(receiver->ignoredPlayers.begin(), receiver->ignoredPlayers.end(), toIgnoreStr) > 0) {
LOG_DEBUG("Player %llu is already ignoring %s", playerId, toIgnoreStr.c_str());
bitStream.Write(ChatIgnoreList::AddResponse::ALREADY_IGNORED);
} else {
// Get the playerId falling back to query if not online
auto* playerData = Game::playerContainer.GetPlayerData(toIgnoreStr);
if (!playerData) {
// Fall back to query
auto player = Database::Get()->GetCharacterInfo(toIgnoreStr);
if (!player || player->name != toIgnoreStr) {
LOG_DEBUG("Player %s not found", toIgnoreStr.c_str());
} else {
ignoredPlayerId = player->id;
}
} else {
ignoredPlayerId = playerData->playerID;
}
if (ignoredPlayerId != LWOOBJID_EMPTY) {
Database::Get()->AddIgnore(static_cast<uint32_t>(playerId), static_cast<uint32_t>(ignoredPlayerId));
GeneralUtils::SetBit(ignoredPlayerId, eObjectBits::CHARACTER);
GeneralUtils::SetBit(ignoredPlayerId, eObjectBits::PERSISTENT);
receiver->ignoredPlayers.push_back(IgnoreData{ ignoredPlayerId, toIgnoreStr });
LOG_DEBUG("Player %llu is ignoring %s", playerId, toIgnoreStr.c_str());
bitStream.Write(ChatIgnoreList::AddResponse::SUCCESS);
} else {
bitStream.Write(ChatIgnoreList::AddResponse::PLAYER_NOT_FOUND);
}
}
LUWString playerNameSend(toIgnoreStr, 33);
bitStream.Write(playerNameSend);
bitStream.Write(ignoredPlayerId);
Game::server->Send(&bitStream, packet->systemAddress, false);
}
void ChatIgnoreList::RemoveIgnore(Packet* packet) {
CINSTREAM_SKIP_HEADER;
LWOOBJID playerId;
inStream.Read(playerId);
auto* receiver = Game::playerContainer.GetPlayerData(playerId);
if (!receiver) {
LOG("Tried to get ignore list, but player %llu not found in container", playerId);
return;
}
inStream.IgnoreBytes(4); // ignore some garbage zeros idk
LUWString removedIgnoreName(33);
inStream.Read(removedIgnoreName);
std::string removedIgnoreStr = removedIgnoreName.GetAsString();
auto toRemove = std::remove(receiver->ignoredPlayers.begin(), receiver->ignoredPlayers.end(), removedIgnoreStr);
if (toRemove == receiver->ignoredPlayers.end()) {
LOG_DEBUG("Player %llu is not ignoring %s", playerId, removedIgnoreStr.c_str());
return;
}
Database::Get()->RemoveIgnore(static_cast<uint32_t>(playerId), static_cast<uint32_t>(toRemove->playerId));
receiver->ignoredPlayers.erase(toRemove, receiver->ignoredPlayers.end());
CBITSTREAM;
WriteOutgoingReplyHeader(bitStream, receiver->playerID, ChatIgnoreList::Response::REMOVE_IGNORE);
bitStream.Write<int8_t>(0);
LUWString playerNameSend(removedIgnoreStr, 33);
bitStream.Write(playerNameSend);
Game::server->Send(&bitStream, packet->systemAddress, false);
}

View File

@@ -0,0 +1,27 @@
#ifndef __CHATIGNORELIST__H__
#define __CHATIGNORELIST__H__
struct Packet;
#include <cstdint>
namespace ChatIgnoreList {
void GetIgnoreList(Packet* packet);
void AddIgnore(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 {
SUCCESS,
ALREADY_IGNORED,
PLAYER_NOT_FOUND,
GENERAL_ERROR,
};
};
#endif //!__CHATIGNORELIST__H__

View File

@@ -19,46 +19,29 @@
#include "eClientMessageType.h"
#include "eGameMessageType.h"
extern PlayerContainer playerContainer;
void ChatPacketHandler::HandleFriendlistRequest(Packet* packet) {
//Get from the packet which player we want to do something with:
CINSTREAM_SKIP_HEADER;
LWOOBJID playerID = 0;
inStream.Read(playerID);
auto player = playerContainer.GetPlayerData(playerID);
auto player = Game::playerContainer.GetPlayerData(playerID);
if (!player) return;
//Get our friends list from the Db. Using a derived table since the friend of a player can be in either column.
std::unique_ptr<sql::PreparedStatement> stmt(Database::CreatePreppedStmt(
"SELECT fr.requested_player, best_friend, ci.name FROM "
"(SELECT CASE "
"WHEN player_id = ? THEN friend_id "
"WHEN friend_id = ? THEN player_id "
"END AS requested_player, best_friend FROM friends) AS fr "
"JOIN charinfo AS ci ON ci.id = fr.requested_player "
"WHERE fr.requested_player IS NOT NULL AND fr.requested_player != ?;"));
stmt->setUInt(1, static_cast<uint32_t>(playerID));
stmt->setUInt(2, static_cast<uint32_t>(playerID));
stmt->setUInt(3, static_cast<uint32_t>(playerID));
std::vector<FriendData> friends;
std::unique_ptr<sql::ResultSet> res(stmt->executeQuery());
while (res->next()) {
auto friendsList = Database::Get()->GetFriendsList(playerID);
for (const auto& friendData : friendsList) {
FriendData fd;
fd.isFTP = false; // not a thing in DLU
fd.friendID = res->getUInt(1);
fd.friendID = friendData.friendID;
GeneralUtils::SetBit(fd.friendID, eObjectBits::PERSISTENT);
GeneralUtils::SetBit(fd.friendID, eObjectBits::CHARACTER);
fd.isBestFriend = res->getInt(2) == 3; //0 = friends, 1 = left_requested, 2 = right_requested, 3 = both_accepted - are now bffs
fd.isBestFriend = friendData.isBestFriend; //0 = friends, 1 = left_requested, 2 = right_requested, 3 = both_accepted - are now bffs
if (fd.isBestFriend) player->countOfBestFriends += 1;
fd.friendName = res->getString(3);
fd.friendName = friendData.friendName;
//Now check if they're online:
auto fr = playerContainer.GetPlayerData(fd.friendID);
auto fr = Game::playerContainer.GetPlayerData(fd.friendID);
if (fr) {
fd.isOnline = true;
@@ -71,7 +54,7 @@ void ChatPacketHandler::HandleFriendlistRequest(Packet* packet) {
fd.zoneID = LWOZONEID();
}
friends.push_back(fd);
player->friends.push_back(fd);
}
//Now, we need to send the friendlist to the server they came from:
@@ -83,22 +66,17 @@ void ChatPacketHandler::HandleFriendlistRequest(Packet* packet) {
BitStreamUtils::WriteHeader(bitStream, eConnectionType::CLIENT, eClientMessageType::GET_FRIENDS_LIST_RESPONSE);
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)friends.size());
bitStream.Write<uint16_t>(player->friends.size());
for (auto& data : friends) {
for (auto& data : player->friends) {
data.Serialize(bitStream);
}
player->friends = friends;
SystemAddress sysAddr = player->sysAddr;
SEND_PACKET;
}
void ChatPacketHandler::HandleFriendRequest(Packet* packet) {
auto maxNumberOfBestFriendsAsString = Game::config->GetValue("max_number_of_best_friends");
// If this config option doesn't exist, default to 5 which is what live used.
auto maxNumberOfBestFriends = maxNumberOfBestFriendsAsString != "" ? std::stoi(maxNumberOfBestFriendsAsString) : 5U;
CINSTREAM_SKIP_HEADER;
LWOOBJID requestorPlayerID;
inStream.Read(requestorPlayerID);
@@ -117,7 +95,7 @@ void ChatPacketHandler::HandleFriendRequest(Packet* packet) {
char isBestFriendRequest{};
inStream.Read(isBestFriendRequest);
auto requestor = playerContainer.GetPlayerData(requestorPlayerID);
auto requestor = Game::playerContainer.GetPlayerData(requestorPlayerID);
if (!requestor) {
LOG("No requestor player %llu sent to %s found.", requestorPlayerID, playerName.c_str());
return;
@@ -127,7 +105,7 @@ void ChatPacketHandler::HandleFriendRequest(Packet* packet) {
SendFriendResponse(requestor, requestor, eAddFriendResponseType::MYTHRAN);
return;
};
std::unique_ptr<PlayerData> requestee(playerContainer.GetPlayerData(playerName));
std::unique_ptr<PlayerData> requestee(Game::playerContainer.GetPlayerData(playerName));
// Check if player is online first
if (isBestFriendRequest && !requestee) {
@@ -155,35 +133,26 @@ void ChatPacketHandler::HandleFriendRequest(Packet* packet) {
// If at this point we dont have a target, then they arent online and we cant send the request.
// Send the response code that corresponds to what the error is.
if (!requestee) {
std::unique_ptr<sql::PreparedStatement> nameQuery(Database::CreatePreppedStmt("SELECT name from charinfo where name = ?;"));
nameQuery->setString(1, playerName);
std::unique_ptr<sql::ResultSet> result(nameQuery->executeQuery());
requestee.reset(new PlayerData());
requestee->playerName = playerName;
auto responseType = Database::Get()->GetCharacterInfo(playerName)
? eAddFriendResponseType::NOTONLINE
: eAddFriendResponseType::INVALIDCHARACTER;
SendFriendResponse(requestor, requestee.get(), result->next() ? eAddFriendResponseType::NOTONLINE : eAddFriendResponseType::INVALIDCHARACTER);
SendFriendResponse(requestor, requestee.get(), responseType);
return;
}
if (isBestFriendRequest) {
std::unique_ptr<sql::PreparedStatement> friendUpdate(Database::CreatePreppedStmt("SELECT * FROM friends WHERE (player_id = ? AND friend_id = ?) OR (player_id = ? AND friend_id = ?) LIMIT 1;"));
friendUpdate->setUInt(1, static_cast<uint32_t>(requestorPlayerID));
friendUpdate->setUInt(2, static_cast<uint32_t>(requestee->playerID));
friendUpdate->setUInt(3, static_cast<uint32_t>(requestee->playerID));
friendUpdate->setUInt(4, static_cast<uint32_t>(requestorPlayerID));
std::unique_ptr<sql::ResultSet> result(friendUpdate->executeQuery());
LWOOBJID queryPlayerID = LWOOBJID_EMPTY;
LWOOBJID queryFriendID = LWOOBJID_EMPTY;
uint8_t oldBestFriendStatus{};
uint8_t bestFriendStatus{};
if (result->next()) {
auto bestFriendInfo = Database::Get()->GetBestFriendStatus(requestorPlayerID, requestee->playerID);
if (bestFriendInfo) {
// Get the IDs
queryPlayerID = result->getInt(1);
queryFriendID = result->getInt(2);
oldBestFriendStatus = result->getInt(3);
LWOOBJID queryPlayerID = bestFriendInfo->playerCharacterId;
LWOOBJID queryFriendID = bestFriendInfo->friendCharacterId;
oldBestFriendStatus = bestFriendInfo->bestFriendStatus;
bestFriendStatus = oldBestFriendStatus;
// Set the bits
@@ -204,22 +173,17 @@ void ChatPacketHandler::HandleFriendRequest(Packet* packet) {
// Only do updates if there was a change in the bff status.
if (oldBestFriendStatus != bestFriendStatus) {
if (requestee->countOfBestFriends >= maxNumberOfBestFriends || requestor->countOfBestFriends >= maxNumberOfBestFriends) {
if (requestee->countOfBestFriends >= maxNumberOfBestFriends) {
auto maxBestFriends = Game::playerContainer.GetMaxNumberOfBestFriends();
if (requestee->countOfBestFriends >= maxBestFriends || requestor->countOfBestFriends >= maxBestFriends) {
if (requestee->countOfBestFriends >= maxBestFriends) {
SendFriendResponse(requestor, requestee.get(), eAddFriendResponseType::THEIRFRIENDLISTFULL, false);
}
if (requestor->countOfBestFriends >= maxNumberOfBestFriends) {
if (requestor->countOfBestFriends >= maxBestFriends) {
SendFriendResponse(requestor, requestee.get(), eAddFriendResponseType::YOURFRIENDSLISTFULL, false);
}
} else {
// Then update the database with this new info.
std::unique_ptr<sql::PreparedStatement> updateQuery(Database::CreatePreppedStmt("UPDATE friends SET best_friend = ? WHERE (player_id = ? AND friend_id = ?) OR (player_id = ? AND friend_id = ?) LIMIT 1;"));
updateQuery->setUInt(1, bestFriendStatus);
updateQuery->setUInt(2, static_cast<uint32_t>(requestorPlayerID));
updateQuery->setUInt(3, static_cast<uint32_t>(requestee->playerID));
updateQuery->setUInt(4, static_cast<uint32_t>(requestee->playerID));
updateQuery->setUInt(5, static_cast<uint32_t>(requestorPlayerID));
updateQuery->executeUpdate();
Database::Get()->SetBestFriendStatus(requestorPlayerID, requestee->playerID, bestFriendStatus);
// Sent the best friend update here if the value is 3
if (bestFriendStatus == 3U) {
requestee->countOfBestFriends += 1;
@@ -242,8 +206,15 @@ void ChatPacketHandler::HandleFriendRequest(Packet* packet) {
if (requestor->sysAddr != UNASSIGNED_SYSTEM_ADDRESS) SendFriendResponse(requestor, requestee.get(), eAddFriendResponseType::WAITINGAPPROVAL, true, true);
}
} else {
// Do not send this if we are requesting to be a best friend.
SendFriendRequest(requestee.get(), requestor);
auto maxFriends = Game::playerContainer.GetMaxNumberOfFriends();
if (requestee->friends.size() >= maxFriends) {
SendFriendResponse(requestor, requestee.get(), eAddFriendResponseType::THEIRFRIENDLISTFULL, false);
} else if (requestor->friends.size() >= maxFriends) {
SendFriendResponse(requestor, requestee.get(), eAddFriendResponseType::YOURFRIENDSLISTFULL, false);
} else {
// Do not send this if we are requesting to be a best friend.
SendFriendRequest(requestee.get(), requestor);
}
}
// If the player is actually a player and not a ghost one defined above, release it from being deleted.
@@ -259,8 +230,8 @@ void ChatPacketHandler::HandleFriendResponse(Packet* packet) {
std::string friendName = PacketUtils::ReadString(0x15, packet, true);
//Now to try and find both of these:
auto requestor = playerContainer.GetPlayerData(playerID);
auto requestee = playerContainer.GetPlayerData(friendName);
auto requestor = Game::playerContainer.GetPlayerData(playerID);
auto requestee = Game::playerContainer.GetPlayerData(friendName);
if (!requestor || !requestee) return;
eAddFriendResponseType serverResponseCode{};
@@ -314,11 +285,7 @@ void ChatPacketHandler::HandleFriendResponse(Packet* packet) {
requesteeData.isOnline = true;
requestor->friends.push_back(requesteeData);
std::unique_ptr<sql::PreparedStatement> statement(Database::CreatePreppedStmt("INSERT IGNORE INTO `friends` (`player_id`, `friend_id`, `best_friend`) VALUES (?,?,?);"));
statement->setUInt(1, static_cast<uint32_t>(requestor->playerID));
statement->setUInt(2, static_cast<uint32_t>(requestee->playerID));
statement->setInt(3, 0);
statement->execute();
Database::Get()->AddFriend(requestor->playerID, requestee->playerID);
}
if (serverResponseCode != eAddFriendResponseType::DECLINED) SendFriendResponse(requestor, requestee, serverResponseCode, isAlreadyBestFriends);
@@ -333,28 +300,20 @@ void ChatPacketHandler::HandleRemoveFriend(Packet* packet) {
//we'll have to query the db here to find the user, since you can delete them while they're offline.
//First, we need to find their ID:
std::unique_ptr<sql::PreparedStatement> stmt(Database::CreatePreppedStmt("SELECT id FROM charinfo WHERE name=? LIMIT 1;"));
stmt->setString(1, friendName.c_str());
LWOOBJID friendID = 0;
std::unique_ptr<sql::ResultSet> res(stmt->executeQuery());
while (res->next()) {
friendID = res->getUInt(1);
auto friendIdResult = Database::Get()->GetCharacterInfo(friendName);
if (friendIdResult) {
friendID = friendIdResult->id;
}
// Convert friendID to LWOOBJID
GeneralUtils::SetBit(friendID, eObjectBits::PERSISTENT);
GeneralUtils::SetBit(friendID, eObjectBits::CHARACTER);
std::unique_ptr<sql::PreparedStatement> deletestmt(Database::CreatePreppedStmt("DELETE FROM friends WHERE (player_id = ? AND friend_id = ?) OR (player_id = ? AND friend_id = ?) LIMIT 1;"));
deletestmt->setUInt(1, static_cast<uint32_t>(playerID));
deletestmt->setUInt(2, static_cast<uint32_t>(friendID));
deletestmt->setUInt(3, static_cast<uint32_t>(friendID));
deletestmt->setUInt(4, static_cast<uint32_t>(playerID));
deletestmt->execute();
Database::Get()->RemoveFriend(playerID, friendID);
//Now, we need to send an update to notify the sender (and possibly, receiver) that their friendship has been ended:
auto goonA = playerContainer.GetPlayerData(playerID);
auto goonA = Game::playerContainer.GetPlayerData(playerID);
if (goonA) {
// Remove the friend from our list of friends
for (auto friendData = goonA->friends.begin(); friendData != goonA->friends.end(); friendData++) {
@@ -367,7 +326,7 @@ void ChatPacketHandler::HandleRemoveFriend(Packet* packet) {
SendRemoveFriend(goonA, friendName, true);
}
auto goonB = playerContainer.GetPlayerData(friendID);
auto goonB = Game::playerContainer.GetPlayerData(friendID);
if (!goonB) return;
// Do it again for other person
for (auto friendData = goonB->friends.begin(); friendData != goonB->friends.end(); friendData++) {
@@ -378,7 +337,7 @@ void ChatPacketHandler::HandleRemoveFriend(Packet* packet) {
}
}
std::string goonAName = GeneralUtils::UTF16ToWTF8(playerContainer.GetName(playerID));
std::string goonAName = GeneralUtils::UTF16ToWTF8(Game::playerContainer.GetName(playerID));
SendRemoveFriend(goonB, goonAName, true);
}
@@ -387,11 +346,11 @@ void ChatPacketHandler::HandleChatMessage(Packet* packet) {
LWOOBJID playerID = LWOOBJID_EMPTY;
inStream.Read(playerID);
auto* sender = playerContainer.GetPlayerData(playerID);
auto* sender = Game::playerContainer.GetPlayerData(playerID);
if (sender == nullptr) return;
if (playerContainer.GetIsMuted(sender)) return;
if (Game::playerContainer.GetIsMuted(sender)) return;
const auto senderName = std::string(sender->playerName.c_str());
@@ -406,12 +365,12 @@ void ChatPacketHandler::HandleChatMessage(Packet* packet) {
if (channel != 8) return;
auto* team = playerContainer.GetTeam(playerID);
auto* team = Game::playerContainer.GetTeam(playerID);
if (team == nullptr) return;
for (const auto memberId : team->memberIDs) {
auto* otherMember = playerContainer.GetPlayerData(memberId);
auto* otherMember = Game::playerContainer.GetPlayerData(memberId);
if (otherMember == nullptr) return;
@@ -445,11 +404,11 @@ void ChatPacketHandler::HandlePrivateChatMessage(Packet* packet) {
std::string message = PacketUtils::ReadString(0xAA, packet, true, 512);
//Get the bois:
auto goonA = playerContainer.GetPlayerData(senderID);
auto goonB = playerContainer.GetPlayerData(receiverName);
auto goonA = Game::playerContainer.GetPlayerData(senderID);
auto goonB = Game::playerContainer.GetPlayerData(receiverName);
if (!goonA || !goonB) return;
if (playerContainer.GetIsMuted(goonA)) return;
if (Game::playerContainer.GetIsMuted(goonA)) return;
std::string goonAName = goonA->playerName.c_str();
std::string goonBName = goonB->playerName.c_str();
@@ -507,25 +466,25 @@ void ChatPacketHandler::HandleTeamInvite(Packet* packet) {
inStream.Read(playerID);
std::string invitedPlayer = PacketUtils::ReadString(0x14, packet, true);
auto* player = playerContainer.GetPlayerData(playerID);
auto* player = Game::playerContainer.GetPlayerData(playerID);
if (player == nullptr) {
return;
}
auto* team = playerContainer.GetTeam(playerID);
auto* team = Game::playerContainer.GetTeam(playerID);
if (team == nullptr) {
team = playerContainer.CreateTeam(playerID);
team = Game::playerContainer.CreateTeam(playerID);
}
auto* other = playerContainer.GetPlayerData(invitedPlayer);
auto* other = Game::playerContainer.GetPlayerData(invitedPlayer);
if (other == nullptr) {
return;
}
if (playerContainer.GetTeam(other->playerID) != nullptr) {
if (Game::playerContainer.GetTeam(other->playerID) != nullptr) {
return;
}
@@ -558,12 +517,12 @@ void ChatPacketHandler::HandleTeamInviteResponse(Packet* packet) {
return;
}
auto* team = playerContainer.GetTeam(leaderID);
auto* team = Game::playerContainer.GetTeam(leaderID);
if (team == nullptr) {
LOG("Failed to find team for leader (%llu)", leaderID);
team = playerContainer.GetTeam(playerID);
team = Game::playerContainer.GetTeam(playerID);
}
if (team == nullptr) {
@@ -571,7 +530,7 @@ void ChatPacketHandler::HandleTeamInviteResponse(Packet* packet) {
return;
}
playerContainer.AddMember(team, playerID);
Game::playerContainer.AddMember(team, playerID);
}
void ChatPacketHandler::HandleTeamLeave(Packet* packet) {
@@ -581,12 +540,12 @@ void ChatPacketHandler::HandleTeamLeave(Packet* packet) {
uint32_t size = 0;
inStream.Read(size);
auto* team = playerContainer.GetTeam(playerID);
auto* team = Game::playerContainer.GetTeam(playerID);
LOG("(%llu) leaving team", playerID);
if (team != nullptr) {
playerContainer.RemoveMember(team, playerID, false, false, true);
Game::playerContainer.RemoveMember(team, playerID, false, false, true);
}
}
@@ -599,24 +558,24 @@ void ChatPacketHandler::HandleTeamKick(Packet* packet) {
LOG("(%llu) kicking (%s) from team", playerID, kickedPlayer.c_str());
auto* kicked = playerContainer.GetPlayerData(kickedPlayer);
auto* kicked = Game::playerContainer.GetPlayerData(kickedPlayer);
LWOOBJID kickedId = LWOOBJID_EMPTY;
if (kicked != nullptr) {
kickedId = kicked->playerID;
} else {
kickedId = playerContainer.GetId(GeneralUtils::UTF8ToUTF16(kickedPlayer));
kickedId = Game::playerContainer.GetId(GeneralUtils::UTF8ToUTF16(kickedPlayer));
}
if (kickedId == LWOOBJID_EMPTY) return;
auto* team = playerContainer.GetTeam(playerID);
auto* team = Game::playerContainer.GetTeam(playerID);
if (team != nullptr) {
if (team->leaderID != playerID || team->leaderID == kickedId) return;
playerContainer.RemoveMember(team, kickedId, false, true, false);
Game::playerContainer.RemoveMember(team, kickedId, false, true, false);
}
}
@@ -629,16 +588,16 @@ void ChatPacketHandler::HandleTeamPromote(Packet* packet) {
LOG("(%llu) promoting (%s) to team leader", playerID, promotedPlayer.c_str());
auto* promoted = playerContainer.GetPlayerData(promotedPlayer);
auto* promoted = Game::playerContainer.GetPlayerData(promotedPlayer);
if (promoted == nullptr) return;
auto* team = playerContainer.GetTeam(playerID);
auto* team = Game::playerContainer.GetTeam(playerID);
if (team != nullptr) {
if (team->leaderID != playerID) return;
playerContainer.PromoteMember(team, promoted->playerID);
Game::playerContainer.PromoteMember(team, promoted->playerID);
}
}
@@ -652,16 +611,16 @@ void ChatPacketHandler::HandleTeamLootOption(Packet* packet) {
char option;
inStream.Read(option);
auto* team = playerContainer.GetTeam(playerID);
auto* team = Game::playerContainer.GetTeam(playerID);
if (team != nullptr) {
if (team->leaderID != playerID) return;
team->lootFlag = option;
playerContainer.TeamStatusUpdate(team);
Game::playerContainer.TeamStatusUpdate(team);
playerContainer.UpdateTeamsOnWorld(team, false);
Game::playerContainer.UpdateTeamsOnWorld(team, false);
}
}
@@ -670,18 +629,18 @@ void ChatPacketHandler::HandleTeamStatusRequest(Packet* packet) {
LWOOBJID playerID = LWOOBJID_EMPTY;
inStream.Read(playerID);
auto* team = playerContainer.GetTeam(playerID);
auto* data = playerContainer.GetPlayerData(playerID);
auto* team = Game::playerContainer.GetTeam(playerID);
auto* data = Game::playerContainer.GetPlayerData(playerID);
if (team != nullptr && data != nullptr) {
if (team->local && data->zoneID.GetMapID() != team->zoneId.GetMapID() && data->zoneID.GetCloneID() != team->zoneId.GetCloneID()) {
playerContainer.RemoveMember(team, playerID, false, false, true, true);
Game::playerContainer.RemoveMember(team, playerID, false, false, true, true);
return;
}
if (team->memberIDs.size() <= 1 && !team->local) {
playerContainer.DisbandTeam(team);
Game::playerContainer.DisbandTeam(team);
return;
}
@@ -692,16 +651,16 @@ void ChatPacketHandler::HandleTeamStatusRequest(Packet* packet) {
ChatPacketHandler::SendTeamSetLeader(data, LWOOBJID_EMPTY);
}
playerContainer.TeamStatusUpdate(team);
Game::playerContainer.TeamStatusUpdate(team);
const auto leaderName = GeneralUtils::UTF8ToUTF16(data->playerName);
for (const auto memberId : team->memberIDs) {
auto* otherMember = playerContainer.GetPlayerData(memberId);
auto* otherMember = Game::playerContainer.GetPlayerData(memberId);
if (memberId == playerID) continue;
const auto memberName = playerContainer.GetName(memberId);
const auto memberName = Game::playerContainer.GetName(memberId);
if (otherMember != nullptr) {
ChatPacketHandler::SendTeamSetOffWorldFlag(otherMember, data->playerID, data->zoneID);
@@ -709,7 +668,7 @@ void ChatPacketHandler::HandleTeamStatusRequest(Packet* packet) {
ChatPacketHandler::SendTeamAddPlayer(data, false, team->local, false, memberId, memberName, otherMember != nullptr ? otherMember->zoneID : LWOZONEID(0, 0, 0));
}
playerContainer.UpdateTeamsOnWorld(team, false);
Game::playerContainer.UpdateTeamsOnWorld(team, false);
}
}
@@ -746,7 +705,7 @@ void ChatPacketHandler::SendTeamInviteConfirm(PlayerData* receiver, bool bLeader
bitStream.Write(ucLootFlag);
bitStream.Write(ucNumOfOtherPlayers);
bitStream.Write(ucResponseCode);
bitStream.Write(static_cast<uint32_t>(wsLeaderName.size()));
bitStream.Write<uint32_t>(wsLeaderName.size());
for (const auto character : wsLeaderName) {
bitStream.Write(character);
}
@@ -771,7 +730,7 @@ void ChatPacketHandler::SendTeamStatus(PlayerData* receiver, LWOOBJID i64LeaderI
bitStream.Write<uint32_t>(0); // BinaryBuffe, no clue what's in here
bitStream.Write(ucLootFlag);
bitStream.Write(ucNumOfOtherPlayers);
bitStream.Write(static_cast<uint32_t>(wsLeaderName.size()));
bitStream.Write<uint32_t>(wsLeaderName.size());
for (const auto character : wsLeaderName) {
bitStream.Write(character);
}
@@ -812,7 +771,7 @@ void ChatPacketHandler::SendTeamAddPlayer(PlayerData* receiver, bool bIsFreeTria
bitStream.Write(bLocal);
bitStream.Write(bNoLootOnDeath);
bitStream.Write(i64PlayerID);
bitStream.Write(static_cast<uint32_t>(wsPlayerName.size()));
bitStream.Write<uint32_t>(wsPlayerName.size());
for (const auto character : wsPlayerName) {
bitStream.Write(character);
}
@@ -843,7 +802,7 @@ void ChatPacketHandler::SendTeamRemovePlayer(PlayerData* receiver, bool bDisband
bitStream.Write(bLocal);
bitStream.Write(i64LeaderID);
bitStream.Write(i64PlayerID);
bitStream.Write(static_cast<uint32_t>(wsPlayerName.size()));
bitStream.Write<uint32_t>(wsPlayerName.size());
for (const auto character : wsPlayerName) {
bitStream.Write(character);
}

View File

@@ -19,12 +19,13 @@
#include "eChatMessageType.h"
#include "eChatInternalMessageType.h"
#include "eWorldMessageType.h"
#include "ChatIgnoreList.h"
#include "Game.h"
//RakNet includes:
#include "RakNetDefines.h"
#include <MessageIdentifiers.h>
#include "MessageIdentifiers.h"
namespace Game {
Logger* logger = nullptr;
@@ -32,16 +33,14 @@ namespace Game {
dConfig* config = nullptr;
dChatFilter* chatFilter = nullptr;
AssetManager* assetManager = nullptr;
bool shouldShutdown = false;
Game::signal_t lastSignal = 0;
std::mt19937 randomEngine;
PlayerContainer playerContainer;
}
Logger* SetupLogger();
void HandlePacket(Packet* packet);
PlayerContainer playerContainer;
int main(int argc, char** argv) {
constexpr uint32_t chatFramerate = mediumFramerate;
constexpr uint32_t chatFrameDelta = mediumFrameDelta;
@@ -49,17 +48,20 @@ int main(int argc, char** argv) {
Diagnostics::SetProcessFileName(argv[0]);
Diagnostics::Initialize();
std::signal(SIGINT, Game::OnSignal);
std::signal(SIGTERM, Game::OnSignal);
//Create all the objects we need to run our service:
Game::logger = SetupLogger();
if (!Game::logger) return EXIT_FAILURE;
//Read our config:
Game::config = new dConfig((BinaryPathFinder::GetBinaryDir() / "chatconfig.ini").string());
Game::config = new dConfig("chatconfig.ini");
Game::logger->SetLogToConsole(Game::config->GetValue("log_to_console") != "0");
Game::logger->SetLogDebugStatements(Game::config->GetValue("log_debug_statements") == "1");
LOG("Starting Chat server...");
LOG("Version: %i.%i", PROJECT_VERSION_MAJOR, PROJECT_VERSION_MINOR);
LOG("Version: %s", PROJECT_VERSION);
LOG("Compiled on: %s", __TIMESTAMP__);
try {
@@ -78,13 +80,8 @@ int main(int argc, char** argv) {
}
//Connect to the MySQL Database
std::string mysql_host = Game::config->GetValue("mysql_host");
std::string mysql_database = Game::config->GetValue("mysql_database");
std::string mysql_username = Game::config->GetValue("mysql_username");
std::string mysql_password = Game::config->GetValue("mysql_password");
try {
Database::Connect(mysql_host, mysql_database, mysql_username, mysql_password);
Database::Connect();
} catch (sql::SQLException& ex) {
LOG("Got an error while connecting to the database: %s", ex.what());
Database::Destroy("ChatServer");
@@ -96,28 +93,30 @@ int main(int argc, char** argv) {
//Find out the master's IP:
std::string masterIP;
uint32_t masterPort = 1000;
sql::PreparedStatement* stmt = Database::CreatePreppedStmt("SELECT ip, port FROM servers WHERE name='master';");
auto res = stmt->executeQuery();
while (res->next()) {
masterIP = res->getString(1).c_str();
masterPort = res->getInt(2);
auto masterInfo = Database::Get()->GetMasterInfo();
if (masterInfo) {
masterIP = masterInfo->ip;
masterPort = masterInfo->port;
}
delete res;
delete stmt;
//It's safe to pass 'localhost' here, as the IP is only used as the external IP.
uint32_t maxClients = 50;
uint32_t maxClients = 999;
uint32_t ourPort = 1501;
if (Game::config->GetValue("max_clients") != "") maxClients = std::stoi(Game::config->GetValue("max_clients"));
if (Game::config->GetValue("port") != "") ourPort = std::atoi(Game::config->GetValue("port").c_str());
std::string ourIP = "localhost";
GeneralUtils::TryParse(Game::config->GetValue("max_clients"), maxClients);
GeneralUtils::TryParse(Game::config->GetValue("chat_server_port"), ourPort);
const auto externalIPString = Game::config->GetValue("external_ip");
if (!externalIPString.empty()) ourIP = externalIPString;
Game::server = new dServer(Game::config->GetValue("external_ip"), ourPort, 0, maxClients, false, true, Game::logger, masterIP, masterPort, ServerType::Chat, Game::config, &Game::shouldShutdown);
Game::server = new dServer(ourIP, ourPort, 0, maxClients, false, true, Game::logger, masterIP, masterPort, ServerType::Chat, Game::config, &Game::lastSignal);
Game::chatFilter = new dChatFilter(Game::assetManager->GetResPath().string() + "/chatplus_en_us", bool(std::stoi(Game::config->GetValue("dont_generate_dcf"))));
bool dontGenerateDCF = false;
GeneralUtils::TryParse(Game::config->GetValue("dont_generate_dcf"), dontGenerateDCF);
Game::chatFilter = new dChatFilter(Game::assetManager->GetResPath().string() + "/chatplus_en_us", dontGenerateDCF);
Game::randomEngine = std::mt19937(time(0));
Game::playerContainer.Initialize();
//Run it until server gets a kill message from Master:
auto t = std::chrono::high_resolution_clock::now();
Packet* packet = nullptr;
@@ -127,7 +126,8 @@ int main(int argc, char** argv) {
uint32_t framesSinceMasterDisconnect = 0;
uint32_t framesSinceLastSQLPing = 0;
while (!Game::shouldShutdown) {
Game::logger->Flush(); // once immediately before main loop
while (!Game::ShouldShutdown()) {
//Check if we're still connected to master:
if (!Game::server->GetIsConnectedToMaster()) {
framesSinceMasterDisconnect++;
@@ -158,15 +158,12 @@ int main(int argc, char** argv) {
//Find out the master's IP for absolutely no reason:
std::string masterIP;
uint32_t masterPort;
sql::PreparedStatement* stmt = Database::CreatePreppedStmt("SELECT ip, port FROM servers WHERE name='master';");
auto res = stmt->executeQuery();
while (res->next()) {
masterIP = res->getString(1).c_str();
masterPort = res->getInt(2);
}
delete res;
delete stmt;
auto masterInfo = Database::Get()->GetMasterInfo();
if (masterInfo) {
masterIP = masterInfo->ip;
masterPort = masterInfo->port;
}
framesSinceLastSQLPing = 0;
} else framesSinceLastSQLPing++;
@@ -211,19 +208,19 @@ void HandlePacket(Packet* packet) {
if (static_cast<eConnectionType>(packet->data[1]) == eConnectionType::CHAT_INTERNAL) {
switch (static_cast<eChatInternalMessageType>(packet->data[3])) {
case eChatInternalMessageType::PLAYER_ADDED_NOTIFICATION:
playerContainer.InsertPlayer(packet);
Game::playerContainer.InsertPlayer(packet);
break;
case eChatInternalMessageType::PLAYER_REMOVED_NOTIFICATION:
playerContainer.RemovePlayer(packet);
Game::playerContainer.RemovePlayer(packet);
break;
case eChatInternalMessageType::MUTE_UPDATE:
playerContainer.MuteUpdate(packet);
Game::playerContainer.MuteUpdate(packet);
break;
case eChatInternalMessageType::CREATE_TEAM:
playerContainer.CreateTeamServer(packet);
Game::playerContainer.CreateTeamServer(packet);
break;
case eChatInternalMessageType::ANNOUNCEMENT: {
@@ -245,7 +242,15 @@ void HandlePacket(Packet* packet) {
break;
case eChatMessageType::GET_IGNORE_LIST:
LOG("Asked for ignore list, but is unimplemented right now.");
ChatIgnoreList::GetIgnoreList(packet);
break;
case eChatMessageType::ADD_IGNORE:
ChatIgnoreList::AddIgnore(packet);
break;
case eChatMessageType::REMOVE_IGNORE:
ChatIgnoreList::RemoveIgnore(packet);
break;
case eChatMessageType::TEAM_GET_STATUS:

View File

@@ -13,11 +13,13 @@
#include "ChatPackets.h"
#include "dConfig.h"
PlayerContainer::PlayerContainer() {
void PlayerContainer::Initialize() {
GeneralUtils::TryParse<uint32_t>(Game::config->GetValue("max_number_of_best_friends"), m_MaxNumberOfBestFriends);
GeneralUtils::TryParse<uint32_t>(Game::config->GetValue("max_number_of_friends"), m_MaxNumberOfFriends);
}
PlayerContainer::~PlayerContainer() {
mPlayers.clear();
m_Players.clear();
}
TeamData::TeamData() {
@@ -41,19 +43,12 @@ void PlayerContainer::InsertPlayer(Packet* packet) {
inStream.Read(data->muteExpire);
data->sysAddr = packet->systemAddress;
mNames[data->playerID] = GeneralUtils::UTF8ToUTF16(data->playerName);
m_Names[data->playerID] = GeneralUtils::UTF8ToUTF16(data->playerName);
mPlayers.insert(std::make_pair(data->playerID, data));
m_Players.insert(std::make_pair(data->playerID, data));
LOG("Added user: %s (%llu), zone: %i", data->playerName.c_str(), data->playerID, data->zoneID.GetMapID());
auto* insertLog = Database::CreatePreppedStmt("INSERT INTO activity_log (character_id, activity, time, map_id) VALUES (?, ?, ?, ?);");
insertLog->setInt(1, data->playerID);
insertLog->setInt(2, 0);
insertLog->setUInt64(3, time(nullptr));
insertLog->setInt(4, data->zoneID.GetMapID());
insertLog->executeUpdate();
Database::Get()->UpdateActivityLog(data->playerID, eActivityType::PlayerLoggedIn, data->zoneID.GetMapID());
}
void PlayerContainer::RemovePlayer(Packet* packet) {
@@ -88,16 +83,9 @@ void PlayerContainer::RemovePlayer(Packet* packet) {
}
LOG("Removed user: %llu", playerID);
mPlayers.erase(playerID);
m_Players.erase(playerID);
auto* insertLog = Database::CreatePreppedStmt("INSERT INTO activity_log (character_id, activity, time, map_id) VALUES (?, ?, ?, ?);");
insertLog->setInt(1, playerID);
insertLog->setInt(2, 1);
insertLog->setUInt64(3, time(nullptr));
insertLog->setInt(4, player->zoneID.GetMapID());
insertLog->executeUpdate();
Database::Get()->UpdateActivityLog(playerID, eActivityType::PlayerLoggedOut, player->zoneID.GetMapID());
}
void PlayerContainer::MuteUpdate(Packet* packet) {
@@ -191,7 +179,7 @@ TeamData* PlayerContainer::CreateLocalTeam(std::vector<LWOOBJID> members) {
TeamData* PlayerContainer::CreateTeam(LWOOBJID leader, bool local) {
auto* team = new TeamData();
team->teamID = ++mTeamIDCounter;
team->teamID = ++m_TeamIDCounter;
team->leaderID = leader;
team->local = local;
@@ -366,7 +354,7 @@ void PlayerContainer::UpdateTeamsOnWorld(TeamData* team, bool deleteTeam) {
if (!deleteTeam) {
bitStream.Write(team->lootFlag);
bitStream.Write(static_cast<char>(team->memberIDs.size()));
bitStream.Write<char>(team->memberIDs.size());
for (const auto memberID : team->memberIDs) {
bitStream.Write(memberID);
}
@@ -376,15 +364,15 @@ void PlayerContainer::UpdateTeamsOnWorld(TeamData* team, bool deleteTeam) {
}
std::u16string PlayerContainer::GetName(LWOOBJID playerID) {
const auto& pair = mNames.find(playerID);
const auto& pair = m_Names.find(playerID);
if (pair == mNames.end()) return u"";
if (pair == m_Names.end()) return u"";
return pair->second;
}
LWOOBJID PlayerContainer::GetId(const std::u16string& playerName) {
for (const auto& pair : mNames) {
for (const auto& pair : m_Names) {
if (pair.second == playerName) {
return pair.first;
}

View File

@@ -7,12 +7,26 @@
#include "dServer.h"
#include <unordered_map>
struct IgnoreData {
inline bool operator==(const std::string& other) const noexcept {
return playerName == other;
}
inline bool operator==(const LWOOBJID& other) const noexcept {
return playerId == other;
}
LWOOBJID playerId;
std::string playerName;
};
struct PlayerData {
LWOOBJID playerID;
std::string playerName;
SystemAddress sysAddr;
LWOZONEID zoneID;
std::vector<FriendData> friends;
std::vector<IgnoreData> ignoredPlayers;
time_t muteExpire;
uint8_t countOfBestFriends = 0;
};
@@ -29,9 +43,9 @@ struct TeamData {
class PlayerContainer {
public:
PlayerContainer();
~PlayerContainer();
void Initialize();
void InsertPlayer(Packet* packet);
void RemovePlayer(Packet* packet);
void MuteUpdate(Packet* packet);
@@ -39,13 +53,13 @@ public:
void BroadcastMuteUpdate(LWOOBJID player, time_t time);
PlayerData* GetPlayerData(const LWOOBJID& playerID) {
auto it = mPlayers.find(playerID);
if (it != mPlayers.end()) return it->second;
auto it = m_Players.find(playerID);
if (it != m_Players.end()) return it->second;
return nullptr;
}
PlayerData* GetPlayerData(const std::string& playerName) {
for (auto player : mPlayers) {
for (auto player : m_Players) {
if (player.second) {
std::string pn = player.second->playerName.c_str();
if (pn == playerName) return player.second;
@@ -67,13 +81,17 @@ public:
std::u16string GetName(LWOOBJID playerID);
LWOOBJID GetId(const std::u16string& playerName);
bool GetIsMuted(PlayerData* data);
uint32_t GetMaxNumberOfBestFriends() { return m_MaxNumberOfBestFriends; }
uint32_t GetMaxNumberOfFriends() { return m_MaxNumberOfFriends; }
std::map<LWOOBJID, PlayerData*>& GetAllPlayerData() { return mPlayers; }
std::map<LWOOBJID, PlayerData*>& GetAllPlayerData() { return m_Players; }
private:
LWOOBJID mTeamIDCounter = 0;
std::map<LWOOBJID, PlayerData*> mPlayers;
LWOOBJID m_TeamIDCounter = 0;
std::map<LWOOBJID, PlayerData*> m_Players;
std::vector<TeamData*> mTeams;
std::unordered_map<LWOOBJID, std::u16string> mNames;
std::unordered_map<LWOOBJID, std::u16string> m_Names;
uint32_t m_MaxNumberOfBestFriends = 5;
uint32_t m_MaxNumberOfFriends = 50;
};

View File

@@ -54,17 +54,17 @@ void RakNet::BitStream::Write<AMFBaseValue&>(AMFBaseValue& value) {
* RakNet writes in the correct byte order - do not reverse this.
*/
void WriteUInt29(RakNet::BitStream* bs, uint32_t v) {
unsigned char b4 = (unsigned char)v;
unsigned char b4 = static_cast<unsigned char>(v);
if (v < 0x00200000) {
b4 = b4 & 0x7F;
if (v > 0x7F) {
unsigned char b3;
v = v >> 7;
b3 = ((unsigned char)(v)) | 0x80;
b3 = static_cast<unsigned char>(v) | 0x80;
if (v > 0x7F) {
unsigned char b2;
v = v >> 7;
b2 = ((unsigned char)(v)) | 0x80;
b2 = static_cast<unsigned char>(v) | 0x80;
bs->Write(b2);
}
@@ -76,11 +76,11 @@ void WriteUInt29(RakNet::BitStream* bs, uint32_t v) {
unsigned char b3;
v = v >> 8;
b3 = ((unsigned char)(v)) | 0x80;
b3 = static_cast<unsigned char>(v) | 0x80;
v = v >> 7;
b2 = ((unsigned char)(v)) | 0x80;
b2 = static_cast<unsigned char>(v) | 0x80;
v = v >> 7;
b1 = ((unsigned char)(v)) | 0x80;
b1 = static_cast<unsigned char>(v) | 0x80;
bs->Write(b1);
bs->Write(b2);
@@ -105,8 +105,8 @@ void WriteFlagNumber(RakNet::BitStream* bs, uint32_t v) {
* RakNet writes in the correct byte order - do not reverse this.
*/
void WriteAMFString(RakNet::BitStream* bs, const std::string& str) {
WriteFlagNumber(bs, (uint32_t)str.size());
bs->Write(str.c_str(), (uint32_t)str.size());
WriteFlagNumber(bs, static_cast<uint32_t>(str.size()));
bs->Write(str.c_str(), static_cast<uint32_t>(str.size()));
}
/**

View File

@@ -4,7 +4,7 @@
#include "Amf3.h"
// RakNet
#include <BitStream.h>
#include "BitStream.h"
/*!
\file AmfSerialize.h

View File

@@ -1,14 +1,6 @@
#include "BinaryIO.h"
#include <string>
void BinaryIO::WriteString(const std::string& stringToWrite, std::ofstream& outstream) {
//BinaryWrite(outstream, uint32_t(stringToWrite.length()));
for (size_t i = 0; i < size_t(stringToWrite.length()); ++i) {
BinaryIO::BinaryWrite(outstream, stringToWrite[i]);
}
}
//For reading null-terminated strings
std::string BinaryIO::ReadString(std::istream& instream) {
std::string toReturn;
@@ -23,36 +15,3 @@ std::string BinaryIO::ReadString(std::istream& instream) {
return toReturn;
}
//For reading strings of a specific size
std::string BinaryIO::ReadString(std::istream& instream, size_t size) {
std::string toReturn;
char buffer;
for (size_t i = 0; i < size; ++i) {
BinaryIO::BinaryRead(instream, buffer);
toReturn += buffer;
}
return toReturn;
}
std::string BinaryIO::ReadWString(std::istream& instream) {
size_t size;
BinaryRead(instream, size);
//toReturn.resize(size);
std::string test;
unsigned char buf;
for (size_t i = 0; i < size; ++i) {
//instream.ignore(1);
BinaryRead(instream, buf);
test += buf;
}
//printf("%s\n", test.c_str());
//instream.read((char*)&toReturn[0], size * 2);
//std::string str(toReturn.begin(), toReturn.end());
return test;
}

View File

@@ -1,8 +1,17 @@
#pragma once
#ifndef __BINARYIO__H__
#define __BINARYIO__H__
#include <iostream>
#include <fstream>
#include <string>
#include "Game.h"
#include "Logger.h"
namespace BinaryIO {
template<typename T>
std::ostream& BinaryWrite(std::ostream& stream, const T& value) {
return stream.write(reinterpret_cast<const char*>(&value), sizeof(T));
@@ -15,13 +24,51 @@ namespace BinaryIO {
return stream.read(reinterpret_cast<char*>(&value), sizeof(T));
}
void WriteString(const std::string& stringToWrite, std::ofstream& outstream);
enum class ReadType : int8_t {
WideString = 0,
String = 1,
};
template<typename SizeType>
inline void ReadString(std::istream& stream, std::u16string& value) {
static_assert(std::is_integral<SizeType>::value, "SizeType must be an integral type.");
SizeType size;
BinaryRead(stream, size);
if (!stream.good()) throw std::runtime_error("Failed to read from istream.");
value.resize(size);
stream.read(reinterpret_cast<char*>(value.data()), size * sizeof(uint16_t));
}
template<typename SizeType>
inline void ReadString(std::istream& stream, std::string& value, ReadType readType) {
static_assert(std::is_integral<SizeType>::value, "SizeType must be an integral type.");
SizeType size;
BinaryRead(stream, size);
if (!stream.good()) throw std::runtime_error("Failed to read from istream.");
value.resize(size);
if (readType == ReadType::WideString) {
uint16_t wideChar;
// Faster to do this than to read a u16string and convert it to a string since we only go through allocator once
for (SizeType i = 0; i < size; ++i) {
BinaryRead(stream, wideChar);
value[i] = static_cast<char>(wideChar);
}
} else {
stream.read(value.data(), size);
}
}
std::string ReadString(std::istream& instream);
std::string ReadString(std::istream& instream, size_t size);
std::string ReadWString(std::istream& instream);
inline bool DoesFileExist(const std::string& name) {
std::ifstream f(name.c_str());
return f.good();
}
}
#endif //!__BINARYIO__H__

View File

@@ -13,9 +13,8 @@
//! Forward declarations
std::unique_ptr<sql::ResultSet> GetModelsFromDatabase();
void WriteSd0Magic(char* input, uint32_t chunkSize);
bool CheckSd0Magic(sql::Blob* streamToCheck);
bool CheckSd0Magic(std::istream& streamToCheck);
/**
* @brief Truncates all models with broken data from the database.
@@ -24,28 +23,24 @@ bool CheckSd0Magic(sql::Blob* streamToCheck);
*/
uint32_t BrickByBrickFix::TruncateBrokenBrickByBrickXml() {
uint32_t modelsTruncated{};
auto modelsToTruncate = GetModelsFromDatabase();
bool previousCommitValue = Database::GetAutoCommit();
Database::SetAutoCommit(false);
while (modelsToTruncate->next()) {
std::unique_ptr<sql::PreparedStatement> ugcModelToDelete(Database::CreatePreppedStmt("DELETE FROM ugc WHERE ugc.id = ?;"));
std::unique_ptr<sql::PreparedStatement> pcModelToDelete(Database::CreatePreppedStmt("DELETE FROM properties_contents WHERE ugc_id = ?;"));
auto modelsToTruncate = Database::Get()->GetAllUgcModels();
bool previousCommitValue = Database::Get()->GetAutoCommit();
Database::Get()->SetAutoCommit(false);
for (auto& model : modelsToTruncate) {
std::string completeUncompressedModel{};
uint32_t chunkCount{};
uint64_t modelId = modelsToTruncate->getInt(1);
std::unique_ptr<sql::Blob> modelAsSd0(modelsToTruncate->getBlob(2));
// Check that header is sd0 by checking for the sd0 magic.
if (CheckSd0Magic(modelAsSd0.get())) {
if (CheckSd0Magic(model.lxfmlData)) {
while (true) {
uint32_t chunkSize{};
modelAsSd0->read(reinterpret_cast<char*>(&chunkSize), sizeof(uint32_t)); // Extract chunk size from istream
model.lxfmlData.read(reinterpret_cast<char*>(&chunkSize), sizeof(uint32_t)); // Extract chunk size from istream
// Check if good here since if at the end of an sd0 file, this will have eof flagged.
if (!modelAsSd0->good()) break;
if (!model.lxfmlData.good()) break;
std::unique_ptr<uint8_t[]> compressedChunk(new uint8_t[chunkSize]);
for (uint32_t i = 0; i < chunkSize; i++) {
compressedChunk[i] = modelAsSd0->get();
compressedChunk[i] = model.lxfmlData.get();
}
// Ignore the valgrind warning about uninitialized values. These are discarded later when we know the actual uncompressed size.
@@ -56,10 +51,10 @@ uint32_t BrickByBrickFix::TruncateBrokenBrickByBrickXml() {
if (actualUncompressedSize != -1) {
uint32_t previousSize = completeUncompressedModel.size();
completeUncompressedModel.append((char*)uncompressedChunk.get());
completeUncompressedModel.append(reinterpret_cast<char*>(uncompressedChunk.get()));
completeUncompressedModel.resize(previousSize + actualUncompressedSize);
} else {
LOG("Failed to inflate chunk %i for model %llu. Error: %i", chunkCount, modelId, err);
LOG("Failed to inflate chunk %i for model %llu. Error: %i", chunkCount, model.id, err);
break;
}
chunkCount++;
@@ -75,26 +70,20 @@ uint32_t BrickByBrickFix::TruncateBrokenBrickByBrickXml() {
"</LXFML>",
completeUncompressedModel.length() >= 15 ? completeUncompressedModel.length() - 15 : 0) == std::string::npos
) {
LOG("Brick-by-brick model %llu will be deleted!", modelId);
ugcModelToDelete->setInt64(1, modelsToTruncate->getInt64(1));
pcModelToDelete->setInt64(1, modelsToTruncate->getInt64(1));
ugcModelToDelete->execute();
pcModelToDelete->execute();
LOG("Brick-by-brick model %llu will be deleted!", model.id);
Database::Get()->DeleteUgcModelData(model.id);
modelsTruncated++;
}
}
} else {
LOG("Brick-by-brick model %llu will be deleted!", modelId);
ugcModelToDelete->setInt64(1, modelsToTruncate->getInt64(1));
pcModelToDelete->setInt64(1, modelsToTruncate->getInt64(1));
ugcModelToDelete->execute();
pcModelToDelete->execute();
LOG("Brick-by-brick model %llu will be deleted!", model.id);
Database::Get()->DeleteUgcModelData(model.id);
modelsTruncated++;
}
}
Database::Commit();
Database::SetAutoCommit(previousCommitValue);
Database::Get()->Commit();
Database::Get()->SetAutoCommit(previousCommitValue);
return modelsTruncated;
}
@@ -106,21 +95,17 @@ uint32_t BrickByBrickFix::TruncateBrokenBrickByBrickXml() {
*/
uint32_t BrickByBrickFix::UpdateBrickByBrickModelsToSd0() {
uint32_t updatedModels = 0;
auto modelsToUpdate = GetModelsFromDatabase();
auto previousAutoCommitState = Database::GetAutoCommit();
Database::SetAutoCommit(false);
std::unique_ptr<sql::PreparedStatement> insertionStatement(Database::CreatePreppedStmt("UPDATE ugc SET lxfml = ? WHERE id = ?;"));
while (modelsToUpdate->next()) {
int64_t modelId = modelsToUpdate->getInt64(1);
std::unique_ptr<sql::Blob> oldLxfml(modelsToUpdate->getBlob(2));
auto modelsToUpdate = Database::Get()->GetAllUgcModels();
auto previousAutoCommitState = Database::Get()->GetAutoCommit();
Database::Get()->SetAutoCommit(false);
for (auto& model : modelsToUpdate) {
// Check if the stored blob starts with zlib magic (0x78 0xDA - best compression of zlib)
// If it does, convert it to sd0.
if (oldLxfml->get() == 0x78 && oldLxfml->get() == 0xDA) {
if (model.lxfmlData.get() == 0x78 && model.lxfmlData.get() == 0xDA) {
// Get and save size of zlib compressed chunk.
oldLxfml->seekg(0, std::ios::end);
uint32_t oldLxfmlSize = static_cast<uint32_t>(oldLxfml->tellg());
oldLxfml->seekg(0);
model.lxfmlData.seekg(0, std::ios::end);
uint32_t oldLxfmlSize = static_cast<uint32_t>(model.lxfmlData.tellg());
model.lxfmlData.seekg(0);
// Allocate 9 extra bytes. 5 for sd0 magic, 4 for the only zlib compressed size.
uint32_t oldLxfmlSizeWithHeader = oldLxfmlSize + 9;
@@ -128,34 +113,27 @@ uint32_t BrickByBrickFix::UpdateBrickByBrickModelsToSd0() {
WriteSd0Magic(sd0ConvertedModel.get(), oldLxfmlSize);
for (uint32_t i = 9; i < oldLxfmlSizeWithHeader; i++) {
sd0ConvertedModel.get()[i] = oldLxfml->get();
sd0ConvertedModel.get()[i] = model.lxfmlData.get();
}
std::string outputString(sd0ConvertedModel.get(), oldLxfmlSizeWithHeader);
std::istringstream outputStringStream(outputString);
insertionStatement->setBlob(1, static_cast<std::istream*>(&outputStringStream));
insertionStatement->setInt64(2, modelId);
try {
insertionStatement->executeUpdate();
LOG("Updated model %i to sd0", modelId);
Database::Get()->UpdateUgcModelData(model.id, outputStringStream);
LOG("Updated model %i to sd0", model.id);
updatedModels++;
} catch (sql::SQLException exception) {
LOG("Failed to update model %i. This model should be inspected manually to see why."
"The database error is %s", modelId, exception.what());
"The database error is %s", model.id, exception.what());
}
}
}
Database::Commit();
Database::SetAutoCommit(previousAutoCommitState);
Database::Get()->Commit();
Database::Get()->SetAutoCommit(previousAutoCommitState);
return updatedModels;
}
std::unique_ptr<sql::ResultSet> GetModelsFromDatabase() {
std::unique_ptr<sql::PreparedStatement> modelsRawDataQuery(Database::CreatePreppedStmt("SELECT id, lxfml FROM ugc;"));
return std::unique_ptr<sql::ResultSet>(modelsRawDataQuery->executeQuery());
}
/**
* @brief Writes sd0 magic at the front of a char*
*
@@ -171,6 +149,6 @@ void WriteSd0Magic(char* input, uint32_t chunkSize) {
*reinterpret_cast<uint32_t*>(input + 5) = chunkSize; // Write the integer to the character array
}
bool CheckSd0Magic(sql::Blob* streamToCheck) {
return streamToCheck->get() == 's' && streamToCheck->get() == 'd' && streamToCheck->get() == '0' && streamToCheck->get() == 0x01 && streamToCheck->get() == 0xFF;
bool CheckSd0Magic(std::istream& streamToCheck) {
return streamToCheck.get() == 's' && streamToCheck.get() == 'd' && streamToCheck.get() == '0' && streamToCheck.get() == 0x01 && streamToCheck.get() == 0xFF;
}

View File

@@ -5,6 +5,7 @@ set(DCOMMON_SOURCES
"dConfig.cpp"
"Diagnostics.cpp"
"Logger.cpp"
"Game.cpp"
"GeneralUtils.cpp"
"LDFFormat.cpp"
"MD5.cpp"

View File

@@ -71,7 +71,7 @@ LONG CALLBACK unhandled_handler(EXCEPTION_POINTERS* e) {
#include <cstring>
#include <exception>
#if defined(__include_backtrace__)
#if defined(INCLUDE_BACKTRACE)
#include <backtrace.h>
#include <backtrace-supported.h>
@@ -115,7 +115,14 @@ void GenerateDump() {
}
void CatchUnhandled(int sig) {
#ifndef __include_backtrace__
std::exception_ptr eptr = std::current_exception();
try {
if (eptr) std::rethrow_exception(eptr);
} catch(const std::exception& e) {
LOG("Caught exception: '%s'", e.what());
}
#ifndef INCLUDE_BACKTRACE
std::string fileName = Diagnostics::GetOutDirectory() + "crash_" + Diagnostics::GetProcessName() + "_" + std::to_string(getpid()) + ".log";
LOG("Encountered signal %i, creating crash dump %s", sig, fileName.c_str());
@@ -167,7 +174,7 @@ void CatchUnhandled(int sig) {
backtrace_symbols_fd(array, size, STDOUT_FILENO);
# endif // defined(__GNUG__)
#else // __include_backtrace__
#else // INCLUDE_BACKTRACE
struct backtrace_state* state = backtrace_create_state(
Diagnostics::GetProcessFileName().c_str(),
@@ -178,7 +185,7 @@ void CatchUnhandled(int sig) {
struct bt_ctx ctx = { state, 0 };
Bt(state);
#endif // __include_backtrace__
#endif // INCLUDE_BACKTRACE
exit(EXIT_FAILURE);
}
@@ -197,10 +204,10 @@ void MakeBacktrace() {
sigact.sa_sigaction = CritErrHdlr;
sigact.sa_flags = SA_RESTART | SA_SIGINFO;
if (sigaction(SIGSEGV, &sigact, (struct sigaction*)nullptr) != 0 ||
sigaction(SIGFPE, &sigact, (struct sigaction*)nullptr) != 0 ||
sigaction(SIGABRT, &sigact, (struct sigaction*)nullptr) != 0 ||
sigaction(SIGILL, &sigact, (struct sigaction*)nullptr) != 0) {
if (sigaction(SIGSEGV, &sigact, nullptr) != 0 ||
sigaction(SIGFPE, &sigact, nullptr) != 0 ||
sigaction(SIGABRT, &sigact, nullptr) != 0 ||
sigaction(SIGILL, &sigact, nullptr) != 0) {
fprintf(stderr, "error setting signal handler for %d (%s)\n",
SIGSEGV,
strsignal(SIGSEGV));

View File

@@ -28,19 +28,17 @@ FdbToSqlite::Convert::Convert(std::string binaryOutPath) {
this->m_BinaryOutPath = binaryOutPath;
}
bool FdbToSqlite::Convert::ConvertDatabase(AssetMemoryBuffer& buffer) {
bool FdbToSqlite::Convert::ConvertDatabase(AssetStream& buffer) {
if (m_ConversionStarted) return false;
std::istream cdClientBuffer(&buffer);
this->m_ConversionStarted = true;
try {
CDClientDatabase::Connect(m_BinaryOutPath + "/CDServer.sqlite");
CDClientDatabase::ExecuteQuery("BEGIN TRANSACTION;");
int32_t numberOfTables = ReadInt32(cdClientBuffer);
ReadTables(numberOfTables, cdClientBuffer);
int32_t numberOfTables = ReadInt32(buffer);
ReadTables(numberOfTables, buffer);
CDClientDatabase::ExecuteQuery("COMMIT;");
} catch (CppSQLite3Exception& e) {

View File

@@ -7,7 +7,7 @@
#include <iosfwd>
#include <map>
class AssetMemoryBuffer;
#include "AssetManager.h"
enum class eSqliteDataType : int32_t;
@@ -27,7 +27,7 @@ namespace FdbToSqlite {
*
* @return true if the database was converted properly, false otherwise.
*/
bool ConvertDatabase(AssetMemoryBuffer& buffer);
bool ConvertDatabase(AssetStream& buffer);
/**
* @brief Reads a 32 bit int from the fdb file.

7
dCommon/Game.cpp Normal file
View File

@@ -0,0 +1,7 @@
#include "Game.h"
namespace Game {
void OnSignal(int signal) {
lastSignal = signal;
}
}

View File

@@ -1,6 +1,8 @@
#pragma once
#include <string>
#include <random>
#include <csignal>
class dServer;
class Logger;
@@ -12,8 +14,10 @@ class AssetManager;
struct SystemAddress;
class EntityManager;
class dZoneManager;
class PlayerContainer;
namespace Game {
using signal_t = volatile std::sig_atomic_t;
extern Logger* logger;
extern dServer* server;
extern InstanceManager* im;
@@ -23,7 +27,14 @@ namespace Game {
extern RakPeerInterface* chatServer;
extern AssetManager* assetManager;
extern SystemAddress chatSysAddr;
extern bool shouldShutdown;
extern signal_t lastSignal;
extern EntityManager* entityManager;
extern dZoneManager* zoneManager;
extern PlayerContainer playerContainer;
extern std::string projectVersion;
inline bool ShouldShutdown() {
return lastSignal != 0;
}
void OnSignal(int signal);
}

View File

@@ -53,7 +53,7 @@ bool _IsSuffixChar(uint8_t c) {
bool GeneralUtils::_NextUTF8Char(std::string_view& slice, uint32_t& out) {
size_t rem = slice.length();
if (slice.empty()) return false;
const uint8_t* bytes = (const uint8_t*)&slice.front();
const uint8_t* bytes = reinterpret_cast<const uint8_t*>(&slice.front());
if (rem > 0) {
uint8_t first = bytes[0];
if (first < 0x80) { // 1 byte character

View File

@@ -9,7 +9,7 @@
#include <functional>
#include <type_traits>
#include <stdexcept>
#include <BitStream.h>
#include "BitStream.h"
#include "NiPoint3.h"
#include "Game.h"
@@ -151,6 +151,11 @@ namespace GeneralUtils {
return std::stod(value);
}
template <>
inline uint16_t Parse(const char* value) {
return std::stoul(value);
}
template <>
inline uint32_t Parse(const char* value) {
return std::stoul(value);
@@ -229,18 +234,30 @@ namespace GeneralUtils {
return T();
}
// on Windows we need to undef these or else they conflict with our numeric limits calls
// DEVELOPERS DEVELOPERS DEVELOPERS DEVELOPERS DEVELOPERS DEVELOPERS DEVELOPERS DEVELOPERS
#ifdef _WIN32
#undef min
#undef max
#endif
/**
* Casts the value of an enum entry to its underlying type
* @param entry Enum entry to cast
* @returns The enum entry's value in its underlying type
*/
template <typename eType>
inline constexpr typename std::underlying_type_t<eType> CastUnderlyingType(const eType entry) {
static_assert(std::is_enum_v<eType>, "Not an enum");
return static_cast<typename std::underlying_type_t<eType>>(entry);
}
// on Windows we need to undef these or else they conflict with our numeric limits calls
// DEVELOPERS DEVELOPERS DEVELOPERS DEVELOPERS DEVELOPERS DEVELOPERS DEVELOPERS DEVELOPERS
#ifdef _WIN32
#undef min
#undef max
#endif
template <typename T>
inline T GenerateRandomNumber() {
// Make sure it is a numeric type
static_assert(std::is_arithmetic<T>::value, "Not an arithmetic type");
return GenerateRandomNumber<T>(std::numeric_limits<T>::min(), std::numeric_limits<T>::max());
}
}

View File

@@ -63,15 +63,15 @@ private:
//! Writes the key to the packet
void WriteKey(RakNet::BitStream* packet) {
packet->Write(static_cast<uint8_t>(this->key.length() * sizeof(uint16_t)));
packet->Write<uint8_t>(this->key.length() * sizeof(uint16_t));
for (uint32_t i = 0; i < this->key.length(); ++i) {
packet->Write(static_cast<uint16_t>(this->key[i]));
packet->Write<uint16_t>(this->key[i]);
}
}
//! Writes the value to the packet
void WriteValue(RakNet::BitStream* packet) {
packet->Write(static_cast<uint8_t>(this->GetValueType()));
packet->Write<uint8_t>(this->GetValueType());
packet->Write(this->value);
}
@@ -179,30 +179,30 @@ template<> inline eLDFType LDFData<std::string>::GetValueType(void) { return LDF
// The specialized version for std::u16string (UTF-16)
template<>
inline void LDFData<std::u16string>::WriteValue(RakNet::BitStream* packet) {
packet->Write(static_cast<uint8_t>(this->GetValueType()));
packet->Write<uint8_t>(this->GetValueType());
packet->Write(static_cast<uint32_t>(this->value.length()));
packet->Write<uint32_t>(this->value.length());
for (uint32_t i = 0; i < this->value.length(); ++i) {
packet->Write(static_cast<uint16_t>(this->value[i]));
packet->Write<uint16_t>(this->value[i]);
}
}
// The specialized version for bool
template<>
inline void LDFData<bool>::WriteValue(RakNet::BitStream* packet) {
packet->Write(static_cast<uint8_t>(this->GetValueType()));
packet->Write<uint8_t>(this->GetValueType());
packet->Write(static_cast<uint8_t>(this->value));
packet->Write<uint8_t>(this->value);
}
// The specialized version for std::string (UTF-8)
template<>
inline void LDFData<std::string>::WriteValue(RakNet::BitStream* packet) {
packet->Write(static_cast<uint8_t>(this->GetValueType()));
packet->Write<uint8_t>(this->GetValueType());
packet->Write(static_cast<uint32_t>(this->value.length()));
packet->Write<uint32_t>(this->value.length());
for (uint32_t i = 0; i < this->value.length(); ++i) {
packet->Write(static_cast<uint8_t>(this->value[i]));
packet->Write<uint8_t>(this->value[i]);
}
}

View File

@@ -6,6 +6,8 @@
#include <stdarg.h>
Writer::~Writer() {
// Flush before we close
Flush();
// Dont try to close stdcout...
if (!m_Outfile || m_IsConsoleWriter) return;
@@ -14,7 +16,7 @@ Writer::~Writer() {
}
void Writer::Log(const char* time, const char* message) {
if (!m_Outfile) return;
if (!m_Outfile || !m_Enabled) return;
fputs(time, m_Outfile);
fputs(message, m_Outfile);

View File

@@ -107,7 +107,7 @@ void Metrics::EndMeasurement(MetricVariable variable) {
}
float Metrics::ToMiliseconds(int64_t nanoseconds) {
return (float)nanoseconds / 1e6;
return static_cast<float>(nanoseconds) / 1e6;
}
std::string Metrics::MetricVariableToString(MetricVariable variable) {
@@ -193,34 +193,34 @@ size_t Metrics::GetPeakRSS() {
/* Windows -------------------------------------------------- */
PROCESS_MEMORY_COUNTERS info;
GetProcessMemoryInfo(GetCurrentProcess(), &info, sizeof(info));
return (size_t)info.PeakWorkingSetSize;
return static_cast<size_t>(info.PeakWorkingSetSize);
#elif (defined(_AIX) || defined(__TOS__AIX__)) || (defined(__sun__) || defined(__sun) || defined(sun) && (defined(__SVR4) || defined(__svr4__)))
/* AIX and Solaris ------------------------------------------ */
struct psinfo psinfo;
int fd = -1;
if ((fd = open("/proc/self/psinfo", O_RDONLY)) == -1)
return (size_t)0L; /* Can't open? */
return static_cast<size_t>(0L); /* Can't open? */
if (read(fd, &psinfo, sizeof(psinfo)) != sizeof(psinfo)) {
close(fd);
return (size_t)0L; /* Can't read? */
return static_cast<size_t>(0L); /* Can't read? */
}
close(fd);
return (size_t)(psinfo.pr_rssize * 1024L);
return static_cast<size_t>(psinfo.pr_rssize * 1024L);
#elif defined(__unix__) || defined(__unix) || defined(unix) || (defined(__APPLE__) && defined(__MACH__))
/* BSD, Linux, and OSX -------------------------------------- */
struct rusage rusage;
getrusage(RUSAGE_SELF, &rusage);
#if defined(__APPLE__) && defined(__MACH__)
return (size_t)rusage.ru_maxrss;
return static_cast<size_t>(rusage.ru_maxrss);
#else
return (size_t)(rusage.ru_maxrss * 1024L);
return static_cast<size_t>(rusage.ru_maxrss * 1024L);
#endif
#else
/* Unknown OS ----------------------------------------------- */
return (size_t)0L; /* Unsupported. */
return static_cast<size_t>(0L); /* Unsupported. */
#endif
}
@@ -234,33 +234,33 @@ size_t Metrics::GetCurrentRSS() {
/* Windows -------------------------------------------------- */
PROCESS_MEMORY_COUNTERS info;
GetProcessMemoryInfo(GetCurrentProcess(), &info, sizeof(info));
return (size_t)info.WorkingSetSize;
return static_cast<size_t>(info.WorkingSetSize);
#elif defined(__APPLE__) && defined(__MACH__)
/* OSX ------------------------------------------------------ */
struct mach_task_basic_info info;
mach_msg_type_number_t infoCount = MACH_TASK_BASIC_INFO_COUNT;
if (task_info(mach_task_self(), MACH_TASK_BASIC_INFO,
(task_info_t)&info, &infoCount) != KERN_SUCCESS)
return (size_t)0L; /* Can't access? */
return (size_t)info.resident_size;
reinterpret_cast<task_info_t>(&info), &infoCount) != KERN_SUCCESS)
return static_cast<size_t>(0L); /* Can't access? */
return static_cast<size_t>(info.resident_size);
#elif defined(__linux__) || defined(__linux) || defined(linux) || defined(__gnu_linux__)
/* Linux ---------------------------------------------------- */
long rss = 0L;
FILE* fp = NULL;
if ((fp = fopen("/proc/self/statm", "r")) == NULL)
return (size_t)0L; /* Can't open? */
return static_cast<size_t>(0L); /* Can't open? */
if (fscanf(fp, "%*s%ld", &rss) != 1) {
fclose(fp);
return (size_t)0L; /* Can't read? */
return static_cast<size_t>(0L); /* Can't read? */
}
fclose(fp);
return (size_t)rss * (size_t)sysconf(_SC_PAGESIZE);
return static_cast<size_t>(rss) * static_cast<size_t>(sysconf(_SC_PAGESIZE));
#else
/* AIX, BSD, Solaris, and Unknown OS ------------------------ */
return (size_t)0L; /* Unsupported. */
return static_cast<size_t>(0L); /* Unsupported. */
#endif
}

View File

@@ -114,13 +114,13 @@ bool NiPoint3::operator!=(const NiPoint3& point) const {
//! Operator for subscripting
float& NiPoint3::operator[](int i) {
float* base = &x;
return (float&)base[i];
return base[i];
}
//! Operator for subscripting
const float& NiPoint3::operator[](int i) const {
const float* base = &x;
return (float&)base[i];
return base[i];
}
//! Operator for addition of vectors
@@ -181,7 +181,7 @@ bool NiPoint3::IsWithinAxisAlignedBox(const NiPoint3& minPoint, const NiPoint3&
if (this->y < minPoint.y) return false;
if (this->y > maxPoint.y) return false;
return (this->z < maxPoint.z&& this->z > minPoint.z);
return (this->z < maxPoint.z && this->z > minPoint.z);
}
//! Checks to see if the point (or vector) is within a sphere
@@ -232,10 +232,21 @@ NiPoint3 NiPoint3::MoveTowards(const NiPoint3& current, const NiPoint3& target,
float dx = target.x - current.x;
float dy = target.y - current.y;
float dz = target.z - current.z;
float lengthSquared = (float)((double)dx * (double)dx + (double)dy * (double)dy + (double)dz * (double)dz);
if ((double)lengthSquared == 0.0 || (double)maxDistanceDelta >= 0.0 && (double)lengthSquared <= (double)maxDistanceDelta * (double)maxDistanceDelta)
float lengthSquared = static_cast<float>(
static_cast<double>(dx) * static_cast<double>(dx) +
static_cast<double>(dy) * static_cast<double>(dy) +
static_cast<double>(dz) * static_cast<double>(dz)
);
if (static_cast<double>(lengthSquared) == 0.0
|| static_cast<double>(maxDistanceDelta) >= 0.0
&& static_cast<double>(lengthSquared)
<= static_cast<double>(maxDistanceDelta) * static_cast<double>(maxDistanceDelta)) {
return target;
float length = (float)std::sqrt((double)lengthSquared);
}
float length = std::sqrt(lengthSquared);
return NiPoint3(current.x + dx / length * maxDistanceDelta, current.y + dy / length * maxDistanceDelta, current.z + dz / length * maxDistanceDelta);
}

View File

@@ -1,3 +1,5 @@
// Source: http://www.zedwood.com/article/cpp-sha512-function
#include "SHA512.h"
#include <cstring>

View File

@@ -1,6 +1,6 @@
#include "ZCompression.h"
#include <zlib.h>
#include "zlib.h"
namespace ZCompression {
int32_t GetMaxCompressedLength(int32_t nLenSrc) {

View File

@@ -4,7 +4,7 @@
#include "Game.h"
#include "Logger.h"
#include <zlib.h>
#include "zlib.h"
AssetManager::AssetManager(const std::filesystem::path& path) {
if (!std::filesystem::is_directory(path)) {
@@ -81,8 +81,8 @@ bool AssetManager::HasFile(const char* name) {
std::replace(fixedName.begin(), fixedName.end(), '/', '\\');
if (fixedName.rfind("client\\res\\", 0) != 0) fixedName = "client\\res\\" + fixedName;
uint32_t crc = crc32b(0xFFFFFFFF, (uint8_t*)fixedName.c_str(), fixedName.size());
crc = crc32b(crc, (Bytef*)"\0\0\0\0", 4);
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()) {
if (item.m_Crc == crc) {
@@ -113,7 +113,7 @@ bool AssetManager::GetFile(const char* name, char** data, uint32_t* len) {
#endif
fseek(file, 0, SEEK_END);
*len = ftell(file);
*data = (char*)malloc(*len);
*data = static_cast<char*>(malloc(*len));
fseek(file, 0, SEEK_SET);
int32_t readInData = fread(*data, sizeof(uint8_t), *len, file);
fclose(file);
@@ -129,8 +129,8 @@ bool AssetManager::GetFile(const char* name, char** data, uint32_t* len) {
fixedName = "client\\res\\" + fixedName;
}
int32_t packIndex = -1;
uint32_t crc = crc32b(0xFFFFFFFF, (uint8_t*)fixedName.c_str(), fixedName.size());
crc = crc32b(crc, (Bytef*)"\0\0\0\0", 4);
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()) {
if (item.m_Crc == crc) {
@@ -152,13 +152,12 @@ bool AssetManager::GetFile(const char* name, char** data, uint32_t* len) {
return success;
}
AssetMemoryBuffer AssetManager::GetFileAsBuffer(const char* name) {
char* buf;
uint32_t len;
AssetStream AssetManager::GetFile(const char* name) {
char* buf; uint32_t len;
bool success = this->GetFile(name, &buf, &len);
return AssetMemoryBuffer(buf, len, success);
return AssetStream(buf, len, success);
}
uint32_t AssetManager::crc32b(uint32_t base, uint8_t* message, size_t l) {
@@ -168,7 +167,7 @@ uint32_t AssetManager::crc32b(uint32_t base, uint8_t* message, size_t l) {
crc = base;
for (i = 0; i < l; i++) {
// xor next byte to upper bits of crc
crc ^= (((unsigned int)message[i]) << 24);
crc ^= (static_cast<unsigned int>(message[i]) << 24);
for (j = 0; j < 8; j++) { // Do eight times.
msb = crc >> 31;
crc <<= 1;

View File

@@ -25,6 +25,10 @@ struct AssetMemoryBuffer : std::streambuf {
this->setg(base, base, base + n);
}
~AssetMemoryBuffer() {
if (m_Success) free(m_Base);
}
pos_type seekpos(pos_type sp, std::ios_base::openmode which) override {
return seekoff(sp - pos_type(off_type(0)), std::ios_base::beg, which);
}
@@ -40,9 +44,17 @@ struct AssetMemoryBuffer : std::streambuf {
setg(eback(), eback() + off, egptr());
return gptr() - eback();
}
};
void close() {
free(m_Base);
struct AssetStream : std::istream {
AssetStream(char* base, std::ptrdiff_t n, bool success) : std::istream(new AssetMemoryBuffer(base, n, success)) {}
~AssetStream() {
delete rdbuf();
}
operator bool() {
return reinterpret_cast<AssetMemoryBuffer*>(rdbuf())->m_Success;
}
};
@@ -56,7 +68,7 @@ public:
bool HasFile(const char* name);
bool GetFile(const char* name, char** data, uint32_t* len);
AssetMemoryBuffer GetFileAsBuffer(const char* name);
AssetStream GetFile(const char* name);
private:
void LoadPackIndex();

View File

@@ -76,7 +76,7 @@ bool Pack::ReadFileFromPack(uint32_t crc, char** data, uint32_t* len) {
fseek(file, pos, SEEK_SET);
if (!isCompressed) {
char* tempData = (char*)malloc(pkRecord.m_UncompressedSize);
char* tempData = static_cast<char*>(malloc(pkRecord.m_UncompressedSize));
int32_t readInData = fread(tempData, sizeof(uint8_t), pkRecord.m_UncompressedSize, file);
*data = tempData;
@@ -90,7 +90,7 @@ bool Pack::ReadFileFromPack(uint32_t crc, char** data, uint32_t* len) {
fseek(file, pos, SEEK_SET);
char* decompressedData = (char*)malloc(pkRecord.m_UncompressedSize);
char* decompressedData = static_cast<char*>(malloc(pkRecord.m_UncompressedSize));
uint32_t currentReadPos = 0;
while (true) {
@@ -100,12 +100,12 @@ bool Pack::ReadFileFromPack(uint32_t crc, char** data, uint32_t* len) {
int32_t readInData = fread(&size, sizeof(uint32_t), 1, file);
pos += 4; // Move pointer position 4 to the right
char* chunk = (char*)malloc(size);
char* chunk = static_cast<char*>(malloc(size));
int32_t readInData2 = fread(chunk, sizeof(int8_t), size, file);
pos += size; // Move pointer position the amount of bytes read to the right
int32_t err;
currentReadPos += ZCompression::Decompress((uint8_t*)chunk, size, reinterpret_cast<uint8_t*>(decompressedData + currentReadPos), ZCompression::MAX_SD0_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);
}

View File

@@ -8,21 +8,10 @@ PackIndex::PackIndex(const std::filesystem::path& filePath) {
BinaryIO::BinaryRead<uint32_t>(m_FileStream, m_Version);
BinaryIO::BinaryRead<uint32_t>(m_FileStream, m_PackPathCount);
for (int i = 0; i < m_PackPathCount; i++) {
uint32_t stringLen = 0;
BinaryIO::BinaryRead<uint32_t>(m_FileStream, stringLen);
std::string path;
for (int j = 0; j < stringLen; j++) {
char inChar;
BinaryIO::BinaryRead<char>(m_FileStream, inChar);
path += inChar;
}
m_PackPaths.push_back(path);
m_PackPaths.resize(m_PackPathCount);
for (auto& item : m_PackPaths) {
BinaryIO::ReadString<uint32_t>(m_FileStream, item, BinaryIO::ReadType::String);
}
BinaryIO::BinaryRead<uint32_t>(m_FileStream, m_PackFileIndexCount);

View File

@@ -10,8 +10,23 @@ dConfig::dConfig(const std::string& filepath) {
LoadConfig();
}
std::filesystem::path GetConfigDir() {
std::filesystem::path config_dir = BinaryPathFinder::GetBinaryDir();
if (const char* env_p = std::getenv("DLU_CONFIG_DIR")) {
config_dir /= env_p;
}
return config_dir;
}
const bool dConfig::Exists(const std::string& filepath) {
std::filesystem::path config_dir = GetConfigDir();
return std::filesystem::exists(config_dir / filepath);
}
void dConfig::LoadConfig() {
std::ifstream in(BinaryPathFinder::GetBinaryDir() / m_ConfigFilePath);
std::filesystem::path config_dir = GetConfigDir();
std::ifstream in(config_dir / m_ConfigFilePath);
if (!in.good()) return;
std::string line{};
@@ -19,7 +34,7 @@ void dConfig::LoadConfig() {
if (!line.empty() && line.front() != '#') ProcessLine(line);
}
std::ifstream sharedConfig(BinaryPathFinder::GetBinaryDir() / "sharedconfig.ini", std::ios::in);
std::ifstream sharedConfig(config_dir / "sharedconfig.ini", std::ios::in);
if (!sharedConfig.good()) return;
line.clear();
@@ -34,17 +49,20 @@ void dConfig::ReloadConfig() {
}
const std::string& dConfig::GetValue(std::string key) {
std::string upper_key(key);
std::transform(upper_key.begin(), upper_key.end(), upper_key.begin(), ::toupper);
if (const char* env_p = std::getenv(upper_key.c_str())) {
this->m_ConfigValues[key] = env_p;
}
return this->m_ConfigValues[key];
}
void dConfig::ProcessLine(const std::string& line) {
auto splitLine = GeneralUtils::SplitString(line, '=');
if (splitLine.size() != 2) return;
auto splitLoc = line.find('=');
auto key = line.substr(0, splitLoc);
auto value = line.substr(splitLoc + 1);
//Make sure that on Linux, we remove special characters:
auto& key = splitLine.at(0);
auto& value = splitLine.at(1);
if (!value.empty() && value.at(value.size() - 1) == '\r') value.erase(value.size() - 1);
if (this->m_ConfigValues.find(key) != this->m_ConfigValues.end()) return;

View File

@@ -7,6 +7,11 @@ class dConfig {
public:
dConfig(const std::string& filepath);
/**
* Checks whether the specified filepath exists
*/
static const bool Exists(const std::string& filepath);
/**
* Gets the specified key from the config. Returns an empty string if the value is not found.
*

View File

@@ -0,0 +1,29 @@
#ifndef __STRINGIFIEDENUM_H__
#define __STRINGIFIEDENUM_H__
#include <string>
#include "magic_enum.hpp"
namespace StringifiedEnum {
template<typename T>
const std::string_view ToString(const T e) {
static_assert(std::is_enum_v<T>, "Not an enum"); // Check type
constexpr auto sv = &magic_enum::enum_entries<T>();
const auto it = std::lower_bound(
sv->begin(), sv->end(), e,
[&](const std::pair<T, std::string_view>& lhs, const T rhs) { return lhs.first < rhs; }
);
std::string_view output;
if (it != sv->end() && it->first == e) {
output = it->second;
} else {
output = "UNKNOWN";
}
return output;
}
}
#endif // !__STRINGIFIEDENUM_H__

View File

@@ -148,11 +148,11 @@ public:
if (size > maxSize) size = maxSize;
for (uint32_t i = 0; i < size; ++i) {
bitStream.Write(static_cast<uint16_t>(friendName[i]));
bitStream.Write<uint16_t>(friendName[i]);
}
for (uint32_t j = 0; j < remSize; ++j) {
bitStream.Write(static_cast<uint16_t>(0));
bitStream.Write<uint16_t>(0);
}
bitStream.Write<uint32_t>(0); //???

File diff suppressed because it is too large Load Diff

View File

@@ -166,7 +166,8 @@ enum ePlayerFlag : int32_t {
NJ_LIGHTNING_SPINJITZU = 2031,
NJ_ICE_SPINJITZU = 2032,
NJ_FIRE_SPINJITZU = 2033,
NJ_WU_SHOW_DAILY_CHEST = 2099
NJ_WU_SHOW_DAILY_CHEST = 2099,
DLU_SKIP_CINEMATICS = 1'000'000,
};
#endif //!__EPLAYERFLAG__H__

View File

@@ -0,0 +1,15 @@
#ifndef __EQUICKBUILDSTATE__H__
#define __EQUICKBUILDSTATE__H__
#include <cstdint>
enum class eQuickBuildState : uint32_t {
OPEN,
COMPLETED = 2,
RESETTING = 4,
BUILDING,
INCOMPLETE
};
#endif //!__EQUICKBUILDSTATE__H__

View File

@@ -1,15 +0,0 @@
#ifndef __EREBUILDSTATE__H__
#define __EREBUILDSTATE__H__
#include <cstdint>
enum class eRebuildState : uint32_t {
OPEN,
COMPLETED = 2,
RESETTING = 4,
BUILDING,
INCOMPLETE
};
#endif //!__EREBUILDSTATE__H__

View File

@@ -34,7 +34,7 @@ enum class eReplicaComponentType : uint32_t {
PLATFORM_BOUNDARY,
MODULE,
ARCADE,
VEHICLE_PHYSICS, // Havok demo based
HAVOK_VEHICLE_PHYSICS,
MOVEMENT_AI,
EXHIBIT,
OVERHEAD_ICON,
@@ -50,11 +50,11 @@ enum class eReplicaComponentType : uint32_t {
PROPERTY_ENTRANCE,
FX,
PROPERTY_MANAGEMENT,
VEHICLE_PHYSICS_NEW, // internal physics based on havok
VEHICLE_PHYSICS,
PHYSICS_SYSTEM,
QUICK_BUILD,
SWITCH,
ZONE_CONTROL, // Minigame
MINI_GAME_CONTROL,
CHANGLING,
CHOICE_BUILD,
PACKAGE,

View File

@@ -3,6 +3,8 @@
#include <cstdint>
#include "magic_enum.hpp"
enum class eWorldMessageType : uint32_t {
VALIDATION = 1, // Session info
CHARACTER_LIST_REQUEST,
@@ -36,7 +38,14 @@ enum class eWorldMessageType : uint32_t {
HANDLE_FUNNESS,
FAKE_PRG_CSR_MESSAGE,
REQUEST_FREE_TRIAL_REFRESH,
GM_SET_FREE_TRIAL_STATUS
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

@@ -37,10 +37,13 @@
#include "CDPropertyTemplateTable.h"
#include "CDFeatureGatingTable.h"
#include "CDRailActivatorComponent.h"
#include "CDRewardCodesTable.h"
#ifndef CDCLIENT_CACHE_ALL
// Uncomment this to cache the full cdclient database into memory. This will make the server load faster, but will use more memory.
// A vanilla CDClient takes about 46MB of memory + the regular world data.
// #define CDCLIENT_CACHE_ALL
// # define CDCLIENT_CACHE_ALL
#endif // CDCLIENT_CACHE_ALL
#ifdef CDCLIENT_CACHE_ALL
#define CDCLIENT_DONT_CACHE_TABLE(x) x
@@ -82,6 +85,7 @@ CDClientManager::CDClientManager() {
CDRailActivatorComponentTable::Instance().LoadValuesFromDatabase();
CDRarityTableTable::Instance().LoadValuesFromDatabase();
CDRebuildComponentTable::Instance().LoadValuesFromDatabase();
CDRewardCodesTable::Instance().LoadValuesFromDatabase();
CDRewardsTable::Instance().LoadValuesFromDatabase();
CDScriptComponentTable::Instance().LoadValuesFromDatabase();
CDSkillBehaviorTable::Instance().LoadValuesFromDatabase();

View File

@@ -10,7 +10,7 @@ void CDComponentsRegistryTable::LoadValuesFromDatabase() {
entry.component_type = static_cast<eReplicaComponentType>(tableData.getIntField("component_type", 0));
entry.component_id = tableData.getIntField("component_id", -1);
this->mappedEntries.insert_or_assign(((uint64_t)entry.component_type) << 32 | ((uint64_t)entry.id), entry.component_id);
this->mappedEntries.insert_or_assign(static_cast<uint64_t>(entry.component_type) << 32 | static_cast<uint64_t>(entry.id), entry.component_id);
this->mappedEntries.insert_or_assign(entry.id, 0);
tableData.nextRow();
@@ -22,7 +22,7 @@ void CDComponentsRegistryTable::LoadValuesFromDatabase() {
int32_t CDComponentsRegistryTable::GetByIDAndType(uint32_t id, eReplicaComponentType componentType, int32_t defaultValue) {
auto exists = mappedEntries.find(id);
if (exists != mappedEntries.end()) {
auto iter = mappedEntries.find(((uint64_t)componentType) << 32 | ((uint64_t)id));
auto iter = mappedEntries.find(static_cast<uint64_t>(componentType) << 32 | static_cast<uint64_t>(id));
return iter == mappedEntries.end() ? defaultValue : iter->second;
}
@@ -38,15 +38,14 @@ int32_t CDComponentsRegistryTable::GetByIDAndType(uint32_t id, eReplicaComponent
entry.component_type = static_cast<eReplicaComponentType>(tableData.getIntField("component_type", 0));
entry.component_id = tableData.getIntField("component_id", -1);
this->mappedEntries.insert_or_assign(((uint64_t)entry.component_type) << 32 | ((uint64_t)entry.id), entry.component_id);
this->mappedEntries.insert_or_assign(static_cast<uint64_t>(entry.component_type) << 32 | static_cast<uint64_t>(entry.id), entry.component_id);
tableData.nextRow();
}
mappedEntries.insert_or_assign(id, 0);
auto iter = this->mappedEntries.find(((uint64_t)componentType) << 32 | ((uint64_t)id));
auto iter = this->mappedEntries.find(static_cast<uint64_t>(componentType) << 32 | static_cast<uint64_t>(id));
return iter == this->mappedEntries.end() ? defaultValue : iter->second;
}

View File

@@ -3,6 +3,8 @@
// Custom Classes
#include "CDTable.h"
#include <unordered_map>
enum class eReplicaComponentType : uint32_t;
struct CDComponentsRegistry {
unsigned int id; //!< The LOT is used as the ID

View File

@@ -42,9 +42,9 @@ std::vector<CDFeatureGating> CDFeatureGatingTable::Query(std::function<bool(CDFe
return data;
}
bool CDFeatureGatingTable::FeatureUnlocked(const std::string& feature) const {
bool CDFeatureGatingTable::FeatureUnlocked(const CDFeatureGating& feature) const {
for (const auto& entry : entries) {
if (entry.featureName == feature) {
if (entry.featureName == feature.featureName && feature >= entry) {
return true;
}
}

View File

@@ -9,6 +9,12 @@ struct CDFeatureGating {
int32_t current;
int32_t minor;
std::string description;
bool operator>=(const CDFeatureGating& b) const {
return (this->major > b.major) ||
(this->major == b.major && this->current > b.current) ||
(this->major == b.major && this->current == b.current && this->minor >= b.minor);
}
};
class CDFeatureGatingTable : public CDTable<CDFeatureGatingTable> {
@@ -21,7 +27,7 @@ public:
// Queries the table with a custom "where" clause
std::vector<CDFeatureGating> Query(std::function<bool(CDFeatureGating)> predicate);
bool FeatureUnlocked(const std::string& feature) const;
bool FeatureUnlocked(const CDFeatureGating& feature) const;
const std::vector<CDFeatureGating>& GetEntries(void) const;
};

View File

@@ -25,7 +25,7 @@ void CDMissionEmailTable::LoadValuesFromDatabase() {
entry.notificationGroup = tableData.getIntField("notificationGroup", -1);
entry.missionID = tableData.getIntField("missionID", -1);
entry.attachmentLOT = tableData.getIntField("attachmentLOT", 0);
entry.localize = (bool)tableData.getIntField("localize", -1);
entry.localize = static_cast<bool>(tableData.getIntField("localize", 1));
entry.locStatus = tableData.getIntField("locStatus", -1);
entry.gate_version = tableData.getStringField("gate_version", "");
@@ -50,4 +50,3 @@ std::vector<CDMissionEmail> CDMissionEmailTable::Query(std::function<bool(CDMiss
const std::vector<CDMissionEmail>& CDMissionEmailTable::GetEntries() const {
return this->entries;
}

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