inital commit

This commit is contained in:
CPunch 2020-08-18 15:42:30 -05:00
commit 9d366e3720
30 changed files with 4032 additions and 0 deletions

4
.gitignore vendored Normal file
View File

@ -0,0 +1,4 @@
.vscode
bin/*
notes.txt
config.ini

9
LICENSE.md Normal file
View File

@ -0,0 +1,9 @@
The OpenFusion MIT except Marlorn License
Copyright 2020 Seth Stubbs
Excluding the individual known as "MarlornWS" and their associates, permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

23
Makefile Normal file
View File

@ -0,0 +1,23 @@
# makefile for OpenFusion
OBJS = src/*.cpp # source files to compile
CC = clang++ # using GNU C++ compiler
WIN_CC = x86_64-w64-mingw32-g++ # using GNU C++ compiler
# -w suppresses all warnings (the part that's commented out helps me find memory leaks, it ruins performance though!)
COMPILER_FLAGS = -std=c++17 -o3 -static #-g3 -fsanitize=address
WIN_COMPILER_FLAGS = -std=c++17 -o3 -static #-g3 -fsanitize=address
#LINKER_FLAGS specifies the libraries we're linking against (NONE, this is a single header library.)
LINKER_FLAGS = -lpthread
WIN_LINKER_FLAGS = -lws2_32 -lwsock32
#OBJ_NAME specifies the name of our exectuable
OBJ_NAME = bin/fusion # location of output for build
WIN_OBJ_NAME = bin/winfusion.exe # location of output for build
all: $(OBJS)
$(CC) $(OBJS) $(COMPILER_FLAGS) $(LINKER_FLAGS) -o $(OBJ_NAME)
windows: $(OBJS)
$(WIN_CC) $(OBJS) $(WIN_COMPILER_FLAGS) $(WIN_LINKER_FLAGS) -o $(WIN_OBJ_NAME)

87
README.md Normal file
View File

@ -0,0 +1,87 @@
# OpenFusion
OpenFusion is a landwalker server for FusionFall. It currently supports versions `beta-20100104` and `beta-20100728` of the original game.
Further documentation pending.
## Usage
tl;dr:
1. Download the client+server bundle from [here](...).
2. Run `FreeClient/installUnity.bat` once
From then on, any time you want to run the "game":
3. Run `OpenFusion/winfusion.exe`
4. Run `FreeClient/OpenFusionClient.exe`
You have two randomized characters available to you on the Character Selection screen, one boy, one girl.
You can also make your own character and play through the tutorial. The tutorial can be skipped by pressing the ~ key.
For a more detailed overview of the game's architecture and how to configure it, read the following sections.
## Architecture
FusionFall consists of the following components:
* A web browser compatible with the old NPAPI plugin interface
* A web server that acts as a gateway for launching the game
* A custom version of the Unity Web Player, which gets loaded as an NPAPI plugin
* A `.unity3d` bundle that contains the game code and essential resources (loading screen, etc.)
* A login server that speaks the FusionFall network protocol over TCP
* A shard server that does the same on another port
The original game made use of the player's actual web browser to launch the game, but since then the NPAPI plugin interface the game relied on has been deprecated and is no longer available in most modern browsers. Both Retro and OpenFusion get around this issue by distributing an older version of Electron, a software package that is essentially a specialized web browser.
The browser/Electron client opens a web page with an `<embed>` tag of MIME type `application/vnd.unity`, where the `src` param is the address of the game's `.unity3d` entrypoint.
This triggers the browser to load an NPAPI plugin that handles this MIME type, the Unity Web Player, which the browser looks for in `C:\Users\USERNAME\AppData\LocalLow\Unity\WebPlayer`.
The Web Player was previously copied there by `installUnity.bat`.
Note that the version of the web player distributed with OpenFusion expects a standard `UnityWeb` magic number for all assets, instead of Retro's modified `streamed` magic number.
This will potentially become relevant later, as people start experimenting and mixing and matching versions.
The web player will execute the game code, which will request the following files from the server: `/assetInfo.php` and `/loginInfo.php`.
`FreeClient/resources/app/files/assetInfo.php` contains the address from which to fetch the rest of the game's assets (the "dongresources").
Normally those would be hosted on the same web server as the gateway, but the OpenFusion distribution (in it's default configuration) doesn't use a web server at all!
It loads the web pages locally using the `file://` schema, and fetches the game's assets from Turner's CDN (which is still hosting them to this day!).
`FreeClient/resources/app/files/loginInfo.php` contains the IP:port pair of the FusionFall login server, which the client will connect to. This login server drives the client while it's in the Character Selection menu, as well as Character Creation and the Tutorial.
When the player clicks "ENTER THE GAME" (or completes the tutorial), the login server sends it the address of the shard server, which the client will then connect to and remain connected to during gameplay.
## Configuration
You can change the ports the FusionFall server listens on in `OpenFusion/config.ini`. Make sure the login server port is in sync with `loginInfo.php`.
The shard port needs no such synchronization.
You can also configure the distance at which you'll be able to see other players, though by default it's already as high as you'll want it.
If you want to play with friends, you can change the IP in `loginInfo.php` to a login server hosted elsewhere.
This just works if you're all under the same LAN, but if you want to play over the internet you'll need to open a port, use a service like Hamachi or nGrok, or host the server on a VPS (just like any other gameserver).
If you're in a region in which Turner's CDN doesn't still have the game's assets cached, you won't be able to play the game in its default configuration.
You'll need to obtain the necessary assets elsewhere and set up your own local web server to host them, because unlike web browsers, the game itself cannot interpret the `file://` schema, and will thus need the assets hosted on an actual HTTP server.
Don't forget to point `assetInfo.php` to where you're hosting the assets and change the `src` param of both the `<embed>` tag and the `<object>` tag in `FreeClient/resources/files/index.html` to where you're hosting the `.unity3d` entrypoint.
If you change `loginInfo.php` or `assetInfo.php`, make sure not to put any newline characters (or any other whitespace) at the end of the file(s).
Some modern IDEs/text editors do this automatically. If all else fails, use Notepad.
## Compiling
OpenFusion can be compiled from source using the included makefile. to compile for windows (on a *nix system) use `make windows`, otherwise to compile it for the current platform you're on just run `make`
## "Gameplay"
Notice the quotes. This is not a full-fledged game that can be played.
It's what's called a landwalker; enough of the server has been implemented to allow players to run around in the game world, and not much else.
![](res/sane_upsell.png)
To make your landwalking experience more pleasant, you can make use of a few admin commands to get around easier:
* A `/speed` of around 2400 or 3000 is nice.
* A `/jump` of about 50 will send you soaring
* [This map](res/dong_number_map.png) (credit to Danny O) is useful for `/warp` coordinates.
* `/goto` is useful for more precise teleportation (ie. for getting into Infected Zones, etc.).

BIN
res/dong_number_map.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.3 MiB

BIN
res/sane_upsell.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 732 KiB

244
src/CNLoginServer.cpp Normal file
View File

@ -0,0 +1,244 @@
#include "CNLoginServer.hpp"
#include "CNShared.hpp"
#include "CNStructs.hpp"
#include "settings.hpp"
/*
This is *not* connected to any database, so i'm sending dummy data just to get the client to work & connect to the shard <3
*/
std::map<CNSocket*, CNLoginData> CNLoginServer::loginSessions;
CNLoginServer::CNLoginServer(uint16_t p) {
port = p;
pHandler = &CNLoginServer::handlePacket;
init();
}
void CNLoginServer::handlePacket(CNSocket* sock, CNPacketData* data) {
switch (data->type) {
case P_CL2LS_REQ_LOGIN: {
sP_CL2LS_REQ_LOGIN* login = (sP_CL2LS_REQ_LOGIN*)data->buf;
sP_LS2CL_REP_LOGIN_SUCC* response = (sP_LS2CL_REP_LOGIN_SUCC*)xmalloc(sizeof(sP_LS2CL_REP_LOGIN_SUCC));
uint64_t cachedKey = sock->getEKey(); // so we can still send the response packet with the correct key
int charCount = 2; // send 4 randomly generated characters for now
DEBUGLOG(
std::cout << "P_CL2LS_REQ_LOGIN:" << std::endl;
std::cout << "\tClient version: " << login->iClientVerA << "." << login->iClientVerB << "." << login->iClientVerC << std::endl;
std::cout << "\tLogin type: " << login->iLoginType << std::endl;
std::cout << "\tID: " << U16toU8(login->szID) << " Password: " << U16toU8(login->szPassword) << std::endl;
)
response->iCharCount = charCount;
response->iSlotNum = 1;
response->iPaymentFlag = 1;
response->iOpenBetaFlag = 0;
response->uiSvrTime = getTime();
// set username in response packet
memcpy(response->szID, login->szID, sizeof(char16_t) * 33);
// update keys
sock->setEKey(CNSocketEncryption::createNewKey(response->uiSvrTime, response->iCharCount + 1, response->iSlotNum + 1));
sock->setFEKey(CNSocketEncryption::createNewKey((uint64_t)(*(uint64_t*)&CNSocketEncryption::defaultKey[0]), login->iClientVerC, 1));
// send the response in with original key
sock->sendPacket(new CNPacketData((void*)response, P_LS2CL_REP_LOGIN_SUCC, sizeof(sP_LS2CL_REP_LOGIN_SUCC), cachedKey));
loginSessions[sock] = CNLoginData();
if (settings::LOGINRANDCHARACTERS) {
// now send the characters :)
for (int i = 0; i < charCount; i++) {
sP_LS2CL_REP_CHAR_INFO* charInfo = (sP_LS2CL_REP_CHAR_INFO*)xmalloc(sizeof(sP_LS2CL_REP_CHAR_INFO));
charInfo->iSlot = (int8_t)i + 1;
charInfo->iLevel = (int16_t)1;
charInfo->sPC_Style.iPC_UID = rand(); // unique identifier for the character
charInfo->sPC_Style.iNameCheck = 1;
charInfo->sPC_Style.iGender = (i%2)+1; // can be 1(boy) or 2(girl)
charInfo->sPC_Style.iFaceStyle = 1;
charInfo->sPC_Style.iHairStyle = 1;
charInfo->sPC_Style.iHairColor = (rand()%19) + 1; // 1 - 19
charInfo->sPC_Style.iSkinColor = (rand()%13) + 1; // 1 - 13
charInfo->sPC_Style.iEyeColor = (rand()%6) + 1; // 1 - 6
charInfo->sPC_Style.iHeight = (rand()%6); // 0 -5
charInfo->sPC_Style.iBody = (rand()%4); // 0 - 3
charInfo->sPC_Style.iClass = 0;
charInfo->sPC_Style2 = sPCStyle2(1, 1, 1);
// past's town hall
charInfo->iX = settings::SPAWN_X;
charInfo->iY = settings::SPAWN_Y;
charInfo->iZ = settings::SPAWN_Z;
U8toU16(std::string("Player"), charInfo->sPC_Style.szFirstName);
U8toU16(std::to_string(i + 1), charInfo->sPC_Style.szLastName);
int64_t UID = charInfo->sPC_Style.iPC_UID;
loginSessions[sock].characters[UID] = Player();
loginSessions[sock].characters[UID].level = charInfo->iLevel;
loginSessions[sock].characters[UID].slot = charInfo->iSlot;
loginSessions[sock].characters[UID].FEKey = sock->getFEKey();
loginSessions[sock].characters[UID].x = charInfo->iX;
loginSessions[sock].characters[UID].y = charInfo->iY;
loginSessions[sock].characters[UID].z = charInfo->iZ;
loginSessions[sock].characters[UID].PCStyle = charInfo->sPC_Style;
loginSessions[sock].characters[UID].PCStyle2 = charInfo->sPC_Style2;
for (int i = 0; i < AEQUIP_COUNT; i++) {
// setup item
charInfo->aEquip[i].iID = 0;
charInfo->aEquip[i].iType = i;
charInfo->aEquip[i].iOpt = 0;
loginSessions[sock].characters[UID].Equip[i] = charInfo->aEquip[i];
}
// set default to the first character
if (i == 0)
loginSessions[sock].selectedChar = UID;
sock->sendPacket(new CNPacketData((void*)charInfo, P_LS2CL_REP_CHAR_INFO, sizeof(sP_LS2CL_REP_CHAR_INFO), sock->getEKey()));
}
}
break;
}
case P_CL2LS_REP_LIVE_CHECK: {
// stubbed, the client really doesn't care LOL
break;
}
case P_CL2LS_REQ_CHECK_CHAR_NAME: {
// naughty words allowed!!!!!!!! (also for some reason, the client will always show 'Player 0' if you manually type a name. It will show up for other connected players though)
sP_CL2LS_REQ_CHECK_CHAR_NAME* nameCheck = (sP_CL2LS_REQ_CHECK_CHAR_NAME*)data->buf;
sP_LS2CL_REP_CHECK_CHAR_NAME_SUCC* response = (sP_LS2CL_REP_CHECK_CHAR_NAME_SUCC*)xmalloc(sizeof(sP_LS2CL_REP_CHECK_CHAR_NAME_SUCC));
DEBUGLOG(
std::cout << "P_CL2LS_REQ_CHECK_CHAR_NAME:" << std::endl;
std::cout << "\tFirstName: " << U16toU8(nameCheck->szFirstName) << " LastName: " << U16toU8(nameCheck->szLastName) << std::endl;
)
memcpy(response->szFirstName, nameCheck->szFirstName, sizeof(char16_t) * 9);
memcpy(response->szLastName, nameCheck->szLastName, sizeof(char16_t) * 17);
// fr*ck allowed!!!
sock->sendPacket(new CNPacketData((void*)response, P_LS2CL_REP_CHECK_CHAR_NAME_SUCC, sizeof(sP_LS2CL_REP_CHECK_CHAR_NAME_SUCC), sock->getEKey()));
break;
}
case P_CL2LS_REQ_SAVE_CHAR_NAME: {
sP_CL2LS_REQ_SAVE_CHAR_NAME* save = (sP_CL2LS_REQ_SAVE_CHAR_NAME*)data->buf;
sP_LS2CL_REP_SAVE_CHAR_NAME_SUCC* response = (sP_LS2CL_REP_SAVE_CHAR_NAME_SUCC*)xmalloc(sizeof(sP_LS2CL_REP_SAVE_CHAR_NAME_SUCC));
DEBUGLOG(
std::cout << "P_CL2LS_REQ_SAVE_CHAR_NAME:" << std::endl;
std::cout << "\tSlot: " << (int)save->iSlotNum << std::endl;
std::cout << "\tGender: " << (int)save->iGender << std::endl;
std::cout << "\tName: " << U16toU8(save->szFirstName) << " " << U16toU8(save->szLastName) << std::endl;
)
response->iSlotNum = save->iSlotNum;
response->iGender = save->iGender;
memcpy(response->szFirstName, save->szFirstName, sizeof(char16_t) * 9);
memcpy(response->szLastName, save->szLastName, sizeof(char16_t) * 17);
sock->sendPacket(new CNPacketData((void*)response, P_LS2CL_REP_SAVE_CHAR_NAME_SUCC, sizeof(sP_LS2CL_REP_SAVE_CHAR_NAME_SUCC), sock->getEKey()));
break;
}
case P_CL2LS_REQ_CHAR_CREATE: {
sP_CL2LS_REQ_CHAR_CREATE* character = (sP_CL2LS_REQ_CHAR_CREATE*)data->buf;
sP_LS2CL_REP_CHAR_CREATE_SUCC* response = (sP_LS2CL_REP_CHAR_CREATE_SUCC*)xmalloc(sizeof(sP_LS2CL_REP_CHAR_CREATE_SUCC));
DEBUGLOG(
std::cout << "P_CL2LS_REQ_CHAR_CREATE:" << std::endl;
std::cout << "\tPC_UID: " << character->PCStyle.iPC_UID << std::endl;
std::cout << "\tNameCheck: " << (int)character->PCStyle.iNameCheck << std::endl;
std::cout << "\tName: " << U16toU8(character->PCStyle.szFirstName) << " " << U16toU8(character->PCStyle.szLastName) << std::endl;
std::cout << "\tGender: " << (int)character->PCStyle.iGender << std::endl;
std::cout << "\tFace: " << (int)character->PCStyle.iFaceStyle << std::endl;
std::cout << "\tHair: " << (int)character->PCStyle.iHairStyle << std::endl;
std::cout << "\tHair Color: " << (int)character->PCStyle.iHairColor << std::endl;
std::cout << "\tSkin Color: " << (int)character->PCStyle.iSkinColor << std::endl;
std::cout << "\tEye Color: " << (int)character->PCStyle.iEyeColor << std::endl;
std::cout << "\tHeight: " << (int)character->PCStyle.iHeight << std::endl;
std::cout << "\tBody: " << (int)character->PCStyle.iBody << std::endl;
std::cout << "\tClass: " << (int)character->PCStyle.iClass << std::endl;
std::cout << "\tiEquipUBID: " << (int)character->sOn_Item.iEquipUBID << std::endl;
std::cout << "\tiEquipLBID: " << (int)character->sOn_Item.iEquipLBID << std::endl;
std::cout << "\tiEquipFootID: " << (int)character->sOn_Item.iEquipFootID << std::endl;
)
character->PCStyle.iNameCheck = 1;
response->PC_Style = character->PCStyle;
response->PC_Style2 = sPCStyle2(1, 1, 1);
response->iLevel = 1;
response->sOn_Item = character->sOn_Item;
int64_t UID = character->PCStyle.iPC_UID;
loginSessions[sock].characters[UID] = Player();
loginSessions[sock].characters[UID].level = 1;
loginSessions[sock].characters[UID].FEKey = sock->getFEKey();
loginSessions[sock].characters[UID].PCStyle = character->PCStyle;
loginSessions[sock].characters[UID].PCStyle2 = sPCStyle2(1, 0, 1);
loginSessions[sock].characters[UID].x = settings::SPAWN_X;
loginSessions[sock].characters[UID].y = settings::SPAWN_Y;
loginSessions[sock].characters[UID].z = settings::SPAWN_Z;
loginSessions[sock].characters[UID].Equip[1].iID = character->sOn_Item.iEquipUBID; // upper body
loginSessions[sock].characters[UID].Equip[1].iType = 1;
loginSessions[sock].characters[UID].Equip[2].iID = character->sOn_Item.iEquipLBID; // lower body
loginSessions[sock].characters[UID].Equip[2].iType = 2;
loginSessions[sock].characters[UID].Equip[3].iID = character->sOn_Item.iEquipFootID; // foot!
loginSessions[sock].characters[UID].Equip[3].iType = 3;
sock->sendPacket(new CNPacketData((void*)response, P_LS2CL_REP_CHAR_CREATE_SUCC, sizeof(sP_LS2CL_REP_CHAR_CREATE_SUCC), sock->getEKey()));
break;
}
case P_CL2LS_REQ_CHAR_SELECT: {
// character selected
sP_CL2LS_REQ_CHAR_SELECT* chararacter = (sP_CL2LS_REQ_CHAR_SELECT*)data->buf;
sP_LS2CL_REP_CHAR_SELECT_SUCC* response = (sP_LS2CL_REP_CHAR_SELECT_SUCC*)xmalloc(sizeof(sP_LS2CL_REP_CHAR_SELECT_SUCC));
DEBUGLOG(
std::cout << "P_CL2LS_REQ_CHAR_SELECT:" << std::endl;
std::cout << "\tPC_UID: " << chararacter->iPC_UID << std::endl;
)
loginSessions[sock].selectedChar = chararacter->iPC_UID;
sock->sendPacket(new CNPacketData((void*)response, P_LS2CL_REP_CHAR_SELECT_SUCC, sizeof(sP_LS2CL_REP_CHAR_SELECT_SUCC), sock->getEKey()));
break;
}
case P_CL2LS_REQ_SHARD_SELECT: {
// tell client to connect to the shard server
sP_CL2LS_REQ_SHARD_SELECT* shard = (sP_CL2LS_REQ_SHARD_SELECT*)data->buf;
sP_LS2CL_REP_SHARD_SELECT_SUCC* response = (sP_LS2CL_REP_SHARD_SELECT_SUCC*)xmalloc(sizeof(sP_LS2CL_REP_SHARD_SELECT_SUCC));
DEBUGLOG(
std::cout << "P_CL2LS_REQ_SHARD_SELECT:" << std::endl;
std::cout << "\tShard: " << (int)shard->iShardNum << std::endl;
)
const char* SHARD_IP = settings::SHARDSERVERIP.c_str();
response->iEnterSerialKey = rand();
response->g_FE_ServerPort = settings::SHARDPORT;
// copy IP to response (this struct uses ASCII encoding so we don't have to goof around converting encodings)
memcpy(response->g_FE_ServerIP, SHARD_IP, strlen(SHARD_IP));
response->g_FE_ServerIP[strlen(SHARD_IP)] = '\0';
// pass player to CNSharedData
CNSharedData::setPlayer(response->iEnterSerialKey, loginSessions[sock].characters[loginSessions[sock].selectedChar]);
sock->sendPacket(new CNPacketData((void*)response, P_LS2CL_REP_SHARD_SELECT_SUCC, sizeof(sP_LS2CL_REP_SHARD_SELECT_SUCC), sock->getEKey()));
sock->kill(); // client should connect to the Shard server now
break;
}
default:
std::cerr << "OpenFusion: LOGIN UNIMPLM ERR. PacketType: " << data->type << std::endl;
break;
}
}
void CNLoginServer::killConnection(CNSocket* cns) {
loginSessions.erase(cns);
}

49
src/CNLoginServer.hpp Normal file
View File

@ -0,0 +1,49 @@
#ifndef _CNLS_HPP
#define _CNLS_HPP
#include "CNProtocol.hpp"
#include "Player.hpp"
#include <map>
enum LOGINPACKETID {
// client to login server
P_CL2LS_REQ_LOGIN = 301989889,
P_CL2LS_REQ_CHECK_CHAR_NAME = 301989890,
P_CL2LS_REQ_SAVE_CHAR_NAME = 301989891,
P_CL2LS_REQ_CHAR_CREATE = 301989892,
P_CL2LS_REQ_CHAR_SELECT = 301989893,
P_CL2LS_REQ_SHARD_SELECT = 301989895,
P_CL2LS_REP_LIVE_CHECK = 301989900,
P_CL2LS_REQ_SHARD_LIST_INFO = 301989896,
// login server 2 client
P_LS2CL_REP_LOGIN_SUCC = 553648129,
P_LS2CL_REP_CHAR_INFO = 553648131,
P_LS2CL_REP_CHECK_CHAR_NAME_SUCC = 553648133,
P_LS2CL_REP_SAVE_CHAR_NAME_SUCC = 553648135,
P_LS2CL_REP_CHAR_CREATE_SUCC = 553648137,
P_LS2CL_REP_CHAR_SELECT_SUCC = 553648139,
P_LS2CL_REP_SHARD_SELECT_SUCC = 553648143,
P_LS2CL_REQ_LIVE_CHECK = 553648150,
P_LS2CL_REP_SHARD_LIST_INFO_SUCC = 553648153
};
struct CNLoginData {
std::map<int64_t, Player> characters;
int64_t selectedChar;
};
// WARNING: THERE CAN ONLY BE ONE OF THESE SERVERS AT A TIME!!!!!! TODO: change loginSessions & packet handlers to be non-static
class CNLoginServer : public CNServer {
private:
static void handlePacket(CNSocket* sock, CNPacketData* data);
static std::map<CNSocket*, CNLoginData> loginSessions;
public:
CNLoginServer(uint16_t p);
void killConnection(CNSocket* cns);
};
#endif

283
src/CNProtocol.cpp Normal file
View File

@ -0,0 +1,283 @@
#include "CNProtocol.hpp"
// ========================================================[[ CNSocketEncryption ]]========================================================
// literally C/P from the client and converted to C++ (does some byte swapping /shrug)
int CNSocketEncryption::Encrypt_byte_change_A(int ERSize, uint8_t* data, int size) {
int num = 0;
int num2 = 0;
int num3 = 0;
while (num + ERSize <= size)
{
int num4 = num + num3;
int num5 = num + (ERSize - 1 - num3);
uint8_t b = data[num4];
data[num4] = data[num5];
data[num5] = b;
num += ERSize;
num3++;
if (num3 > ERSize / 2)
{
num3 = 0;
}
}
num2 = ERSize - (num + ERSize - size);
return num + num2;
}
int CNSocketEncryption::xorData(uint8_t* buffer, uint8_t* key, int size) {
// xor every 8 bytes with 8 byte key
for (int i = 0; i < size; i++) {
buffer[i] ^= key[i % keyLength];
}
return size;
}
uint64_t CNSocketEncryption::createNewKey(uint64_t uTime, int32_t iv1, int32_t iv2) {
uint64_t num = (uint64_t)(iv1 + 1);
uint64_t num2 = (uint64_t)(iv2 + 1);
uint64_t dEKey = (uint64_t)(*(uint64_t*)&defaultKey[0]);
return dEKey * (uTime * num * num2);
}
int CNSocketEncryption::encryptData(uint8_t* buffer, uint8_t* key, int size) {
int eRSize = size % (keyLength / 2 + 1) * 2 + keyLength; // C/P from client
int size2 = xorData(buffer, key, size);
return Encrypt_byte_change_A(eRSize, buffer, size2);
}
int CNSocketEncryption::decryptData(uint8_t* buffer, uint8_t* key, int size) {
int eRSize = size % (keyLength / 2 + 1) * 2 + keyLength; // size % of 18????
int size2 = Encrypt_byte_change_A(eRSize, buffer, size);
return xorData(buffer, key, size2);
}
// ========================================================[[ CNPacketData ]]========================================================
CNPacketData::CNPacketData(void* b, uint32_t t, int l, uint64_t k): buf(b), type(t), size(l), key(k) {}
CNPacketData::~CNPacketData() {
free(buf); // we own the buffer
}
// ========================================================[[ CNSocket ]]========================================================
CNSocket::CNSocket(SOCKET s, PacketHandler ph): sock(s), pHandler(ph) {
EKey = (uint64_t)(*(uint64_t*)&CNSocketEncryption::defaultKey[0]);
}
bool CNSocket::sendData(uint8_t* data, int size) {
int sentBytes = 0;
while (sentBytes < size) {
int sent = send(sock, (buffer_t*)(data + sentBytes), size - sentBytes, 0); // no flags defined
if (SOCKETERROR(sent))
return false; // error occured while sending bytes
sentBytes += sent;
}
return true; // it worked!
}
void CNSocket::setEKey(uint64_t k) {
EKey = k;
}
void CNSocket::setFEKey(uint64_t k) {
FEKey = k;
}
uint64_t CNSocket::getEKey() {
return EKey;
}
uint64_t CNSocket::getFEKey() {
return FEKey;
}
bool CNSocket::isAlive() {
return alive;
}
void CNSocket::kill() {
alive = false;
#ifdef _WIN32
shutdown(sock, SD_BOTH);
closesocket(sock);
#else
shutdown(sock, SHUT_RDWR);
close(sock);
#endif
}
void CNSocket::sendPacket(CNPacketData* pak) {
int tmpSize = pak->size + sizeof(uint32_t);
uint8_t* tmpBuf = (uint8_t*)xmalloc(tmpSize);
// copy packet type to the front of the buffer & then the actual buffer
memcpy(tmpBuf, (void*)&pak->type, sizeof(uint32_t));
memcpy(tmpBuf+sizeof(uint32_t), pak->buf, pak->size);
// encrypt the packet
CNSocketEncryption::encryptData((uint8_t*)tmpBuf, (uint8_t*)(&pak->key), tmpSize);
// send packet size
sendData((uint8_t*)&tmpSize, sizeof(uint32_t));
// send packet data!
sendData(tmpBuf, tmpSize);
delete pak;
free(tmpBuf); // free tmp buffer
}
void CNSocket::step() {
if (readSize <= 0) {
// we aren't reading a packet yet, try to start looking for one
int recved = recv(sock, (buffer_t*)readBuffer, sizeof(int32_t), 0);
if (!SOCKETERROR(recved)) {
// we got out packet size!!!!
readSize = *((int32_t*)readBuffer);
// sanity check
if (readSize > MAX_PACKETSIZE) {
kill();
return;
}
// we'll just leave bufferIndex at 0 since we already have the packet size, it's safe to overwrite those bytes
activelyReading = true;
}
}
if (readSize > 0 && readBufferIndex < readSize) {
// read until the end of the packet! (or at least try too)
int recved = recv(sock, (buffer_t*)(readBuffer + readBufferIndex), readSize - readBufferIndex, 0);
if (!SOCKETERROR(recved))
readBufferIndex += recved;
}
if (activelyReading && readBufferIndex - readSize <= 0) {
// decrypt readBuffer and copy to CNPacketData
CNSocketEncryption::decryptData(readBuffer, (uint8_t*)(&EKey), readSize);
// this doesn't leak memory because we free it in CNPacketData's deconstructor LOL
void* tmpBuf = xmalloc(readSize-sizeof(int32_t));
memcpy(tmpBuf, readBuffer+sizeof(uint32_t), readSize-sizeof(int32_t));
CNPacketData tmp(tmpBuf, *((uint32_t*)readBuffer), readSize-sizeof(int32_t), EKey);
// CALL PACKET HANDLER!!
pHandler(this, &tmp); // tmp's deconstructor will be called when readStep returns so that tmpBuffer we made will be cleaned up :)
// reset vars :)
readSize = 0;
readBufferIndex = 0;
activelyReading = false;
}
}
// ========================================================[[ CNServer ]]========================================================
void CNServer::init() {
// create socket file descriptor
sock = socket(AF_INET, SOCK_STREAM, 0);
if (SOCKETINVALID(sock)) {
std::cerr << "[FATAL] OpenFusion: socket failed" << std::endl;
exit(EXIT_FAILURE);
}
// attach socket to the port
int opt = 1;
#ifdef _WIN32
if (setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, (const char*)&opt, sizeof(opt)) != 0) {
#else
if (setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt)) != 0) {
#endif
std::cerr << "[FATAL] OpenFusion: setsockopt failed" << std::endl;
exit(EXIT_FAILURE);
}
address.sin_family = AF_INET;
address.sin_addr.s_addr = INADDR_ANY;
address.sin_port = htons(port);
addressSize = sizeof(address);
// Bind to the port
if (SOCKETERROR(bind(sock, (struct sockaddr *)&address, addressSize))) {
std::cerr << "[FATAL] OpenFusion: bind failed" << std::endl;
exit(EXIT_FAILURE);
}
if (SOCKETERROR(listen(sock, SOMAXCONN))) {
std::cerr << "[FATAL] OpenFusion: listen failed" << std::endl;
exit(EXIT_FAILURE);
}
// set server listener to non-blocking
#ifdef _WIN32
unsigned long mode = 1;
if (ioctlsocket(sock, FIONBIO, &mode) != 0) {
#else
if (fcntl(sock, F_SETFL, (fcntl(sock, F_GETFL, 0) | O_NONBLOCK)) != 0) {
#endif
std::cerr << "[FATAL] OpenFusion: fcntl failed" << std::endl;
exit(EXIT_FAILURE);
}
}
CNServer::CNServer() {};
CNServer::CNServer(uint16_t p): port(p) {}
void CNServer::start() {
std::cout << "Starting server at *:" << port << std::endl;
// listen to new connections, add to connection list
while (true) {
// listen for a new connection
SOCKET newConnection = accept(sock, (struct sockaddr *)&(address), (socklen_t*)&(addressSize));
if (!SOCKETINVALID(newConnection)) {
// new connection! make sure to set non-blocking!
#ifdef _WIN32
unsigned long mode = 1;
if (ioctlsocket(newConnection, FIONBIO, &mode) != 0) {
#else
if (fcntl(newConnection, F_SETFL, (fcntl(sock, F_GETFL, 0) | O_NONBLOCK)) != 0) {
#endif
std::cerr << "[FATAL] OpenFusion: fcntl failed on new connection" << std::endl;
exit(EXIT_FAILURE);
}
std::cout << "New connection! " << inet_ntoa(address.sin_addr) << std::endl;
// add connection to list!
CNSocket* tmp = new CNSocket(newConnection, pHandler);
connections.push_back(tmp);
}
// for each connection, check if it's alive, if not kill it!
std::list<CNSocket*>::iterator i = connections.begin();
while (i != connections.end()) {
CNSocket* cSock = *i;
if (cSock->isAlive()) {
cSock->step();
++i; // go to the next element
} else {
killConnection(cSock);
connections.erase(i++);
delete cSock;
}
}
#ifdef _WIN32
Sleep(0);
#else
sleep(0); // so your cpu isn't at 100% all the time, we don't need all of that! im not hacky! you're hacky!
#endif
}
}
void CNServer::killConnection(CNSocket* cns) {} // stubbed lol

138
src/CNProtocol.hpp Normal file
View File

@ -0,0 +1,138 @@
#ifndef _CNP_HPP
#define _CNP_HPP
#define MAX_PACKETSIZE 8192
#define DEBUGLOG(x) x
#include <iostream>
#include <stdio.h>
#include <stdint.h>
#ifdef _WIN32
// windows (UNTESTED)
#include <winsock2.h>
#include <windows.h>
#include <ws2tcpip.h>
#pragma comment(lib, "Ws2_32.lib")
typedef char buffer_t;
//#define errno WSAGetLastError()
#define SOCKETINVALID(x) (x == INVALID_SOCKET)
#define SOCKETERROR(x) (x == SOCKET_ERROR)
#else
// posix platform
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <errno.h>
typedef int SOCKET;
typedef void buffer_t;
#define SOCKETINVALID(x) (x < 0)
#define SOCKETERROR(x) (x == -1)
#endif
#include <fcntl.h>
#include <string>
#include <cstring>
#include <csignal>
#include <list>
#include <queue>
/*
Packets format (sent from the client):
[4 bytes] - size of packet (including these 4 bytes!)
[size bytes] - Encrypted packet (byte swapped && xor'd with 8 byte key; see CNSocketEncryption)
[4 bytes] - packet type (which is a combination of the first 4 bytes of the packet and a checksum in some versions)
[structure]
*/
// error checking calloc wrapper
inline void* xmalloc(size_t sz) {
void* res = calloc(1, sz);
if (res == NULL) {
std::cerr << "[FATAL] OpenFusion: calloc failed to allocate memory!" << std::endl;
exit(EXIT_FAILURE);
}
return res;
}
namespace CNSocketEncryption {
// you won't believe how complicated they made it in the client :facepalm:
static constexpr const char* defaultKey = "m@rQn~W#";
static const unsigned int keyLength = 8;
int Encrypt_byte_change_A(int ERSize, uint8_t* data, int size);
int xorData(uint8_t* buffer, uint8_t* key, int size);
uint64_t createNewKey(uint64_t uTime, int32_t iv1, int32_t iv2);
int encryptData(uint8_t* buffer, uint8_t* key, int size);
int decryptData(uint8_t* buffer, uint8_t* key, int size);
}
class CNPacketData {
public:
void* buf;
int size;
uint32_t type;
uint64_t key;
CNPacketData(void* b, uint32_t t, int l, uint64_t k);
~CNPacketData();
};
class CNSocket;
typedef void (*PacketHandler)(CNSocket* sock, CNPacketData* data);
class CNSocket {
private:
uint64_t EKey;
uint64_t FEKey;
int32_t readSize = 0;
uint8_t* readBuffer = new uint8_t[MAX_PACKETSIZE];
int readBufferIndex = 0;
bool activelyReading = false;
bool alive = true;
bool sendData(uint8_t* data, int size);
public:
SOCKET sock;
PacketHandler pHandler;
CNSocket(SOCKET s, PacketHandler ph);
void setEKey(uint64_t k);
void setFEKey(uint64_t k);
uint64_t getEKey();
uint64_t getFEKey();
void kill();
void sendPacket(CNPacketData* pak);
void step();
bool isAlive();
};
// in charge of accepting new connections and making sure each connection is kept alive
class CNServer {
protected:
std::list<CNSocket*> connections;
SOCKET sock;
uint16_t port;
socklen_t addressSize;
struct sockaddr_in address;
void init();
public:
PacketHandler pHandler;
CNServer();
CNServer(uint16_t p);
void start();
virtual void killConnection(CNSocket* cns);
};
#endif

33
src/CNShardServer.cpp Normal file
View File

@ -0,0 +1,33 @@
#include "CNProtocol.hpp"
#include "CNStructs.hpp"
#include "CNShardServer.hpp"
#include "PlayerManager.hpp"
#include "CNShared.hpp"
#include <iostream>
#include <sstream>
#include <cstdlib>
std::map<uint32_t, PacketHandler> CNShardServer::ShardPackets;
CNShardServer::CNShardServer(uint16_t p) {
port = p;
pHandler = &CNShardServer::handlePacket;
init();
}
void CNShardServer::handlePacket(CNSocket* sock, CNPacketData* data) {
if (ShardPackets.find(data->type) != ShardPackets.end())
ShardPackets[data->type](sock, data);
else
std::cerr << "OpenFusion: SHARD UNIMPLM ERR. PacketType: " << data->type << std::endl;
}
void CNShardServer::killConnection(CNSocket* cns) {
// remove from CNSharedData
Player cachedPlr = PlayerManager::getPlayer(cns);
PlayerManager::removePlayer(cns);
CNSharedData::erasePlayer(cachedPlr.SerialKey);
std::cout << U16toU8(cachedPlr.PCStyle.szFirstName) << " " << U16toU8(cachedPlr.PCStyle.szLastName) << " left" << std::endl;
}

49
src/CNShardServer.hpp Normal file
View File

@ -0,0 +1,49 @@
#ifndef _CNSS_HPP
#define _CNSS_HPP
#include "CNProtocol.hpp"
#include <map>
enum SHARDPACKETID {
// client 2 shard
P_CL2FE_REQ_PC_ENTER = 318767105,
P_CL2FE_REQ_PC_LOADING_COMPLETE = 318767245,
P_CL2FE_REQ_PC_MOVE = 318767107,
P_CL2FE_REQ_PC_STOP = 318767108,
P_CL2FE_REQ_PC_JUMP = 318767109,
P_CL2FE_REQ_PC_MOVEPLATFORM = 318767168,
P_CL2FE_REQ_PC_GOTO = 318767124,
P_CL2FE_GM_REQ_PC_SET_VALUE = 318767211,
P_CL2FE_REQ_SEND_FREECHAT_MESSAGE = 318767111,
P_CL2FE_REQ_PC_AVATAR_EMOTES_CHAT = 318767184,
// shard 2 client
P_FE2CL_REP_PC_ENTER_SUCC = 822083586,
P_FE2CL_REP_PC_LOADING_COMPLETE_SUCC = 822083833,
P_FE2CL_PC_NEW = 822083587,
P_FE2CL_PC_MOVE = 822083592,
P_FE2CL_PC_STOP = 822083593,
P_FE2CL_PC_JUMP = 822083594,
P_FE2CL_PC_EXIT = 822083590,
P_FE2CL_PC_MOVEPLATFORM = 822083704,
P_FE2CL_REP_PC_GOTO_SUCC = 822083633,
P_FE2CL_GM_REP_PC_SET_VALUE = 822083781,
P_FE2CL_REP_PC_AVATAR_EMOTES_CHAT = 822083730
};
#define REGISTER_SHARD_PACKET(pactype, handlr) CNShardServer::ShardPackets[pactype] = handlr;
// WARNING: THERE CAN ONLY BE ONE OF THESE SERVERS AT A TIME!!!!!! TODO: change players & packet handlers to be non-static
class CNShardServer : public CNServer {
private:
static void handlePacket(CNSocket* sock, CNPacketData* data);
public:
static std::map<uint32_t, PacketHandler> ShardPackets;
CNShardServer(uint16_t p);
void killConnection(CNSocket* cns);
};
#endif

25
src/CNShared.cpp Normal file
View File

@ -0,0 +1,25 @@
#include "CNShared.hpp"
#ifdef __MINGW32__
#include "mingw/mingw.mutex.h"
#else
#include <mutex>
#endif
std::map<int64_t, Player> CNSharedData::players;
std::mutex playerCrit;
void CNSharedData::setPlayer(int64_t sk, Player& plr) {
std::lock_guard<std::mutex> lock(playerCrit); // the lock will be removed when the function ends
players[sk] = plr;
}
Player CNSharedData::getPlayer(int64_t sk) {
return players[sk];
}
void CNSharedData::erasePlayer(int64_t sk) {
std::lock_guard<std::mutex> lock(playerCrit); // the lock will be removed when the function ends
players.erase(sk);
}

23
src/CNShared.hpp Normal file
View File

@ -0,0 +1,23 @@
/*
CNShared.hpp
There's some data shared between the Login Server and the Shard Server. Of course all of this needs to be thread-safe. No mucking about on this one!
*/
#ifndef _CNSD_HPP
#define _CNSD_HPP
#include <map>
#include <string>
#include "Player.hpp"
namespace CNSharedData {
// serialkey corresponds to player data
extern std::map<int64_t, Player> players;
void setPlayer(int64_t sk, Player& plr);
Player getPlayer(int64_t sk);
void erasePlayer(int64_t sk);
}
#endif

24
src/CNStructs.cpp Normal file
View File

@ -0,0 +1,24 @@
#include "CNStructs.hpp"
std::string U16toU8(char16_t* src) {
std::wstring_convert<std::codecvt_utf8_utf16<char16_t>,char16_t> convert;
return convert.to_bytes(src);
}
// returns number of char16_t that was written at des
int U8toU16(std::string src, char16_t* des) {
std::wstring_convert<std::codecvt_utf8_utf16<char16_t>,char16_t> convert;
std::u16string tmp = convert.from_bytes(src);
// copy utf16 string to buffer
memcpy(des, tmp.c_str(), sizeof(char16_t) * tmp.length());
des[tmp.length()] = '\0';
return tmp.length();
}
uint64_t getTime() {
struct timeval tp;
gettimeofday(&tp, NULL);
return tp.tv_sec * 1000 + tp.tv_usec / 1000;
}

533
src/CNStructs.hpp Normal file
View File

@ -0,0 +1,533 @@
/*
CNStructs.hpp - defines some basic structs & useful methods for packets used by FusionFall
NOTE: this is missing the vast majority of packets, I have also ommitted the ERR & FAIL packets for simplicity
*/
#ifndef _CNS_HPP
#define _CNS_HPP
#include <iostream>
#include <stdio.h>
#include <stdint.h>
#include <sys/time.h>
#include <cstring>
#include <string>
#include <locale>
#include <codecvt>
// TODO: rewrite U16toU8 & U8toU16 to not use codecvt
std::string U16toU8(char16_t* src);
// returns number of char16_t that was written at des
int U8toU16(std::string src, char16_t* des);
uint64_t getTime();
//#define CNPROTO_VERSION_0728
#ifdef CNPROTO_VERSION_0728
#define AEQUIP_COUNT 12
#else
#define AEQUIP_COUNT 9
#endif
// ========================================================[[ General Purpose ]]========================================================
// sets the same byte alignment as the structs in the client
#pragma pack(push, 4)
struct sPCStyle {
int64_t iPC_UID;
int8_t iNameCheck;
char16_t szFirstName[9];
char16_t szLastName[17];
int8_t iGender;
int8_t iFaceStyle;
int8_t iHairStyle;
int8_t iHairColor;
int8_t iSkinColor;
int8_t iEyeColor;
int8_t iHeight;
int8_t iBody;
int32_t iClass;
};
#pragma pack(1)
struct sPCStyle2 {
int8_t iAppearanceFlag;
int8_t iTutorialFlag;
int8_t iPayzoneFlag;
sPCStyle2() {}
sPCStyle2(int8_t a, int8_t t, int8_t p):
iAppearanceFlag(a), iTutorialFlag(t), iPayzoneFlag(p) {}
};
#pragma pack(2)
struct sOnItem {
int16_t iEquipHandID;
int16_t iEquipUBID;
int16_t iEquipLBID;
int16_t iEquipFootID;
int16_t iEquipHeadID;
int16_t iEquipFaceID;
int16_t iEquipBackID;
};
struct sOnItem_Index {
int16_t iEquipUBID_index;
int16_t iEquipLBID_index;
int16_t iEquipFootID_index;
int16_t iFaceStyle;
int16_t iHairStyle;
};
struct sNano {
int16_t iID;
int16_t iSkillID;
int16_t iStamina;
};
#pragma pack(4)
struct sItemBase {
int16_t iType;
int16_t iID;
int32_t iOpt;
int32_t iTimeLimit;
#ifdef CNPROTO_VERSION_0728
int32_t iSerial;
#endif
};
struct sTimeBuff {
uint64_t iTimeLimit;
uint64_t iTimeDuration;
int32_t iTimeRepeat;
int32_t iValue;
int32_t iConfirmNum;
};
struct sRunningQuest {
int32_t m_aCurrTaskID;
int32_t m_aKillNPCID[3];
int32_t m_aKillNPCCount[3];
int32_t m_aNeededItemID[3];
int32_t m_aNeededItemCount[3];
};
struct sPCLoadData2CL {
int16_t iUserLevel;
sPCStyle PCStyle;
sPCStyle2 PCStyle2;
int16_t iLevel;
int16_t iMentor;
int16_t iMentorCount;
int32_t iHP;
int32_t iBatteryW;
int32_t iBatteryN;
int32_t iCandy;
int32_t iFusionMatter;
int8_t iSpecialState;
int32_t iMapNum;
int32_t iX;
int32_t iY;
int32_t iZ;
int32_t iAngle;
sItemBase aEquip[AEQUIP_COUNT];
sItemBase aInven[50];
sItemBase aQInven[50];
sNano aNanoBank[37];
int16_t aNanoSlots[3];
int16_t iActiveNanoSlotNum;
int32_t iConditionBitFlag;
int32_t eCSTB___Add;
sTimeBuff TimeBuff;
int64_t aQuestFlag[32];
int64_t aRepeatQuestFlag[8];
sRunningQuest aRunningQuest[9];
int32_t iCurrentMissionID;
int32_t iWarpLocationFlag;
int64_t aWyvernLocationFlag[2];
int32_t iBuddyWarpTime;
int32_t iFatigue;
int32_t iFatigue_Level;
int32_t iFatigueRate;
int64_t iFirstUseFlag1;
int64_t iFirstUseFlag2;
int32_t aiPCSkill[33];
sPCLoadData2CL() {};
};
struct sPCAppearanceData {
int32_t iID;
sPCStyle PCStyle;
int32_t iConditionBitFlag;
int8_t iPCState;
int8_t iSpecialState;
int16_t iLv;
int32_t iHP;
int32_t iMapNum;
int32_t iX;
int32_t iY;
int32_t iZ;
int32_t iAngle;
sItemBase ItemEquip[AEQUIP_COUNT];
sNano Nano;
int32_t eRT;
};
// ========================================================[[ Client2LoginServer packets ]]========================================================
#pragma pack(4)
struct sP_CL2LS_REQ_LOGIN {
char16_t szID[33];
char16_t szPassword[33];
int32_t iClientVerA;
int32_t iClientVerB;
int32_t iClientVerC;
int32_t iLoginType;
uint8_t szCookie_TEGid[64];
uint8_t szCookie_authid[255];
};
struct sP_CL2LS_REQ_CHECK_CHAR_NAME {
int32_t iFNCode;
int32_t iLNCode;
int32_t iMNCode;
char16_t szFirstName[9];
char16_t szLastName[17];
};
struct sP_CL2LS_REQ_SAVE_CHAR_NAME {
int8_t iSlotNum;
int8_t iGender;
int32_t iFNCode;
int32_t iLNCode;
int32_t iMNCode;
char16_t szFirstName[9];
char16_t szLastName[17];
};
struct sP_CL2LS_REQ_CHAR_CREATE {
sPCStyle PCStyle;
sOnItem sOn_Item;
sOnItem_Index sOn_Item_Index;
};
struct sP_CL2LS_REQ_CHAR_SELECT {
int64_t iPC_UID;
};
struct sP_CL2LS_REP_LIVE_CHECK {
int32_t unused;
};
#pragma pack(1)
struct sP_CL2LS_REQ_SHARD_SELECT {
int8_t iShardNum;
};
struct sP_CL2LS_REQ_SHARD_LIST_INFO {
uint8_t unused;
};
// ========================================================[[ LoginServer2Client packets ]]========================================================
#pragma pack(4)
struct sP_LS2CL_REP_LOGIN_SUCC {
int8_t iCharCount;
int8_t iSlotNum;
int8_t iPaymentFlag;
int8_t iTempForPacking4; // UNUSED
uint64_t uiSvrTime; // UNIX timestamp
char16_t szID[33];
uint32_t iOpenBetaFlag;
};
#pragma pack(2)
struct sP_LS2CL_REP_CHECK_CHAR_NAME_SUCC {
char16_t szFirstName[9];
char16_t szLastName[17];
};
#pragma pack(4)
struct sP_LS2CL_REP_SAVE_CHAR_NAME_SUCC {
int64_t iPC_UID;
int8_t iSlotNum;
int8_t iGender;
char16_t szFirstName[9];
char16_t szLastName[17];
};
struct sP_LS2CL_REP_CHAR_CREATE_SUCC {
int16_t iLevel;
sPCStyle PC_Style;
sPCStyle2 PC_Style2;
sOnItem sOn_Item;
};
struct sP_LS2CL_REP_CHAR_INFO {
int8_t iSlot;
int16_t iLevel;
sPCStyle sPC_Style;
sPCStyle2 sPC_Style2;
int32_t iX;
int32_t iY;
int32_t iZ;
sItemBase aEquip[AEQUIP_COUNT];
};
struct sP_LS2CL_REP_SHARD_SELECT_SUCC {
uint8_t g_FE_ServerIP[16]; // Ascii
int32_t g_FE_ServerPort;
int64_t iEnterSerialKey;
};
#pragma pack(1)
struct sP_LS2CL_REP_CHAR_SELECT_SUCC {
int8_t unused;
};
struct sP_LS2CL_REP_SHARD_LIST_INFO_SUCC {
uint8_t aShardConnectFlag[27];
};
// ========================================================[[ Client2ShardServer packets ]]========================================================
#pragma pack(4)
struct sP_CL2FE_REQ_PC_ENTER {
char16_t szID[33];
int32_t iTempValue;
int64_t iEnterSerialKey;
};
struct sP_CL2FE_REQ_PC_LOADING_COMPLETE {
int32_t iPC_ID;
};
struct sP_CL2FE_REQ_PC_MOVE {
uint64_t iCliTime;
int32_t iX;
int32_t iY;
int32_t iZ;
float fVX;
float fVY;
float fVZ;
int32_t iAngle;
uint8_t cKeyValue;
int32_t iSpeed;
};
struct sP_CL2FE_REQ_PC_STOP {
uint64_t iCliTime;
int32_t iX;
int32_t iY;
int32_t iZ;
};
struct sP_CL2FE_REQ_PC_JUMP {
uint64_t iCliTime;
int32_t iX;
int32_t iY;
int32_t iZ;
int32_t iVX;
int32_t iVY;
int32_t iVZ;
int32_t iAngle;
uint8_t cKeyValue;
int32_t iSpeed;
};
struct sP_CL2FE_REQ_PC_MOVEPLATFORM {
uint64_t iCliTime;
int32_t iLcX;
int32_t iLcY;
int32_t iLcZ;
int32_t iX;
int32_t iY;
int32_t iZ;
float fVX;
float fVY;
float fVZ;
int32_t bDown;
uint32_t iPlatformID;
int32_t iAngle;
uint32_t cKeyValue;
int32_t iSpeed;
};
struct sP_CL2FE_REQ_PC_GOTO {
int32_t iToX;
int32_t iToY;
int32_t iToZ;
};
struct sP_CL2FE_GM_REQ_PC_SET_VALUE {
int32_t iPC_ID;
int32_t iSetValueType;
int32_t iSetValue;
};
struct sP_CL2FE_REQ_SEND_FREECHAT_MESSAGE {
char16_t szFreeChat[128];
int32_t iEmoteCode;
};
struct sP_CL2FE_REQ_PC_AVATAR_EMOTES_CHAT {
int32_t iID_From;
int32_t iEmoteCode;
};
// ========================================================[[ ShardServer2Client packets ]]========================================================
struct sP_FE2CL_REP_PC_ENTER_SUCC {
int32_t iID;
sPCLoadData2CL PCLoadData2CL;
uint64_t uiSvrTime;
};
struct sP_FE2CL_REP_PC_LOADING_COMPLETE_SUCC {
int32_t iPC_ID;
};
// literally just a wrapper for a sPCAppearanceData struct :/
struct sP_FE2CL_PC_NEW {
sPCAppearanceData PCAppearanceData;
};
struct sP_FE2CL_PC_MOVE {
uint64_t iCliTime;
int32_t iX;
int32_t iY;
int32_t iZ;
float fVX;
float fVY;
float fVZ;
int32_t iAngle;
uint8_t cKeyValue;
int32_t iSpeed;
int32_t iID;
uint64_t iSvrTime;
};
struct sP_FE2CL_PC_STOP {
uint64_t iCliTime;
int32_t iX;
int32_t iY;
int32_t iZ;
int32_t iID;
uint64_t iSvrTime;
};
struct sP_FE2CL_PC_JUMP {
uint64_t iCliTime;
int32_t iX;
int32_t iY;
int32_t iZ;
int32_t iVX;
int32_t iVY;
int32_t iVZ;
int32_t iAngle;
uint8_t cKeyValue;
int32_t iSpeed;
int32_t iID;
uint64_t iSvrTime;
};
struct sP_FE2CL_PC_MOVEPLATFORM {
uint64_t iCliTime;
int32_t iLcX;
int32_t iLcY;
int32_t iLcZ;
int32_t iX;
int32_t iY;
int32_t iZ;
float fVX;
float fVY;
float fVZ;
int32_t bDown;
uint32_t iPlatformID;
int32_t iAngle;
int8_t cKeyValue;
int32_t iSpeed;
int32_t iPC_ID;
uint64_t iSvrTime;
};
struct sP_FE2CL_GM_REP_PC_SET_VALUE {
int32_t iPC_ID;
int32_t iSetValueType;
int32_t iSetValue;
};
struct sP_FE2CL_PC_EXIT {
int32_t iID;
int32_t iExitType;
};
struct sP_FE2CL_REP_PC_GOTO_SUCC {
int32_t iX;
int32_t iY;
int32_t iZ;
};
struct sP_FE2CL_REP_PC_AVATAR_EMOTES_CHAT {
int32_t iID_From;
int32_t iEmoteCode;
};
#pragma pack(pop)
#endif

37
src/ChatManager.cpp Normal file
View File

@ -0,0 +1,37 @@
#include "CNShardServer.hpp"
#include "CNStructs.hpp"
#include "ChatManager.hpp"
#include "PlayerManager.hpp"
void ChatManager::init() {
REGISTER_SHARD_PACKET(P_CL2FE_REQ_SEND_FREECHAT_MESSAGE, chatHandler);
REGISTER_SHARD_PACKET(P_CL2FE_REQ_PC_AVATAR_EMOTES_CHAT, emoteHandler);
}
void ChatManager::chatHandler(CNSocket* sock, CNPacketData* data) {
sP_CL2FE_REQ_SEND_FREECHAT_MESSAGE* chat = (sP_CL2FE_REQ_SEND_FREECHAT_MESSAGE*)data->buf;
// stubbed for now
}
void ChatManager::emoteHandler(CNSocket* sock, CNPacketData* data) {
// you can dance with friends!!!!!!!!
sP_CL2FE_REQ_PC_AVATAR_EMOTES_CHAT* emote = (sP_CL2FE_REQ_PC_AVATAR_EMOTES_CHAT*)data->buf;
PlayerView plr = PlayerManager::players[sock];
// send to client
sP_FE2CL_REP_PC_AVATAR_EMOTES_CHAT* resp = (sP_FE2CL_REP_PC_AVATAR_EMOTES_CHAT*)xmalloc(sizeof(sP_FE2CL_REP_PC_AVATAR_EMOTES_CHAT));
resp->iEmoteCode = emote->iEmoteCode;
resp->iID_From = plr.plr.iID;
sock->sendPacket(new CNPacketData((void*)resp, P_FE2CL_REP_PC_AVATAR_EMOTES_CHAT, sizeof(sP_FE2CL_REP_PC_AVATAR_EMOTES_CHAT), sock->getFEKey()));
// send to visible players (players within render distance)
for (CNSocket* otherSock : plr.viewable) {
resp = (sP_FE2CL_REP_PC_AVATAR_EMOTES_CHAT*)xmalloc(sizeof(sP_FE2CL_REP_PC_AVATAR_EMOTES_CHAT));
resp->iEmoteCode = emote->iEmoteCode;
resp->iID_From = plr.plr.iID;
otherSock->sendPacket(new CNPacketData((void*)resp, P_FE2CL_REP_PC_AVATAR_EMOTES_CHAT, sizeof(sP_FE2CL_REP_PC_AVATAR_EMOTES_CHAT), otherSock->getFEKey()));
}
}

13
src/ChatManager.hpp Normal file
View File

@ -0,0 +1,13 @@
#ifndef _CM_HPP
#define _CM_HPP
#include "CNShardServer.hpp"
namespace ChatManager {
void init();
void chatHandler(CNSocket* sock, CNPacketData* data);
void emoteHandler(CNSocket* sock, CNPacketData* data);
}
#endif

462
src/INIReader.hpp Normal file
View File

@ -0,0 +1,462 @@
// Read an INI file into easy-to-access name/value pairs.
// inih and INIReader are released under the New BSD license (see LICENSE.txt).
// Go to the project home page for more info:
//
// https://github.com/benhoyt/inih
/* inih -- simple .INI file parser
inih is released under the New BSD license (see LICENSE.txt). Go to the project
home page for more info:
https://github.com/benhoyt/inih
*/
#ifndef __INI_H__
#define __INI_H__
/* Make this header file easier to include in C++ code */
#ifdef __cplusplus
extern "C" {
#endif
#include <stdio.h>
/* Typedef for prototype of handler function. */
typedef int (*ini_handler)(void* user, const char* section,
const char* name, const char* value);
/* Typedef for prototype of fgets-style reader function. */
typedef char* (*ini_reader)(char* str, int num, void* stream);
/* Parse given INI-style file. May have [section]s, name=value pairs
(whitespace stripped), and comments starting with ';' (semicolon). Section
is "" if name=value pair parsed before any section heading. name:value
pairs are also supported as a concession to Python's configparser.
For each name=value pair parsed, call handler function with given user
pointer as well as section, name, and value (data only valid for duration
of handler call). Handler should return nonzero on success, zero on error.
Returns 0 on success, line number of first error on parse error (doesn't
stop on first error), -1 on file open error, or -2 on memory allocation
error (only when INI_USE_STACK is zero).
*/
int ini_parse(const char* filename, ini_handler handler, void* user);
/* Same as ini_parse(), but takes a FILE* instead of filename. This doesn't
close the file when it's finished -- the caller must do that. */
int ini_parse_file(FILE* file, ini_handler handler, void* user);
/* Same as ini_parse(), but takes an ini_reader function pointer instead of
filename. Used for implementing custom or string-based I/O. */
int ini_parse_stream(ini_reader reader, void* stream, ini_handler handler,
void* user);
/* Nonzero to allow multi-line value parsing, in the style of Python's
configparser. If allowed, ini_parse() will call the handler with the same
name for each subsequent line parsed. */
#ifndef INI_ALLOW_MULTILINE
#define INI_ALLOW_MULTILINE 1
#endif
/* Nonzero to allow a UTF-8 BOM sequence (0xEF 0xBB 0xBF) at the start of
the file. See http://code.google.com/p/inih/issues/detail?id=21 */
#ifndef INI_ALLOW_BOM
#define INI_ALLOW_BOM 1
#endif
/* Nonzero to allow inline comments (with valid inline comment characters
specified by INI_INLINE_COMMENT_PREFIXES). Set to 0 to turn off and match
Python 3.2+ configparser behaviour. */
#ifndef INI_ALLOW_INLINE_COMMENTS
#define INI_ALLOW_INLINE_COMMENTS 1
#endif
#ifndef INI_INLINE_COMMENT_PREFIXES
#define INI_INLINE_COMMENT_PREFIXES ";"
#endif
/* Nonzero to use stack, zero to use heap (malloc/free). */
#ifndef INI_USE_STACK
#define INI_USE_STACK 1
#endif
/* Stop parsing on first error (default is to keep parsing). */
#ifndef INI_STOP_ON_FIRST_ERROR
#define INI_STOP_ON_FIRST_ERROR 0
#endif
/* Maximum line length for any line in INI file. */
#ifndef INI_MAX_LINE
#define INI_MAX_LINE 200
#endif
#ifdef __cplusplus
}
#endif
/* inih -- simple .INI file parser
inih is released under the New BSD license (see LICENSE.txt). Go to the project
home page for more info:
https://github.com/benhoyt/inih
*/
#if defined(_MSC_VER) && !defined(_CRT_SECURE_NO_WARNINGS)
#define _CRT_SECURE_NO_WARNINGS
#endif
#include <stdio.h>
#include <ctype.h>
#include <string.h>
#if !INI_USE_STACK
#include <stdlib.h>
#endif
#define MAX_SECTION 50
#define MAX_NAME 50
/* Strip whitespace chars off end of given string, in place. Return s. */
inline static char* rstrip(char* s)
{
char* p = s + strlen(s);
while (p > s && isspace((unsigned char)(*--p)))
*p = '\0';
return s;
}
/* Return pointer to first non-whitespace char in given string. */
inline static char* lskip(const char* s)
{
while (*s && isspace((unsigned char)(*s)))
s++;
return (char*)s;
}
/* Return pointer to first char (of chars) or inline comment in given string,
or pointer to null at end of string if neither found. Inline comment must
be prefixed by a whitespace character to register as a comment. */
inline static char* find_chars_or_comment(const char* s, const char* chars)
{
#if INI_ALLOW_INLINE_COMMENTS
int was_space = 0;
while (*s && (!chars || !strchr(chars, *s)) &&
!(was_space && strchr(INI_INLINE_COMMENT_PREFIXES, *s))) {
was_space = isspace((unsigned char)(*s));
s++;
}
#else
while (*s && (!chars || !strchr(chars, *s))) {
s++;
}
#endif
return (char*)s;
}
/* Version of strncpy that ensures dest (size bytes) is null-terminated. */
inline static char* strncpy0(char* dest, const char* src, size_t size)
{
strncpy(dest, src, size);
dest[size - 1] = '\0';
return dest;
}
/* See documentation in header file. */
inline int ini_parse_stream(ini_reader reader, void* stream, ini_handler handler,
void* user)
{
/* Uses a fair bit of stack (use heap instead if you need to) */
#if INI_USE_STACK
char line[INI_MAX_LINE];
#else
char* line;
#endif
char section[MAX_SECTION] = "";
char prev_name[MAX_NAME] = "";
char* start;
char* end;
char* name;
char* value;
int lineno = 0;
int error = 0;
#if !INI_USE_STACK
line = (char*)malloc(INI_MAX_LINE);
if (!line) {
return -2;
}
#endif
/* Scan through stream line by line */
while (reader(line, INI_MAX_LINE, stream) != NULL) {
lineno++;
start = line;
#if INI_ALLOW_BOM
if (lineno == 1 && (unsigned char)start[0] == 0xEF &&
(unsigned char)start[1] == 0xBB &&
(unsigned char)start[2] == 0xBF) {
start += 3;
}
#endif
start = lskip(rstrip(start));
if (*start == ';' || *start == '#') {
/* Per Python configparser, allow both ; and # comments at the
start of a line */
}
#if INI_ALLOW_MULTILINE
else if (*prev_name && *start && start > line) {
#if INI_ALLOW_INLINE_COMMENTS
end = find_chars_or_comment(start, NULL);
if (*end)
*end = '\0';
rstrip(start);
#endif
/* Non-blank line with leading whitespace, treat as continuation
of previous name's value (as per Python configparser). */
if (!handler(user, section, prev_name, start) && !error)
error = lineno;
}
#endif
else if (*start == '[') {
/* A "[section]" line */
end = find_chars_or_comment(start + 1, "]");
if (*end == ']') {
*end = '\0';
strncpy0(section, start + 1, sizeof(section));
*prev_name = '\0';
}
else if (!error) {
/* No ']' found on section line */
error = lineno;
}
}
else if (*start) {
/* Not a comment, must be a name[=:]value pair */
end = find_chars_or_comment(start, "=:");
if (*end == '=' || *end == ':') {
*end = '\0';
name = rstrip(start);
value = lskip(end + 1);
#if INI_ALLOW_INLINE_COMMENTS
end = find_chars_or_comment(value, NULL);
if (*end)
*end = '\0';
#endif
rstrip(value);
/* Valid name[=:]value pair found, call handler */
strncpy0(prev_name, name, sizeof(prev_name));
if (!handler(user, section, name, value) && !error)
error = lineno;
}
else if (!error) {
/* No '=' or ':' found on name[=:]value line */
error = lineno;
}
}
#if INI_STOP_ON_FIRST_ERROR
if (error)
break;
#endif
}
#if !INI_USE_STACK
free(line);
#endif
return error;
}
/* See documentation in header file. */
inline int ini_parse_file(FILE* file, ini_handler handler, void* user)
{
return ini_parse_stream((ini_reader)fgets, file, handler, user);
}
/* See documentation in header file. */
inline int ini_parse(const char* filename, ini_handler handler, void* user)
{
FILE* file;
int error;
file = fopen(filename, "r");
if (!file)
return -1;
error = ini_parse_file(file, handler, user);
fclose(file);
return error;
}
#endif /* __INI_H__ */
#ifndef __INIREADER_H__
#define __INIREADER_H__
#include <map>
#include <set>
#include <string>
// Read an INI file into easy-to-access name/value pairs. (Note that I've gone
// for simplicity here rather than speed, but it should be pretty decent.)
class INIReader
{
public:
// Empty Constructor
INIReader() {};
// Construct INIReader and parse given filename. See ini.h for more info
// about the parsing.
INIReader(std::string filename);
// Construct INIReader and parse given file. See ini.h for more info
// about the parsing.
INIReader(FILE *file);
// Return the result of ini_parse(), i.e., 0 on success, line number of
// first error on parse error, or -1 on file open error.
int ParseError() const;
// Return the list of sections found in ini file
const std::set<std::string>& Sections() const;
// Get a string value from INI file, returning default_value if not found.
std::string Get(std::string section, std::string name,
std::string default_value) const;
// Get an integer (long) value from INI file, returning default_value if
// not found or not a valid integer (decimal "1234", "-1234", or hex "0x4d2").
long GetInteger(std::string section, std::string name, long default_value) const;
// Get a real (floating point double) value from INI file, returning
// default_value if not found or not a valid floating point value
// according to strtod().
double GetReal(std::string section, std::string name, double default_value) const;
// Get a single precision floating point number value from INI file, returning
// default_value if not found or not a valid floating point value
// according to strtof().
float GetFloat(std::string section, std::string name, float default_value) const;
// Get a boolean value from INI file, returning default_value if not found or if
// not a valid true/false value. Valid true values are "true", "yes", "on", "1",
// and valid false values are "false", "no", "off", "0" (not case sensitive).
bool GetBoolean(std::string section, std::string name, bool default_value) const;
protected:
int _error;
std::map<std::string, std::string> _values;
std::set<std::string> _sections;
static std::string MakeKey(std::string section, std::string name);
static int ValueHandler(void* user, const char* section, const char* name,
const char* value);
};
#endif // __INIREADER_H__
#ifndef __INIREADER__
#define __INIREADER__
#include <algorithm>
#include <cctype>
#include <cstdlib>
inline INIReader::INIReader(std::string filename)
{
_error = ini_parse(filename.c_str(), ValueHandler, this);
}
inline INIReader::INIReader(FILE *file)
{
_error = ini_parse_file(file, ValueHandler, this);
}
inline int INIReader::ParseError() const
{
return _error;
}
inline const std::set<std::string>& INIReader::Sections() const
{
return _sections;
}
inline std::string INIReader::Get(std::string section, std::string name, std::string default_value) const
{
std::string key = MakeKey(section, name);
return _values.count(key) ? _values.at(key) : default_value;
}
inline long INIReader::GetInteger(std::string section, std::string name, long default_value) const
{
std::string valstr = Get(section, name, "");
const char* value = valstr.c_str();
char* end;
// This parses "1234" (decimal) and also "0x4D2" (hex)
long n = strtol(value, &end, 0);
return end > value ? n : default_value;
}
inline double INIReader::GetReal(std::string section, std::string name, double default_value) const
{
std::string valstr = Get(section, name, "");
const char* value = valstr.c_str();
char* end;
double n = strtod(value, &end);
return end > value ? n : default_value;
}
inline float INIReader::GetFloat(std::string section, std::string name, float default_value) const
{
std::string valstr = Get(section, name, "");
const char* value = valstr.c_str();
char* end;
float n = strtof(value, &end);
return end > value ? n : default_value;
}
inline bool INIReader::GetBoolean(std::string section, std::string name, bool default_value) const
{
std::string valstr = Get(section, name, "");
// Convert to lower case to make string comparisons case-insensitive
std::transform(valstr.begin(), valstr.end(), valstr.begin(), ::tolower);
if (valstr == "true" || valstr == "yes" || valstr == "on" || valstr == "1")
return true;
else if (valstr == "false" || valstr == "no" || valstr == "off" || valstr == "0")
return false;
else
return default_value;
}
inline std::string INIReader::MakeKey(std::string section, std::string name)
{
std::string key = section + "=" + name;
// Convert to lower case to make section/name lookups case-insensitive
std::transform(key.begin(), key.end(), key.begin(), ::tolower);
return key;
}
inline int INIReader::ValueHandler(void* user, const char* section, const char* name,
const char* value)
{
INIReader* reader = (INIReader*)user;
std::string key = MakeKey(section, name);
if (reader->_values[key].size() > 0)
reader->_values[key] += "\n";
reader->_values[key] += value;
reader->_sections.insert(section);
return 1;
}
#endif // __INIREADER__

1
src/Player.cpp Normal file
View File

@ -0,0 +1 @@
#include "Player.hpp"

25
src/Player.hpp Normal file
View File

@ -0,0 +1,25 @@
#include <string>
#include <cstring>
#ifndef _PLR_HPP
#define _PLR_HPP
#include "CNProtocol.hpp"
#include "CNStructs.hpp"
struct Player {
int64_t SerialKey;
int32_t iID;
uint64_t FEKey;
int level;
int HP;
int slot;
sPCStyle PCStyle;
sPCStyle2 PCStyle2;
int x, y, z, angle;
sItemBase Equip[AEQUIP_COUNT];
};
#endif

364
src/PlayerManager.cpp Normal file
View File

@ -0,0 +1,364 @@
#include "CNProtocol.hpp"
#include "PlayerManager.hpp"
#include "CNShardServer.hpp"
#include "CNShared.hpp"
#include "settings.hpp"
#include <algorithm>
#include <vector>
#include <cmath>
std::map<CNSocket*, PlayerView> PlayerManager::players;
void PlayerManager::init() {
// register packet types
REGISTER_SHARD_PACKET(P_CL2FE_REQ_PC_ENTER, PlayerManager::enterPlayer);
REGISTER_SHARD_PACKET(P_CL2FE_REQ_PC_LOADING_COMPLETE, PlayerManager::loadPlayer);
REGISTER_SHARD_PACKET(P_CL2FE_REQ_PC_MOVE, PlayerManager::movePlayer);
REGISTER_SHARD_PACKET(P_CL2FE_REQ_PC_STOP, PlayerManager::stopPlayer);
REGISTER_SHARD_PACKET(P_CL2FE_REQ_PC_JUMP, PlayerManager::jumpPlayer);
REGISTER_SHARD_PACKET(P_CL2FE_REQ_PC_MOVEPLATFORM, PlayerManager::movePlatformPlayer);
REGISTER_SHARD_PACKET(P_CL2FE_REQ_PC_GOTO, PlayerManager::gotoPlayer);
REGISTER_SHARD_PACKET(P_CL2FE_GM_REQ_PC_SET_VALUE, PlayerManager::setSpecialPlayer);
}
void PlayerManager::addPlayer(CNSocket* key, Player plr) {
players[key] = PlayerView();
players[key].viewable = std::list<CNSocket*>();
players[key].plr = plr;
}
void PlayerManager::removePlayer(CNSocket* key) {
PlayerView cachedView = players[key];
// if players have them in their viewable lists, remove it
for (CNSocket* otherSock : players[key].viewable) {
players[otherSock].viewable.remove(key); // gone
// now sent PC_EXIT packet
sP_FE2CL_PC_EXIT* exitPacket = (sP_FE2CL_PC_EXIT*)xmalloc(sizeof(sP_FE2CL_PC_EXIT));
exitPacket->iID = players[key].plr.iID;
otherSock->sendPacket(new CNPacketData((void*)exitPacket, P_FE2CL_PC_EXIT, sizeof(sP_FE2CL_PC_EXIT), otherSock->getFEKey()));
}
players.erase(key);
}
Player PlayerManager::getPlayer(CNSocket* key) {
return players[key].plr;
}
void PlayerManager::updatePlayerPosition(CNSocket* sock, int X, int Y, int Z) {
players[sock].plr.x = X;
players[sock].plr.y = Y;
players[sock].plr.z = Z;
std::vector<CNSocket*> noView;
std::vector<CNSocket*> yesView;
// TODO: oh god this is sooooo perfomance heavy the more players you have
for (auto pair : players) {
if (pair.first == sock)
continue; // ignore our own connection
int diffX = abs(pair.second.plr.x - X); // the map is like a grid, X and Y are your position on the map, Z is the height. very different from other games...
int diffY = abs(pair.second.plr.y - Y);
double dist = sqrt(pow(diffX, 2) + pow(diffY, 2));
if (dist > settings::VIEWDISTANCE) {
noView.push_back(pair.first);
} else {
yesView.push_back(pair.first);
}
}
std::list<CNSocket*> cachedview(players[sock].viewable); // copies the viewable
for (CNSocket* otherSock : cachedview) {
if (std::find(noView.begin(), noView.end(), otherSock) != noView.end()) {
// sock shouldn't be visible, send PC_EXIT packet & remove them
sP_FE2CL_PC_EXIT* exitPacket = (sP_FE2CL_PC_EXIT*)xmalloc(sizeof(sP_FE2CL_PC_EXIT));
sP_FE2CL_PC_EXIT* exitPacketOther = (sP_FE2CL_PC_EXIT*)xmalloc(sizeof(sP_FE2CL_PC_EXIT));
exitPacket->iID = players[sock].plr.iID;
exitPacketOther->iID = players[otherSock].plr.iID;
otherSock->sendPacket(new CNPacketData((void*)exitPacket, P_FE2CL_PC_EXIT, sizeof(sP_FE2CL_PC_EXIT), otherSock->getFEKey()));
sock->sendPacket(new CNPacketData((void*)exitPacketOther, P_FE2CL_PC_EXIT, sizeof(sP_FE2CL_PC_EXIT), sock->getFEKey()));
players[sock].viewable.remove(otherSock);
players[otherSock].viewable.remove(sock);
}
}
cachedview = players[sock].viewable;
for (CNSocket* otherSock : yesView) {
if (std::find(cachedview.begin(), cachedview.end(), otherSock) == cachedview.end()) {
// this needs to be added to the viewable players, send PC_ENTER
sP_FE2CL_PC_NEW* newPlayer = (sP_FE2CL_PC_NEW*)xmalloc(sizeof(sP_FE2CL_PC_NEW)); // current connection to other player
sP_FE2CL_PC_NEW* newOtherPlayer = (sP_FE2CL_PC_NEW*)xmalloc(sizeof(sP_FE2CL_PC_NEW)); // other player to current connection
Player otherPlr = players[otherSock].plr;
Player plr = players[sock].plr;
newPlayer->PCAppearanceData.iID = plr.iID;
newPlayer->PCAppearanceData.iHP = plr.HP;
newPlayer->PCAppearanceData.iLv = plr.level;
newPlayer->PCAppearanceData.iX = plr.x;
newPlayer->PCAppearanceData.iY = plr.y;
newPlayer->PCAppearanceData.iZ = plr.z;
newPlayer->PCAppearanceData.iAngle = plr.angle;
newPlayer->PCAppearanceData.PCStyle = plr.PCStyle;
memcpy(newPlayer->PCAppearanceData.ItemEquip, plr.Equip, sizeof(sItemBase) * AEQUIP_COUNT);
newOtherPlayer->PCAppearanceData.iID = otherPlr.iID;
newOtherPlayer->PCAppearanceData.iHP = otherPlr.HP;
newOtherPlayer->PCAppearanceData.iLv = otherPlr.level;
newOtherPlayer->PCAppearanceData.iX = otherPlr.x;
newOtherPlayer->PCAppearanceData.iY = otherPlr.y;
newOtherPlayer->PCAppearanceData.iZ = otherPlr.z;
newOtherPlayer->PCAppearanceData.iAngle = otherPlr.angle;
newOtherPlayer->PCAppearanceData.PCStyle = otherPlr.PCStyle;
memcpy(newOtherPlayer->PCAppearanceData.ItemEquip, otherPlr.Equip, sizeof(sItemBase) * AEQUIP_COUNT);
sock->sendPacket(new CNPacketData((void*)newOtherPlayer, P_FE2CL_PC_NEW, sizeof(sP_FE2CL_PC_NEW), sock->getFEKey()));
otherSock->sendPacket(new CNPacketData((void*)newPlayer, P_FE2CL_PC_NEW, sizeof(sP_FE2CL_PC_NEW), otherSock->getFEKey()));
players[sock].viewable.push_back(otherSock);
players[otherSock].viewable.push_back(sock);
}
}
}
void PlayerManager::enterPlayer(CNSocket* sock, CNPacketData* data) {
sP_CL2FE_REQ_PC_ENTER* enter = (sP_CL2FE_REQ_PC_ENTER*)data->buf;
sP_FE2CL_REP_PC_ENTER_SUCC* response = (sP_FE2CL_REP_PC_ENTER_SUCC*)xmalloc(sizeof(sP_FE2CL_REP_PC_ENTER_SUCC));
// TODO: check if serialkey exists, if it doesn't send sP_FE2CL_REP_PC_ENTER_FAIL
Player plr = CNSharedData::getPlayer(enter->iEnterSerialKey);
DEBUGLOG(
std::cout << "P_CL2FE_REQ_PC_ENTER:" << std::endl;
std::cout << "\tID: " << U16toU8(enter->szID) << std::endl;
std::cout << "\tSerial: " << enter->iEnterSerialKey << std::endl;
std::cout << "\tTemp: " << enter->iTempValue << std::endl;
std::cout << "\tPC_UID: " << plr.PCStyle.iPC_UID << std::endl;
)
response->iID = rand();
response->uiSvrTime = getTime();
response->PCLoadData2CL.iUserLevel = 1;
response->PCLoadData2CL.iHP = 1000 * plr.level;
response->PCLoadData2CL.iLevel = plr.level;
response->PCLoadData2CL.iMentor = 1;
response->PCLoadData2CL.iMentorCount = 4;
response->PCLoadData2CL.iMapNum = 0;
response->PCLoadData2CL.iX = plr.x;
response->PCLoadData2CL.iY = plr.y;
response->PCLoadData2CL.iZ = plr.z;
response->PCLoadData2CL.iActiveNanoSlotNum = -1;
response->PCLoadData2CL.iFatigue = 50;
response->PCLoadData2CL.PCStyle = plr.PCStyle;
response->PCLoadData2CL.PCStyle2 = plr.PCStyle2;
for (int i = 0; i < AEQUIP_COUNT; i++)
response->PCLoadData2CL.aEquip[i] = plr.Equip[i];
// don't ask..
for (int i = 1; i < 37; i++) {
response->PCLoadData2CL.aNanoBank[i].iID = i;
response->PCLoadData2CL.aNanoBank[i].iSkillID = 1;
response->PCLoadData2CL.aNanoBank[i].iStamina = 150;
}
response->PCLoadData2CL.aNanoSlots[0] = 1;
response->PCLoadData2CL.aNanoSlots[1] = 2;
response->PCLoadData2CL.aNanoSlots[2] = 3;
response->PCLoadData2CL.aQuestFlag[0] = -1;
plr.iID = response->iID;
plr.SerialKey = enter->iEnterSerialKey;
plr.HP = response->PCLoadData2CL.iHP;
sock->setEKey(CNSocketEncryption::createNewKey(response->uiSvrTime, response->iID + 1, response->PCLoadData2CL.iFusionMatter + 1));
sock->setFEKey(plr.FEKey);
sock->sendPacket(new CNPacketData((void*)response, P_FE2CL_REP_PC_ENTER_SUCC, sizeof(sP_FE2CL_REP_PC_ENTER_SUCC), sock->getFEKey()));
addPlayer(sock, plr);
}
void PlayerManager::loadPlayer(CNSocket* sock, CNPacketData* data) {
sP_CL2FE_REQ_PC_LOADING_COMPLETE* complete = (sP_CL2FE_REQ_PC_LOADING_COMPLETE*)data->buf;
sP_FE2CL_REP_PC_LOADING_COMPLETE_SUCC* response = (sP_FE2CL_REP_PC_LOADING_COMPLETE_SUCC*)xmalloc(sizeof(sP_FE2CL_REP_PC_LOADING_COMPLETE_SUCC));
DEBUGLOG(
std::cout << "P_CL2FE_REQ_PC_LOADING_COMPLETE:" << std::endl;
std::cout << "\tPC_ID: " << complete->iPC_ID << std::endl;
)
response->iPC_ID = complete->iPC_ID;
sock->sendPacket(new CNPacketData((void*)response, P_FE2CL_REP_PC_LOADING_COMPLETE_SUCC, sizeof(sP_FE2CL_REP_PC_LOADING_COMPLETE_SUCC), sock->getFEKey()));
}
void PlayerManager::movePlayer(CNSocket* sock, CNPacketData* data) {
sP_CL2FE_REQ_PC_MOVE* moveData = (sP_CL2FE_REQ_PC_MOVE*)data->buf;
updatePlayerPosition(sock, moveData->iX, moveData->iY, moveData->iZ);
players[sock].plr.angle = moveData->iAngle;
uint64_t tm = getTime();
for (CNSocket* otherSock : players[sock].viewable) {
sP_FE2CL_PC_MOVE* moveResponse = (sP_FE2CL_PC_MOVE*)xmalloc(sizeof(sP_FE2CL_PC_MOVE));
moveResponse->iID = players[sock].plr.iID;
moveResponse->cKeyValue = moveData->cKeyValue;
moveResponse->iX = moveData->iX;
moveResponse->iY = moveData->iY;
moveResponse->iZ = moveData->iZ;
moveResponse->iAngle = moveData->iAngle;
moveResponse->fVX = moveData->fVX;
moveResponse->fVY = moveData->fVY;
moveResponse->fVZ = moveData->fVZ;
moveResponse->iSpeed = moveData->iSpeed;
moveResponse->iCliTime = moveData->iCliTime; // maybe don't send this??? seems unneeded...
moveResponse->iSvrTime = tm;
otherSock->sendPacket(new CNPacketData((void*)moveResponse, P_FE2CL_PC_MOVE, sizeof(sP_FE2CL_PC_MOVE), otherSock->getFEKey()));
}
}
void PlayerManager::stopPlayer(CNSocket* sock, CNPacketData* data) {
sP_CL2FE_REQ_PC_STOP* stopData = (sP_CL2FE_REQ_PC_STOP*)data->buf;
updatePlayerPosition(sock, stopData->iX, stopData->iY, stopData->iZ);
DEBUGLOG(
std::cout << "P_CL2FE_REQ_PC_STOP:" << std::endl;
std::cout << "\tX: " << stopData->iX << std::endl;
std::cout << "\tY: " << stopData->iY << std::endl;
std::cout << "\tZ: " << stopData->iZ << std::endl;
)
uint64_t tm = getTime();
for (CNSocket* otherSock : players[sock].viewable) {
sP_FE2CL_PC_STOP* stopResponse = (sP_FE2CL_PC_STOP*)xmalloc(sizeof(sP_FE2CL_PC_STOP));
stopResponse->iID = players[sock].plr.iID;
stopResponse->iX = stopData->iX;
stopResponse->iY = stopData->iY;
stopResponse->iZ = stopData->iZ;
stopResponse->iCliTime = stopData->iCliTime; // maybe don't send this??? seems unneeded...
stopResponse->iSvrTime = tm;
otherSock->sendPacket(new CNPacketData((void*)stopResponse, P_FE2CL_PC_STOP, sizeof(sP_FE2CL_PC_STOP), otherSock->getFEKey()));
}
}
void PlayerManager::jumpPlayer(CNSocket* sock, CNPacketData* data) {
sP_CL2FE_REQ_PC_JUMP* jumpData = (sP_CL2FE_REQ_PC_JUMP*)data->buf;
updatePlayerPosition(sock, jumpData->iX, jumpData->iY, jumpData->iZ);
uint64_t tm = getTime();
for (CNSocket* otherSock : players[sock].viewable) {
sP_FE2CL_PC_JUMP* jumpResponse = (sP_FE2CL_PC_JUMP*)xmalloc(sizeof(sP_FE2CL_PC_JUMP));
jumpResponse->iID = players[sock].plr.iID;
jumpResponse->cKeyValue = jumpData->cKeyValue;
jumpResponse->iX = jumpData->iX;
jumpResponse->iY = jumpData->iY;
jumpResponse->iZ = jumpData->iZ;
jumpResponse->iAngle = jumpData->iAngle;
jumpResponse->iVX = jumpData->iVX;
jumpResponse->iVY = jumpData->iVY;
jumpResponse->iVZ = jumpData->iVZ;
jumpResponse->iSpeed = jumpData->iSpeed;
jumpResponse->iCliTime = jumpData->iCliTime; // maybe don't send this??? seems unneeded...
jumpResponse->iSvrTime = tm;
otherSock->sendPacket(new CNPacketData((void*)jumpResponse, P_FE2CL_PC_JUMP, sizeof(sP_FE2CL_PC_JUMP), otherSock->getFEKey()));
}
}
void PlayerManager::movePlatformPlayer(CNSocket* sock, CNPacketData* data) {
sP_CL2FE_REQ_PC_MOVEPLATFORM* platformData = (sP_CL2FE_REQ_PC_MOVEPLATFORM*)data->buf;
updatePlayerPosition(sock, platformData->iX, platformData->iY, platformData->iZ);
uint64_t tm = getTime();
for (CNSocket* otherSock : players[sock].viewable) {
sP_FE2CL_PC_MOVEPLATFORM* platResponse = (sP_FE2CL_PC_MOVEPLATFORM*)xmalloc(sizeof(sP_FE2CL_PC_MOVEPLATFORM));
platResponse->iPC_ID = players[sock].plr.iID;
platResponse->iCliTime = platformData->iCliTime;
platResponse->iSvrTime = tm;
platResponse->iX = platformData->iX;
platResponse->iY = platformData->iY;
platResponse->iZ = platformData->iZ;
platResponse->iAngle = platformData->iAngle;
platResponse->fVX = platformData->fVX;
platResponse->fVY = platformData->fVY;
platResponse->fVZ = platformData->fVZ;
platResponse->iLcX = platformData->iLcX;
platResponse->iLcY = platformData->iLcY;
platResponse->iLcZ = platformData->iLcZ;
platResponse->iSpeed = platformData->iSpeed;
platResponse->bDown = platformData->bDown;
platResponse->cKeyValue = platformData->cKeyValue;
platResponse->iPlatformID = platformData->iPlatformID;
otherSock->sendPacket(new CNPacketData((void*)platResponse, P_FE2CL_PC_MOVEPLATFORM, sizeof(sP_FE2CL_PC_MOVEPLATFORM), otherSock->getFEKey()));
}
}
void PlayerManager::gotoPlayer(CNSocket* sock, CNPacketData* data) {
sP_CL2FE_REQ_PC_GOTO* gotoData = (sP_CL2FE_REQ_PC_GOTO*)data->buf;
sP_FE2CL_REP_PC_GOTO_SUCC* response = (sP_FE2CL_REP_PC_GOTO_SUCC*)xmalloc(sizeof(sP_FE2CL_REP_PC_GOTO_SUCC));
DEBUGLOG(
std::cout << "P_CL2FE_REQ_PC_GOTO:" << std::endl;
std::cout << "\tX: " << gotoData->iToX << std::endl;
std::cout << "\tY: " << gotoData->iToY << std::endl;
std::cout << "\tZ: " << gotoData->iToZ << std::endl;
)
response->iX = gotoData->iToX;
response->iY = gotoData->iToY;
response->iZ = gotoData->iToZ;
sock->sendPacket(new CNPacketData((void*)response, P_FE2CL_REP_PC_GOTO_SUCC, sizeof(sP_FE2CL_REP_PC_GOTO_SUCC), sock->getFEKey()));
}
void PlayerManager::setSpecialPlayer(CNSocket* sock, CNPacketData* data) {
sP_CL2FE_GM_REQ_PC_SET_VALUE* setData = (sP_CL2FE_GM_REQ_PC_SET_VALUE*)data->buf;
sP_FE2CL_GM_REP_PC_SET_VALUE* response = (sP_FE2CL_GM_REP_PC_SET_VALUE*)xmalloc(sizeof(sP_FE2CL_GM_REP_PC_SET_VALUE));
DEBUGLOG(
std::cout << "P_CL2FE_GM_REQ_PC_SET_VALUE:" << std::endl;
std::cout << "\tPC_ID: " << setData->iPC_ID << std::endl;
std::cout << "\tSetValueType: " << setData->iSetValueType << std::endl;
std::cout << "\tSetValue: " << setData->iSetValue << std::endl;
)
response->iPC_ID = setData->iPC_ID;
response->iSetValue = setData->iSetValue;
response->iSetValueType = setData->iSetValueType;
sock->sendPacket(new CNPacketData((void*)response, P_FE2CL_GM_REP_PC_SET_VALUE, sizeof(sP_FE2CL_GM_REP_PC_SET_VALUE), sock->getFEKey()));
}

39
src/PlayerManager.hpp Normal file
View File

@ -0,0 +1,39 @@
#ifndef _PM_HPP
#define _PM_HPP
#include "Player.hpp"
#include "CNProtocol.hpp"
#include "CNStructs.hpp"
#include "CNShardServer.hpp"
#include <map>
#include <list>
struct PlayerView {
std::list<CNSocket*> viewable;
Player plr;
};
namespace PlayerManager {
extern std::map<CNSocket*, PlayerView> players;
void init();
void addPlayer(CNSocket* key, Player plr);
void removePlayer(CNSocket* key);
Player getPlayer(CNSocket* key);
void updatePlayerPosition(CNSocket* sock, int X, int Y, int Z);
void enterPlayer(CNSocket* sock, CNPacketData* data);
void loadPlayer(CNSocket* sock, CNPacketData* data);
void movePlayer(CNSocket* sock, CNPacketData* data);
void stopPlayer(CNSocket* sock, CNPacketData* data);
void jumpPlayer(CNSocket* sock, CNPacketData* data);
void movePlatformPlayer(CNSocket* sock, CNPacketData* data);
void gotoPlayer(CNSocket* sock, CNPacketData* data);
void setSpecialPlayer(CNSocket* sock, CNPacketData* data);
}
#endif

47
src/main.cpp Normal file
View File

@ -0,0 +1,47 @@
#include "CNLoginServer.hpp"
#include "CNShardServer.hpp"
#include "PlayerManager.hpp"
#include "ChatManager.hpp"
#include "settings.hpp"
#ifdef __MINGW32__
#include "mingw/mingw.thread.h"
#else
#include <thread>
#endif
#include <string>
void startLogin(uint16_t port) {
CNLoginServer server(port);
server.start();
}
void startShard(uint16_t port) {
CNShardServer server(port);
server.start();
}
int main() {
#ifdef _WIN32
WSADATA wsaData;
if (WSAStartup(MAKEWORD(1,1), &wsaData) != 0) {
std::cerr << "OpenFusion: WSAStartup failed" << std::endl;
exit(EXIT_FAILURE);
}
#endif
settings::init();
std::cout << "[INFO] Intializing Packet Managers..." << std::endl;
PlayerManager::init();
ChatManager::init();
std::cout << "[INFO] Starting Server Threads..." << std::endl;
std::thread loginThread(startLogin, settings::LOGINPORT);
std::thread shardThread(startShard, settings::SHARDPORT);
getchar(); // blocks until input
#ifdef _WIN32
WSACleanup();
#endif
return 0;
}

109
src/mingw/mingw.invoke.h Normal file
View File

@ -0,0 +1,109 @@
/// \file mingw.invoke.h
/// \brief Lightweight `invoke` implementation, for C++11 and C++14.
///
/// (c) 2018-2019 by Nathaniel J. McClatchey, San Jose, CA, United States
/// \author Nathaniel J. McClatchey, PhD
///
/// \copyright Simplified (2-clause) BSD License.
///
/// \note This file may become part of the mingw-w64 runtime package. If/when
/// this happens, the appropriate license will be added, i.e. this code will
/// become dual-licensed, and the current BSD 2-clause license will stay.
#ifndef MINGW_INVOKE_H_
#define MINGW_INVOKE_H_
#include <type_traits> // For std::result_of, etc.
#include <utility> // For std::forward
#include <functional> // For std::reference_wrapper
namespace mingw_stdthread
{
namespace detail
{
// For compatibility, implement std::invoke for C++11 and C++14
#if __cplusplus < 201703L
template<bool PMemFunc, bool PMemData>
struct Invoker
{
template<class F, class... Args>
inline static typename std::result_of<F(Args...)>::type invoke (F&& f, Args&&... args)
{
return std::forward<F>(f)(std::forward<Args>(args)...);
}
};
template<bool>
struct InvokerHelper;
template<>
struct InvokerHelper<false>
{
template<class T1>
inline static auto get (T1&& t1) -> decltype(*std::forward<T1>(t1))
{
return *std::forward<T1>(t1);
}
template<class T1>
inline static auto get (const std::reference_wrapper<T1>& t1) -> decltype(t1.get())
{
return t1.get();
}
};
template<>
struct InvokerHelper<true>
{
template<class T1>
inline static auto get (T1&& t1) -> decltype(std::forward<T1>(t1))
{
return std::forward<T1>(t1);
}
};
template<>
struct Invoker<true, false>
{
template<class T, class F, class T1, class... Args>
inline static auto invoke (F T::* f, T1&& t1, Args&&... args) ->\
decltype((InvokerHelper<std::is_base_of<T,typename std::decay<T1>::type>::value>::get(std::forward<T1>(t1)).*f)(std::forward<Args>(args)...))
{
return (InvokerHelper<std::is_base_of<T,typename std::decay<T1>::type>::value>::get(std::forward<T1>(t1)).*f)(std::forward<Args>(args)...);
}
};
template<>
struct Invoker<false, true>
{
template<class T, class F, class T1, class... Args>
inline static auto invoke (F T::* f, T1&& t1, Args&&... args) ->\
decltype(InvokerHelper<std::is_base_of<T,typename std::decay<T1>::type>::value>::get(t1).*f)
{
return InvokerHelper<std::is_base_of<T,typename std::decay<T1>::type>::value>::get(t1).*f;
}
};
template<class F, class... Args>
struct InvokeResult
{
typedef Invoker<std::is_member_function_pointer<typename std::remove_reference<F>::type>::value,
std::is_member_object_pointer<typename std::remove_reference<F>::type>::value &&
(sizeof...(Args) == 1)> invoker;
inline static auto invoke (F&& f, Args&&... args) -> decltype(invoker::invoke(std::forward<F>(f), std::forward<Args>(args)...))
{
return invoker::invoke(std::forward<F>(f), std::forward<Args>(args)...);
}
};
template<class F, class...Args>
auto invoke (F&& f, Args&&... args) -> decltype(InvokeResult<F, Args...>::invoke(std::forward<F>(f), std::forward<Args>(args)...))
{
return InvokeResult<F, Args...>::invoke(std::forward<F>(f), std::forward<Args>(args)...);
}
#else
using std::invoke;
#endif
} // Namespace "detail"
} // Namespace "mingw_stdthread"
#endif

491
src/mingw/mingw.mutex.h Normal file
View File

@ -0,0 +1,491 @@
/**
* @file mingw.mutex.h
* @brief std::mutex et al implementation for MinGW
** (c) 2013-2016 by Mega Limited, Auckland, New Zealand
* @author Alexander Vassilev
*
* @copyright Simplified (2-clause) BSD License.
* You should have received a copy of the license along with this
* program.
*
* This code is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
* @note
* This file may become part of the mingw-w64 runtime package. If/when this happens,
* the appropriate license will be added, i.e. this code will become dual-licensed,
* and the current BSD 2-clause license will stay.
*/
#ifndef WIN32STDMUTEX_H
#define WIN32STDMUTEX_H
#if !defined(__cplusplus) || (__cplusplus < 201103L)
#error A C++11 compiler is required!
#endif
// Recursion checks on non-recursive locks have some performance penalty, and
// the C++ standard does not mandate them. The user might want to explicitly
// enable or disable such checks. If the user has no preference, enable such
// checks in debug builds, but not in release builds.
#ifdef STDMUTEX_RECURSION_CHECKS
#elif defined(NDEBUG)
#define STDMUTEX_RECURSION_CHECKS 0
#else
#define STDMUTEX_RECURSION_CHECKS 1
#endif
#include <chrono>
#include <system_error>
#include <atomic>
#include <mutex> //need for call_once()
#if STDMUTEX_RECURSION_CHECKS || !defined(NDEBUG)
#include <cstdio>
#endif
#include <sdkddkver.h> // Detect Windows version.
#if (defined(__MINGW32__) && !defined(__MINGW64_VERSION_MAJOR))
#pragma message "The Windows API that MinGW-w32 provides is not fully compatible\
with Microsoft's API. We'll try to work around this, but we can make no\
guarantees. This problem does not exist in MinGW-w64."
#include <windows.h> // No further granularity can be expected.
#else
#if STDMUTEX_RECURSION_CHECKS
#include <processthreadsapi.h> // For GetCurrentThreadId
#endif
#include <synchapi.h> // For InitializeCriticalSection, etc.
#include <errhandlingapi.h> // For GetLastError
#include <handleapi.h>
#endif
// Need for the implementation of invoke
#include "mingw.invoke.h"
#if !defined(_WIN32_WINNT) || (_WIN32_WINNT < 0x0501)
#error To use the MinGW-std-threads library, you will need to define the macro _WIN32_WINNT to be 0x0501 (Windows XP) or higher.
#endif
namespace mingw_stdthread
{
// The _NonRecursive class has mechanisms that do not play nice with direct
// manipulation of the native handle. This forward declaration is part of
// a friend class declaration.
#if STDMUTEX_RECURSION_CHECKS
namespace vista
{
class condition_variable;
}
#endif
// To make this namespace equivalent to the thread-related subset of std,
// pull in the classes and class templates supplied by std but not by this
// implementation.
using std::lock_guard;
using std::unique_lock;
using std::adopt_lock_t;
using std::defer_lock_t;
using std::try_to_lock_t;
using std::adopt_lock;
using std::defer_lock;
using std::try_to_lock;
class recursive_mutex
{
CRITICAL_SECTION mHandle;
public:
typedef LPCRITICAL_SECTION native_handle_type;
native_handle_type native_handle() {return &mHandle;}
recursive_mutex() noexcept : mHandle()
{
InitializeCriticalSection(&mHandle);
}
recursive_mutex (const recursive_mutex&) = delete;
recursive_mutex& operator=(const recursive_mutex&) = delete;
~recursive_mutex() noexcept
{
DeleteCriticalSection(&mHandle);
}
void lock()
{
EnterCriticalSection(&mHandle);
}
void unlock()
{
LeaveCriticalSection(&mHandle);
}
bool try_lock()
{
return (TryEnterCriticalSection(&mHandle)!=0);
}
};
#if STDMUTEX_RECURSION_CHECKS
struct _OwnerThread
{
// If this is to be read before locking, then the owner-thread variable must
// be atomic to prevent a torn read from spuriously causing errors.
std::atomic<DWORD> mOwnerThread;
constexpr _OwnerThread () noexcept : mOwnerThread(0) {}
static void on_deadlock (void)
{
using namespace std;
fprintf(stderr, "FATAL: Recursive locking of non-recursive mutex\
detected. Throwing system exception\n");
fflush(stderr);
throw system_error(make_error_code(errc::resource_deadlock_would_occur));
}
DWORD checkOwnerBeforeLock() const
{
DWORD self = GetCurrentThreadId();
if (mOwnerThread.load(std::memory_order_relaxed) == self)
on_deadlock();
return self;
}
void setOwnerAfterLock(DWORD id)
{
mOwnerThread.store(id, std::memory_order_relaxed);
}
void checkSetOwnerBeforeUnlock()
{
DWORD self = GetCurrentThreadId();
if (mOwnerThread.load(std::memory_order_relaxed) != self)
on_deadlock();
mOwnerThread.store(0, std::memory_order_relaxed);
}
};
#endif
// Though the Slim Reader-Writer (SRW) locks used here are not complete until
// Windows 7, implementing partial functionality in Vista will simplify the
// interaction with condition variables.
#if defined(_WIN32) && (WINVER >= _WIN32_WINNT_VISTA)
namespace windows7
{
class mutex
{
SRWLOCK mHandle;
// Track locking thread for error checking.
#if STDMUTEX_RECURSION_CHECKS
friend class vista::condition_variable;
_OwnerThread mOwnerThread {};
#endif
public:
typedef PSRWLOCK native_handle_type;
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wzero-as-null-pointer-constant"
constexpr mutex () noexcept : mHandle(SRWLOCK_INIT) { }
#pragma GCC diagnostic pop
mutex (const mutex&) = delete;
mutex & operator= (const mutex&) = delete;
void lock (void)
{
// Note: Undefined behavior if called recursively.
#if STDMUTEX_RECURSION_CHECKS
DWORD self = mOwnerThread.checkOwnerBeforeLock();
#endif
AcquireSRWLockExclusive(&mHandle);
#if STDMUTEX_RECURSION_CHECKS
mOwnerThread.setOwnerAfterLock(self);
#endif
}
void unlock (void)
{
#if STDMUTEX_RECURSION_CHECKS
mOwnerThread.checkSetOwnerBeforeUnlock();
#endif
ReleaseSRWLockExclusive(&mHandle);
}
// TryAcquireSRW functions are a Windows 7 feature.
#if (WINVER >= _WIN32_WINNT_WIN7)
bool try_lock (void)
{
#if STDMUTEX_RECURSION_CHECKS
DWORD self = mOwnerThread.checkOwnerBeforeLock();
#endif
BOOL ret = TryAcquireSRWLockExclusive(&mHandle);
#if STDMUTEX_RECURSION_CHECKS
if (ret)
mOwnerThread.setOwnerAfterLock(self);
#endif
return ret;
}
#endif
native_handle_type native_handle (void)
{
return &mHandle;
}
};
} // Namespace windows7
#endif // Compiling for Vista
namespace xp
{
class mutex
{
CRITICAL_SECTION mHandle;
std::atomic_uchar mState;
// Track locking thread for error checking.
#if STDMUTEX_RECURSION_CHECKS
friend class vista::condition_variable;
_OwnerThread mOwnerThread {};
#endif
public:
typedef PCRITICAL_SECTION native_handle_type;
constexpr mutex () noexcept : mHandle(), mState(2) { }
mutex (const mutex&) = delete;
mutex & operator= (const mutex&) = delete;
~mutex() noexcept
{
// Undefined behavior if the mutex is held (locked) by any thread.
// Undefined behavior if a thread terminates while holding ownership of the
// mutex.
DeleteCriticalSection(&mHandle);
}
void lock (void)
{
unsigned char state = mState.load(std::memory_order_acquire);
while (state) {
if ((state == 2) && mState.compare_exchange_weak(state, 1, std::memory_order_acquire))
{
InitializeCriticalSection(&mHandle);
mState.store(0, std::memory_order_release);
break;
}
if (state == 1)
{
Sleep(0);
state = mState.load(std::memory_order_acquire);
}
}
#if STDMUTEX_RECURSION_CHECKS
DWORD self = mOwnerThread.checkOwnerBeforeLock();
#endif
EnterCriticalSection(&mHandle);
#if STDMUTEX_RECURSION_CHECKS
mOwnerThread.setOwnerAfterLock(self);
#endif
}
void unlock (void)
{
#if STDMUTEX_RECURSION_CHECKS
mOwnerThread.checkSetOwnerBeforeUnlock();
#endif
LeaveCriticalSection(&mHandle);
}
bool try_lock (void)
{
unsigned char state = mState.load(std::memory_order_acquire);
if ((state == 2) && mState.compare_exchange_strong(state, 1, std::memory_order_acquire))
{
InitializeCriticalSection(&mHandle);
mState.store(0, std::memory_order_release);
}
if (state == 1)
return false;
#if STDMUTEX_RECURSION_CHECKS
DWORD self = mOwnerThread.checkOwnerBeforeLock();
#endif
BOOL ret = TryEnterCriticalSection(&mHandle);
#if STDMUTEX_RECURSION_CHECKS
if (ret)
mOwnerThread.setOwnerAfterLock(self);
#endif
return ret;
}
native_handle_type native_handle (void)
{
return &mHandle;
}
};
} // Namespace "xp"
#if (WINVER >= _WIN32_WINNT_WIN7)
using windows7::mutex;
#else
using xp::mutex;
#endif
class recursive_timed_mutex
{
static constexpr DWORD kWaitAbandoned = 0x00000080l;
static constexpr DWORD kWaitObject0 = 0x00000000l;
static constexpr DWORD kInfinite = 0xffffffffl;
inline bool try_lock_internal (DWORD ms) noexcept
{
DWORD ret = WaitForSingleObject(mHandle, ms);
#ifndef NDEBUG
if (ret == kWaitAbandoned)
{
using namespace std;
fprintf(stderr, "FATAL: Thread terminated while holding a mutex.");
terminate();
}
#endif
return (ret == kWaitObject0) || (ret == kWaitAbandoned);
}
protected:
HANDLE mHandle;
// Track locking thread for error checking of non-recursive timed_mutex. For
// standard compliance, this must be defined in same class and at the same
// access-control level as every other variable in the timed_mutex.
#if STDMUTEX_RECURSION_CHECKS
friend class vista::condition_variable;
_OwnerThread mOwnerThread {};
#endif
public:
typedef HANDLE native_handle_type;
native_handle_type native_handle() const {return mHandle;}
recursive_timed_mutex(const recursive_timed_mutex&) = delete;
recursive_timed_mutex& operator=(const recursive_timed_mutex&) = delete;
recursive_timed_mutex(): mHandle(CreateMutex(NULL, FALSE, NULL)) {}
~recursive_timed_mutex()
{
CloseHandle(mHandle);
}
void lock()
{
DWORD ret = WaitForSingleObject(mHandle, kInfinite);
// If (ret == WAIT_ABANDONED), then the thread that held ownership was
// terminated. Behavior is undefined, but Windows will pass ownership to this
// thread.
#ifndef NDEBUG
if (ret == kWaitAbandoned)
{
using namespace std;
fprintf(stderr, "FATAL: Thread terminated while holding a mutex.");
terminate();
}
#endif
if ((ret != kWaitObject0) && (ret != kWaitAbandoned))
{
throw std::system_error(GetLastError(), std::system_category());
}
}
void unlock()
{
if (!ReleaseMutex(mHandle))
throw std::system_error(GetLastError(), std::system_category());
}
bool try_lock()
{
return try_lock_internal(0);
}
template <class Rep, class Period>
bool try_lock_for(const std::chrono::duration<Rep,Period>& dur)
{
using namespace std::chrono;
auto timeout = duration_cast<milliseconds>(dur).count();
while (timeout > 0)
{
constexpr auto kMaxStep = static_cast<decltype(timeout)>(kInfinite-1);
auto step = (timeout < kMaxStep) ? timeout : kMaxStep;
if (try_lock_internal(static_cast<DWORD>(step)))
return true;
timeout -= step;
}
return false;
}
template <class Clock, class Duration>
bool try_lock_until(const std::chrono::time_point<Clock,Duration>& timeout_time)
{
return try_lock_for(timeout_time - Clock::now());
}
};
// Override if, and only if, it is necessary for error-checking.
#if STDMUTEX_RECURSION_CHECKS
class timed_mutex: recursive_timed_mutex
{
public:
timed_mutex(const timed_mutex&) = delete;
timed_mutex& operator=(const timed_mutex&) = delete;
void lock()
{
DWORD self = mOwnerThread.checkOwnerBeforeLock();
recursive_timed_mutex::lock();
mOwnerThread.setOwnerAfterLock(self);
}
void unlock()
{
mOwnerThread.checkSetOwnerBeforeUnlock();
recursive_timed_mutex::unlock();
}
template <class Rep, class Period>
bool try_lock_for(const std::chrono::duration<Rep,Period>& dur)
{
DWORD self = mOwnerThread.checkOwnerBeforeLock();
bool ret = recursive_timed_mutex::try_lock_for(dur);
if (ret)
mOwnerThread.setOwnerAfterLock(self);
return ret;
}
template <class Clock, class Duration>
bool try_lock_until(const std::chrono::time_point<Clock,Duration>& timeout_time)
{
return try_lock_for(timeout_time - Clock::now());
}
bool try_lock ()
{
return try_lock_for(std::chrono::milliseconds(0));
}
};
#else
typedef recursive_timed_mutex timed_mutex;
#endif
class once_flag
{
// When available, the SRW-based mutexes should be faster than the
// CriticalSection-based mutexes. Only try_lock will be unavailable in Vista,
// and try_lock is not used by once_flag.
#if (_WIN32_WINNT == _WIN32_WINNT_VISTA)
windows7::mutex mMutex;
#else
mutex mMutex;
#endif
std::atomic_bool mHasRun;
once_flag(const once_flag&) = delete;
once_flag& operator=(const once_flag&) = delete;
template<class Callable, class... Args>
friend void call_once(once_flag& once, Callable&& f, Args&&... args);
public:
constexpr once_flag() noexcept: mMutex(), mHasRun(false) {}
};
template<class Callable, class... Args>
void call_once(once_flag& flag, Callable&& func, Args&&... args)
{
if (flag.mHasRun.load(std::memory_order_acquire))
return;
lock_guard<decltype(flag.mMutex)> lock(flag.mMutex);
if (flag.mHasRun.load(std::memory_order_acquire))
return;
detail::invoke(std::forward<Callable>(func),std::forward<Args>(args)...);
flag.mHasRun.store(true, std::memory_order_release);
}
} // Namespace mingw_stdthread
// Push objects into std, but only if they are not already there.
namespace std
{
// Because of quirks of the compiler, the common "using namespace std;"
// directive would flatten the namespaces and introduce ambiguity where there
// was none. Direct specification (std::), however, would be unaffected.
// Take the safe option, and include only in the presence of MinGW's win32
// implementation.
#if defined(__MINGW32__ ) && !defined(_GLIBCXX_HAS_GTHREADS)
using mingw_stdthread::recursive_mutex;
using mingw_stdthread::mutex;
using mingw_stdthread::recursive_timed_mutex;
using mingw_stdthread::timed_mutex;
using mingw_stdthread::once_flag;
using mingw_stdthread::call_once;
#elif !defined(MINGW_STDTHREAD_REDUNDANCY_WARNING) // Skip repetition
#define MINGW_STDTHREAD_REDUNDANCY_WARNING
#pragma message "This version of MinGW seems to include a win32 port of\
pthreads, and probably already has C++11 std threading classes implemented,\
based on pthreads. These classes, found in namespace std, are not overridden\
by the mingw-std-thread library. If you would still like to use this\
implementation (as it is more lightweight), use the classes provided in\
namespace mingw_stdthread."
#endif
}
#endif // WIN32STDMUTEX_H

View File

@ -0,0 +1,503 @@
/// \file mingw.shared_mutex.h
/// \brief Standard-compliant shared_mutex for MinGW
///
/// (c) 2017 by Nathaniel J. McClatchey, Athens OH, United States
/// \author Nathaniel J. McClatchey
///
/// \copyright Simplified (2-clause) BSD License.
///
/// \note This file may become part of the mingw-w64 runtime package. If/when
/// this happens, the appropriate license will be added, i.e. this code will
/// become dual-licensed, and the current BSD 2-clause license will stay.
/// \note Target Windows version is determined by WINVER, which is determined in
/// <windows.h> from _WIN32_WINNT, which can itself be set by the user.
// Notes on the namespaces:
// - The implementation can be accessed directly in the namespace
// mingw_stdthread.
// - Objects will be brought into namespace std by a using directive. This
// will cause objects declared in std (such as MinGW's implementation) to
// hide this implementation's definitions.
// - To avoid poluting the namespace with implementation details, all objects
// to be pushed into std will be placed in mingw_stdthread::visible.
// The end result is that if MinGW supplies an object, it is automatically
// used. If MinGW does not supply an object, this implementation's version will
// instead be used.
#ifndef MINGW_SHARED_MUTEX_H_
#define MINGW_SHARED_MUTEX_H_
#if !defined(__cplusplus) || (__cplusplus < 201103L)
#error A C++11 compiler is required!
#endif
#include <cassert>
// For descriptive errors.
#include <system_error>
// Implementing a shared_mutex without OS support will require atomic read-
// modify-write capacity.
#include <atomic>
// For timing in shared_lock and shared_timed_mutex.
#include <chrono>
#include <limits>
// Use MinGW's shared_lock class template, if it's available. Requires C++14.
// If unavailable (eg. because this library is being used in C++11), then an
// implementation of shared_lock is provided by this header.
#if (__cplusplus >= 201402L)
#include <shared_mutex>
#endif
// For defer_lock_t, adopt_lock_t, and try_to_lock_t
#include "mingw.mutex.h"
// For this_thread::yield.
//#include "mingw.thread.h"
// Might be able to use native Slim Reader-Writer (SRW) locks.
#ifdef _WIN32
#include <sdkddkver.h> // Detect Windows version.
#if (defined(__MINGW32__) && !defined(__MINGW64_VERSION_MAJOR))
#pragma message "The Windows API that MinGW-w32 provides is not fully compatible\
with Microsoft's API. We'll try to work around this, but we can make no\
guarantees. This problem does not exist in MinGW-w64."
#include <windows.h> // No further granularity can be expected.
#else
#include <synchapi.h>
#endif
#endif
namespace mingw_stdthread
{
// Define a portable atomics-based shared_mutex
namespace portable
{
class shared_mutex
{
typedef uint_fast16_t counter_type;
std::atomic<counter_type> mCounter {0};
static constexpr counter_type kWriteBit = 1 << (std::numeric_limits<counter_type>::digits - 1);
#if STDMUTEX_RECURSION_CHECKS
// Runtime checker for verifying owner threads. Note: Exclusive mode only.
_OwnerThread mOwnerThread {};
#endif
public:
typedef shared_mutex * native_handle_type;
shared_mutex () = default;
// No form of copying or moving should be allowed.
shared_mutex (const shared_mutex&) = delete;
shared_mutex & operator= (const shared_mutex&) = delete;
~shared_mutex ()
{
// Terminate if someone tries to destroy an owned mutex.
assert(mCounter.load(std::memory_order_relaxed) == 0);
}
void lock_shared (void)
{
counter_type expected = mCounter.load(std::memory_order_relaxed);
do
{
// Delay if writing or if too many readers are attempting to read.
if (expected >= kWriteBit - 1)
{
using namespace std;
expected = mCounter.load(std::memory_order_relaxed);
continue;
}
if (mCounter.compare_exchange_weak(expected,
static_cast<counter_type>(expected + 1),
std::memory_order_acquire,
std::memory_order_relaxed))
break;
}
while (true);
}
bool try_lock_shared (void)
{
counter_type expected = mCounter.load(std::memory_order_relaxed) & static_cast<counter_type>(~kWriteBit);
if (expected + 1 == kWriteBit)
return false;
else
return mCounter.compare_exchange_strong( expected,
static_cast<counter_type>(expected + 1),
std::memory_order_acquire,
std::memory_order_relaxed);
}
void unlock_shared (void)
{
using namespace std;
#ifndef NDEBUG
if (!(mCounter.fetch_sub(1, memory_order_release) & static_cast<counter_type>(~kWriteBit)))
throw system_error(make_error_code(errc::operation_not_permitted));
#else
mCounter.fetch_sub(1, memory_order_release);
#endif
}
// Behavior is undefined if a lock was previously acquired.
void lock (void)
{
#if STDMUTEX_RECURSION_CHECKS
DWORD self = mOwnerThread.checkOwnerBeforeLock();
#endif
using namespace std;
// Might be able to use relaxed memory order...
// Wait for the write-lock to be unlocked, then claim the write slot.
counter_type current;
while ((current = mCounter.fetch_or(kWriteBit, std::memory_order_acquire)) & kWriteBit);
//this_thread::yield();
// Wait for readers to finish up.
while (current != kWriteBit)
{
//this_thread::yield();
current = mCounter.load(std::memory_order_acquire);
}
#if STDMUTEX_RECURSION_CHECKS
mOwnerThread.setOwnerAfterLock(self);
#endif
}
bool try_lock (void)
{
#if STDMUTEX_RECURSION_CHECKS
DWORD self = mOwnerThread.checkOwnerBeforeLock();
#endif
counter_type expected = 0;
bool ret = mCounter.compare_exchange_strong(expected, kWriteBit,
std::memory_order_acquire,
std::memory_order_relaxed);
#if STDMUTEX_RECURSION_CHECKS
if (ret)
mOwnerThread.setOwnerAfterLock(self);
#endif
return ret;
}
void unlock (void)
{
#if STDMUTEX_RECURSION_CHECKS
mOwnerThread.checkSetOwnerBeforeUnlock();
#endif
using namespace std;
#ifndef NDEBUG
if (mCounter.load(memory_order_relaxed) != kWriteBit)
throw system_error(make_error_code(errc::operation_not_permitted));
#endif
mCounter.store(0, memory_order_release);
}
native_handle_type native_handle (void)
{
return this;
}
};
} // Namespace portable
// The native shared_mutex implementation primarily uses features of Windows
// Vista, but the features used for try_lock and try_lock_shared were not
// introduced until Windows 7. To allow limited use while compiling for Vista,
// I define the class without try_* functions in that case.
// Only fully-featured implementations will be placed into namespace std.
#if defined(_WIN32) && (WINVER >= _WIN32_WINNT_VISTA)
namespace vista
{
class condition_variable_any;
}
namespace windows7
{
// We already #include "mingw.mutex.h". May as well reduce redundancy.
class shared_mutex : windows7::mutex
{
// Allow condition_variable_any (and only condition_variable_any) to treat a
// shared_mutex as its base class.
friend class vista::condition_variable_any;
public:
using windows7::mutex::native_handle_type;
using windows7::mutex::lock;
using windows7::mutex::unlock;
using windows7::mutex::native_handle;
void lock_shared (void)
{
AcquireSRWLockShared(native_handle());
}
void unlock_shared (void)
{
ReleaseSRWLockShared(native_handle());
}
// TryAcquireSRW functions are a Windows 7 feature.
#if (WINVER >= _WIN32_WINNT_WIN7)
bool try_lock_shared (void)
{
return TryAcquireSRWLockShared(native_handle()) != 0;
}
using windows7::mutex::try_lock;
#endif
};
} // Namespace windows7
#endif // Compiling for Vista
#if (defined(_WIN32) && (WINVER >= _WIN32_WINNT_WIN7))
using windows7::shared_mutex;
#else
using portable::shared_mutex;
#endif
class shared_timed_mutex : shared_mutex
{
typedef shared_mutex Base;
public:
using Base::lock;
using Base::try_lock;
using Base::unlock;
using Base::lock_shared;
using Base::try_lock_shared;
using Base::unlock_shared;
template< class Clock, class Duration >
bool try_lock_until ( const std::chrono::time_point<Clock,Duration>& cutoff )
{
do
{
if (try_lock())
return true;
}
while (std::chrono::steady_clock::now() < cutoff);
return false;
}
template< class Rep, class Period >
bool try_lock_for (const std::chrono::duration<Rep,Period>& rel_time)
{
return try_lock_until(std::chrono::steady_clock::now() + rel_time);
}
template< class Clock, class Duration >
bool try_lock_shared_until ( const std::chrono::time_point<Clock,Duration>& cutoff )
{
do
{
if (try_lock_shared())
return true;
}
while (std::chrono::steady_clock::now() < cutoff);
return false;
}
template< class Rep, class Period >
bool try_lock_shared_for (const std::chrono::duration<Rep,Period>& rel_time)
{
return try_lock_shared_until(std::chrono::steady_clock::now() + rel_time);
}
};
#if __cplusplus >= 201402L
using std::shared_lock;
#else
// If not supplied by shared_mutex (eg. because C++14 is not supported), I
// supply the various helper classes that the header should have defined.
template<class Mutex>
class shared_lock
{
Mutex * mMutex;
bool mOwns;
// Reduce code redundancy
void verify_lockable (void)
{
using namespace std;
if (mMutex == nullptr)
throw system_error(make_error_code(errc::operation_not_permitted));
if (mOwns)
throw system_error(make_error_code(errc::resource_deadlock_would_occur));
}
public:
typedef Mutex mutex_type;
shared_lock (void) noexcept
: mMutex(nullptr), mOwns(false)
{
}
shared_lock (shared_lock<Mutex> && other) noexcept
: mMutex(other.mutex_), mOwns(other.owns_)
{
other.mMutex = nullptr;
other.mOwns = false;
}
explicit shared_lock (mutex_type & m)
: mMutex(&m), mOwns(true)
{
mMutex->lock_shared();
}
shared_lock (mutex_type & m, defer_lock_t) noexcept
: mMutex(&m), mOwns(false)
{
}
shared_lock (mutex_type & m, adopt_lock_t)
: mMutex(&m), mOwns(true)
{
}
shared_lock (mutex_type & m, try_to_lock_t)
: mMutex(&m), mOwns(m.try_lock_shared())
{
}
template< class Rep, class Period >
shared_lock( mutex_type& m, const std::chrono::duration<Rep,Period>& timeout_duration )
: mMutex(&m), mOwns(m.try_lock_shared_for(timeout_duration))
{
}
template< class Clock, class Duration >
shared_lock( mutex_type& m, const std::chrono::time_point<Clock,Duration>& timeout_time )
: mMutex(&m), mOwns(m.try_lock_shared_until(timeout_time))
{
}
shared_lock& operator= (shared_lock<Mutex> && other) noexcept
{
if (&other != this)
{
if (mOwns)
mMutex->unlock_shared();
mMutex = other.mMutex;
mOwns = other.mOwns;
other.mMutex = nullptr;
other.mOwns = false;
}
return *this;
}
~shared_lock (void)
{
if (mOwns)
mMutex->unlock_shared();
}
shared_lock (const shared_lock<Mutex> &) = delete;
shared_lock& operator= (const shared_lock<Mutex> &) = delete;
// Shared locking
void lock (void)
{
verify_lockable();
mMutex->lock_shared();
mOwns = true;
}
bool try_lock (void)
{
verify_lockable();
mOwns = mMutex->try_lock_shared();
return mOwns;
}
template< class Clock, class Duration >
bool try_lock_until( const std::chrono::time_point<Clock,Duration>& cutoff )
{
verify_lockable();
do
{
mOwns = mMutex->try_lock_shared();
if (mOwns)
return mOwns;
}
while (std::chrono::steady_clock::now() < cutoff);
return false;
}
template< class Rep, class Period >
bool try_lock_for (const std::chrono::duration<Rep,Period>& rel_time)
{
return try_lock_until(std::chrono::steady_clock::now() + rel_time);
}
void unlock (void)
{
using namespace std;
if (!mOwns)
throw system_error(make_error_code(errc::operation_not_permitted));
mMutex->unlock_shared();
mOwns = false;
}
// Modifiers
void swap (shared_lock<Mutex> & other) noexcept
{
using namespace std;
swap(mMutex, other.mMutex);
swap(mOwns, other.mOwns);
}
mutex_type * release (void) noexcept
{
mutex_type * ptr = mMutex;
mMutex = nullptr;
mOwns = false;
return ptr;
}
// Observers
mutex_type * mutex (void) const noexcept
{
return mMutex;
}
bool owns_lock (void) const noexcept
{
return mOwns;
}
explicit operator bool () const noexcept
{
return owns_lock();
}
};
template< class Mutex >
void swap( shared_lock<Mutex>& lhs, shared_lock<Mutex>& rhs ) noexcept
{
lhs.swap(rhs);
}
#endif // C++11
} // Namespace mingw_stdthread
namespace std
{
// Because of quirks of the compiler, the common "using namespace std;"
// directive would flatten the namespaces and introduce ambiguity where there
// was none. Direct specification (std::), however, would be unaffected.
// Take the safe option, and include only in the presence of MinGW's win32
// implementation.
#if (__cplusplus < 201703L) || (defined(__MINGW32__ ) && !defined(_GLIBCXX_HAS_GTHREADS))
using mingw_stdthread::shared_mutex;
#endif
#if (__cplusplus < 201402L) || (defined(__MINGW32__ ) && !defined(_GLIBCXX_HAS_GTHREADS))
using mingw_stdthread::shared_timed_mutex;
using mingw_stdthread::shared_lock;
#elif !defined(MINGW_STDTHREAD_REDUNDANCY_WARNING) // Skip repetition
#define MINGW_STDTHREAD_REDUNDANCY_WARNING
#pragma message "This version of MinGW seems to include a win32 port of\
pthreads, and probably already has C++ std threading classes implemented,\
based on pthreads. These classes, found in namespace std, are not overridden\
by the mingw-std-thread library. If you would still like to use this\
implementation (as it is more lightweight), use the classes provided in\
namespace mingw_stdthread."
#endif
} // Namespace std
#endif // MINGW_SHARED_MUTEX_H_

360
src/mingw/mingw.thread.h Normal file
View File

@ -0,0 +1,360 @@
/**
* @file mingw.thread.h
* @brief std::thread implementation for MinGW
* (c) 2013-2016 by Mega Limited, Auckland, New Zealand
* @author Alexander Vassilev
*
* @copyright Simplified (2-clause) BSD License.
* You should have received a copy of the license along with this
* program.
*
* This code is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
* @note
* This file may become part of the mingw-w64 runtime package. If/when this happens,
* the appropriate license will be added, i.e. this code will become dual-licensed,
* and the current BSD 2-clause license will stay.
*/
#ifndef WIN32STDTHREAD_H
#define WIN32STDTHREAD_H
#if !defined(__cplusplus) || (__cplusplus < 201103L)
#error A C++11 compiler is required!
#endif
// Use the standard classes for std::, if available.
#include <thread>
#include <cstddef> // For std::size_t
#include <cerrno> // Detect error type.
#include <exception> // For std::terminate
#include <system_error> // For std::system_error
#include <functional> // For std::hash
#include <tuple> // For std::tuple
#include <chrono> // For sleep timing.
#include <memory> // For std::unique_ptr
#include <iosfwd> // Stream output for thread ids.
#include <utility> // For std::swap, std::forward
#include "mingw.invoke.h"
#if (defined(__MINGW32__) && !defined(__MINGW64_VERSION_MAJOR))
#pragma message "The Windows API that MinGW-w32 provides is not fully compatible\
with Microsoft's API. We'll try to work around this, but we can make no\
guarantees. This problem does not exist in MinGW-w64."
#include <windows.h> // No further granularity can be expected.
#else
#include <synchapi.h> // For WaitForSingleObject
#include <handleapi.h> // For CloseHandle, etc.
#include <sysinfoapi.h> // For GetNativeSystemInfo
#include <processthreadsapi.h> // For GetCurrentThreadId
#endif
#include <process.h> // For _beginthreadex
#ifndef NDEBUG
#include <cstdio>
#endif
#if !defined(_WIN32_WINNT) || (_WIN32_WINNT < 0x0501)
#error To use the MinGW-std-threads library, you will need to define the macro _WIN32_WINNT to be 0x0501 (Windows XP) or higher.
#endif
// Instead of INVALID_HANDLE_VALUE, _beginthreadex returns 0.
namespace mingw_stdthread
{
namespace detail
{
template<std::size_t...>
struct IntSeq {};
template<std::size_t N, std::size_t... S>
struct GenIntSeq : GenIntSeq<N-1, N-1, S...> { };
template<std::size_t... S>
struct GenIntSeq<0, S...> { typedef IntSeq<S...> type; };
// Use a template specialization to avoid relying on compiler optimization
// when determining the parameter integer sequence.
template<class Func, class T, typename... Args>
class ThreadFuncCall;
// We can't define the Call struct in the function - the standard forbids template methods in that case
template<class Func, std::size_t... S, typename... Args>
class ThreadFuncCall<Func, detail::IntSeq<S...>, Args...>
{
static_assert(sizeof...(S) == sizeof...(Args), "Args must match.");
using Tuple = std::tuple<typename std::decay<Args>::type...>;
typename std::decay<Func>::type mFunc;
Tuple mArgs;
public:
ThreadFuncCall(Func&& aFunc, Args&&... aArgs)
: mFunc(std::forward<Func>(aFunc)),
mArgs(std::forward<Args>(aArgs)...)
{
}
void callFunc()
{
detail::invoke(std::move(mFunc), std::move(std::get<S>(mArgs)) ...);
}
};
// Allow construction of threads without exposing implementation.
class ThreadIdTool;
} // Namespace "detail"
class thread
{
public:
class id
{
DWORD mId = 0;
friend class thread;
friend class std::hash<id>;
friend class detail::ThreadIdTool;
explicit id(DWORD aId) noexcept : mId(aId){}
public:
id (void) noexcept = default;
friend bool operator==(id x, id y) noexcept {return x.mId == y.mId; }
friend bool operator!=(id x, id y) noexcept {return x.mId != y.mId; }
friend bool operator< (id x, id y) noexcept {return x.mId < y.mId; }
friend bool operator<=(id x, id y) noexcept {return x.mId <= y.mId; }
friend bool operator> (id x, id y) noexcept {return x.mId > y.mId; }
friend bool operator>=(id x, id y) noexcept {return x.mId >= y.mId; }
template<class _CharT, class _Traits>
friend std::basic_ostream<_CharT, _Traits>&
operator<<(std::basic_ostream<_CharT, _Traits>& __out, id __id)
{
if (__id.mId == 0)
{
return __out << "(invalid std::thread::id)";
}
else
{
return __out << __id.mId;
}
}
};
private:
static constexpr HANDLE kInvalidHandle = nullptr;
static constexpr DWORD kInfinite = 0xffffffffl;
HANDLE mHandle;
id mThreadId;
template <class Call>
static unsigned __stdcall threadfunc(void* arg)
{
std::unique_ptr<Call> call(static_cast<Call*>(arg));
call->callFunc();
return 0;
}
static unsigned int _hardware_concurrency_helper() noexcept
{
SYSTEM_INFO sysinfo;
// This is one of the few functions used by the library which has a nearly-
// equivalent function defined in earlier versions of Windows. Include the
// workaround, just as a reminder that it does exist.
#if defined(_WIN32_WINNT) && (_WIN32_WINNT >= 0x0501)
::GetNativeSystemInfo(&sysinfo);
#else
::GetSystemInfo(&sysinfo);
#endif
return sysinfo.dwNumberOfProcessors;
}
public:
typedef HANDLE native_handle_type;
id get_id() const noexcept {return mThreadId;}
native_handle_type native_handle() const {return mHandle;}
thread(): mHandle(kInvalidHandle), mThreadId(){}
thread(thread&& other)
:mHandle(other.mHandle), mThreadId(other.mThreadId)
{
other.mHandle = kInvalidHandle;
other.mThreadId = id{};
}
thread(const thread &other)=delete;
template<class Func, typename... Args>
explicit thread(Func&& func, Args&&... args) : mHandle(), mThreadId()
{
using ArgSequence = typename detail::GenIntSeq<sizeof...(Args)>::type;
using Call = detail::ThreadFuncCall<Func, ArgSequence, Args...>;
auto call = new Call(
std::forward<Func>(func), std::forward<Args>(args)...);
unsigned id_receiver;
auto int_handle = _beginthreadex(NULL, 0, threadfunc<Call>,
static_cast<LPVOID>(call), 0, &id_receiver);
if (int_handle == 0)
{
mHandle = kInvalidHandle;
int errnum = errno;
delete call;
// Note: Should only throw EINVAL, EAGAIN, EACCES
throw std::system_error(errnum, std::generic_category());
} else {
mThreadId.mId = id_receiver;
mHandle = reinterpret_cast<HANDLE>(int_handle);
}
}
bool joinable() const {return mHandle != kInvalidHandle;}
// Note: Due to lack of synchronization, this function has a race condition
// if called concurrently, which leads to undefined behavior. The same applies
// to all other member functions of this class, but this one is mentioned
// explicitly.
void join()
{
using namespace std;
if (get_id() == id(GetCurrentThreadId()))
throw system_error(make_error_code(errc::resource_deadlock_would_occur));
if (mHandle == kInvalidHandle)
throw system_error(make_error_code(errc::no_such_process));
if (!joinable())
throw system_error(make_error_code(errc::invalid_argument));
WaitForSingleObject(mHandle, kInfinite);
CloseHandle(mHandle);
mHandle = kInvalidHandle;
mThreadId = id{};
}
~thread()
{
if (joinable())
{
#ifndef NDEBUG
std::printf("Error: Must join() or detach() a thread before \
destroying it.\n");
#endif
std::terminate();
}
}
thread& operator=(const thread&) = delete;
thread& operator=(thread&& other) noexcept
{
if (joinable())
{
#ifndef NDEBUG
std::printf("Error: Must join() or detach() a thread before \
moving another thread to it.\n");
#endif
std::terminate();
}
swap(std::forward<thread>(other));
return *this;
}
void swap(thread&& other) noexcept
{
std::swap(mHandle, other.mHandle);
std::swap(mThreadId.mId, other.mThreadId.mId);
}
static unsigned int hardware_concurrency() noexcept
{
static unsigned int cached = _hardware_concurrency_helper();
return cached;
}
void detach()
{
if (!joinable())
{
using namespace std;
throw system_error(make_error_code(errc::invalid_argument));
}
if (mHandle != kInvalidHandle)
{
CloseHandle(mHandle);
mHandle = kInvalidHandle;
}
mThreadId = id{};
}
};
namespace detail
{
class ThreadIdTool
{
public:
static thread::id make_id (DWORD base_id) noexcept
{
return thread::id(base_id);
}
};
} // Namespace "detail"
namespace this_thread
{
inline thread::id get_id() noexcept
{
return detail::ThreadIdTool::make_id(GetCurrentThreadId());
}
inline void yield() noexcept {Sleep(0);}
template< class Rep, class Period >
void sleep_for( const std::chrono::duration<Rep,Period>& sleep_duration)
{
static constexpr DWORD kInfinite = 0xffffffffl;
using namespace std::chrono;
using rep = milliseconds::rep;
rep ms = duration_cast<milliseconds>(sleep_duration).count();
while (ms > 0)
{
constexpr rep kMaxRep = static_cast<rep>(kInfinite - 1);
auto sleepTime = (ms < kMaxRep) ? ms : kMaxRep;
Sleep(static_cast<DWORD>(sleepTime));
ms -= sleepTime;
}
}
template <class Clock, class Duration>
void sleep_until(const std::chrono::time_point<Clock,Duration>& sleep_time)
{
sleep_for(sleep_time-Clock::now());
}
}
} // Namespace mingw_stdthread
namespace std
{
// Because of quirks of the compiler, the common "using namespace std;"
// directive would flatten the namespaces and introduce ambiguity where there
// was none. Direct specification (std::), however, would be unaffected.
// Take the safe option, and include only in the presence of MinGW's win32
// implementation.
#if defined(__MINGW32__ ) && !defined(_GLIBCXX_HAS_GTHREADS)
using mingw_stdthread::thread;
// Remove ambiguity immediately, to avoid problems arising from the above.
//using std::thread;
namespace this_thread
{
using namespace mingw_stdthread::this_thread;
}
#elif !defined(MINGW_STDTHREAD_REDUNDANCY_WARNING) // Skip repetition
#define MINGW_STDTHREAD_REDUNDANCY_WARNING
#pragma message "This version of MinGW seems to include a win32 port of\
pthreads, and probably already has C++11 std threading classes implemented,\
based on pthreads. These classes, found in namespace std, are not overridden\
by the mingw-std-thread library. If you would still like to use this\
implementation (as it is more lightweight), use the classes provided in\
namespace mingw_stdthread."
#endif
// Specialize hash for this implementation's thread::id, even if the
// std::thread::id already has a hash.
template<>
struct hash<mingw_stdthread::thread::id>
{
typedef mingw_stdthread::thread::id argument_type;
typedef size_t result_type;
size_t operator() (const argument_type & i) const noexcept
{
return i.mId;
}
};
}
#endif // WIN32STDTHREAD_H

40
src/settings.cpp Normal file
View File

@ -0,0 +1,40 @@
#include <iostream>
#include "settings.hpp"
#include "INIReader.hpp"
// defaults :)
int settings::LOGINPORT = 8001;
bool settings::LOGINRANDCHARACTERS = false;
int settings::SHARDPORT = 8002;
std::string settings::SHARDSERVERIP = "127.0.0.1";
int settings::VIEWDISTANCE = 25000;
// default spawn point is city hall
int settings::SPAWN_X = 179213;
int settings::SPAWN_Y = 268451;
int settings::SPAWN_Z = -4210;
void settings::init() {
INIReader reader("config.ini");
if (reader.ParseError() != 0) {
if (reader.ParseError() == -1)
std::cerr << "[WARN] Settings: missing config.ini file!" << std::endl;
else
std::cerr << "[WARN] Settings: invalid config.ini syntax at line " << reader.ParseError() << std::endl;
return;
}
LOGINPORT = reader.GetInteger("login", "port", LOGINPORT);
LOGINRANDCHARACTERS = reader.GetBoolean("login", "randomcharacters", LOGINRANDCHARACTERS);
SHARDPORT = reader.GetInteger("shard", "port", SHARDPORT);
SHARDSERVERIP = reader.Get("shard", "ip", "127.0.0.1");
VIEWDISTANCE = reader.GetInteger("shard", "view", VIEWDISTANCE);
SPAWN_X = reader.GetInteger("shard", "spawnx", SPAWN_X);
SPAWN_Y = reader.GetInteger("shard", "spawny", SPAWN_Y);
SPAWN_Z = reader.GetInteger("shard", "spawnz", SPAWN_Z);
}

17
src/settings.hpp Normal file
View File

@ -0,0 +1,17 @@
#ifndef _SETT_HPP
#define _SETT_HPP
namespace settings {
extern int LOGINPORT;
extern bool LOGINRANDCHARACTERS;
extern int SHARDPORT;
extern std::string SHARDSERVERIP;
extern int VIEWDISTANCE;
extern int SPAWN_X;
extern int SPAWN_Y;
extern int SPAWN_Z;
void init();
}
#endif