├── .gitignore ├── src ├── crypto.hh ├── threads.hh ├── config.hh ├── reader.hh ├── communication.hh ├── info.hh ├── time.cc ├── houses.hh ├── objects.hh ├── crypto.cc ├── script.hh ├── writer.hh ├── threads.cc ├── map.hh ├── moveuse.hh ├── operate.hh ├── reader.cc ├── query.hh ├── magic.hh ├── shm.cc ├── common.hh ├── config.cc ├── connections.hh ├── utils.cc ├── containers.hh ├── main.cc └── connections.cc ├── tools ├── pubkey.go ├── genpem.go └── makefile.go ├── tibia-game.service ├── tibia.pem ├── LICENSE.txt ├── Makefile └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | .vscode 2 | bin 3 | build 4 | local 5 | *.log 6 | -------------------------------------------------------------------------------- /src/crypto.hh: -------------------------------------------------------------------------------- 1 | #ifndef TIBIA_CRYPTO_HH_ 2 | #define TIBIA_CRYPTO_HH_ 1 3 | 4 | #include "common.hh" 5 | #include 6 | 7 | struct TRSAPrivateKey{ 8 | TRSAPrivateKey(void); 9 | ~TRSAPrivateKey(void); 10 | bool initFromFile(const char *FileName); 11 | bool decrypt(uint8 *Data); // single 128 bytes block 12 | 13 | // DATA 14 | // ================= 15 | RSA *m_RSA; 16 | }; 17 | 18 | struct TXTEASymmetricKey{ 19 | void init(TReadBuffer *Buffer); 20 | void encrypt(uint8 *Data); // single 8 bytes block 21 | void decrypt(uint8 *Data); // single 8 bytes block 22 | 23 | // DATA 24 | // ================= 25 | uint32 m_SymmetricKey[4]; 26 | }; 27 | 28 | #endif //TIBIA_CRYPTO_HH_ 29 | -------------------------------------------------------------------------------- /tools/pubkey.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "crypto/x509" 5 | "encoding/pem" 6 | "fmt" 7 | "log" 8 | "os" 9 | ) 10 | 11 | func main() { 12 | keyFile := "key.pem" 13 | if len(os.Args) > 1 { 14 | keyFile = os.Args[1] 15 | } 16 | 17 | keyData, err := os.ReadFile(keyFile) 18 | if err != nil { 19 | log.Panicf("failed to read key \"%v\": %v", keyFile, err) 20 | } 21 | 22 | keyBlock, _ := pem.Decode(keyData) 23 | if keyBlock == nil { 24 | log.Panicf("key \"%v\" is empty") 25 | } 26 | 27 | privateKey, err := x509.ParsePKCS1PrivateKey(keyBlock.Bytes) 28 | if err != nil { 29 | log.Panic("failed to read PKCS1 RSA private key: %v", err) 30 | } 31 | 32 | fmt.Println("N", privateKey.N) 33 | fmt.Println("E", privateKey.E) 34 | } 35 | -------------------------------------------------------------------------------- /tibia-game.service: -------------------------------------------------------------------------------- 1 | # Basic SYSTEMD service file for the Tibia Game Server 2 | 3 | [Unit] 4 | Description=Tibia Game Server 5 | After=network.target tibia-querymanager.service 6 | Requires=tibia-querymanager.service 7 | 8 | [Install] 9 | WantedBy=multi-user.target 10 | 11 | [Service] 12 | Type=simple 13 | User=tibia-game 14 | Group=tibia-game 15 | ExecStart=/opt/tibia/game/bin/game 16 | WorkingDirectory=/opt/tibia/game 17 | Restart=always 18 | RestartSec=10 19 | # After a SIGTERM, the server will issue shutdown warnings for 5 minutes and 20 | # take another minute or two to save everything and exit. Not waiting for this 21 | # process to complete would cause data to not be properly saved. 22 | TimeoutStopSec=600 23 | LimitCORE=infinity 24 | StandardOutput=journal 25 | StandardError=journal 26 | SyslogIdentifier=%n 27 | -------------------------------------------------------------------------------- /tibia.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN RSA PRIVATE KEY----- 2 | MIICXAIBAAKBgQDLoibNahc41DeQhZUTth3FrhMNYek5xbQrlZ0O4BFr0LukdGY1 3 | A3xHKAXt4d6Y539cQxpKqVPb+eSo44076c1PfL31BTDNFToN4uB3eUsOw3hROPSx 4 | 0Jkh72FAIhl6QynId0AHyrqeAgY59ZsTyXyJCSCWpFnl0WvK3Qf6nVBH/wIDAQAB 5 | AoGAB9DoA19sl8JRhasS70hAuUs2sP9Ol+iWQ0wBVMZV9Nj0stnC6IsDNKn9HEXc 6 | qOrN0SlEM5RvQxTC6ZaeX6vYNQDSJj/aigSyiNTMvw+38TD09z6EBlI4+0piGIJd 7 | qY1ky0w3bdVhWiY2VFyGZ50NkfwjrpZSdLmsbhExzR4b/mkCQQDldLUeePo3RKZD 8 | 7+CVLFkTX/Yof8RLPOh1nukgftpvgaGcD1FXSwrIyKCHVAuwFnGgPfEr/2OAj60D 9 | 7Ya6XPeHAkEA4zC3U67ukzQxfyELrLTUBnsnKfkxejySsJ/M29SKCuHV2NOWxO8W 10 | s9/c0RFvp0wI4uOxH00fIPYyI2h6TpxZyQJBAKQ0ZPktskKjCilMHPgkCIro/Yv2 11 | A0+kgubJliP/I+rwZer8u0UxGsKdcOPnrYWSSjZWnaTS2y5Bo5tP/D6aETkCQDAc 12 | AtZPtumpJcob/1LlP/jXX2W+BUIzTYTlcgYjLdA8HoK527V8Q7x3bVVAcfplWYRi 13 | XwGX3T2npNpmp2+6IDECQBZuNbXiwHhDdsH7sSt36BGTFJNtVk3GJl/Bf50VrL6N 14 | 6NBNU3d4jQwArvSsanFYrZw+ayxS4rcJdZasO5mm+EI= 15 | -----END RSA PRIVATE KEY----- 16 | -------------------------------------------------------------------------------- /src/threads.hh: -------------------------------------------------------------------------------- 1 | #ifndef TIBIA_THREADS_HH_ 2 | #define TIBIA_THREADS_HH_ 1 3 | 4 | #include "common.hh" 5 | #include 6 | 7 | typedef pthread_t ThreadHandle; 8 | typedef int (ThreadFunction)(void *); 9 | 10 | // TODO(fusion): Probably have another way to tell whether the thread was 11 | // successfully spawned or not? 12 | constexpr ThreadHandle INVALID_THREAD_HANDLE = 0; 13 | 14 | ThreadHandle StartThread(ThreadFunction *Function, void *Argument, bool Detach); 15 | ThreadHandle StartThread(ThreadFunction *Function, void *Argument, size_t StackSize, bool Detach); 16 | ThreadHandle StartThread(ThreadFunction *Function, void *Argument, void *Stack, size_t StackSize, bool Detach); 17 | int JoinThread(ThreadHandle Handle); 18 | void DelayThread(int Seconds, int MicroSeconds); 19 | 20 | struct Semaphore { 21 | Semaphore(int Value); 22 | ~Semaphore(void); 23 | void up(void); 24 | void down(void); 25 | 26 | // DATA 27 | // ================= 28 | int value; 29 | pthread_mutex_t mutex; 30 | pthread_cond_t condition; 31 | }; 32 | 33 | #endif //TIBIA_THREADS_HH_ 34 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | This is free and unencumbered software released into the public domain. 2 | 3 | Anyone is free to copy, modify, publish, use, compile, sell, or 4 | distribute this software, either in source code form or as a compiled 5 | binary, for any purpose, commercial or non-commercial, and by any 6 | means. 7 | 8 | In jurisdictions that recognize copyright laws, the author or authors 9 | of this software dedicate any and all copyright interest in the 10 | software to the public domain. We make this dedication for the benefit 11 | of the public at large and to the detriment of our heirs and 12 | successors. We intend this dedication to be an overt act of 13 | relinquishment in perpetuity of all present and future rights to this 14 | software under copyright law. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 17 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 18 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 19 | IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR 20 | OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, 21 | ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 22 | OTHER DEALINGS IN THE SOFTWARE. 23 | 24 | For more information, please refer to -------------------------------------------------------------------------------- /src/config.hh: -------------------------------------------------------------------------------- 1 | #ifndef TIBIA_CONFIG_HH_ 2 | #define TIBIA_CONFIG_HH_ 1 3 | 4 | #include "common.hh" 5 | #include "enums.hh" 6 | 7 | struct TDatabaseSettings { 8 | char Product[30]; 9 | char Database[30]; 10 | char Login[30]; 11 | char Password[30]; 12 | char Host[30]; 13 | char Port[6]; 14 | }; 15 | 16 | struct TQueryManagerSettings { 17 | char Host[50]; 18 | int Port; 19 | char Password[30]; 20 | }; 21 | 22 | extern char BINPATH[4096]; 23 | extern char DATAPATH[4096]; 24 | extern char LOGPATH[4096]; 25 | extern char MAPPATH[4096]; 26 | extern char MONSTERPATH[4096]; 27 | extern char NPCPATH[4096]; 28 | extern char ORIGMAPPATH[4096]; 29 | extern char SAVEPATH[4096]; 30 | extern char USERPATH[4096]; 31 | extern int SHMKey; 32 | extern int AdminPort; 33 | extern int GamePort; 34 | extern int QueryManagerPort; 35 | extern char AdminAddress[16]; 36 | extern char GameAddress[16]; 37 | extern char QueryManagerAddress[16]; 38 | extern char QueryManagerAdminPW[9]; 39 | extern char QueryManagerGamePW[9]; 40 | extern char QueryManagerWebPW[9]; 41 | extern int DebugLevel; 42 | extern bool PrivateWorld; 43 | extern TWorldType WorldType; 44 | extern char WorldName[30]; 45 | extern int MaxPlayers; 46 | extern int MaxNewbies; 47 | extern int PremiumPlayerBuffer; 48 | extern int PremiumNewbieBuffer; 49 | extern int Beat; 50 | extern int RebootTime; 51 | extern TDatabaseSettings ADMIN_DATABASE; 52 | extern TDatabaseSettings VOLATILE_DATABASE; 53 | extern TDatabaseSettings WEB_DATABASE; 54 | extern TDatabaseSettings FORUM_DATABASE; 55 | extern TDatabaseSettings MANAGER_DATABASE; 56 | extern int NumberOfQueryManagers; 57 | extern TQueryManagerSettings QUERY_MANAGER[10]; 58 | 59 | void ReadConfig(void); 60 | 61 | #endif //TIBIA_CONFIG_HH_ 62 | -------------------------------------------------------------------------------- /src/reader.hh: -------------------------------------------------------------------------------- 1 | #ifndef TIBIA_READER_HH_ 2 | #define TIBIA_READER_HH_ 1 3 | 4 | #include "common.hh" 5 | 6 | struct TPlayerData; 7 | 8 | typedef void TRefreshSectorFunction(int SectorX, int SectorY, int SectorZ, 9 | const uint8 *Data, int Size); 10 | typedef void TSendMailsFunction(TPlayerData *PlayerData); 11 | 12 | enum TReaderThreadOrderType: int { 13 | READER_ORDER_TERMINATE = 0, 14 | READER_ORDER_LOADSECTOR = 1, 15 | READER_ORDER_LOADCHARACTER = 2, 16 | }; 17 | 18 | enum TReaderThreadReplyType: int { 19 | READER_REPLY_SECTORDATA = 0, 20 | READER_REPLY_CHARACTERDATA = 1, 21 | }; 22 | 23 | struct TReaderThreadOrder { 24 | TReaderThreadOrderType OrderType; 25 | int SectorX; 26 | int SectorY; 27 | int SectorZ; 28 | uint32 CharacterID; 29 | }; 30 | 31 | struct TReaderThreadReply { 32 | TReaderThreadReplyType ReplyType; 33 | int SectorX; 34 | int SectorY; 35 | int SectorZ; 36 | uint8 *Data; 37 | int Size; 38 | }; 39 | 40 | void InitReaderBuffers(void); 41 | void InsertOrder(TReaderThreadOrderType OrderType, 42 | int SectorX, int SectorY, int SectorZ, uint32 CharacterID); 43 | void GetOrder(TReaderThreadOrder *Order); 44 | void TerminateReaderOrder(void); 45 | void LoadSectorOrder(int SectorX, int SectorY, int SectorZ); 46 | void LoadCharacterOrder(uint32 CharacterID); 47 | void ProcessLoadSectorOrder(int SectorX, int SectorY, int SectorZ); 48 | void ProcessLoadCharacterOrder(uint32 CharacterID); 49 | int ReaderThreadLoop(void *Unused); 50 | 51 | void InsertReply(TReaderThreadReplyType ReplyType, 52 | int SectorX, int SectorY, int SectorZ, uint8 *Data, int Size); 53 | bool GetReply(TReaderThreadReply *Reply); 54 | void SectorReply(int SectorX, int SectorY, int SectorZ, uint8 *Data, int Size); 55 | void CharacterReply(uint32 CharacterID); 56 | void ProcessSectorReply(TRefreshSectorFunction *RefreshSector, 57 | int SectorX, int SectorY, int SectorZ, uint8 *Data, int Size); 58 | void ProcessCharacterReply(TSendMailsFunction *SendMails, uint32 CharacterID); 59 | void ProcessReaderThreadReplies(TRefreshSectorFunction *RefreshSector, TSendMailsFunction *SendMails); 60 | 61 | void InitReader(void); 62 | void ExitReader(void); 63 | 64 | #endif //TIBIA_READER_HH 65 | -------------------------------------------------------------------------------- /src/communication.hh: -------------------------------------------------------------------------------- 1 | #ifndef TIBIA_COMMUNICATION_HH_ 2 | #define TIBIA_COMMUNICATION_HH_ 1 3 | 4 | #include "common.hh" 5 | #include "connections.hh" 6 | 7 | struct TPlayerData; 8 | 9 | enum{ 10 | LOGIN_MESSAGE_ERROR = SV_CMD_LOGIN_ERROR, 11 | LOGIN_MESSAGE_PREMIUM = SV_CMD_LOGIN_PREMIUM, 12 | LOGIN_MESSAGE_WAITINGLIST = SV_CMD_LOGIN_WAITINGLIST, 13 | }; 14 | 15 | struct TWaitinglistEntry { 16 | TWaitinglistEntry *Next; 17 | char Name[30]; 18 | uint32 NextTry; 19 | bool FreeAccount; 20 | bool Newbie; 21 | bool Sleeping; 22 | }; 23 | 24 | void GetCommunicationThreadStack(int *StackNumber, void **Stack); 25 | void AttachCommunicationThreadStack(int StackNumber); 26 | void ReleaseCommunicationThreadStack(int StackNumber); 27 | void InitCommunicationThreadStacks(void); 28 | void ExitCommunicationThreadStacks(void); 29 | 30 | bool LagDetected(void); 31 | void NetLoad(int Amount, bool Send); 32 | void NetLoadSummary(void); 33 | void NetLoadCheck(void); 34 | void InitLoadHistory(void); 35 | void ExitLoadHistory(void); 36 | 37 | bool WriteToSocket(TConnection *Connection, uint8 *Buffer, int Size, int MaxSize); 38 | bool SendLoginMessage(TConnection *Connection, int Type, const char *Message, int WaitingTime); 39 | bool SendData(TConnection *Connection); 40 | 41 | bool GetWaitinglistEntry(const char *Name, uint32 *NextTry, bool *FreeAccount, bool *Newbie); 42 | void InsertWaitinglistEntry(const char *Name, uint32 NextTry, bool FreeAccount, bool Newbie); 43 | void DeleteWaitinglistEntry(const char *Name); 44 | int GetWaitinglistPosition(const char *Name, bool FreeAccount, bool Newbie); 45 | int CheckWaitingTime(const char *Name, TConnection *Connection, bool FreeAccount, bool Newbie); 46 | 47 | int ReadFromSocket(TConnection *Connection, uint8 *Buffer, int Size); 48 | bool CallGameThread(TConnection *Connection); 49 | bool CheckConnection(TConnection *Connection); 50 | TPlayerData *PerformRegistration(TConnection *Connection, char *PlayerName, 51 | uint32 AccountID, const char *PlayerPassword, bool GamemasterClient); 52 | bool HandleLogin(TConnection *Connection); 53 | bool ReceiveCommand(TConnection *Connection); 54 | 55 | void IncrementActiveConnections(void); 56 | void DecrementActiveConnections(void); 57 | void CommunicationThread(int Socket); 58 | int HandleConnection(void *Data); 59 | bool OpenSocket(void); 60 | int AcceptorThreadLoop(void *Unused); 61 | 62 | void CheckThreadlibVersion(void); 63 | void InitCommunication(void); 64 | void ExitCommunication(void); 65 | 66 | #endif //TIBIA_COMMUNICATION_HH_ 67 | -------------------------------------------------------------------------------- /src/info.hh: -------------------------------------------------------------------------------- 1 | #ifndef TIBIA_INFO_HH_ 2 | #define TIBIA_INFO_HH_ 1 3 | 4 | #include "common.hh" 5 | #include "map.hh" 6 | 7 | const char *GetLiquidName(int LiquidType); 8 | uint8 GetLiquidColor(int LiquidType); 9 | const char *GetName(Object Obj); 10 | const char *GetInfo(Object Obj); 11 | int GetWeight(Object Obj, int Count); 12 | int GetCompleteWeight(Object Obj); 13 | int GetRowWeight(Object Obj); 14 | uint32 GetObjectCreatureID(Object Obj); 15 | int GetObjectBodyPosition(Object Obj); 16 | int GetObjectRNum(Object Obj); 17 | bool ObjectInRange(uint32 CreatureID, Object Obj, int Range); 18 | bool ObjectAccessible(uint32 CreatureID, Object Obj, int Range); 19 | int ObjectDistance(Object Obj1, Object Obj2); 20 | Object GetBodyContainer(uint32 CreatureID, int Position); 21 | Object GetBodyObject(uint32 CreatureID, int Position); 22 | Object GetTopObject(int x, int y, int z, bool Move); 23 | Object GetContainer(uint32 CreatureID, int x, int y, int z); 24 | Object GetObject(uint32 CreatureID, int x, int y, int z, int RNum, ObjectType Type); 25 | Object GetRowObject(Object Obj, ObjectType Type, uint32 Value, bool Recurse); 26 | Object GetInventoryObject(uint32 CreatureID, ObjectType Type, uint32 Value); 27 | bool IsHeldByContainer(Object Obj, Object Con); 28 | int CountObjectsInContainer(Object Con); 29 | int CountObjects(Object Obj); 30 | int CountObjects(Object Obj, ObjectType Type, uint32 Value); 31 | int CountInventoryObjects(uint32 CreatureID, ObjectType Type, uint32 Value); 32 | int CountMoney(Object Obj); 33 | int CountInventoryMoney(uint32 CreatureID); 34 | void CalculateChange(int Amount, int *Gold, int *Platinum, int *Crystal); 35 | int GetHeight(int x, int y, int z); 36 | bool JumpPossible(int x, int y, int z, bool AvoidPlayers); 37 | bool FieldPossible(int x, int y, int z, int FieldType); 38 | bool SearchFreeField(int *x, int *y, int *z, int Distance, uint16 HouseID, bool Jump); 39 | bool SearchLoginField(int *x, int *y, int *z, int Distance, bool Player); 40 | bool SearchSpawnField(int *x, int *y, int *z, int Distance, bool Player); 41 | bool SearchFlightField(uint32 FugitiveID, uint32 PursuerID, int *x, int *y, int *z); 42 | bool SearchSummonField(int *x, int *y, int *z, int Distance); 43 | bool ThrowPossible(int OrigX, int OrigY, int OrigZ, 44 | int DestX, int DestY, int DestZ, int Power); 45 | void GetCreatureLight(uint32 CreatureID, int *Brightness, int *Color); 46 | int GetInventoryWeight(uint32 CreatureID); 47 | bool CheckRight(uint32 CharacterID, RIGHT Right); 48 | bool CheckBanishmentRight(uint32 CharacterID, int Reason, int Action); 49 | const char *GetBanishmentReason(int Reason); 50 | void InitInfo(void); 51 | void ExitInfo(void); 52 | 53 | #endif //TIBIA_INFO_HH_ 54 | -------------------------------------------------------------------------------- /src/time.cc: -------------------------------------------------------------------------------- 1 | #include "common.hh" 2 | 3 | // IMPORTANT(fusion): `RoundNr` is just the number of seconds since startup which 4 | // is why `GetRoundAtTime` and `GetRoundForNextMinute` straight up uses it as 5 | // seconds. It is incremented every 1 second inside `AdvanceGame`. 6 | uint32 RoundNr = 0; 7 | 8 | uint32 ServerMilliseconds = 0; 9 | 10 | struct tm GetLocalTimeTM(time_t t){ 11 | struct tm result; 12 | #if COMPILER_MSVC 13 | localtime_s(&result, &t); 14 | #else 15 | localtime_r(&t, &result); 16 | #endif 17 | return result; 18 | } 19 | 20 | void GetRealTime(int *Hour, int *Minute){ 21 | struct tm LocalTime = GetLocalTimeTM(time(NULL)); 22 | *Hour = LocalTime.tm_hour; 23 | *Minute = LocalTime.tm_min; 24 | } 25 | 26 | void GetTime(int *Hour, int *Minute){ 27 | // NOTE(fusion): This maps each real time hour to a game time day. 28 | struct tm LocalTime = GetLocalTimeTM(time(NULL)); 29 | int Time = LocalTime.tm_sec + LocalTime.tm_min * 60; 30 | *Hour = (Time / 150); 31 | *Minute = (Time % 150) * 2 / 5; 32 | } 33 | 34 | void GetDate(int *Year, int *Cycle, int *Day){ 35 | // NOTE(fusion): This maps each real time week to a game time year. 36 | time_t RealTime = time(NULL); 37 | struct tm LocalTime = GetLocalTimeTM(RealTime); 38 | *Year = (int)(((RealTime / 86400) + 4) / 7); 39 | *Cycle = LocalTime.tm_wday; 40 | *Day = LocalTime.tm_hour; 41 | } 42 | 43 | void GetAmbiente(int *Brightness, int *Color){ 44 | int Hour, Minute; 45 | GetTime(&Hour, &Minute); 46 | 47 | int Time = Minute + Hour * 60; 48 | if(Time < 60){ 49 | *Brightness = 0x33; 50 | *Color = 0xD7; 51 | }else if(Time < 120){ 52 | *Brightness = 0x66; 53 | *Color = 0xD7; 54 | }else if(Time < 180){ 55 | *Brightness = 0x99; 56 | *Color = 0xAD; 57 | }else if(Time < 240){ 58 | *Brightness = 0xCC; 59 | *Color = 0xAD; 60 | }else if(Time <= 1200){ 61 | *Brightness = 0xFF; 62 | *Color = 0xD7; 63 | }else if(Time <= 1260){ 64 | *Brightness = 0xCC; 65 | *Color = 0xD0; 66 | }else if(Time <= 1320){ 67 | *Brightness = 0x99; 68 | *Color = 0xD0; 69 | }else if(Time <= 1380){ 70 | *Brightness = 0x66; 71 | *Color = 0xD7; 72 | }else{ 73 | *Brightness = 0x33; 74 | *Color = 0xD7; 75 | } 76 | } 77 | 78 | uint32 GetRoundAtTime(int Hour, int Minute){ 79 | struct tm LocalTime = GetLocalTimeTM(time(NULL)); 80 | int SecondsToTime = (Hour - LocalTime.tm_hour) * 3600 81 | + (Minute - LocalTime.tm_min) * 60 82 | + (0 - LocalTime.tm_sec); 83 | if(SecondsToTime < 0){ 84 | SecondsToTime += 86400; 85 | } 86 | return SecondsToTime + RoundNr; 87 | } 88 | 89 | uint32 GetRoundForNextMinute(void){ 90 | struct tm LocalTime = GetLocalTimeTM(time(NULL)); 91 | int SecondsToNextMinute = 60 - LocalTime.tm_sec; 92 | return SecondsToNextMinute + RoundNr + 30; 93 | } 94 | -------------------------------------------------------------------------------- /tools/genpem.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "crypto/rand" 5 | "crypto/rsa" 6 | "crypto/x509" 7 | "encoding/pem" 8 | "errors" 9 | "math/big" 10 | "os" 11 | "slices" 12 | ) 13 | 14 | func GenerateDefaultKey() (*rsa.PrivateKey, error) { 15 | // NOTE(fusion): Generate key from known P, Q, and E. There isn't a helper 16 | // function from the standard library so we need to build the private key 17 | // ourselves. 18 | const ( 19 | P = "1201758001370723323398753778257470257713354828752713123415294815" + 20 | "0506251412291888866940292054989907714155267326586216043845592229" + 21 | "084368540020196135619327879" 22 | 23 | Q = "1189892136861686835188050824611210139447876026576932541274639840" + 24 | "5473436969889506919017477758618276066588858607419440134394668095" + 25 | "105156501566867770737187273" 26 | 27 | E = "65537" 28 | ) 29 | 30 | p, ok := new(big.Int).SetString(P, 10) 31 | if !ok || !p.ProbablyPrime(4) { 32 | return nil, errors.New("invalid default P prime") 33 | } 34 | 35 | q, ok := new(big.Int).SetString(Q, 10) 36 | if !ok || !q.ProbablyPrime(4) { 37 | return nil, errors.New("invalid default Q prime") 38 | } 39 | 40 | e, ok := new(big.Int).SetString(E, 10) 41 | if !ok || !e.IsInt64() || !e.ProbablyPrime(4) { 42 | return nil, errors.New("invalid default E prime") 43 | } 44 | 45 | pMinus1 := new(big.Int).Sub(p, big.NewInt(1)) 46 | qMinus1 := new(big.Int).Sub(q, big.NewInt(1)) 47 | gcd := new(big.Int).GCD(nil, nil, pMinus1, qMinus1) 48 | phi := new(big.Int).Mul(pMinus1, qMinus1) 49 | lambda := new(big.Int).Div(phi, gcd) 50 | d := new(big.Int).ModInverse(e, lambda) 51 | n := new(big.Int).Mul(p, q) 52 | 53 | privateKey := rsa.PrivateKey{ 54 | PublicKey: rsa.PublicKey{ 55 | N: n, 56 | E: int(e.Int64()), 57 | }, 58 | D: d, 59 | Primes: []*big.Int{p, q}, 60 | } 61 | 62 | return &privateKey, nil 63 | } 64 | 65 | func GenerateKey() (*rsa.PrivateKey, error) { 66 | return rsa.GenerateKey(rand.Reader, 1024) 67 | } 68 | 69 | func main() { 70 | var ( 71 | privateKey *rsa.PrivateKey 72 | err error 73 | ) 74 | 75 | if len(os.Args) > 1 && slices.Contains(os.Args[1:], "-default") { 76 | privateKey, err = GenerateDefaultKey() 77 | } else { 78 | privateKey, err = GenerateKey() 79 | } 80 | 81 | if err != nil { 82 | panic("failed to generate key: " + err.Error()) 83 | } 84 | 85 | // NOTE(fusion): `Validate()` will only perform minor sanity checks. To actually 86 | // check that the output key is valid use `openssl rsa -in KEY.PEM -check`. 87 | err = privateKey.Validate() 88 | if err != nil { 89 | panic("invalid private key: " + err.Error()) 90 | } 91 | 92 | // NOTE(fusion): Dump private key into stdout. 93 | block := pem.Block{ 94 | Type: "RSA PRIVATE KEY", 95 | Bytes: x509.MarshalPKCS1PrivateKey(privateKey), 96 | } 97 | pem.Encode(os.Stdout, &block) 98 | } 99 | -------------------------------------------------------------------------------- /src/houses.hh: -------------------------------------------------------------------------------- 1 | #ifndef TIBIA_HOUSES_HH_ 2 | #define TIBIA_HOUSES_HH_ 1 3 | 4 | #include "common.hh" 5 | #include "containers.hh" 6 | #include "map.hh" 7 | 8 | #define MAX_HOUSE_GUEST_NAME 60 9 | 10 | struct TPlayer; 11 | struct TPlayerData; 12 | 13 | struct THelpDepot { 14 | uint32 CharacterID; 15 | Object Box; 16 | int DepotNr; 17 | }; 18 | 19 | struct THouseArea { 20 | uint16 ID; 21 | int SQMPrice; 22 | int DepotNr; 23 | }; 24 | 25 | struct THouseGuest { 26 | char Name[MAX_HOUSE_GUEST_NAME]; 27 | }; 28 | 29 | struct THouse { 30 | THouse(void); 31 | 32 | // TODO(fusion): Same as `TChannel` in `operate.hh`. 33 | THouse(const THouse &Other); 34 | void operator=(const THouse &Other); 35 | 36 | // DATA 37 | // ================= 38 | uint16 ID; 39 | char Name[50]; 40 | char Description[500]; 41 | int Size; 42 | int Rent; 43 | int DepotNr; 44 | bool NoAuction; 45 | bool GuildHouse; 46 | int ExitX; 47 | int ExitY; 48 | int ExitZ; 49 | int CenterX; 50 | int CenterY; 51 | int CenterZ; 52 | uint32 OwnerID; 53 | char OwnerName[30]; 54 | int LastTransition; 55 | int PaidUntil; 56 | vector Subowner; 57 | int Subowners; 58 | vector Guest; 59 | int Guests; 60 | int Help; 61 | }; 62 | 63 | THouseArea *GetHouseArea(uint16 ID); 64 | int CheckAccessRight(const char *Rule, TPlayer *Player); 65 | THouse *GetHouse(uint16 ID); 66 | bool IsOwner(uint16 HouseID, TPlayer *Player); 67 | bool IsSubowner(uint16 HouseID, TPlayer *Player, int TimeStamp); 68 | bool IsGuest(uint16 HouseID, TPlayer *Player, int TimeStamp); 69 | bool IsInvited(uint16 HouseID, TPlayer *Player, int TimeStamp); 70 | const char *GetHouseName(uint16 HouseID); 71 | const char *GetHouseOwner(uint16 HouseID); 72 | void ShowSubownerList(uint16 HouseID, TPlayer *Player, char *Buffer); 73 | void ShowGuestList(uint16 HouseID, TPlayer *Player, char *Buffer); 74 | void ChangeSubowners(uint16 HouseID, TPlayer *Player, const char *Buffer); 75 | void ChangeGuests(uint16 HouseID, TPlayer *Player, const char *Buffer); 76 | void GetExitPosition(uint16 HouseID, int *x, int *y, int *z); 77 | void KickGuest(uint16 HouseID, TPlayer *Guest); 78 | void KickGuest(uint16 HouseID, TPlayer *Host, TPlayer *Guest); 79 | void KickGuests(uint16 HouseID); 80 | bool MayOpenDoor(Object Door, TPlayer *Player); 81 | void ShowNameDoor(Object Door, TPlayer *Player, char *Buffer); 82 | void ChangeNameDoor(Object Door, TPlayer *Player, const char *Buffer); 83 | void CleanField(int x, int y, int z, Object Depot); 84 | void CleanHouse(THouse *House, TPlayerData *PlayerData); 85 | void ClearHouse(THouse *House); 86 | bool FinishAuctions(void); 87 | bool TransferHouses(void); 88 | bool EvictFreeAccounts(void); 89 | bool EvictDeletedCharacters(void); 90 | bool EvictExGuildLeaders(void); 91 | void CollectRent(void); 92 | void ProcessRent(void); 93 | bool StartAuctions(void); 94 | bool UpdateHouseOwners(void); 95 | void PrepareHouseCleanup(void); 96 | void FinishHouseCleanup(void); 97 | void CleanHouseField(int x, int y, int z); 98 | void LoadHouseAreas(void); 99 | void LoadHouses(void); 100 | void LoadOwners(void); 101 | void SaveOwners(void); 102 | void ProcessHouses(void); 103 | void InitHouses(void); 104 | void ExitHouses(void); 105 | 106 | #endif //TIBIA_HOUSES_HH_ 107 | -------------------------------------------------------------------------------- /tools/makefile.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "bytes" 5 | "fmt" 6 | "io/fs" 7 | "os" 8 | "path" 9 | "strings" 10 | ) 11 | 12 | var ( 13 | outputExe = "game" 14 | compilerExe = "g++" 15 | compilerOptions = []string{ 16 | "-m64", 17 | "-fno-strict-aliasing", 18 | "-pedantic", 19 | "-Wall", 20 | "-Wextra", 21 | "-Wno-deprecated-declarations", 22 | "-Wno-unused-parameter", 23 | "-Wno-format-truncation", 24 | "-std=c++11", 25 | "-pthread", 26 | "-DOS_LINUX=1", 27 | "-DARCH_X64=1", 28 | } 29 | debugOptions = []string{"-g", "-Og", "-DENABLE_ASSERTIONS=1"} 30 | releaseOptions = []string{"-O2"} 31 | linkerOptions = []string{ 32 | "-Wl,-t", 33 | "-lcrypto", 34 | } 35 | ) 36 | 37 | func main() { 38 | if len(os.Args) < 2 { 39 | fmt.Println("USAGE: makefile.exe SRCDIR") 40 | os.Exit(1) 41 | } 42 | 43 | sourceDir := os.Args[1] 44 | buildDir := path.Join(path.Dir(sourceDir), "build") 45 | 46 | type Object struct{ obj, src string } 47 | objectFiles := []Object{} 48 | headerFiles := []string{} 49 | 50 | fs.WalkDir(os.DirFS(sourceDir), ".", 51 | func(rel string, d fs.DirEntry, err error) error { 52 | if err == nil && !d.IsDir() { 53 | ext := path.Ext(rel) 54 | switch ext { 55 | case ".cpp", ".cxx", ".cc", ".c": 56 | src := rel 57 | obj := rel[:len(src)-len(ext)] + ".obj" 58 | objectFiles = append(objectFiles, Object{obj, src}) 59 | case ".hpp", ".hxx", ".hh", ".h": 60 | hdr := rel 61 | headerFiles = append(headerFiles, hdr) 62 | } 63 | } 64 | return nil 65 | }) 66 | 67 | output := bytes.Buffer{} 68 | 69 | // VARIABLES 70 | fmt.Fprintf(&output, "SRCDIR = %v\n", sourceDir) 71 | fmt.Fprintf(&output, "BUILDDIR = %v\n", buildDir) 72 | fmt.Fprintf(&output, "OUTPUTEXE = %v\n", outputExe) 73 | fmt.Fprint(&output, "\n") 74 | 75 | fmt.Fprintf(&output, "CC = %v\n", compilerExe) 76 | fmt.Fprintf(&output, "CFLAGS = %v\n", strings.Join(compilerOptions, " ")) 77 | fmt.Fprintf(&output, "LFLAGS = %v\n", strings.Join(linkerOptions, " ")) 78 | fmt.Fprint(&output, "\n") 79 | 80 | // DEBUG SWITCH 81 | fmt.Fprint(&output, "DEBUG ?= 0\n") 82 | fmt.Fprint(&output, "ifneq ($(DEBUG), 0)\n") 83 | fmt.Fprintf(&output, "\tCFLAGS += %v\n", strings.Join(debugOptions, " ")) 84 | fmt.Fprint(&output, "else\n") 85 | fmt.Fprintf(&output, "\tCFLAGS += %v\n", strings.Join(releaseOptions, " ")) 86 | fmt.Fprint(&output, "endif\n\n") 87 | 88 | // HEADERS 89 | fmt.Fprint(&output, "HEADERS =") 90 | for _, header := range headerFiles { 91 | fmt.Fprintf(&output, " $(SRCDIR)/%v", header) 92 | } 93 | fmt.Fprint(&output, "\n\n") 94 | 95 | // EXECUTABLE 96 | fmt.Fprint(&output, "$(BUILDDIR)/$(OUTPUTEXE):") 97 | for _, object := range objectFiles { 98 | fmt.Fprintf(&output, " $(BUILDDIR)/%v", object.obj) 99 | } 100 | fmt.Fprint(&output, "\n") 101 | fmt.Fprint(&output, "\t$(CC) $(CFLAGS) -o $@ $^ $(LFLAGS)\n\n") 102 | 103 | // OBJECTS 104 | for _, object := range objectFiles { 105 | fmt.Fprintf(&output, "$(BUILDDIR)/%v: $(SRCDIR)/%v $(HEADERS)\n", object.obj, object.src) 106 | fmt.Fprint(&output, "\t@mkdir -p $(@D)\n") 107 | fmt.Fprint(&output, "\t$(CC) -c $(CFLAGS) -o $@ $<\n\n") 108 | } 109 | 110 | // PHONY 111 | fmt.Fprint(&output, ".PHONY: clean\n\n") 112 | fmt.Fprint(&output, "clean:\n\t@rm -rf $(BUILDDIR)\n\n") 113 | 114 | if err := os.WriteFile("Makefile", output.Bytes(), 0644); err != nil { 115 | fmt.Printf("failed to write Makefile: %v\n", err) 116 | os.Exit(1) 117 | } 118 | 119 | return 120 | } 121 | -------------------------------------------------------------------------------- /src/objects.hh: -------------------------------------------------------------------------------- 1 | #ifndef TIBIA_OBJECTS_HH_ 2 | #define TIBIA_OBJECTS_HH_ 1 3 | 4 | #include "common.hh" 5 | #include "enums.hh" 6 | 7 | enum : int{ 8 | TYPEID_MAP_CONTAINER = 0, 9 | TYPEID_HEAD_CONTAINER = 1, 10 | TYPEID_NECK_CONTAINER = 2, 11 | TYPEID_BAG_CONTAINER = 3, 12 | TYPEID_TORSO_CONTAINER = 4, 13 | TYPEID_RIGHTHAND_CONTAINER = 5, 14 | TYPEID_LEFTHAND_CONTAINER = 6, 15 | TYPEID_LEGS_CONTAINER = 7, 16 | TYPEID_FEET_CONTAINER = 8, 17 | TYPEID_FINGER_CONTAINER = 9, 18 | TYPEID_AMMO_CONTAINER = 10, 19 | TYPEID_CREATURE_CONTAINER = 99, 20 | }; 21 | 22 | struct ObjectType { 23 | ObjectType(void) { this->setTypeID(0); } 24 | ObjectType(int TypeID) { this->setTypeID(TypeID); } 25 | void setTypeID(int TypeID); 26 | bool getFlag(FLAG Flag); 27 | uint32 getAttribute(TYPEATTRIBUTE Attribute); 28 | int getAttributeOffset(INSTANCEATTRIBUTE Attribute); 29 | const char *getName(int Count); 30 | const char *getDescription(void); 31 | 32 | bool isMapContainer(void){ 33 | return this->TypeID == TYPEID_MAP_CONTAINER; 34 | } 35 | 36 | bool isBodyContainer(void){ 37 | return this->TypeID == TYPEID_HEAD_CONTAINER 38 | || this->TypeID == TYPEID_NECK_CONTAINER 39 | || this->TypeID == TYPEID_BAG_CONTAINER 40 | || this->TypeID == TYPEID_TORSO_CONTAINER 41 | || this->TypeID == TYPEID_RIGHTHAND_CONTAINER 42 | || this->TypeID == TYPEID_LEFTHAND_CONTAINER 43 | || this->TypeID == TYPEID_LEGS_CONTAINER 44 | || this->TypeID == TYPEID_FEET_CONTAINER 45 | || this->TypeID == TYPEID_FINGER_CONTAINER 46 | || this->TypeID == TYPEID_AMMO_CONTAINER; 47 | } 48 | 49 | bool isCreatureContainer(void){ 50 | return this->TypeID == TYPEID_CREATURE_CONTAINER; 51 | } 52 | 53 | bool isTwoHanded(void){ 54 | return this->getFlag(CLOTHES) 55 | && this->getAttribute(BODYPOSITION) == 0; 56 | } 57 | 58 | bool isWeapon(void){ 59 | return this->getFlag(WEAPON) 60 | || this->getFlag(BOW) 61 | || this->getFlag(THROW) 62 | || this->getFlag(WAND); 63 | } 64 | 65 | bool isCloseWeapon(void){ 66 | if(!this->getFlag(WEAPON)){ 67 | return false; 68 | } 69 | 70 | int WeaponType = this->getAttribute(WEAPONTYPE); 71 | return WeaponType == WEAPON_SWORD 72 | || WeaponType == WEAPON_CLUB 73 | || WeaponType == WEAPON_AXE; 74 | } 75 | 76 | ObjectType getDisguise(void){ 77 | if(this->getFlag(DISGUISE)){ 78 | return (int)this->getAttribute(DISGUISETARGET); 79 | }else{ 80 | return *this; 81 | } 82 | } 83 | 84 | bool operator==(const ObjectType &Other) const { 85 | return this->TypeID == Other.TypeID; 86 | } 87 | 88 | bool operator!=(const ObjectType &Other) const { 89 | return this->TypeID != Other.TypeID; 90 | } 91 | 92 | // DATA 93 | // ================= 94 | int TypeID; 95 | }; 96 | 97 | struct TObjectType { 98 | const char *Name; 99 | const char *Description; 100 | uint8 Flags[9]; 101 | uint32 Attributes[62]; 102 | int AttributeOffsets[18]; 103 | }; 104 | 105 | int GetFlagByName(const char *Name); 106 | int GetTypeAttributeByName(const char *Name); 107 | int GetInstanceAttributeByName(const char *Name); 108 | const char *GetFlagName(int Flag); 109 | const char *GetTypeAttributeName(int Attribute); 110 | const char *GetInstanceAttributeName(int Attribute); 111 | bool ObjectTypeExists(int TypeID); 112 | bool ObjectTypeExists(uint8 Group, uint8 Number); 113 | ObjectType GetNewObjectType(uint8 Group, uint8 Number); 114 | void GetOldObjectType(ObjectType Type, uint8 *Group, uint8 *Number); 115 | ObjectType GetSpecialObject(SPECIALMEANING Meaning); 116 | ObjectType GetObjectTypeByName(const char *SearchName, bool Movable); 117 | void InitObjects(void); 118 | void ExitObjects(void); 119 | 120 | #endif //TIBIA_OBJECTS_HH_ 121 | -------------------------------------------------------------------------------- /src/crypto.cc: -------------------------------------------------------------------------------- 1 | #include "crypto.hh" 2 | 3 | #include 4 | #include 5 | 6 | static void DumpOpenSSLErrors(const char *Where, const char *What){ 7 | error("OpenSSL error(s) while executing %s at %s:\n", What, Where); 8 | ERR_print_errors_cb( 9 | [](const char *str, usize len, void *u) -> int { 10 | // NOTE(fusion): These error strings already have trailing newlines, 11 | // for whatever reason. 12 | error("> %s", str); 13 | return 1; 14 | }, NULL); 15 | } 16 | 17 | // TRSAPrivateKey 18 | // ============================================================================= 19 | TRSAPrivateKey::TRSAPrivateKey(void){ 20 | m_RSA = NULL; 21 | } 22 | 23 | TRSAPrivateKey::~TRSAPrivateKey(void){ 24 | if(m_RSA){ 25 | RSA_free(m_RSA); 26 | m_RSA = NULL; 27 | } 28 | } 29 | 30 | bool TRSAPrivateKey::initFromFile(const char *FileName){ 31 | if(m_RSA != NULL){ 32 | error("TRSAPrivateKey::init: Key already initialized.\n"); 33 | return false; 34 | } 35 | 36 | FILE *File = fopen(FileName, "rb"); 37 | if(File == NULL){ 38 | error("TRSAPrivateKey::initFromFile: Failed to open \"%s\".\n", FileName); 39 | return false; 40 | } 41 | 42 | m_RSA = PEM_read_RSAPrivateKey(File, NULL, NULL, NULL); 43 | fclose(File); 44 | 45 | if(m_RSA == NULL){ 46 | error("TRSAPrivateKey::initFromFile: Failed to read key from \"%s\".\n", FileName); 47 | DumpOpenSSLErrors("TRSAPrivateKey::initFromFile", "PEM_read_RSAPrivateKey"); 48 | }else if(RSA_size(m_RSA) != 128){ 49 | error("TRSAPrivateKey::initFromFile: File \"%s\" doesn't contain a 1024-bit key", FileName); 50 | RSA_free(m_RSA); 51 | m_RSA = NULL; 52 | } 53 | 54 | return (m_RSA != NULL); 55 | } 56 | 57 | bool TRSAPrivateKey::decrypt(uint8 *Data){ 58 | if(m_RSA == NULL){ 59 | error("TRSAPrivateKey::decrypt: Key not initialized.\n"); 60 | return false; 61 | } 62 | 63 | // TODO(fusion): Pass in the length of `Data` for checking. 64 | ASSERT(RSA_size(m_RSA) == 128); 65 | 66 | if(RSA_private_decrypt(128, Data, Data, m_RSA, RSA_NO_PADDING) == -1){ 67 | DumpOpenSSLErrors("TRSAPrivateKey::decrypt", "RSA_private_decrypt"); 68 | return false; 69 | } 70 | 71 | return true; 72 | } 73 | 74 | // TXTEASymmetricKey 75 | // ============================================================================= 76 | void TXTEASymmetricKey::init(TReadBuffer *Buffer){ 77 | m_SymmetricKey[0] = Buffer->readQuad(); 78 | m_SymmetricKey[1] = Buffer->readQuad(); 79 | m_SymmetricKey[2] = Buffer->readQuad(); 80 | m_SymmetricKey[3] = Buffer->readQuad(); 81 | } 82 | 83 | void TXTEASymmetricKey::encrypt(uint8 *Data){ 84 | // TODO(fusion): This assumes both data endpoints have the same byte order. 85 | // It's unlikely that there is anything other than little-endian but we 86 | // should use a few helping functions to ensure compatibility. 87 | uint32 Sum = 0x00000000UL; 88 | uint32 Delta = 0x9E3779B9UL; 89 | uint32 V0 = *(uint32*)(&Data[0]); 90 | uint32 V1 = *(uint32*)(&Data[4]); 91 | for(int i = 0; i < 32; i += 1){ 92 | V0 += (((V1 << 4) ^ (V1 >> 5)) + V1) ^ (Sum + m_SymmetricKey[Sum & 3]); 93 | Sum += Delta; 94 | V1 += (((V0 << 4) ^ (V0 >> 5)) + V0) ^ (Sum + m_SymmetricKey[(Sum >> 11) & 3]); 95 | } 96 | *(uint32*)(&Data[0]) = V0; 97 | *(uint32*)(&Data[4]) = V1; 98 | } 99 | 100 | void TXTEASymmetricKey::decrypt(uint8 *Data){ 101 | // TODO(fusion): Same as above. 102 | uint32 Sum = 0xC6EF3720UL; 103 | uint32 Delta = 0x9E3779B9UL; 104 | uint32 V0 = *(uint32*)(&Data[0]); 105 | uint32 V1 = *(uint32*)(&Data[4]); 106 | for(int i = 0; i < 32; i += 1){ 107 | V1 -= (((V0 << 4) ^ (V0 >> 5)) + V0) ^ (Sum + m_SymmetricKey[(Sum >> 11) & 3]); 108 | Sum -= Delta; 109 | V0 -= (((V1 << 4) ^ (V1 >> 5)) + V1) ^ (Sum + m_SymmetricKey[Sum & 3]); 110 | } 111 | *(uint32*)(&Data[0]) = V0; 112 | *(uint32*)(&Data[4]) = V1; 113 | } 114 | -------------------------------------------------------------------------------- /src/script.hh: -------------------------------------------------------------------------------- 1 | #ifndef TIBIA_SCRIPT_HH_ 2 | #define TIBIA_SCRIPT_HH_ 1 3 | 4 | #include "common.hh" 5 | #include "enums.hh" 6 | 7 | #define MAX_IDENT_LENGTH 30 8 | 9 | struct TReadScriptFile { 10 | TReadScriptFile(void); 11 | ~TReadScriptFile(void); 12 | void open(const char *FileName); 13 | void close(void); 14 | void error(const char *Text); 15 | void nextToken(void); 16 | char *getIdentifier(void); 17 | int getNumber(void); 18 | char *getString(void); 19 | uint8 *getBytesequence(void); 20 | void getCoordinate(int *x, int *y, int *z); 21 | char getSpecial(void); 22 | 23 | char *readIdentifier(void){ 24 | this->nextToken(); 25 | return this->getIdentifier(); 26 | } 27 | 28 | int readNumber(void){ 29 | this->nextToken(); 30 | 31 | int Sign = 1; 32 | if(this->Token == SPECIAL && this->Special == '-'){ 33 | Sign = -1; 34 | this->nextToken(); 35 | } 36 | 37 | return Sign * this->getNumber(); 38 | } 39 | 40 | char *readString(void){ 41 | this->nextToken(); 42 | return this->getString(); 43 | } 44 | 45 | uint8 *readBytesequence(void){ 46 | this->nextToken(); 47 | return this->getBytesequence(); 48 | } 49 | 50 | void readCoordinate(int *x, int *y, int *z){ 51 | this->nextToken(); 52 | this->getCoordinate(x, y, z); 53 | } 54 | 55 | char readSpecial(void){ 56 | this->nextToken(); 57 | return this->getSpecial(); 58 | } 59 | 60 | void readSymbol(char Symbol){ 61 | if(this->readSpecial() != Symbol){ 62 | this->error("symbol mismatch"); 63 | } 64 | } 65 | 66 | // DATA 67 | // ================= 68 | TOKEN Token; 69 | FILE *File[3]; 70 | char Filename[3][4096]; 71 | int Line[3]; 72 | char String[4000]; 73 | int RecursionDepth; 74 | uint8 *Bytes; 75 | int Number; 76 | int CoordX; 77 | int CoordY; 78 | int CoordZ; 79 | char Special; 80 | }; 81 | 82 | struct TWriteScriptFile { 83 | TWriteScriptFile(void); 84 | ~TWriteScriptFile(void); 85 | void open(const char *FileName); 86 | void close(void); 87 | void error(const char *Text); 88 | void writeLn(void); 89 | void writeText(const char *Text); 90 | void writeNumber(int Number); 91 | void writeString(const char *Text); 92 | void writeCoordinate(int x ,int y ,int z); 93 | void writeBytesequence(const uint8 *Sequence, int Length); 94 | 95 | // DATA 96 | // ================= 97 | FILE *File; 98 | char Filename[4096]; 99 | int Line; 100 | }; 101 | 102 | struct TReadBinaryFile: TReadStream { 103 | TReadBinaryFile(void); 104 | void open(const char *FileName); 105 | void close(void); 106 | void error(const char *Text); 107 | int getPosition(void); 108 | int getSize(void); 109 | void seek(int Offset); 110 | 111 | // VIRTUAL FUNCTIONS 112 | // ================= 113 | uint8 readByte(void) override; 114 | void readBytes(uint8 *Buffer, int Count) override; 115 | bool eof(void) override; 116 | void skip(int Count) override; 117 | 118 | // TODO(fusion): Appended virtual functions. These are not in the base class 119 | // VTABLE which can be problematic if we intend to use polymorphism, although 120 | // that doesn't seem to be case. 121 | virtual ~TReadBinaryFile(void); // VTABLE[8] 122 | // Duplicate destructor that also calls operator delete. // VTABLE[9] 123 | 124 | // DATA 125 | // ================= 126 | FILE *File; 127 | char Filename[4096]; 128 | int FileSize; 129 | }; 130 | 131 | struct TWriteBinaryFile: TWriteStream { 132 | TWriteBinaryFile(void); 133 | void open(const char *FileName); 134 | void close(void); 135 | void error(const char *Text); 136 | 137 | // VIRTUAL FUNCTIONS 138 | // ================= 139 | void writeByte(uint8 Byte) override; 140 | void writeBytes(const uint8 *Buffer, int Count) override; 141 | 142 | // TODO(fusion): Appended virtual functions. These are not in the base class 143 | // VTABLE which can be problematic if we intend to use polymorphism, although 144 | // that doesn't seem to be case. 145 | virtual ~TWriteBinaryFile(void); 146 | 147 | // DATA 148 | // ================= 149 | FILE *File; 150 | char Filename[4096]; 151 | }; 152 | 153 | #endif //TIBIA_SCRIPT_HH_ 154 | -------------------------------------------------------------------------------- /src/writer.hh: -------------------------------------------------------------------------------- 1 | #ifndef TIBIA_WRITER_HH_ 2 | #define TIBIA_WRITER_HH_ 1 3 | 4 | #include "common.hh" 5 | #include "containers.hh" 6 | #include "operate.hh" 7 | 8 | enum TWriterThreadOrderType: int { 9 | WRITER_ORDER_TERMINATE = 0, 10 | WRITER_ORDER_LOGOUT = 1, 11 | WRITER_ORDER_PLAYERLIST = 2, 12 | WRITER_ORDER_KILLSTATISTICS = 3, 13 | WRITER_ORDER_PUNISHMENT = 4, 14 | WRITER_ORDER_CHARACTERDEATH = 5, 15 | WRITER_ORDER_ADDBUDDY = 6, 16 | WRITER_ORDER_REMOVEBUDDY = 7, 17 | WRITER_ORDER_DECREMENTISONLINE = 8, 18 | WRITER_ORDER_SAVEPLAYERDATA = 9, 19 | }; 20 | 21 | enum TWriterThreadReplyType: int { 22 | WRITER_REPLY_BROADCAST = 0, 23 | WRITER_REPLY_DIRECT = 1, 24 | WRITER_REPLY_LOGOUT = 2, 25 | }; 26 | 27 | struct TProtocolThreadOrder{ 28 | char ProtocolName[20]; 29 | char Text[256]; 30 | }; 31 | 32 | struct TWriterThreadOrder{ 33 | TWriterThreadOrderType OrderType; 34 | const void *Data; 35 | }; 36 | 37 | struct TLogoutOrderData{ 38 | uint32 CharacterID; 39 | int Level; 40 | int Profession; 41 | time_t LastLoginTime; 42 | int TutorActivities; 43 | char Residence[30]; 44 | }; 45 | 46 | struct TPlayerlistOrderData{ 47 | int NumberOfPlayers; 48 | const char *PlayerNames; 49 | int *PlayerLevels; 50 | int *PlayerProfessions; 51 | }; 52 | 53 | struct TKillStatisticsOrderData{ 54 | int NumberOfRaces; 55 | const char *RaceNames; 56 | int *KilledPlayers; 57 | int *KilledCreatures; 58 | }; 59 | 60 | struct TPunishmentOrderData{ 61 | uint32 GamemasterID; 62 | char GamemasterName[30]; 63 | char CriminalName[30]; 64 | char CriminalIPAddress[16]; 65 | int Reason; 66 | int Action; 67 | char Comment[200]; 68 | int NumberOfStatements; 69 | vector *ReportedStatements; 70 | uint32 StatementID; 71 | bool IPBanishment; 72 | }; 73 | 74 | struct TCharacterDeathOrderData{ 75 | uint32 CharacterID; 76 | int Level; 77 | uint32 Offender; 78 | char Remark[30]; 79 | bool Unjustified; 80 | time_t Time; 81 | }; 82 | 83 | struct TBuddyOrderData{ 84 | uint32 AccountID; 85 | uint32 Buddy; 86 | }; 87 | 88 | struct TWriterThreadReply{ 89 | TWriterThreadReplyType ReplyType; 90 | const void *Data; 91 | }; 92 | 93 | struct TBroadcastReplyData{ 94 | char Message[100]; 95 | }; 96 | 97 | struct TDirectReplyData{ 98 | uint32 CharacterID; 99 | char Message[100]; 100 | }; 101 | 102 | void InitProtocol(void); 103 | void InsertProtocolOrder(const char *ProtocolName, const char *Text); 104 | void GetProtocolOrder(TProtocolThreadOrder *Order); 105 | void WriteProtocol(const char *ProtocolName, const char *Text); 106 | int ProtocolThreadLoop(void *Unused); 107 | void InitLog(const char *ProtocolName); 108 | void Log(const char *ProtocolName, const char *Text, ...) ATTR_PRINTF(2, 3); 109 | 110 | void InitWriterBuffers(void); 111 | int GetOrderBufferSpace(void); 112 | void InsertOrder(TWriterThreadOrderType OrderType, const void *Data); 113 | void GetOrder(TWriterThreadOrder *Order); 114 | void TerminateWriterOrder(void); 115 | void LogoutOrder(TPlayer *Player); 116 | void PlayerlistOrder(int NumberOfPlayers, const char *PlayerNames, 117 | int *PlayerLevels, int *PlayerProfessions); 118 | void KillStatisticsOrder(int NumberOfRaces, const char *RaceNames, 119 | int *KilledPlayers, int *KilledCreatures); 120 | void PunishmentOrder(TCreature *Gamemaster, const char *Name, const char *IPAddress, 121 | int Reason, int Action, const char *Comment, int NumberOfStatements, 122 | vector *ReportedStatements, uint32 StatementID, 123 | bool IPBanishment); 124 | void CharacterDeathOrder(TCreature *Creature, int OldLevel, 125 | uint32 OffenderID, const char *Remark, bool Unjustified); 126 | void AddBuddyOrder(TCreature *Creature, uint32 BuddyID); 127 | void RemoveBuddyOrder(TCreature *Creature, uint32 BuddyID); 128 | void DecrementIsOnlineOrder(uint32 CharacterID); 129 | void SavePlayerDataOrder(void); 130 | void ProcessLogoutOrder(TLogoutOrderData *Data); 131 | void ProcessPlayerlistOrder(TPlayerlistOrderData *Data); 132 | void ProcessKillStatisticsOrder(TKillStatisticsOrderData *Data); 133 | void ProcessPunishmentOrder(TPunishmentOrderData *Data); 134 | void ProcessCharacterDeathOrder(TCharacterDeathOrderData *Data); 135 | void ProcessAddBuddyOrder(TBuddyOrderData *Data); 136 | void ProcessRemoveBuddyOrder(TBuddyOrderData *Data); 137 | void ProcessDecrementIsOnlineOrder(uint32 CharacterID); 138 | int WriterThreadLoop(void *Unused); 139 | 140 | void InsertReply(TWriterThreadReplyType ReplyType, const void *Data); 141 | void BroadcastReply(const char *Text, ...) ATTR_PRINTF(1, 2); 142 | void DirectReply(uint32 CharacterID, const char *Text, ...) ATTR_PRINTF(2, 3); 143 | void LogoutReply(const char *PlayerName); 144 | bool GetReply(TWriterThreadReply *Reply); 145 | void ProcessBroadcastReply(TBroadcastReplyData *Data); 146 | void ProcessDirectReply(TDirectReplyData *Data); 147 | void ProcessLogoutReply(const char *Name); 148 | void ProcessWriterThreadReplies(void); 149 | 150 | void ClearPlayers(void); 151 | void InitWriter(void); 152 | void AbortWriter(void); 153 | void ExitWriter(void); 154 | 155 | #endif //TIBIA_WRITER_HH_ 156 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | SRCDIR = src 2 | BUILDDIR = build 3 | OUTPUTEXE = game 4 | 5 | CC = g++ 6 | CFLAGS = -m64 -fno-strict-aliasing -pedantic -Wall -Wextra -Wno-deprecated-declarations -Wno-unused-parameter -Wno-format-truncation -std=c++11 -pthread -DOS_LINUX=1 -DARCH_X64=1 7 | LFLAGS = -Wl,-t -lcrypto 8 | 9 | DEBUG ?= 0 10 | ifneq ($(DEBUG), 0) 11 | CFLAGS += -g -Og -DENABLE_ASSERTIONS=1 12 | else 13 | CFLAGS += -O2 14 | endif 15 | 16 | HEADERS = $(SRCDIR)/common.hh $(SRCDIR)/communication.hh $(SRCDIR)/config.hh $(SRCDIR)/connections.hh $(SRCDIR)/containers.hh $(SRCDIR)/cr.hh $(SRCDIR)/crypto.hh $(SRCDIR)/enums.hh $(SRCDIR)/houses.hh $(SRCDIR)/info.hh $(SRCDIR)/magic.hh $(SRCDIR)/map.hh $(SRCDIR)/moveuse.hh $(SRCDIR)/objects.hh $(SRCDIR)/operate.hh $(SRCDIR)/query.hh $(SRCDIR)/reader.hh $(SRCDIR)/script.hh $(SRCDIR)/threads.hh $(SRCDIR)/writer.hh 17 | 18 | $(BUILDDIR)/$(OUTPUTEXE): $(BUILDDIR)/communication.obj $(BUILDDIR)/config.obj $(BUILDDIR)/connections.obj $(BUILDDIR)/cract.obj $(BUILDDIR)/crcombat.obj $(BUILDDIR)/crmain.obj $(BUILDDIR)/crnonpl.obj $(BUILDDIR)/crplayer.obj $(BUILDDIR)/crskill.obj $(BUILDDIR)/crypto.obj $(BUILDDIR)/houses.obj $(BUILDDIR)/info.obj $(BUILDDIR)/magic.obj $(BUILDDIR)/main.obj $(BUILDDIR)/map.obj $(BUILDDIR)/moveuse.obj $(BUILDDIR)/objects.obj $(BUILDDIR)/operate.obj $(BUILDDIR)/query.obj $(BUILDDIR)/reader.obj $(BUILDDIR)/receiving.obj $(BUILDDIR)/script.obj $(BUILDDIR)/sending.obj $(BUILDDIR)/shm.obj $(BUILDDIR)/strings.obj $(BUILDDIR)/threads.obj $(BUILDDIR)/time.obj $(BUILDDIR)/utils.obj $(BUILDDIR)/writer.obj 19 | $(CC) $(CFLAGS) -o $@ $^ $(LFLAGS) 20 | 21 | $(BUILDDIR)/communication.obj: $(SRCDIR)/communication.cc $(HEADERS) 22 | @mkdir -p $(@D) 23 | $(CC) -c $(CFLAGS) -o $@ $< 24 | 25 | $(BUILDDIR)/config.obj: $(SRCDIR)/config.cc $(HEADERS) 26 | @mkdir -p $(@D) 27 | $(CC) -c $(CFLAGS) -o $@ $< 28 | 29 | $(BUILDDIR)/connections.obj: $(SRCDIR)/connections.cc $(HEADERS) 30 | @mkdir -p $(@D) 31 | $(CC) -c $(CFLAGS) -o $@ $< 32 | 33 | $(BUILDDIR)/cract.obj: $(SRCDIR)/cract.cc $(HEADERS) 34 | @mkdir -p $(@D) 35 | $(CC) -c $(CFLAGS) -o $@ $< 36 | 37 | $(BUILDDIR)/crcombat.obj: $(SRCDIR)/crcombat.cc $(HEADERS) 38 | @mkdir -p $(@D) 39 | $(CC) -c $(CFLAGS) -o $@ $< 40 | 41 | $(BUILDDIR)/crmain.obj: $(SRCDIR)/crmain.cc $(HEADERS) 42 | @mkdir -p $(@D) 43 | $(CC) -c $(CFLAGS) -o $@ $< 44 | 45 | $(BUILDDIR)/crnonpl.obj: $(SRCDIR)/crnonpl.cc $(HEADERS) 46 | @mkdir -p $(@D) 47 | $(CC) -c $(CFLAGS) -o $@ $< 48 | 49 | $(BUILDDIR)/crplayer.obj: $(SRCDIR)/crplayer.cc $(HEADERS) 50 | @mkdir -p $(@D) 51 | $(CC) -c $(CFLAGS) -o $@ $< 52 | 53 | $(BUILDDIR)/crskill.obj: $(SRCDIR)/crskill.cc $(HEADERS) 54 | @mkdir -p $(@D) 55 | $(CC) -c $(CFLAGS) -o $@ $< 56 | 57 | $(BUILDDIR)/crypto.obj: $(SRCDIR)/crypto.cc $(HEADERS) 58 | @mkdir -p $(@D) 59 | $(CC) -c $(CFLAGS) -o $@ $< 60 | 61 | $(BUILDDIR)/houses.obj: $(SRCDIR)/houses.cc $(HEADERS) 62 | @mkdir -p $(@D) 63 | $(CC) -c $(CFLAGS) -o $@ $< 64 | 65 | $(BUILDDIR)/info.obj: $(SRCDIR)/info.cc $(HEADERS) 66 | @mkdir -p $(@D) 67 | $(CC) -c $(CFLAGS) -o $@ $< 68 | 69 | $(BUILDDIR)/magic.obj: $(SRCDIR)/magic.cc $(HEADERS) 70 | @mkdir -p $(@D) 71 | $(CC) -c $(CFLAGS) -o $@ $< 72 | 73 | $(BUILDDIR)/main.obj: $(SRCDIR)/main.cc $(HEADERS) 74 | @mkdir -p $(@D) 75 | $(CC) -c $(CFLAGS) -o $@ $< 76 | 77 | $(BUILDDIR)/map.obj: $(SRCDIR)/map.cc $(HEADERS) 78 | @mkdir -p $(@D) 79 | $(CC) -c $(CFLAGS) -o $@ $< 80 | 81 | $(BUILDDIR)/moveuse.obj: $(SRCDIR)/moveuse.cc $(HEADERS) 82 | @mkdir -p $(@D) 83 | $(CC) -c $(CFLAGS) -o $@ $< 84 | 85 | $(BUILDDIR)/objects.obj: $(SRCDIR)/objects.cc $(HEADERS) 86 | @mkdir -p $(@D) 87 | $(CC) -c $(CFLAGS) -o $@ $< 88 | 89 | $(BUILDDIR)/operate.obj: $(SRCDIR)/operate.cc $(HEADERS) 90 | @mkdir -p $(@D) 91 | $(CC) -c $(CFLAGS) -o $@ $< 92 | 93 | $(BUILDDIR)/query.obj: $(SRCDIR)/query.cc $(HEADERS) 94 | @mkdir -p $(@D) 95 | $(CC) -c $(CFLAGS) -o $@ $< 96 | 97 | $(BUILDDIR)/reader.obj: $(SRCDIR)/reader.cc $(HEADERS) 98 | @mkdir -p $(@D) 99 | $(CC) -c $(CFLAGS) -o $@ $< 100 | 101 | $(BUILDDIR)/receiving.obj: $(SRCDIR)/receiving.cc $(HEADERS) 102 | @mkdir -p $(@D) 103 | $(CC) -c $(CFLAGS) -o $@ $< 104 | 105 | $(BUILDDIR)/script.obj: $(SRCDIR)/script.cc $(HEADERS) 106 | @mkdir -p $(@D) 107 | $(CC) -c $(CFLAGS) -o $@ $< 108 | 109 | $(BUILDDIR)/sending.obj: $(SRCDIR)/sending.cc $(HEADERS) 110 | @mkdir -p $(@D) 111 | $(CC) -c $(CFLAGS) -o $@ $< 112 | 113 | $(BUILDDIR)/shm.obj: $(SRCDIR)/shm.cc $(HEADERS) 114 | @mkdir -p $(@D) 115 | $(CC) -c $(CFLAGS) -o $@ $< 116 | 117 | $(BUILDDIR)/strings.obj: $(SRCDIR)/strings.cc $(HEADERS) 118 | @mkdir -p $(@D) 119 | $(CC) -c $(CFLAGS) -o $@ $< 120 | 121 | $(BUILDDIR)/threads.obj: $(SRCDIR)/threads.cc $(HEADERS) 122 | @mkdir -p $(@D) 123 | $(CC) -c $(CFLAGS) -o $@ $< 124 | 125 | $(BUILDDIR)/time.obj: $(SRCDIR)/time.cc $(HEADERS) 126 | @mkdir -p $(@D) 127 | $(CC) -c $(CFLAGS) -o $@ $< 128 | 129 | $(BUILDDIR)/utils.obj: $(SRCDIR)/utils.cc $(HEADERS) 130 | @mkdir -p $(@D) 131 | $(CC) -c $(CFLAGS) -o $@ $< 132 | 133 | $(BUILDDIR)/writer.obj: $(SRCDIR)/writer.cc $(HEADERS) 134 | @mkdir -p $(@D) 135 | $(CC) -c $(CFLAGS) -o $@ $< 136 | 137 | .PHONY: clean 138 | 139 | clean: 140 | @rm -rf $(BUILDDIR) 141 | 142 | -------------------------------------------------------------------------------- /src/threads.cc: -------------------------------------------------------------------------------- 1 | #include "threads.hh" 2 | 3 | struct TThreadStarter { 4 | ThreadFunction *Function; 5 | void *Argument; 6 | bool Detach; 7 | }; 8 | 9 | static void *ThreadStarter(void *Pointer){ 10 | TThreadStarter *Starter = (TThreadStarter*)Pointer; 11 | ThreadFunction *Function = Starter->Function; 12 | void *Argument = Starter->Argument; 13 | bool Detach = Starter->Detach; 14 | delete Starter; 15 | 16 | int Result = Function(Argument); 17 | 18 | // TODO(fusion): Just store the integer as a pointer and avoid allocation? 19 | int *ResultPointer = NULL; 20 | if(!Detach){ 21 | ResultPointer = new int; 22 | *ResultPointer = Result; 23 | } 24 | pthread_exit(ResultPointer); 25 | 26 | return NULL; // Unreachable. 27 | } 28 | 29 | ThreadHandle StartThread(ThreadFunction *Function, void *Argument, bool Detach){ 30 | TThreadStarter *Starter = new TThreadStarter; 31 | Starter->Function = Function; 32 | Starter->Argument = Argument; 33 | Starter->Detach = Detach; 34 | 35 | pthread_t Handle; 36 | int err = pthread_create(&Handle, NULL, ThreadStarter, Starter); 37 | if(err != 0){ 38 | error("StartThread: Kann Thread nicht anlegen; Fehlercode %d.\n", err); 39 | return INVALID_THREAD_HANDLE; 40 | } 41 | 42 | if(Detach){ 43 | pthread_detach(Handle); 44 | } 45 | 46 | return (ThreadHandle)Handle; 47 | } 48 | 49 | ThreadHandle StartThread(ThreadFunction *Function, void *Argument, size_t StackSize, bool Detach){ 50 | TThreadStarter *Starter = new TThreadStarter; 51 | Starter->Function = Function; 52 | Starter->Argument = Argument; 53 | Starter->Detach = Detach; 54 | 55 | pthread_t Handle; 56 | pthread_attr_t Attr; 57 | pthread_attr_init(&Attr); 58 | pthread_attr_setstacksize(&Attr, StackSize); 59 | int err = pthread_create(&Handle, &Attr, ThreadStarter, Starter); 60 | pthread_attr_destroy(&Attr); 61 | if(err != 0){ 62 | error("StartThread: Kann Thread nicht anlegen; Fehlercode %d.\n", err); 63 | return INVALID_THREAD_HANDLE; 64 | } 65 | 66 | if(Detach){ 67 | pthread_detach(Handle); 68 | } 69 | 70 | return (ThreadHandle)Handle; 71 | } 72 | 73 | ThreadHandle StartThread(ThreadFunction *Function, void *Argument, void *Stack, size_t StackSize, bool Detach){ 74 | TThreadStarter *Starter = new TThreadStarter; 75 | Starter->Function = Function; 76 | Starter->Argument = Argument; 77 | Starter->Detach = Detach; 78 | 79 | pthread_t Handle; 80 | pthread_attr_t Attr; 81 | pthread_attr_init(&Attr); 82 | pthread_attr_setstack(&Attr, Stack, StackSize); 83 | int err = pthread_create(&Handle, &Attr, ThreadStarter, Starter); 84 | pthread_attr_destroy(&Attr); 85 | if(err != 0){ 86 | error("StartThread: Kann Thread nicht anlegen; Fehlercode %d.\n", err); 87 | return INVALID_THREAD_HANDLE; 88 | } 89 | 90 | if(Detach){ 91 | pthread_detach(Handle); 92 | } 93 | 94 | return (ThreadHandle)Handle; 95 | } 96 | 97 | int JoinThread(ThreadHandle Handle){ 98 | int Result = 0; 99 | int *ResultPointer; 100 | 101 | pthread_join((pthread_t)Handle, (void**)&ResultPointer); 102 | if(ResultPointer != NULL){ 103 | Result = *ResultPointer; 104 | delete ResultPointer; 105 | } 106 | 107 | return Result; 108 | } 109 | 110 | void DelayThread(int Seconds, int MicroSeconds){ 111 | if(Seconds == 0 && MicroSeconds == 0){ 112 | sched_yield(); 113 | }else if(MicroSeconds == 0){ 114 | sleep(Seconds); 115 | }else{ 116 | usleep(MicroSeconds + Seconds * 1000000); 117 | } 118 | } 119 | 120 | Semaphore::Semaphore(int Value){ 121 | this->value = Value; 122 | 123 | // TODO(fusion): These should probably be non-recoverable errors. 124 | 125 | if(pthread_mutex_init(&this->mutex, NULL) != 0){ 126 | error("Semaphore::Semaphore: Kann Mutex nicht einrichten.\n"); 127 | } 128 | 129 | if(pthread_cond_init(&this->condition, NULL) != 0){ 130 | error("Semaphore::Semaphore: Kann Wartebedingung nicht einrichten.\n"); 131 | } 132 | } 133 | 134 | Semaphore::~Semaphore(void){ 135 | // IMPORTANT(fusion): Due to how initialization is rolled out, `exit` may be 136 | // called after threads are spawned but before `ExitAll` is registered as an 137 | // exit handler. This means such threads may still be running or left global 138 | // semaphores in an inconsistent state if abruptly terminated. Either way, 139 | // they are still considered "in use". 140 | // In this case, calling `destroy` on either mutex or condition variable is 141 | // undefined behaviour as per the manual but the actual implementation would 142 | // fail on `mutex_destroy` with `EBUSY` and hang on `cond_destroy`. 143 | // The temporary solution is to check the result from `mutex_destroy` before 144 | // attempting to call `cond_destroy` to avoid hanging at exit. 145 | 146 | int ErrorCode; 147 | if((ErrorCode = pthread_mutex_destroy(&this->mutex)) != 0){ 148 | error("Semaphore::~Semaphore: Kann Mutex nicht freigeben: (%d) %s.\n", 149 | ErrorCode, strerrordesc_np(ErrorCode)); 150 | }else if((ErrorCode = pthread_cond_destroy(&this->condition)) != 0){ 151 | error("Semaphore::~Semaphore: Kann Wartebedingung nicht freigeben: (%d) %s.\n", 152 | ErrorCode, strerrordesc_np(ErrorCode)); 153 | } 154 | } 155 | 156 | void Semaphore::down(void){ 157 | pthread_mutex_lock(&this->mutex); 158 | while(this->value <= 0){ // TODO(fusion): Make sure this is always a load operation? 159 | pthread_cond_wait(&this->condition, &this->mutex); 160 | } 161 | this->value -= 1; 162 | pthread_mutex_unlock(&this->mutex); 163 | } 164 | 165 | void Semaphore::up(void){ 166 | pthread_mutex_lock(&this->mutex); 167 | this->value += 1; 168 | pthread_mutex_unlock(&this->mutex); 169 | pthread_cond_signal(&this->condition); 170 | } 171 | -------------------------------------------------------------------------------- /src/map.hh: -------------------------------------------------------------------------------- 1 | #ifndef TIBIA_MAP_HH_ 2 | #define TIBIA_MAP_HH_ 1 3 | 4 | #include "common.hh" 5 | #include "objects.hh" 6 | #include "script.hh" 7 | 8 | // NOTE(fusion): This is used by hash table entries and sectors to tell whether 9 | // they're currently loaded or swapped out to disk. 10 | enum : uint8 { 11 | STATUS_FREE = 0, 12 | STATUS_LOADED = 1, 13 | STATUS_SWAPPED = 2, 14 | 15 | // TODO(fusion): It seems this is only used with the `NONE` entry in the 16 | // hash table. I haven't seen it used **yet** but It may have a purpose 17 | // aside from preventing swap outs. 18 | STATUS_PERMANENT = 255, 19 | }; 20 | 21 | // NOTE(fusion): This is used to determine precedence order of different objects 22 | // in a map container. 23 | enum : int { 24 | PRIORITY_BANK = 0, 25 | PRIORITY_CLIP = 1, 26 | PRIORITY_BOTTOM = 2, 27 | PRIORITY_TOP = 3, 28 | PRIORITY_CREATURE = 4, 29 | PRIORITY_LOW = 5, 30 | }; 31 | 32 | struct Object { 33 | constexpr Object(void) : ObjectID(0) {} 34 | constexpr explicit Object(uint32 ObjectID): ObjectID(ObjectID) {} 35 | 36 | bool exists(void); 37 | ObjectType getObjectType(void); 38 | void setObjectType(ObjectType Type); 39 | Object getNextObject(void); 40 | void setNextObject(Object NextObject); 41 | Object getContainer(void); 42 | void setContainer(Object Con); 43 | uint32 getCreatureID(void); 44 | uint32 getAttribute(INSTANCEATTRIBUTE Attribute); 45 | void setAttribute(INSTANCEATTRIBUTE Attribute, uint32 Value); 46 | 47 | constexpr bool operator==(const Object &Other) const { 48 | return this->ObjectID == Other.ObjectID; 49 | } 50 | 51 | constexpr bool operator!=(const Object &Other) const { 52 | return this->ObjectID != Other.ObjectID; 53 | } 54 | 55 | // DATA 56 | // ================= 57 | uint32 ObjectID; 58 | }; 59 | 60 | constexpr Object NONE; 61 | 62 | struct TObject { 63 | uint32 ObjectID; 64 | Object NextObject; 65 | Object Container; 66 | ObjectType Type; 67 | uint32 Attributes[4]; 68 | }; 69 | 70 | struct TObjectBlock { 71 | TObject Object[32768]; 72 | }; 73 | 74 | struct TSector { 75 | Object MapCon[32][32]; 76 | uint32 TimeStamp; 77 | uint8 Status; 78 | uint8 MapFlags; 79 | }; 80 | 81 | struct TDepotInfo { 82 | char Town[20]; 83 | int Size; 84 | }; 85 | 86 | struct TMark { 87 | char Name[20]; 88 | int x; 89 | int y; 90 | int z; 91 | }; 92 | 93 | struct TCronEntry { 94 | Object Obj; 95 | uint32 RoundNr; 96 | int Previous; 97 | int Next; 98 | }; 99 | 100 | // NOTE(fusion): Map config values. 101 | extern int SectorXMin; 102 | extern int SectorXMax; 103 | extern int SectorYMin; 104 | extern int SectorYMax; 105 | extern int SectorZMin; 106 | extern int SectorZMax; 107 | extern int RefreshedCylinders; 108 | extern int NewbieStartPositionX; 109 | extern int NewbieStartPositionY; 110 | extern int NewbieStartPositionZ; 111 | extern int VeteranStartPositionX; 112 | extern int VeteranStartPositionY; 113 | extern int VeteranStartPositionZ; 114 | 115 | // NOTE(fusion): Cron management functions. Most for internal use. 116 | Object CronCheck(void); 117 | void CronExpire(Object Obj, int Delay); 118 | void CronChange(Object Obj, int NewDelay); 119 | uint32 CronInfo(Object Obj, bool Delete); 120 | uint32 CronStop(Object Obj); 121 | 122 | // NOTE(fusion): Map management functions. Most for internal use. 123 | void SwapObject(TWriteBinaryFile *File, Object Obj, uintptr FileNumber); 124 | void SwapSector(void); 125 | void UnswapSector(uintptr FileNumber); 126 | void DeleteSwappedSectors(void); 127 | void LoadObjects(TReadScriptFile *Script, TWriteStream *Stream, bool Skip); 128 | void LoadObjects(TReadStream *Stream, Object Con); 129 | void InitSector(int SectorX, int SectorY, int SectorZ); 130 | void LoadSector(const char *FileName, int SectorX, int SectorY, int SectorZ); 131 | void LoadMap(void); 132 | void SaveObjects(Object Obj, TWriteStream *Stream, bool Stop); 133 | void SaveObjects(TReadStream *Stream, TWriteScriptFile *Script); 134 | void SaveSector(char *FileName, int SectorX, int SectorY, int SectorZ); 135 | void SaveMap(void); 136 | void RefreshSector(int SectorX, int SectorY, int SectorZ, TReadStream *Stream); 137 | void PatchSector(int SectorX, int SectorY, int SectorZ, bool FullSector, 138 | TReadScriptFile *Script, bool SaveHouses); 139 | void InitMap(void); 140 | void ExitMap(bool Save); 141 | 142 | // NOTE(fusion): Object related functions. 143 | TObject *AccessObject(Object Obj); 144 | Object CreateObject(void); 145 | void DeleteObject(Object Obj); 146 | void ChangeObject(Object Obj, ObjectType NewType); 147 | void ChangeObject(Object Obj, INSTANCEATTRIBUTE Attribute, uint32 Value); 148 | int GetObjectPriority(Object Obj); 149 | void PlaceObject(Object Obj, Object Con, bool Append); 150 | void CutObject(Object Obj); 151 | void MoveObject(Object Obj, Object Con); 152 | Object AppendObject(Object Con, ObjectType Type); 153 | Object SetObject(Object Con, ObjectType Type, uint32 CreatureID); 154 | Object CopyObject(Object Con, Object Source); 155 | Object SplitObject(Object Obj, int Count); 156 | void MergeObjects(Object Obj, Object Dest); 157 | Object GetFirstContainerObject(Object Con); 158 | Object GetContainerObject(Object Con, int Index); 159 | Object GetMapContainer(int x, int y, int z); 160 | Object GetMapContainer(Object Obj); 161 | Object GetFirstObject(int x, int y, int z); 162 | Object GetFirstSpecObject(int x, int y, int z, ObjectType Type); 163 | uint8 GetMapContainerFlags(Object Obj); 164 | void GetObjectCoordinates(Object Obj, int *x, int *y, int *z); 165 | bool CoordinateFlag(int x, int y, int z, FLAG Flag); 166 | bool IsOnMap(int x, int y, int z); 167 | bool IsPremiumArea(int x, int y, int z); 168 | bool IsNoLogoutField(int x, int y, int z); 169 | bool IsProtectionZone(int x, int y, int z); 170 | bool IsHouse(int x, int y, int z); 171 | uint16 GetHouseID(int x, int y, int z); 172 | void SetHouseID(int x, int y, int z, uint16 ID); 173 | int GetDepotNumber(const char *Town); 174 | const char *GetDepotName(int DepotNumber); 175 | int GetDepotSize(int DepotNumber, bool PremiumAccount); 176 | bool GetMarkPosition(const char *Name, int *x, int *y, int *z); 177 | void GetStartPosition(int *x, int *y, int *z, bool Newbie); 178 | 179 | #endif //TIBIA_MAP_HH_ 180 | -------------------------------------------------------------------------------- /src/moveuse.hh: -------------------------------------------------------------------------------- 1 | #ifndef TIBIA_MOVEUSE_HH_ 2 | #define TIBIA_MOVEUSE_HH_ 1 3 | 4 | #include "common.hh" 5 | #include "containers.hh" 6 | #include "map.hh" 7 | #include "script.hh" 8 | 9 | #define MOVEUSE_MAX_PARAMETERS 5 10 | 11 | struct TPlayerData; 12 | 13 | enum MoveUseActionType: int { 14 | MOVEUSE_ACTION_CREATEONMAP = 0, 15 | MOVEUSE_ACTION_CREATE = 1, 16 | MOVEUSE_ACTION_MONSTERONMAP = 2, 17 | MOVEUSE_ACTION_MONSTER = 3, 18 | MOVEUSE_ACTION_EFFECTONMAP = 4, 19 | MOVEUSE_ACTION_EFFECT = 5, 20 | MOVEUSE_ACTION_TEXTONMAP = 6, 21 | MOVEUSE_ACTION_TEXT = 7, 22 | MOVEUSE_ACTION_CHANGEONMAP = 8, 23 | MOVEUSE_ACTION_CHANGE = 9, 24 | MOVEUSE_ACTION_CHANGEREL = 10, 25 | MOVEUSE_ACTION_SETATTRIBUTE = 11, 26 | MOVEUSE_ACTION_CHANGEATTRIBUTE = 12, 27 | MOVEUSE_ACTION_SETQUESTVALUE = 13, 28 | MOVEUSE_ACTION_DAMAGE = 14, 29 | MOVEUSE_ACTION_SETSTART = 15, 30 | MOVEUSE_ACTION_WRITENAME = 16, 31 | MOVEUSE_ACTION_WRITETEXT = 17, 32 | MOVEUSE_ACTION_LOGOUT = 18, 33 | MOVEUSE_ACTION_MOVEALLONMAP = 19, 34 | MOVEUSE_ACTION_MOVEALL = 20, 35 | MOVEUSE_ACTION_MOVEALLREL = 21, 36 | MOVEUSE_ACTION_MOVETOPONMAP = 22, 37 | MOVEUSE_ACTION_MOVETOP = 23, 38 | MOVEUSE_ACTION_MOVETOPREL = 24, 39 | MOVEUSE_ACTION_MOVE = 25, 40 | MOVEUSE_ACTION_MOVEREL = 26, 41 | MOVEUSE_ACTION_RETRIEVE = 27, 42 | MOVEUSE_ACTION_DELETEALLONMAP = 28, 43 | MOVEUSE_ACTION_DELETETOPONMAP = 29, 44 | MOVEUSE_ACTION_DELETEONMAP = 30, 45 | MOVEUSE_ACTION_DELETE = 31, 46 | MOVEUSE_ACTION_DELETEININVENTORY = 32, 47 | MOVEUSE_ACTION_DESCRIPTION = 33, 48 | MOVEUSE_ACTION_LOADDEPOT = 34, 49 | MOVEUSE_ACTION_SAVEDEPOT = 35, 50 | MOVEUSE_ACTION_SENDMAIL = 36, 51 | MOVEUSE_ACTION_NOP = 37, 52 | }; 53 | 54 | enum MoveUseConditionType: int { 55 | MOVEUSE_CONDITION_ISPOSITION = 0, 56 | MOVEUSE_CONDITION_ISTYPE = 1, 57 | MOVEUSE_CONDITION_ISCREATURE = 2, 58 | MOVEUSE_CONDITION_ISPLAYER = 3, 59 | MOVEUSE_CONDITION_HASFLAG = 4, 60 | MOVEUSE_CONDITION_HASTYPEATTRIBUTE = 5, 61 | MOVEUSE_CONDITION_HASINSTANCEATTRIBUTE = 6, 62 | MOVEUSE_CONDITION_HASTEXT = 7, 63 | MOVEUSE_CONDITION_ISPEACEFUL = 8, 64 | MOVEUSE_CONDITION_MAYLOGOUT = 9, 65 | MOVEUSE_CONDITION_HASPROFESSION = 10, 66 | MOVEUSE_CONDITION_HASLEVEL = 11, 67 | MOVEUSE_CONDITION_HASRIGHT = 12, 68 | MOVEUSE_CONDITION_HASQUESTVALUE = 13, 69 | MOVEUSE_CONDITION_TESTSKILL = 14, 70 | MOVEUSE_CONDITION_COUNTOBJECTS = 15, 71 | MOVEUSE_CONDITION_COUNTOBJECTSONMAP = 16, 72 | MOVEUSE_CONDITION_ISOBJECTTHERE = 17, 73 | MOVEUSE_CONDITION_ISCREATURETHERE = 18, 74 | MOVEUSE_CONDITION_ISPLAYERTHERE = 19, 75 | MOVEUSE_CONDITION_ISOBJECTININVENTORY = 20, 76 | MOVEUSE_CONDITION_ISPROTECTIONZONE = 21, 77 | MOVEUSE_CONDITION_ISHOUSE = 22, 78 | MOVEUSE_CONDITION_ISHOUSEOWNER = 23, 79 | MOVEUSE_CONDITION_ISDRESSED = 24, 80 | MOVEUSE_CONDITION_RANDOM = 25, 81 | }; 82 | 83 | enum MoveUseEventType: int { 84 | MOVEUSE_EVENT_USE = 0, 85 | MOVEUSE_EVENT_MULTIUSE = 1, 86 | MOVEUSE_EVENT_MOVEMENT = 2, 87 | MOVEUSE_EVENT_COLLISION = 3, 88 | MOVEUSE_EVENT_SEPARATION = 4, 89 | }; 90 | 91 | enum MoveUseModifierType: int { 92 | MOVEUSE_MODIFIER_NORMAL = 0, 93 | MOVEUSE_MODIFIER_INVERT = 1, 94 | MOVEUSE_MODIFIER_TRUE = 2, 95 | }; 96 | 97 | enum MoveUseParameterType: int { 98 | MOVEUSE_PARAMETER_OBJECT = 0, 99 | MOVEUSE_PARAMETER_TYPE = 1, 100 | MOVEUSE_PARAMETER_FLAG = 2, 101 | MOVEUSE_PARAMETER_TYPEATTRIBUTE = 3, 102 | MOVEUSE_PARAMETER_INSTANCEATTRIBUTE = 4, 103 | MOVEUSE_PARAMETER_COORDINATE = 5, 104 | MOVEUSE_PARAMETER_VECTOR = 6, 105 | MOVEUSE_PARAMETER_RIGHT = 7, 106 | MOVEUSE_PARAMETER_SKILL = 8, 107 | MOVEUSE_PARAMETER_NUMBER = 9, 108 | MOVEUSE_PARAMETER_TEXT = 10, 109 | MOVEUSE_PARAMETER_COMPARISON = 11, 110 | }; 111 | 112 | struct TMoveUseAction { 113 | MoveUseActionType Action; 114 | int Parameters[MOVEUSE_MAX_PARAMETERS]; 115 | }; 116 | 117 | struct TMoveUseRule { 118 | int FirstCondition; 119 | int LastCondition; 120 | int FirstAction; 121 | int LastAction; 122 | }; 123 | 124 | struct TMoveUseCondition { 125 | MoveUseModifierType Modifier; 126 | MoveUseConditionType Condition; 127 | int Parameters[MOVEUSE_MAX_PARAMETERS]; 128 | }; 129 | 130 | struct TMoveUseDatabase { 131 | TMoveUseDatabase(void) : Rules(1, 100, 100), NumberOfRules(0) {} 132 | 133 | vector Rules; 134 | int NumberOfRules; 135 | }; 136 | 137 | struct TDelayedMail { 138 | uint32 CharacterID; 139 | int DepotNumber; 140 | uint8 *Packet; 141 | int PacketSize; 142 | }; 143 | 144 | int PackAbsoluteCoordinate(int x, int y, int z); 145 | void UnpackAbsoluteCoordinate(int Packed, int *x, int *y, int *z); 146 | int PackRelativeCoordinate(int x, int y, int z); 147 | void UnpackRelativeCoordinate(int Packed, int *x, int *y, int *z); 148 | 149 | Object GetEventObject(int Nr, Object User, Object Obj1, Object Obj2, Object Temp); 150 | bool Compare(int Value1, int Operator, int Value2); 151 | bool CheckCondition(MoveUseEventType EventType, TMoveUseCondition *Condition, 152 | Object User, Object Obj1, Object Obj2, Object *Temp); 153 | Object CreateObject(Object Con, ObjectType Type, uint32 Value); 154 | void ChangeObject(Object Obj, ObjectType NewType, uint32 Value); 155 | void MoveOneObject(Object Obj, Object Con); 156 | void MoveAllObjects(Object Obj, Object Dest, Object Exclude, bool MoveUnmovable); 157 | void DeleteAllObjects(Object Obj, Object Exclude, bool DeleteUnmovable); 158 | void ClearField(Object Obj, Object Exclude); 159 | void LoadDepotBox(uint32 CreatureID, int Nr, Object Con); 160 | void SaveDepotBox(uint32 CreatureID, int Nr, Object Con); 161 | void SendMail(Object Obj); 162 | void SendMails(TPlayerData *PlayerData); 163 | void TextEffect(const char *Text, int x, int y, int z, int Radius); 164 | void ExecuteAction(MoveUseEventType EventType, TMoveUseAction *Action, 165 | Object User, Object Obj1, Object Obj2, Object *Temp); 166 | bool HandleEvent(MoveUseEventType EventType, Object User, Object Obj1, Object Obj2); 167 | 168 | void UseContainer(uint32 CreatureID, Object Con, int NextContainerNr); 169 | void UseChest(uint32 CreatureID, Object Chest); 170 | void UseLiquidContainer(uint32 CreatureID, Object Obj, Object Dest); 171 | void UseFood(uint32 CreatureID, Object Obj); 172 | void UseTextObject(uint32 CreatureID, Object Obj); 173 | void UseAnnouncer(uint32 CreatureID, Object Obj); 174 | void UseKeyDoor(uint32 CreatureID, Object Key, Object Door); 175 | void UseNameDoor(uint32 CreatureID, Object Door); 176 | void UseLevelDoor(uint32 CreatureID, Object Door); 177 | void UseQuestDoor(uint32 CreatureID, Object Door); 178 | void UseWeapon(uint32 CreatureID, Object Weapon, Object Target); 179 | void UseChangeObject(uint32 CreatureID, Object Obj); 180 | void UseObject(uint32 CreatureID, Object Obj); 181 | void UseObjects(uint32 CreatureID, Object Obj1, Object Obj2); 182 | void MovementEvent(Object Obj, Object Start, Object Dest); 183 | void SeparationEvent(Object Obj, Object Start); 184 | void CollisionEvent(Object Obj, Object Dest); 185 | 186 | void LoadParameters(TReadScriptFile *Script, int *Parameters, int NumberOfParameters, ...); 187 | void LoadCondition(TReadScriptFile *Script, TMoveUseCondition *Condition); 188 | void LoadAction(TReadScriptFile *Script, TMoveUseAction *Action); 189 | void LoadDataBase(void); 190 | 191 | 192 | void InitMoveUse(void); 193 | void ExitMoveUse(void); 194 | 195 | #endif //TIBIA_MOVEUSE_HH_ 196 | -------------------------------------------------------------------------------- /src/operate.hh: -------------------------------------------------------------------------------- 1 | #ifndef TIBIA_OPERATE_HH_ 2 | #define TIBIA_OPERATE_HH_ 1 3 | 4 | #include "common.hh" 5 | #include "containers.hh" 6 | #include "cr.hh" 7 | #include "map.hh" 8 | 9 | enum : int { 10 | CREATURE_HEALTH_CHANGED = 1, 11 | CREATURE_LIGHT_CHANGED = 2, 12 | CREATURE_OUTFIT_CHANGED = 3, 13 | CREATURE_SPEED_CHANGED = 4, 14 | CREATURE_SKULL_CHANGED = 5, 15 | CREATURE_PARTY_CHANGED = 6, 16 | }; 17 | 18 | enum : int { 19 | OBJECT_DELETED = 0, 20 | OBJECT_CREATED = 1, 21 | OBJECT_CHANGED = 2, 22 | OBJECT_MOVED = 3, 23 | }; 24 | 25 | enum : int { 26 | CHANNEL_GUILD = 0, 27 | CHANNEL_GAMEMASTER = 1, 28 | CHANNEL_TUTOR = 2, 29 | CHANNEL_RULEVIOLATIONS = 3, 30 | CHANNEL_GAMECHAT = 4, 31 | CHANNEL_TRADE = 5, 32 | CHANNEL_RLCHAT = 6, 33 | CHANNEL_HELP = 7, 34 | CHANNEL_PRIVATE = 8, 35 | PUBLIC_CHANNELS = 8, 36 | MAX_CHANNELS = 0xFFFF, 37 | }; 38 | 39 | struct TChannel { 40 | TChannel(void); 41 | 42 | // TODO(fusion): `TChannel` will primarily live inside the `Channel` vector, 43 | // which needs to resize its internal array as needed. To resize, it needs 44 | // to copy/swap elements, which would be impossible since `TChannel` itself 45 | // owns a few vectors which were made NON-COPYABLE to prevent memory bugs. 46 | // To fix this problem and allow `TChannel` to be copyable, we need to 47 | // implement the copy constructor and assignment manually. They're annoying 48 | // and expensive but should allow everything to compile again. 49 | // This problem arises from the fact that our unconventional `vector` type 50 | // has a weird interface but still manages its underlying memory. If we are 51 | // to resolve this completely, we'd need to re-implement `vector` properly 52 | // and preferably with move semantics (if we want to follow the C++ route, 53 | // which we may not). 54 | TChannel(const TChannel &Other); 55 | void operator=(const TChannel &Other); 56 | 57 | // DATA 58 | // ================= 59 | uint32 Moderator; 60 | char ModeratorName[30]; 61 | vector Subscriber; 62 | int Subscribers; 63 | vector InvitedPlayer; 64 | int InvitedPlayers; 65 | }; 66 | 67 | struct TParty { 68 | TParty(void); 69 | 70 | // TODO(fusion): Same as `TChannel`. 71 | TParty(const TParty &Other); 72 | void operator=(const TParty &Other); 73 | 74 | // DATA 75 | // ================= 76 | uint32 Leader; 77 | vector Member; 78 | int Members; 79 | vector InvitedPlayer; 80 | int InvitedPlayers; 81 | }; 82 | 83 | struct TStatement { 84 | uint32 StatementID; 85 | int TimeStamp; 86 | uint32 CharacterID; 87 | int Mode; 88 | int Channel; 89 | uint32 Text; 90 | bool Reported; 91 | }; 92 | 93 | struct TListener { 94 | uint32 StatementID; 95 | uint32 CharacterID; 96 | }; 97 | 98 | struct TReportedStatement { 99 | uint32 StatementID; 100 | int TimeStamp; 101 | uint32 CharacterID; 102 | int Mode; 103 | int Channel; 104 | char Text[256]; 105 | }; 106 | 107 | void AnnounceMovingCreature(uint32 CreatureID, Object Con); 108 | void AnnounceChangedCreature(uint32 CreatureID, int Type); 109 | void AnnounceChangedField(Object Obj, int Type); 110 | void AnnounceChangedContainer(Object Obj, int Type); 111 | void AnnounceChangedInventory(Object Obj, int Type); 112 | void AnnounceChangedObject(Object Obj, int Type); 113 | void AnnounceGraphicalEffect(int x, int y, int z, int Type); 114 | void AnnounceTextualEffect(int x, int y, int z, int Color, const char *Text); 115 | void AnnounceMissile(int OrigX, int OrigY, int OrigZ, 116 | int DestX, int DestY, int DestZ, int Type); 117 | void CheckTopMoveObject(uint32 CreatureID, Object Obj, Object Ignore); 118 | void CheckTopUseObject(uint32 CreatureID, Object Obj); 119 | void CheckTopMultiuseObject(uint32 CreatureID, Object Obj); 120 | void CheckMoveObject(uint32 CreatureID, Object Obj, bool Take); 121 | void CheckMapDestination(uint32 CreatureID, Object Obj, Object MapCon); 122 | void CheckMapPlace(uint32 CreatureID, ObjectType Type, Object MapCon); 123 | void CheckContainerDestination(Object Obj, Object Con); 124 | void CheckContainerPlace(ObjectType Type, Object Con, Object OldObj); 125 | void CheckDepotSpace(uint32 CreatureID, Object Source, Object Destination, int Count); 126 | void CheckInventoryDestination(Object Obj, Object Con, bool Split); 127 | void CheckInventoryPlace(ObjectType Type, Object Con, Object OldObj); 128 | void CheckWeight(uint32 CreatureID, Object Obj, int Count); 129 | void CheckWeight(uint32 CreatureID, ObjectType Type, uint32 Value, int OldWeight); 130 | void NotifyCreature(uint32 CreatureID, Object Obj, bool Inventory); 131 | void NotifyCreature(uint32 CreatureID, ObjectType Type, bool Inventory); 132 | void NotifyAllCreatures(Object Obj, int Type, Object OldCon); 133 | void NotifyTrades(Object Obj); 134 | void NotifyDepot(uint32 CreatureID, Object Obj, int Count); 135 | void CloseContainer(Object Con, bool Force); 136 | Object Create(Object Con, ObjectType Type, uint32 Value); 137 | Object Copy(Object Con, Object Source); 138 | void Move(uint32 CreatureID, Object Obj, Object Con, int Count, bool NoMerge, Object Ignore); 139 | void Merge(uint32 CreatureID, Object Obj, Object Dest, int Count, Object Ignore); 140 | void Change(Object Obj, ObjectType NewType, uint32 Value); 141 | void Change(Object Obj, INSTANCEATTRIBUTE Attribute, uint32 Value); 142 | void Delete(Object Obj, int Count); 143 | void Empty(Object Con, int Remainder); 144 | void GraphicalEffect(int x, int y, int z, int Type); 145 | void GraphicalEffect(Object Obj, int Type); 146 | void TextualEffect(Object Obj, int Color, const char *Format, ...) ATTR_PRINTF(3, 4); 147 | void Missile(Object Start, Object Dest, int Type); 148 | void Look(uint32 CreatureID, Object Obj); 149 | void Talk(uint32 CreatureID, int Mode, const char *Addressee, const char *Text, bool CheckSpamming); 150 | void Use(uint32 CreatureID, Object Obj1, Object Obj2, uint8 Info); 151 | void Turn(uint32 CreatureID, Object Obj); 152 | void CreatePool(Object Con, ObjectType Type, uint32 Value); 153 | void EditText(uint32 CreatureID, Object Obj, const char *Text); 154 | Object CreateAtCreature(uint32 CreatureID, ObjectType Type, uint32 Value); 155 | void DeleteAtCreature(uint32 CreatureID, ObjectType Type, int Amount, uint32 Value); 156 | 157 | void ProcessCronSystem(void); 158 | bool SectorRefreshable(int SectorX, int SectorY, int SectorZ); 159 | void RefreshSector(int SectorX, int SectorY, int SectorZ, const uint8 *Data, int Count); 160 | void RefreshMap(void); 161 | void RefreshCylinders(void); 162 | void ApplyPatch(int SectorX, int SectorY, int SectorZ, 163 | bool FullSector, TReadScriptFile *Script, bool SaveHouses); 164 | void ApplyPatches(void); 165 | 166 | uint32 LogCommunication(uint32 CreatureID, int Mode, int Channel, const char *Text); 167 | uint32 LogListener(uint32 StatementID, TPlayer *Player); 168 | void ProcessCommunicationControl(void); 169 | int GetCommunicationContext(uint32 CharacterID, uint32 StatementID, 170 | int *NumberOfStatements, vector **ReportedStatements); 171 | 172 | int GetNumberOfChannels(void); 173 | bool ChannelActive(int ChannelID); 174 | bool ChannelAvailable(int ChannelID, uint32 CharacterID); 175 | const char *GetChannelName(int ChannelID, uint32 CharacterID); 176 | bool ChannelSubscribed(int ChannelID, uint32 CharacterID); 177 | uint32 GetFirstSubscriber(int ChannelID); 178 | uint32 GetNextSubscriber(void); 179 | bool MayOpenChannel(uint32 CharacterID); 180 | void OpenChannel(uint32 CharacterID); 181 | void CloseChannel(int ChannelID); 182 | void InviteToChannel(uint32 CharacterID, const char *Name); 183 | void ExcludeFromChannel(uint32 CharacterID, const char *Name); 184 | bool JoinChannel(int ChannelID, uint32 CharacterID); 185 | void LeaveChannel(int ChannelID, uint32 CharacterID, bool Close); 186 | void LeaveAllChannels(uint32 CharacterID); 187 | 188 | TParty *GetParty(uint32 LeaderID); 189 | bool IsInvitedToParty(uint32 GuestID, uint32 HostID); 190 | void DisbandParty(uint32 LeaderID); 191 | void InviteToParty(uint32 HostID, uint32 GuestID); 192 | void RevokeInvitation(uint32 HostID, uint32 GuestID); 193 | void JoinParty(uint32 GuestID, uint32 HostID); 194 | void PassLeadership(uint32 OldLeaderID, uint32 NewLeaderID); 195 | void LeaveParty(uint32 MemberID, bool Forced); 196 | 197 | #endif //TIBIA_OPERATE_HH_ 198 | -------------------------------------------------------------------------------- /src/reader.cc: -------------------------------------------------------------------------------- 1 | #include "reader.hh" 2 | #include "config.hh" 3 | #include "cr.hh" 4 | #include "map.hh" 5 | #include "threads.hh" 6 | 7 | static ThreadHandle ReaderThread; 8 | 9 | static TReaderThreadOrder OrderBuffer[200]; 10 | static int OrderPointerWrite; 11 | static int OrderPointerRead; 12 | static Semaphore OrderBufferEmpty(NARRAY(OrderBuffer)); 13 | static Semaphore OrderBufferFull(0); 14 | 15 | static TReaderThreadReply ReplyBuffer[200]; 16 | static int ReplyPointerWrite; 17 | static int ReplyPointerRead; 18 | 19 | static TDynamicWriteBuffer HelpBuffer(KB(64)); 20 | 21 | // Reader Orders 22 | // ============================================================================= 23 | void InitReaderBuffers(void){ 24 | OrderPointerWrite = 0; 25 | OrderPointerRead = 0; 26 | ReplyPointerWrite = 0; 27 | ReplyPointerRead = 0; 28 | } 29 | 30 | void InsertOrder(TReaderThreadOrderType OrderType, 31 | int SectorX, int SectorY, int SectorZ, uint32 CharacterID){ 32 | int Orders = (OrderPointerWrite - OrderPointerRead); 33 | if(Orders >= NARRAY(OrderBuffer)){ 34 | error("InsertOrder (Reader): Order-Puffer ist voll => Vergrößern.\n"); 35 | } 36 | 37 | OrderBufferEmpty.down(); 38 | int WritePos = OrderPointerWrite % NARRAY(OrderBuffer); 39 | OrderBuffer[WritePos].OrderType = OrderType; 40 | OrderBuffer[WritePos].SectorX = SectorX; 41 | OrderBuffer[WritePos].SectorY = SectorY; 42 | OrderBuffer[WritePos].SectorZ = SectorZ; 43 | OrderBuffer[WritePos].CharacterID = CharacterID; 44 | OrderPointerWrite += 1; 45 | OrderBufferFull.up(); 46 | } 47 | 48 | void GetOrder(TReaderThreadOrder *Order){ 49 | OrderBufferFull.down(); 50 | *Order = OrderBuffer[OrderPointerRead % NARRAY(OrderBuffer)]; 51 | OrderPointerRead += 1; 52 | OrderBufferEmpty.up(); 53 | } 54 | 55 | void TerminateReaderOrder(void){ 56 | InsertOrder(READER_ORDER_TERMINATE, 0, 0, 0, 0); 57 | } 58 | 59 | void LoadSectorOrder(int SectorX, int SectorY, int SectorZ){ 60 | InsertOrder(READER_ORDER_LOADSECTOR, SectorX, SectorY, SectorZ, 0); 61 | } 62 | 63 | void LoadCharacterOrder(uint32 CharacterID){ 64 | InsertOrder(READER_ORDER_LOADCHARACTER, 0, 0, 0, CharacterID); 65 | } 66 | 67 | void ProcessLoadSectorOrder(int SectorX, int SectorY, int SectorZ){ 68 | // TODO(fusion): We parsed sector files way too many times now. And there 69 | // is also a drop in loader quality. 70 | char FileName[4096]; 71 | snprintf(FileName, sizeof(FileName), "%s/%04d-%04d-%02d.sec", 72 | ORIGMAPPATH, SectorX, SectorY, SectorZ); 73 | if(!FileExists(FileName)){ 74 | return; 75 | } 76 | 77 | int OffsetX = -1; 78 | int OffsetY = -1; 79 | bool Refreshable = false; 80 | HelpBuffer.Position = 0; 81 | 82 | TReadScriptFile Script; 83 | Script.open(FileName); 84 | while(true){ 85 | Script.nextToken(); 86 | if(Script.Token == ENDOFFILE){ 87 | Script.close(); 88 | break; 89 | } 90 | 91 | if(Script.Token == SPECIAL && Script.getSpecial() == ','){ 92 | continue; 93 | } 94 | 95 | if(Script.Token == BYTES){ 96 | uint8 *Offset = Script.getBytesequence(); 97 | OffsetX = (int)Offset[0]; 98 | OffsetY = (int)Offset[1]; 99 | Refreshable = false; 100 | Script.readSymbol(':'); 101 | }else if(Script.Token == IDENTIFIER){ 102 | if(OffsetX == -1 || OffsetY == -1){ 103 | Script.error("coordinate expected"); 104 | } 105 | 106 | const char *Identifier = Script.getIdentifier(); 107 | if(strcmp(Identifier, "refresh") == 0){ 108 | Refreshable = true; 109 | }else if(strcmp(Identifier, "content") == 0){ 110 | Script.readSymbol('='); 111 | 112 | if(Refreshable){ 113 | HelpBuffer.writeByte((uint8)OffsetX); 114 | HelpBuffer.writeByte((uint8)OffsetY); 115 | } 116 | 117 | LoadObjects(&Script, &HelpBuffer, !Refreshable); 118 | } 119 | } 120 | } 121 | 122 | int Size = HelpBuffer.Position; 123 | if(Size > 0){ 124 | uint8 *Data = new uint8[Size]; 125 | memcpy(Data, HelpBuffer.Data, Size); 126 | SectorReply(SectorX, SectorY, SectorZ, Data, Size); 127 | } 128 | } 129 | 130 | void ProcessLoadCharacterOrder(uint32 CharacterID){ 131 | while(true){ 132 | TPlayerData *Slot = AssignPlayerPoolSlot(CharacterID, true); 133 | if(Slot == NULL){ 134 | error("ProcessLoadCharacterOrder: Kann keinen Slot für Spielerdaten zuweisen.\n"); 135 | break; 136 | } 137 | 138 | if(Slot->Locked == GetGameThreadID()){ 139 | break; 140 | } 141 | 142 | if(Slot->Locked == gettid()){ 143 | IncreasePlayerPoolSlotSticky(Slot); 144 | ReleasePlayerPoolSlot(Slot); 145 | CharacterReply(CharacterID); 146 | break; 147 | } 148 | 149 | DelayThread(1, 0); 150 | } 151 | } 152 | 153 | int ReaderThreadLoop(void *Unused){ 154 | TReaderThreadOrder Order = {}; 155 | while(true){ 156 | GetOrder(&Order); 157 | if(Order.OrderType == READER_ORDER_TERMINATE){ 158 | break; 159 | } 160 | 161 | switch(Order.OrderType){ 162 | case READER_ORDER_LOADSECTOR:{ 163 | ProcessLoadSectorOrder(Order.SectorX, Order.SectorY, Order.SectorZ); 164 | break; 165 | } 166 | 167 | case READER_ORDER_LOADCHARACTER:{ 168 | ProcessLoadCharacterOrder(Order.CharacterID); 169 | break; 170 | } 171 | 172 | default:{ 173 | error("ReaderThreadLoop: Unbekanntes Kommando %d.\n", Order.OrderType); 174 | break; 175 | } 176 | } 177 | } 178 | 179 | return 0; 180 | } 181 | 182 | // Reader Replies 183 | // ============================================================================= 184 | void InsertReply(TReaderThreadReplyType ReplyType, 185 | int SectorX, int SectorY, int SectorZ, uint8 *Data, int Size){ 186 | int Replies = (ReplyPointerWrite - ReplyPointerRead); 187 | while(Replies > NARRAY(ReplyBuffer)){ 188 | error("InsertReply (Reader): Puffer ist voll; warte...\n"); 189 | DelayThread(5, 0); 190 | } 191 | 192 | int WritePos = ReplyPointerWrite % NARRAY(ReplyBuffer); 193 | ReplyBuffer[WritePos].ReplyType = ReplyType; 194 | ReplyBuffer[WritePos].SectorX = SectorX; 195 | ReplyBuffer[WritePos].SectorY = SectorY; 196 | ReplyBuffer[WritePos].SectorZ = SectorZ; 197 | ReplyBuffer[WritePos].Data = Data; 198 | ReplyBuffer[WritePos].Size = Size; 199 | ReplyPointerWrite += 1; 200 | } 201 | 202 | bool GetReply(TReaderThreadReply *Reply){ 203 | bool Result = (ReplyPointerRead < ReplyPointerWrite); 204 | if(Result){ 205 | *Reply = ReplyBuffer[ReplyPointerRead % NARRAY(ReplyBuffer)]; 206 | ReplyPointerRead += 1; 207 | } 208 | return Result; 209 | } 210 | 211 | void SectorReply(int SectorX, int SectorY, int SectorZ, uint8 *Data, int Size){ 212 | InsertReply(READER_REPLY_SECTORDATA, SectorX, SectorY, SectorZ, Data, Size); 213 | } 214 | 215 | void CharacterReply(uint32 CharacterID){ 216 | InsertReply(READER_REPLY_CHARACTERDATA, 0, 0, 0, NULL, (int)CharacterID); 217 | } 218 | 219 | void ProcessSectorReply(TRefreshSectorFunction *RefreshSector, 220 | int SectorX, int SectorY, int SectorZ, uint8 *Data, int Size){ 221 | RefreshSector(SectorX, SectorY, SectorZ, Data, Size); 222 | delete[] Data; 223 | } 224 | 225 | void ProcessCharacterReply(TSendMailsFunction *SendMails, uint32 CharacterID){ 226 | TPlayerData *Slot = AttachPlayerPoolSlot(CharacterID, true); 227 | if(Slot == NULL){ 228 | DecreasePlayerPoolSlotSticky(Slot); 229 | return; 230 | } 231 | 232 | SendMails(Slot); 233 | DecreasePlayerPoolSlotSticky(Slot); 234 | ReleasePlayerPoolSlot(Slot); 235 | } 236 | 237 | void ProcessReaderThreadReplies(TRefreshSectorFunction *RefreshSector, TSendMailsFunction *SendMails){ 238 | TReaderThreadReply Reply = {}; 239 | while(GetReply(&Reply)){ 240 | switch(Reply.ReplyType){ 241 | case READER_REPLY_SECTORDATA:{ 242 | ProcessSectorReply(RefreshSector, 243 | Reply.SectorX, Reply.SectorY, Reply.SectorZ, 244 | Reply.Data, Reply.Size); 245 | break; 246 | } 247 | 248 | case READER_REPLY_CHARACTERDATA:{ 249 | ProcessCharacterReply(SendMails, (uint32)Reply.Size); 250 | break; 251 | } 252 | 253 | default:{ 254 | error("ProcessReaderThreadReplies: Unbekannte Rückmeldung %d.\n", Reply.ReplyType); 255 | break; 256 | } 257 | } 258 | } 259 | } 260 | 261 | // Initialization 262 | // ============================================================================= 263 | void InitReader(void){ 264 | InitReaderBuffers(); 265 | ReaderThread = StartThread(ReaderThreadLoop, NULL, false); 266 | if(ReaderThread == INVALID_THREAD_HANDLE){ 267 | throw "cannot start reader thread"; 268 | } 269 | } 270 | 271 | void ExitReader(void){ 272 | if(ReaderThread != INVALID_THREAD_HANDLE){ 273 | TerminateReaderOrder(); 274 | JoinThread(ReaderThread); 275 | ReaderThread = INVALID_THREAD_HANDLE; 276 | } 277 | } 278 | -------------------------------------------------------------------------------- /src/query.hh: -------------------------------------------------------------------------------- 1 | #ifndef TIBIA_QUERY_HH_ 2 | #define TIBIA_QUERY_HH_ 1 3 | 4 | #include "common.hh" 5 | #include "threads.hh" 6 | 7 | enum : int { 8 | QUERY_STATUS_OK = 0, 9 | QUERY_STATUS_ERROR = 1, 10 | QUERY_STATUS_FAILED = 3, 11 | }; 12 | 13 | struct TQueryManagerConnection{ 14 | TQueryManagerConnection(void) : TQueryManagerConnection(KB(16)) {} 15 | TQueryManagerConnection(int QueryBufferSize); 16 | ~TQueryManagerConnection(void); 17 | 18 | void connect(void); 19 | void disconnect(void); 20 | int write(const uint8 *Buffer, int Size); 21 | int read(uint8 *Buffer, int Size, int Timeout); 22 | bool isConnected(void){ 23 | return this->Socket != -1; 24 | } 25 | 26 | void prepareQuery(int QueryType); 27 | void sendFlag(bool Flag); 28 | void sendByte(uint8 Byte); 29 | void sendWord(uint16 Word); 30 | void sendQuad(uint32 Quad); 31 | void sendString(const char *String); 32 | void sendBytes(const uint8 *Buffer, int Count); 33 | 34 | bool getFlag(void); 35 | uint8 getByte(void); 36 | uint16 getWord(void); 37 | uint32 getQuad(void); 38 | void getString(char *Buffer, int MaxLength); 39 | void getBytes(uint8 *Buffer, int Count); 40 | 41 | int executeQuery(int Timeout, bool AutoReconnect); 42 | 43 | int checkAccountPassword(uint32 AccountID, const char *Password, const char *IPAddress); 44 | int loginAdmin(uint32 AccountID, bool PrivateWorld, int *NumberOfCharacters, 45 | char (*Characters)[30], char (*Worlds)[30], uint8 (*IPAddresses)[4], 46 | uint16 *Ports, uint16 *PremiumDaysLeft); 47 | int loadWorldConfig(int *WorldType, int *RebootTime, int *IPAddress, 48 | int *Port, int *MaxPlayers, int *PremiumPlayerBuffer, int *MaxNewbies, 49 | int *PremiumNewbieBuffer); 50 | int loginGame(uint32 AccountID, char *PlayerName, const char *Password, 51 | const char *IPAddress, bool PrivateWorld, bool PremiumAccountRequired, 52 | bool GamemasterRequired, uint32 *CharacterID, int *Sex, char *Guild, 53 | char *Rank, char *Title, int *NumberOfBuddies, uint32 *BuddyIDs, 54 | char (*BuddyNames)[30], uint8 *Rights, bool *PremiumAccountActivated); 55 | int logoutGame(uint32 CharacterID, int Level, const char *Profession, 56 | const char *Residence, time_t LastLoginTime, int TutorActivities); 57 | int setNotation(uint32 GamemasterID, const char *PlayerName, const char *IPAddress, 58 | const char *Reason, const char *Comment, uint32 *BanishmentID); 59 | int setNamelock(uint32 GamemasterID, const char *PlayerName, const char *IPAddress, 60 | const char *Reason, const char *Comment); 61 | int banishAccount(uint32 GamemasterID, const char *PlayerName, const char *IPAddress, 62 | const char *Reason, const char *Comment, bool *FinalWarning, int *Days, 63 | uint32 *BanishmentID); 64 | int reportStatement(uint32 ReporterID, const char *PlayerName, const char *Reason, 65 | const char *Comment, uint32 BanishmentID, uint32 StatementID, 66 | int NumberOfStatements, uint32 *StatementIDs, int *TimeStamps, 67 | uint32 *CharacterIDs, const char (*Channels)[30], const char (*Texts)[256]); 68 | int banishIPAddress(uint32 GamemasterID, const char *PlayerName, const char *IPAddress, 69 | const char *Reason, const char *Comment); 70 | int logCharacterDeath(uint32 CharacterID, int Level, uint32 Offender, 71 | const char *Remark, bool Unjustified, time_t Time); 72 | int addBuddy(uint32 AccountID, uint32 Buddy); 73 | int removeBuddy(uint32 AccountID, uint32 Buddy); 74 | int decrementIsOnline(uint32 CharacterID); 75 | int finishAuctions(int *NumberOfAuctions, uint16 *HouseIDs, 76 | uint32 *CharacterIDs, char (*CharacterNames)[30], int *Bids); 77 | int excludeFromAuctions(uint32 CharacterID, bool Banish); 78 | int transferHouses(int *NumberOfTransfers, uint16 *HouseIDs, 79 | uint32 *NewOwnerIDs, char (*NewOwnerNames)[30], int *Prices); 80 | int cancelHouseTransfer(uint16 HouseID); 81 | int evictFreeAccounts(int *NumberOfEvictions, uint16 *HouseIDs, uint32 *OwnerIDs); 82 | int evictDeletedCharacters(int *NumberOfEvictions, uint16 *HouseIDs); 83 | int evictExGuildleaders(int NumberOfGuildhouses, 84 | int *NumberOfEvictions, uint16 *HouseIDs, uint32 *Guildleaders); 85 | int insertHouseOwner(uint16 HouseID, uint32 OwnerID, int PaidUntil); 86 | int updateHouseOwner(uint16 HouseID, uint32 OwnerID, int PaidUntil); 87 | int deleteHouseOwner(uint16 HouseID); 88 | int getHouseOwners(int *NumberOfOwners, uint16 *HouseIDs, 89 | uint32 *OwnerIDs, char (*OwnerNames)[30], int *PaidUntils); 90 | int getAuctions(int *NumberOfAuctions, uint16 *HouseIDs); 91 | int startAuction(uint16 HouseID); 92 | int insertHouses(int NumberOfHouses, uint16 *HouseIDs, 93 | const char **Names, int *Rents, const char **Descriptions, 94 | int *Sizes, int *PositionsX,int *PositionsY,int *PositionsZ, 95 | char (*Towns)[30], bool *Guildhouses); 96 | int clearIsOnline(int *NumberOfAffectedPlayers); 97 | int createPlayerlist(int NumberOfPlayers, const char **Names, 98 | int *Levels, const char (*Professions)[30], bool *NewRecord); 99 | int logKilledCreatures(int NumberOfRaces, const char **Names, 100 | int *KilledPlayers,int *KilledCreatures); 101 | int loadPlayers(uint32 MinimumCharacterID, int *NumberOfPlayers, 102 | char (*Names)[30], uint32 *CharacterIDs); 103 | int getKeptCharacters(uint32 MinimumCharacterID, 104 | int *NumberOfPlayers, uint32 *CharacterIDs); 105 | int getDeletedCharacters(uint32 MinimumCharacterID, 106 | int *NumberOfPlayers, uint32 *CharacterIDs); 107 | int deleteOldCharacter(uint32 CharacterID); 108 | int getHiddenCharacters(uint32 MinimumCharacterID, 109 | int *NumberOfPlayers, uint32 *CharacterIDs); 110 | int createHighscores(int NumberOfPlayers, uint32 *CharacterIDs, 111 | int *ExpPoints, int *ExpLevel, int *Fist, int *Club, int *Axe, 112 | int *Sword, int *Distance, int *Shielding, int *Magic, int *Fishing); 113 | int createCensus(void); 114 | int createKillStatistics(void); 115 | int getPlayersOnline(int *NumberOfWorlds, char (*Names)[30], uint16 *Players); 116 | int getWorlds(int *NumberOfWorlds, char (*Names)[30]); 117 | int getServerLoad(const char *World, int Period, int *Data); 118 | 119 | int insertPaymentDataOld(uint32 PurchaseNr, uint32 ReferenceNr, 120 | const char *FirstName, const char *LastName, const char *Company, 121 | const char *Street, const char *Zip, const char *City, const char *Country, 122 | const char *State, const char *Phone, const char *Fax, const char *EMail, 123 | const char *PaymentMethod, uint32 ProductID, const char *Registrant, 124 | uint32 AccountID, uint32 *PaymentID); 125 | int addPaymentOld(uint32 AccountID, const char *Description, 126 | uint32 PaymentID, int Days, int *ActionTaken); 127 | int cancelPaymentOld(uint32 PurchaseNr, uint32 ReferenceNr, 128 | uint32 AccountID, bool *IllegalUse, char *EMailAddress); 129 | 130 | int insertPaymentDataNew(uint32 PurchaseNr, uint32 ReferenceNr, 131 | const char *FirstName, const char *LastName, const char *Company, 132 | const char *Street, const char *Zip, const char *City, const char *Country, 133 | const char *State, const char *Phone, const char *Fax, const char *EMail, 134 | const char *PaymentMethod, uint32 ProductID, const char *Registrant, 135 | const char *PaymentKey, uint32 *PaymentID); 136 | int addPaymentNew(const char *PaymentKey, uint32 PaymentID, 137 | int *ActionTaken, char *EMailReceiver); 138 | int cancelPaymentNew(uint32 PurchaseNr, uint32 ReferenceNr, 139 | const char *PaymentKey, bool *IllegalUse, bool *Present, 140 | char *EMailAddress); 141 | 142 | // DATA 143 | // ================= 144 | int BufferSize; 145 | uint8 *Buffer; 146 | TReadBuffer ReadBuffer; 147 | TWriteBuffer WriteBuffer; 148 | int Socket; 149 | bool QueryOk; 150 | }; 151 | 152 | struct TQueryManagerConnectionPool{ 153 | TQueryManagerConnectionPool(int Connections); 154 | void init(void); 155 | void exit(void); 156 | TQueryManagerConnection *getConnection(void); 157 | void releaseConnection(TQueryManagerConnection *Connection); 158 | 159 | // DATA 160 | // ================= 161 | int NumberOfConnections; 162 | TQueryManagerConnection *QueryManagerConnection; 163 | bool *QueryManagerConnectionFree; 164 | Semaphore FreeQueryManagerConnections; 165 | Semaphore QueryManagerConnectionMutex; 166 | }; 167 | 168 | struct TQueryManagerPoolConnection{ 169 | TQueryManagerPoolConnection(TQueryManagerConnectionPool *Pool); 170 | ~TQueryManagerPoolConnection(void); 171 | 172 | // TODO(fusion): I don't know if this was the indended way to access this 173 | // structure but it is only a small wrapper and accessing members directly 174 | // would be too verbose, specially when it is also named `QueryManagerConnection`. 175 | 176 | TQueryManagerConnection *operator->(void){ 177 | return this->QueryManagerConnection; 178 | } 179 | 180 | operator bool(void) const { 181 | return this->QueryManagerConnection != NULL; 182 | } 183 | 184 | // DATA 185 | // ================= 186 | TQueryManagerConnectionPool *QueryManagerConnectionPool; 187 | TQueryManagerConnection *QueryManagerConnection; 188 | }; 189 | 190 | void SetQueryManagerLoginData(int Type, const char *Data); 191 | 192 | #endif //TIBIA_QUERY_HH_ 193 | -------------------------------------------------------------------------------- /src/magic.hh: -------------------------------------------------------------------------------- 1 | #ifndef TIBIA_MAGIC_HH_ 2 | #define TIBIA_MAGIC_HH_ 1 3 | 4 | #include "common.hh" 5 | #include "cr.hh" 6 | #include "map.hh" 7 | 8 | enum : int { 9 | FIELD_TYPE_FIRE = 1, 10 | FIELD_TYPE_POISON = 2, 11 | FIELD_TYPE_ENERGY = 3, 12 | FIELD_TYPE_MAGICWALL = 4, 13 | FIELD_TYPE_WILDGROWTH = 5, 14 | }; 15 | 16 | struct TImpact{ 17 | // VIRTUAL FUNCTIONS 18 | // ========================================================================= 19 | virtual void handleField(int x, int y, int z); // VTABLE[0] 20 | virtual void handleCreature(TCreature *Victim); // VTABLE[1] 21 | virtual bool isAggressive(void); // VTABLE[2] 22 | 23 | // NOTE(fusion): I don't think the original version had a destructor declared 24 | // here but the compiler complains when calling delete (which seems to only be 25 | // used in `TMonster::IdleStimulus`). 26 | virtual ~TImpact(void){ 27 | // no-op 28 | } 29 | 30 | // DATA 31 | // ========================================================================= 32 | //void *VTABLE; // IMPLICIT 33 | }; 34 | 35 | struct TDamageImpact: TImpact { 36 | TDamageImpact(TCreature *Actor, int DamageType, int Power, bool AllowDefense); 37 | void handleCreature(TCreature *Victim) override; 38 | 39 | TCreature *Actor; 40 | int DamageType; 41 | int Power; 42 | bool AllowDefense; 43 | }; 44 | 45 | struct TFieldImpact: TImpact { 46 | TFieldImpact(TCreature *Actor, int FieldType); 47 | void handleField(int x, int y, int z) override; 48 | 49 | TCreature *Actor; 50 | int FieldType; 51 | }; 52 | 53 | struct THealingImpact: TImpact { 54 | THealingImpact(TCreature *Actor, int Power); 55 | void handleCreature(TCreature *Victim) override; 56 | bool isAggressive(void) override; 57 | 58 | TCreature *Actor; 59 | int Power; 60 | }; 61 | 62 | struct TSpeedImpact: TImpact { 63 | TSpeedImpact(TCreature *Actor, int Percent, int Duration); 64 | void handleCreature(TCreature *Victim) override; 65 | 66 | TCreature *Actor; 67 | int Percent; 68 | int Duration; 69 | }; 70 | 71 | struct TDrunkenImpact: TImpact { 72 | TDrunkenImpact(TCreature *Actor, int Power, int Duration); 73 | void handleCreature(TCreature *Victim) override; 74 | 75 | TCreature *Actor; 76 | int Power; 77 | int Duration; 78 | }; 79 | 80 | struct TStrengthImpact: TImpact { 81 | TStrengthImpact(TCreature *Actor, int Skills, int Percent, int Duration); 82 | void handleCreature(TCreature *Victim) override; 83 | 84 | TCreature *Actor; 85 | int Skills; 86 | int Percent; 87 | int Duration; 88 | }; 89 | 90 | struct TOutfitImpact: TImpact { 91 | TOutfitImpact(TCreature *Actor, TOutfit Outfit, int Duration); 92 | void handleCreature(TCreature *Victim) override; 93 | 94 | TCreature *Actor; 95 | TOutfit Outfit; 96 | int Duration; 97 | }; 98 | 99 | struct TSummonImpact: TImpact { 100 | TSummonImpact(TCreature *Actor, int Race, int Maximum); 101 | void handleField(int x, int y, int z) override; 102 | 103 | TCreature *Actor; 104 | int Race; 105 | int Maximum; 106 | }; 107 | 108 | void ActorShapeSpell(TCreature *Actor, TImpact *Impact, int Effect); 109 | void VictimShapeSpell(TCreature *Actor, TCreature *Victim, 110 | int Range, int Animation, TImpact *Impact, int Effect); 111 | void OriginShapeSpell(TCreature *Actor, int Radius, TImpact *Impact, int Effect); 112 | void CircleShapeSpell(TCreature *Actor, int DestX, int DestY, int DestZ, 113 | int Range, int Animation, int Radius, TImpact *Impact, int Effect); 114 | void DestinationShapeSpell(TCreature *Actor, TCreature *Victim, 115 | int Range, int Animation, int Radius, TImpact *Impact, int Effect); 116 | void AngleShapeSpell(TCreature *Actor, int Angle, int Range, TImpact *Impact, int Effect); 117 | void CheckSpellbook(TCreature *Actor, int SpellNr); 118 | void CheckAccount(TCreature *Actor, int SpellNr); 119 | void CheckLevel(TCreature *Actor, int SpellNr); 120 | void CheckRuneLevel(TCreature *Actor, int SpellNr); 121 | void CheckMagicItem(TCreature *Actor, ObjectType Type); 122 | void CheckRing(TCreature *Actor, int SpellNr); 123 | void CheckAffectedPlayers(TCreature *Actor, int x, int y, int z); 124 | void CheckMana(TCreature *Actor, int ManaPoints, int SoulPoints, int Delay); 125 | int ComputeDamage(TCreature *Actor, int SpellNr, int Damage, int Variation); 126 | bool IsAggressiveSpell(int SpellNr); 127 | void MassCombat(TCreature *Actor, Object Target, int ManaPoints, int SoulPoints, 128 | int Damage, int Effect, int Radius, int DamageType, int Animation); 129 | void AngleCombat(TCreature *Actor, int ManaPoints, int SoulPoints, 130 | int Damage, int Effect, int Range, int Angle, int DamageType); 131 | void Combat(TCreature *Actor, Object Target, int ManaPoints, int SoulPoints, 132 | int Damage, int Effect, int Animation, int DamageType); 133 | int GetDirection(int dx, int dy); // TODO(fusion): Move this one elsewhere? Maybe `info.cc`. 134 | void KillAllMonsters(TCreature *Actor, int Effect, int Radius); 135 | void CreateField(int x, int y, int z, int FieldType, uint32 Owner, bool Peaceful); 136 | void CreateField(TCreature *Actor, Object Target, int ManaPoints, int SoulPoints, int FieldType); 137 | void CreateField(TCreature *Actor, int ManaPoints, int SoulPoints, int FieldType); 138 | void MassCreateField(TCreature *Actor, Object Target, 139 | int ManaPoints, int SoulPoints, int FieldType, int Radius); 140 | void CreateFieldWall(TCreature *Actor, Object Target, 141 | int ManaPoints, int SoulPoints, int FieldType, int Width); 142 | void DeleteField(TCreature *Actor, Object Target, int ManaPoints, int SoulPoints); 143 | void CleanupField(TCreature *Actor, Object Target, int ManaPoints, int SoulPoints); 144 | void CleanupField(TCreature *Actor); 145 | void Teleport(TCreature *Actor, const char *Param); 146 | void TeleportToCreature(TCreature *Actor, const char *Name); 147 | void TeleportPlayerToMe(TCreature *Actor, const char *Name); 148 | void MagicRope(TCreature *Actor, int ManaPoints, int SoulPoints); 149 | void MagicClimbing(TCreature *Actor, int ManaPoints, int SoulPoints, const char *Param); 150 | void MagicClimbing(TCreature *Actor, const char *Param); 151 | void CreateThing(TCreature *Actor, const char *Param1, const char *Param2); 152 | void CreateMoney(TCreature *Actor, const char *Param); 153 | void CreateFood(TCreature *Actor, int ManaPoints, int SoulPoints); 154 | void CreateArrows(TCreature *Actor, int ManaPoints, int SoulPoints, int ArrowType, int Count); 155 | void SummonCreature(TCreature *Actor, int ManaPoints, int Race, bool God); 156 | void SummonCreature(TCreature *Actor, int ManaPoints, const char *RaceName, bool God); 157 | void StartMonsterraid(TCreature *Actor, const char *RaidName); 158 | void RaiseDead(TCreature *Actor, Object Target, int ManaPoints, int SoulPoints); 159 | void MassRaiseDead(TCreature *Actor, Object Target, int ManaPoints, int SoulPoints, int Radius); 160 | void Heal(TCreature *Actor, int ManaPoints, int SoulPoints, int Amount); 161 | void MassHeal(TCreature *Actor, Object Target, int ManaPoints, int SoulPoints, int Amount, int Radius); 162 | void HealFriend(TCreature *Actor, const char *TargetName, int ManaPoints, int SoulPoints, int Amount); 163 | void RefreshMana(TCreature *Actor, int ManaPoints, int SoulPoints, int Amount); 164 | void MagicGoStrength(TCreature *Actor, TCreature *Target, int ManaPoints, int SoulPoints, int Percent, int Duration); 165 | void Shielding(TCreature *Actor, int ManaPoints, int SoulPoints, int Duration); 166 | void NegatePoison(TCreature *Actor, TCreature *Target, int ManaPoints, int SoulPoints); 167 | void Enlight(TCreature *Actor, int ManaPoints, int SoulPoints, int Radius, int Duration); 168 | void Invisibility(TCreature *Actor, int ManaPoints, int SoulPoints, int Duration); 169 | void CancelInvisibility(TCreature *Actor, Object Target, int ManaPoints, int SoulPoints, int Radius); 170 | void CreatureIllusion(TCreature *Actor, int ManaPoints, int SoulPoints, const char *RaceName, int Duration); 171 | void ObjectIllusion(TCreature *Actor, int ManaPoints, int SoulPoints, Object Target, int Duration); 172 | void ChangeData(TCreature *Actor, const char *Param); 173 | void EnchantObject(TCreature *Actor, int ManaPoints, int SoulPoints, ObjectType OldType, ObjectType NewType); 174 | void Convince(TCreature *Actor, TCreature *Target); 175 | void Challenge(TCreature *Actor, int ManaPoints, int SoulPoints, int Radius); 176 | void FindPerson(TCreature *Actor, int ManaPoints, int SoulPoints, const char *TargetName); 177 | void GetPosition(TCreature *Actor); 178 | void GetQuestValue(TCreature *Actor, const char *Param); 179 | void SetQuestValue(TCreature *Actor, const char *Param1, const char *Param2); 180 | void ClearQuestValues(TCreature *Actor); 181 | void CreateKnowledge(TCreature *Actor, const char *Param1, const char *Param2); 182 | void ChangeProfession(TCreature *Actor, const char *Param); 183 | void EditGuests(TCreature *Actor); 184 | void EditSubowners(TCreature *Actor); 185 | void EditNameDoor(TCreature *Actor); 186 | void KickGuest(TCreature *Actor, const char *GuestName); 187 | void Notation(TCreature *Actor, const char *Name, const char *Comment); 188 | void NameLock(TCreature *Actor, const char *Name); 189 | void BanishAccount(TCreature *Actor, const char *Name, int Duration, const char *Reason); 190 | void DeleteAccount(TCreature *Actor, const char *Name, const char *Reason); 191 | void BanishCharacter(TCreature *Actor, const char *Name, int Duration, const char *Reason); 192 | void DeleteCharacter(TCreature *Actor, const char *Name, const char *Reason); 193 | void IPBanishment(TCreature *Actor, const char *Name, const char *Reason); 194 | void SetNameRule(TCreature *Actor, const char *Name); 195 | void KickPlayer(TCreature *Actor, const char *Name); 196 | void HomeTeleport(TCreature *Actor, const char *Name); 197 | 198 | // TODO(fusion): These are unsafe like strcpy. 199 | void GetMagicItemDescription(Object Obj, char *SpellString, int *MagicLevel); 200 | void GetSpellbook(uint32 CharacterID, char *Buffer); 201 | 202 | int GetSpellLevel(int SpellNr); 203 | 204 | int CheckForSpell(uint32 CreatureID, const char *Text); 205 | void UseMagicItem(uint32 CreatureID, Object Obj, Object Dest); 206 | void DrinkPotion(uint32 CreatureID, Object Obj); 207 | 208 | void InitMagic(void); 209 | void ExitMagic(void); 210 | 211 | #endif //TIBIA_MAGIC_HH_ 212 | -------------------------------------------------------------------------------- /src/shm.cc: -------------------------------------------------------------------------------- 1 | #include "common.hh" 2 | #include "config.hh" 3 | #include "enums.hh" 4 | #include "threads.hh" 5 | #include "writer.hh" 6 | 7 | #include 8 | 9 | // NOTE(fusion): This looks like an interface to external tools. Looking at the 10 | // `bin` directory this program was in, there are other programs that probably 11 | // use this interface to control certain aspects of the server. My previous 12 | // assumption that each connection was dispatched into its own process may not 13 | // be correct after all. 14 | 15 | struct TSharedMemory { 16 | int Command; 17 | char CommandBuffer[256]; 18 | uint32 RoundNr; 19 | uint32 ObjectCounter; 20 | uint32 Errors; 21 | int PlayersOnline; 22 | int NewbiesOnline; 23 | int PrintBufferPosition; 24 | char PrintBuffer[200][128]; 25 | GAMESTATE GameState; 26 | pid_t GameProcessID; 27 | pid_t GameThreadID; 28 | }; 29 | 30 | static TSharedMemory *SHM = NULL; 31 | static bool IsGameServer = false; 32 | static bool VerboseOutput = false; 33 | 34 | void StartGame(void){ 35 | if(SHM != NULL){ 36 | if(SHM->GameState == GAME_STARTING){ 37 | SHM->GameState = GAME_RUNNING; 38 | } 39 | }else{ 40 | error("StartGame: SharedMemory existiert nicht.\n"); 41 | } 42 | } 43 | 44 | void CloseGame(void){ 45 | if(SHM != NULL){ 46 | if(SHM->GameState == GAME_RUNNING){ 47 | SHM->GameState = GAME_CLOSING; 48 | } 49 | }else{ 50 | error("CloseGame: SharedMemory existiert nicht.\n"); 51 | } 52 | } 53 | 54 | void EndGame(void){ 55 | if(SHM != NULL){ 56 | SHM->GameState = GAME_ENDING; 57 | }else{ 58 | error("EndGame: SharedMemory existiert nicht.\n"); 59 | } 60 | } 61 | 62 | bool LoginAllowed(void){ 63 | bool Result = false; 64 | if(SHM != NULL){ 65 | Result = SHM->GameState == GAME_RUNNING; 66 | }else{ 67 | error("IsLoginAllowed: SharedMemory existiert nicht.\n"); 68 | } 69 | return Result; 70 | } 71 | 72 | bool GameRunning(void){ 73 | bool Result = false; 74 | if(SHM != NULL){ 75 | Result = SHM->GameState == GAME_STARTING 76 | || SHM->GameState == GAME_RUNNING 77 | || SHM->GameState == GAME_CLOSING; 78 | }else{ 79 | error("GameRunning: SharedMemory existiert nicht.\n"); 80 | } 81 | return Result; 82 | } 83 | 84 | bool GameStarting(void){ 85 | bool Result = false; 86 | if(SHM != NULL){ 87 | Result = SHM->GameState == GAME_STARTING; 88 | }else{ 89 | error("GameStarting: SharedMemory existiert nicht.\n"); 90 | } 91 | return Result; 92 | } 93 | 94 | bool GameEnding(void){ 95 | bool Result = false; 96 | if(SHM != NULL){ 97 | Result = SHM->GameState == GAME_CLOSING 98 | || SHM->GameState == GAME_ENDING; 99 | }else{ 100 | error("GameEnding: SharedMemory existiert nicht.\n"); 101 | } 102 | return Result; 103 | } 104 | 105 | pid_t GetGameProcessID(void){ 106 | pid_t Pid = 0; 107 | if(SHM != NULL){ 108 | Pid = SHM->GameProcessID; 109 | } 110 | return Pid; 111 | } 112 | 113 | pid_t GetGameThreadID(void){ 114 | pid_t Pid = 0; 115 | if(SHM != NULL){ 116 | Pid = SHM->GameThreadID; 117 | } 118 | return Pid; 119 | } 120 | 121 | static void ErrorHandler(const char *Text){ 122 | if(VerboseOutput){ 123 | printf("%s", Text); 124 | } 125 | 126 | if(SHM != NULL){ 127 | SHM->Errors += 1; 128 | if(SHM->Errors <= 0x8000){ 129 | Log("error", Text); 130 | if(SHM->Errors == 0x8000){ 131 | Log("error", "Zu viele Fehler. Keine weitere Protokollierung.\n"); 132 | } 133 | } 134 | } 135 | } 136 | 137 | static void PrintHandler(int Level, const char *Text){ 138 | static Semaphore LogfileMutex(1); 139 | 140 | if(Level > DebugLevel){ 141 | return; 142 | } 143 | 144 | if(VerboseOutput){ 145 | printf("%s", Text); 146 | } 147 | 148 | if(SHM != NULL){ 149 | // TODO(fusion): Does it even make sense to have this Semaphore here? 150 | // It controls writes to the print buffer but reads outside this scope 151 | // may be partial. But then, it doesn't seem like it is read anywhere 152 | // else. Perhaps some external monitor tool, in which case do we even 153 | // print from multiple threads? 154 | LogfileMutex.down(); 155 | int Line = SHM->PrintBufferPosition; 156 | char *Buffer = SHM->PrintBuffer[Line]; 157 | // TODO(fusion): Ughh... 158 | strncpy(Buffer, Text, sizeof(SHM->PrintBuffer[0]) - 1); 159 | Buffer[sizeof(SHM->PrintBuffer[0]) - 2] = 0; 160 | if(Buffer[0] != 0){ 161 | usize TextLen = strlen(Buffer); 162 | if(Buffer[TextLen - 1] != '\n'){ 163 | strcat(Buffer, "\n"); 164 | } 165 | } 166 | SHM->PrintBufferPosition = (Line + 1) % NARRAY(SHM->PrintBuffer); 167 | LogfileMutex.up(); 168 | } 169 | } 170 | 171 | int GetPrintlogPosition(void){ 172 | int Result = 0; 173 | if(SHM != NULL){ 174 | Result = SHM->PrintBufferPosition; 175 | } 176 | return Result; 177 | } 178 | 179 | char *GetPrintlogLine(int Line){ 180 | char *Result = NULL; 181 | if(SHM != NULL && Line >= 0 && Line < NARRAY(SHM->PrintBuffer)){ 182 | Result = SHM->PrintBuffer[Line]; 183 | } 184 | return Result; 185 | } 186 | 187 | void IncrementObjectCounter(void){ 188 | if(SHM != NULL){ 189 | SHM->ObjectCounter += 1; 190 | } 191 | } 192 | 193 | void DecrementObjectCounter(void){ 194 | if(SHM != NULL){ 195 | SHM->ObjectCounter -= 1; 196 | } 197 | } 198 | 199 | uint32 GetObjectCounter(void){ 200 | uint32 ObjectCounter = 0; 201 | if(SHM != NULL){ 202 | ObjectCounter = SHM->ObjectCounter; 203 | } 204 | return ObjectCounter; 205 | } 206 | 207 | void IncrementPlayersOnline(void){ 208 | if(SHM != NULL){ 209 | SHM->PlayersOnline += 1; 210 | } 211 | } 212 | 213 | void DecrementPlayersOnline(void){ 214 | if(SHM != NULL){ 215 | SHM->PlayersOnline -= 1; 216 | } 217 | } 218 | 219 | int GetPlayersOnline(void){ 220 | int PlayersOnline = 0; 221 | if(SHM != NULL){ 222 | PlayersOnline = SHM->PlayersOnline; 223 | } 224 | return PlayersOnline; 225 | } 226 | 227 | void IncrementNewbiesOnline(void){ 228 | if(SHM != NULL){ 229 | SHM->NewbiesOnline += 1; 230 | } 231 | } 232 | 233 | void DecrementNewbiesOnline(void){ 234 | if(SHM != NULL){ 235 | SHM->NewbiesOnline -= 1; 236 | } 237 | } 238 | 239 | int GetNewbiesOnline(void){ 240 | int NewbiesOnline = 0; 241 | if(SHM != NULL){ 242 | NewbiesOnline = SHM->NewbiesOnline; 243 | } 244 | return NewbiesOnline; 245 | } 246 | 247 | void SetRoundNr(uint32 RoundNr){ 248 | if(SHM != NULL){ 249 | SHM->RoundNr = RoundNr; 250 | } 251 | } 252 | 253 | uint32 GetRoundNr(void){ 254 | uint32 RoundNr = 0; 255 | if(SHM != NULL){ 256 | RoundNr = SHM->RoundNr; 257 | } 258 | return RoundNr; 259 | } 260 | 261 | void SetCommand(int Command, char *Text){ 262 | if(SHM != NULL){ 263 | SHM->Command = Command; 264 | if(Text == NULL){ 265 | SHM->CommandBuffer[0] = 0; 266 | }else{ 267 | strncpy(SHM->CommandBuffer, Text, sizeof(SHM->CommandBuffer)); 268 | SHM->CommandBuffer[sizeof(SHM->CommandBuffer) - 1] = 0; 269 | } 270 | } 271 | } 272 | 273 | int GetCommand(void){ 274 | int Command = 0; 275 | if(SHM != NULL){ 276 | Command = SHM->Command; 277 | } 278 | return Command; 279 | } 280 | 281 | char *GetCommandBuffer(void){ 282 | char *Buffer = NULL; 283 | if(SHM != NULL && SHM->Command != 0){ 284 | Buffer = SHM->CommandBuffer; 285 | } 286 | return Buffer; 287 | } 288 | 289 | static bool DeleteSHM(void){ 290 | int SHMID = shmget(SHMKey, 0, 0); 291 | if(SHMID == -1){ 292 | if(VerboseOutput){ 293 | puts("DeleteSHM: SharedMemory existiert nicht."); 294 | } 295 | return true; 296 | } 297 | 298 | if(shmctl(SHMID, IPC_RMID, NULL) == -1){ 299 | if(VerboseOutput){ 300 | // TODO(fusion): Include `errno` in the error message? 301 | puts("DeleteSHM: Kann SharedMemory nicht löschen."); 302 | } 303 | return false; 304 | } 305 | 306 | return true; 307 | } 308 | 309 | static void CreateSHM(void){ 310 | bool Deleted = false; 311 | while(true){ 312 | int SHMID = shmget(SHMKey, sizeof(TSharedMemory), IPC_CREAT | IPC_EXCL | 0777); 313 | if(SHMID != -1){ 314 | return; 315 | } 316 | 317 | if(errno != EEXIST || Deleted){ 318 | if(VerboseOutput){ 319 | printf("CreateSHM: Kann SharedMemory nicht anlegen (Fehler %d).\n", errno); 320 | } 321 | throw "Cannot create SharedMemory"; 322 | } 323 | 324 | if(!DeleteSHM()){ 325 | throw "Cannot delete SharedMemory"; 326 | } 327 | 328 | Deleted = true; 329 | } 330 | } 331 | 332 | static void AttachSHM(void){ 333 | int SHMID = shmget(SHMKey, sizeof(TSharedMemory), 0); 334 | if(SHMID == -1){ 335 | if(VerboseOutput){ 336 | // TODO(fusion): Include `errno` in the error message? 337 | puts("AttachSHM: Kann SharedMemory nicht fassen."); 338 | } 339 | SHM = NULL; 340 | throw "Cannot get SharedMemory"; 341 | } 342 | 343 | SHM = (TSharedMemory*)shmat(SHMID, NULL, 0); 344 | if(SHM == (void*)-1){ 345 | if(VerboseOutput){ 346 | puts("AttachSHM: Kann SharedMemory nicht anbinden."); 347 | } 348 | 349 | SHM = NULL; 350 | throw "Cannot attach SharedMemory"; 351 | } 352 | } 353 | 354 | static void DetachSHM(void){ 355 | if(SHM != NULL){ 356 | if(shmdt(SHM) == -1 && VerboseOutput){ 357 | puts("DetachSHM: Kann SharedMemory nicht löschen."); 358 | } 359 | SHM = NULL; 360 | } 361 | } 362 | 363 | void InitSHM(bool Verbose){ 364 | IsGameServer = true; 365 | VerboseOutput = Verbose; 366 | 367 | CreateSHM(); 368 | AttachSHM(); 369 | SetErrorFunction(ErrorHandler); 370 | SetPrintFunction(PrintHandler); 371 | 372 | // NOTE(fusion): `CreateSHM` ensures a new shared memory segment is created 373 | // and it should be always zero initialized as per the manual. Nevertheless 374 | // the decompiled version was also clearing SHM, probably just in case. 375 | memset(SHM, 0, sizeof(TSharedMemory)); 376 | 377 | strncpy(SHM->PrintBuffer[0], 378 | "SHM initialized. System printing is working!\n", 379 | sizeof(SHM->PrintBuffer[0])); 380 | SHM->PrintBuffer[0][sizeof(SHM->PrintBuffer[0]) - 1] = 0; 381 | 382 | SHM->PrintBufferPosition = 1; 383 | SHM->GameState = GAME_STARTING; 384 | SHM->GameProcessID = getpid(); 385 | SHM->GameThreadID = gettid(); 386 | } 387 | 388 | void ExitSHM(void){ 389 | SetErrorFunction(NULL); 390 | SetPrintFunction(NULL); 391 | DetachSHM(); 392 | DeleteSHM(); 393 | } 394 | 395 | void InitSHMExtern(bool Verbose){ 396 | VerboseOutput = Verbose; 397 | AttachSHM(); 398 | } 399 | 400 | void ExitSHMExtern(void){ 401 | DetachSHM(); 402 | } 403 | -------------------------------------------------------------------------------- /src/common.hh: -------------------------------------------------------------------------------- 1 | #ifndef TIBIA_COMMON_HH_ 2 | #define TIBIA_COMMON_HH_ 1 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | 15 | #include 16 | 17 | typedef uint8_t uint8; 18 | typedef uint16_t uint16; 19 | typedef uint32_t uint32; 20 | typedef int64_t int64; 21 | typedef uint64_t uint64; 22 | typedef uintptr_t uintptr; 23 | typedef size_t usize; 24 | 25 | #define STATIC_ASSERT(expr) static_assert((expr), #expr) 26 | #define NARRAY(arr) (int)(sizeof(arr) / sizeof(arr[0])) 27 | #define ISPOW2(x) ((x) != 0 && ((x) & ((x) - 1)) == 0) 28 | #define KB(x) ((usize)(x) << 10) 29 | #define MB(x) ((usize)(x) << 20) 30 | #define GB(x) ((usize)(x) << 30) 31 | 32 | #if !OS_WINDOWS && !OS_LINUX 33 | # if defined(_WIN32) 34 | # define OS_WINDOWS 1 35 | # elif defined(__linux__) || defined(__gnu_linux__) 36 | # define OS_LINUX 1 37 | # else 38 | # error "Operating system not supported." 39 | # endif 40 | #endif 41 | 42 | #if !COMPILER_MSVC && !COMPILER_GCC && !COMPILER_CLANG 43 | # if defined(_MSC_VER) 44 | # define COMPILER_MSVC 1 45 | # elif defined(__GNUC__) 46 | # define COMPILER_GCC 1 47 | # elif defined(__clang__) 48 | # define COMPILER_CLANG 1 49 | # endif 50 | #endif 51 | 52 | #if COMPILER_GCC || COMPILER_CLANG 53 | # define ATTR_FALLTHROUGH __attribute__((fallthrough)) 54 | # define ATTR_PRINTF(x, y) __attribute__((format(printf, x, y))) 55 | #else 56 | # define ATTR_FALLTHROUGH 57 | # define ATTR_PRINTF(x, y) 58 | #endif 59 | 60 | #if COMPILER_MSVC 61 | # define TRAP() __debugbreak() 62 | #elif COMPILER_GCC || COMPILER_CLANG 63 | # define TRAP() __builtin_trap() 64 | #else 65 | # define TRAP() abort() 66 | #endif 67 | 68 | #define ASSERT_ALWAYS(expr) if(!(expr)) { TRAP(); } 69 | #if ENABLE_ASSERTIONS 70 | # define ASSERT(expr) ASSERT_ALWAYS(expr) 71 | #else 72 | # define ASSERT(expr) ((void)(expr)) 73 | #endif 74 | 75 | // NOTE(fusion): The server will only compile on Linux due to a few Linux specific 76 | // features being used. Making it compile on Windows shouldn't be too difficult but 77 | // would require a few design changes. 78 | STATIC_ASSERT(OS_LINUX); 79 | #include 80 | #include 81 | 82 | // NOTE(fusion): This is the member name for the thread id in `struct sigevent` 83 | // when `sigev_notify` is `SIGEV_THREAD_ID` but for whatever reason glibc doesn't 84 | // define it. 85 | #ifndef sigev_notify_thread_id 86 | # define sigev_notify_thread_id _sigev_un._tid 87 | #endif 88 | 89 | // Constants 90 | // ============================================================================= 91 | // TODO(fusion): There are many constants that are hardcoded as decompilation 92 | // artifacts. We should define them here and use when appropriate. It is not 93 | // as simple because I've been using `NARRAY` in some cases and they're used 94 | // essentially everywhere. 95 | 96 | //#define MAX_NAME 30 // used with most short strings (should replace MAX_IDENT_LENGTH too) 97 | //#define MAX_IPADDRESS 16 98 | //#define MAX_BUDDIES 100 99 | //#define MAX_SKILLS 25 100 | //#define MAX_SPELLS 256 101 | //#define MAX_QUESTS 500 102 | #define MAX_DEPOTS 9 103 | //#define MAX_OPEN_CONTAINERS 16 104 | #define MAX_SPELL_SYLLABLES 10 105 | 106 | 107 | // shm.cc 108 | // ============================================================================= 109 | void StartGame(void); 110 | void CloseGame(void); 111 | void EndGame(void); 112 | bool LoginAllowed(void); 113 | bool GameRunning(void); 114 | bool GameStarting(void); 115 | bool GameEnding(void); 116 | pid_t GetGameProcessID(void); 117 | pid_t GetGameThreadID(void); 118 | int GetPrintlogPosition(void); 119 | char *GetPrintlogLine(int Line); 120 | void IncrementObjectCounter(void); 121 | void DecrementObjectCounter(void); 122 | uint32 GetObjectCounter(void); 123 | void IncrementPlayersOnline(void); 124 | void DecrementPlayersOnline(void); 125 | int GetPlayersOnline(void); 126 | void IncrementNewbiesOnline(void); 127 | void DecrementNewbiesOnline(void); 128 | int GetNewbiesOnline(void); 129 | void SetRoundNr(uint32 RoundNr); 130 | uint32 GetRoundNr(void); 131 | void SetCommand(int Command, char *Text); 132 | int GetCommand(void); 133 | char *GetCommandBuffer(void); 134 | void InitSHM(bool Verbose); 135 | void ExitSHM(void); 136 | void InitSHMExtern(bool Verbose); 137 | void ExitSHMExtern(void); 138 | 139 | // strings.cc 140 | // ============================================================================= 141 | const char *AddStaticString(const char *String); 142 | uint32 AddDynamicString(const char *String); 143 | const char *GetDynamicString(uint32 Number); 144 | void DeleteDynamicString(uint32 Number); 145 | void CleanupDynamicStrings(void); 146 | void InitStrings(void); 147 | void ExitStrings(void); 148 | 149 | bool IsCountable(const char *s); 150 | const char *Plural(const char *s, int Count); 151 | const char *SearchForWord(const char *Pattern, const char *Text); 152 | const char *SearchForNumber(int Count, const char *Text); 153 | bool MatchString(const char *Pattern, const char *String); 154 | void AddSlashes(char *Destination, const char *Source); 155 | void Trim(char *Text); 156 | void Trim(char *Destination, const char *Source); 157 | char *Capitals(char *Text); 158 | 159 | // time.cc 160 | // ============================================================================= 161 | extern uint32 RoundNr; 162 | extern uint32 ServerMilliseconds; 163 | struct tm GetLocalTimeTM(time_t t); 164 | void GetRealTime(int *Hour, int *Minute); 165 | void GetTime(int *Hour, int *Minute); 166 | void GetDate(int *Year, int *Cycle, int *Day); 167 | void GetAmbiente(int *Brightness, int *Color); 168 | uint32 GetRoundAtTime(int Hour, int Minute); 169 | uint32 GetRoundForNextMinute(void); 170 | 171 | // utils.cc 172 | // ============================================================================= 173 | typedef void TErrorFunction(const char *Text); 174 | typedef void TPrintFunction(int Level, const char *Text); 175 | void SetErrorFunction(TErrorFunction *Function); 176 | void SetPrintFunction(TPrintFunction *Function); 177 | void SilentHandler(const char *Text); 178 | void SilentHandler(int Level, const char *Text); 179 | void LogFileHandler(const char *Text); 180 | void LogFileHandler(int Level, const char *Text); 181 | void error(const char *Text, ...) ATTR_PRINTF(1, 2); 182 | void print(int Level, const char *Text, ...) ATTR_PRINTF(2, 3); 183 | int random(int Min, int Max); 184 | bool FileExists(const char *FileName); 185 | 186 | bool isSpace(int c); 187 | bool isAlpha(int c); 188 | bool isEngAlpha(int c); 189 | bool isDigit(int c); 190 | int toLower(int c); 191 | int toUpper(int c); 192 | char *strLower(char *s); 193 | char *strUpper(char *s); 194 | int stricmp(const char *s1, const char *s2, int Max = INT_MAX); 195 | char *findFirst(char *s, char c); 196 | char *findLast(char *s, char c); 197 | 198 | bool CheckBitIndex(int BitSetBytes, int Index); 199 | bool CheckBit(uint8 *BitSet, int Index); 200 | void SetBit(uint8 *BitSet, int Index); 201 | void ClearBit(uint8 *BitSet, int Index); 202 | 203 | template 204 | void RandomShuffle(T *Buffer, int Size){ 205 | if(Buffer == NULL){ 206 | error("RandomShuffle: Buffer ist NULL.\n"); 207 | return; 208 | } 209 | 210 | int Max = Size - 1; 211 | for(int Min = 0; Min < Max; Min += 1){ 212 | int Swap = random(Min, Max); 213 | if(Swap != Min){ 214 | std::swap(Buffer[Min], Buffer[Swap]); 215 | } 216 | } 217 | } 218 | 219 | struct TReadStream { 220 | // VIRTUAL FUNCTIONS 221 | // ================= 222 | virtual bool readFlag(void); // VTABLE[0] 223 | virtual uint8 readByte(void) = 0; // VTABLE[1] 224 | virtual uint16 readWord(void); // VTABLE[2] 225 | virtual uint32 readQuad(void); // VTABLE[3] 226 | virtual void readString(char *Buffer, int MaxLength); // VTABLE[4] 227 | virtual void readBytes(uint8 *Buffer, int Count); // VTABLE[5] 228 | virtual bool eof(void) = 0; // VTABLE[6] 229 | virtual void skip(int Count) = 0; // VTABLE[7] 230 | }; 231 | 232 | struct TReadBuffer: TReadStream { 233 | TReadBuffer(const uint8 *Data, int Size); 234 | 235 | // VIRTUAL FUNCTIONS 236 | // ================= 237 | uint8 readByte(void) override; 238 | uint16 readWord(void) override; 239 | uint32 readQuad(void) override; 240 | void readBytes(uint8 *Buffer, int Count) override; 241 | bool eof(void) override; 242 | void skip(int Count) override; 243 | 244 | // DATA 245 | // ================= 246 | const uint8 *Data; 247 | int Size; 248 | int Position; 249 | }; 250 | 251 | struct TWriteStream { 252 | // VIRTUAL FUNCTIONS 253 | // ================= 254 | virtual void writeFlag(bool Flag); // VTABLE[0] 255 | virtual void writeByte(uint8 Byte) = 0; // VTABLE[1] 256 | virtual void writeWord(uint16 Word); // VTABLE[2] 257 | virtual void writeQuad(uint32 Quad); // VTABLE[3] 258 | virtual void writeString(const char *String); // VTABLE[4] 259 | virtual void writeBytes(const uint8 *Buffer, int Count); // VTABLE[5] 260 | }; 261 | 262 | struct TWriteBuffer: TWriteStream { 263 | TWriteBuffer(uint8 *Data, int Size); 264 | 265 | // VIRTUAL FUNCTIONS 266 | // ================= 267 | void writeByte(uint8 Byte) override; 268 | void writeWord(uint16 Word) override; 269 | void writeQuad(uint32 Quad) override; 270 | void writeBytes(const uint8 *Buffer, int Count) override; 271 | 272 | // DATA 273 | // ================= 274 | uint8 *Data; 275 | int Size; 276 | int Position; 277 | }; 278 | 279 | struct TDynamicWriteBuffer: TWriteBuffer { 280 | TDynamicWriteBuffer(int InitialSize); 281 | void resizeBuffer(void); 282 | 283 | // VIRTUAL FUNCTIONS 284 | // ================= 285 | void writeByte(uint8 Byte) override; 286 | void writeWord(uint16 Word) override; 287 | void writeQuad(uint32 Quad) override; 288 | void writeBytes(const uint8 *Buffer, int Count) override; 289 | 290 | // TODO(fusion): Appended virtual functions. These are not in the base class 291 | // VTABLE which can be problematic if we intend to use polymorphism, although 292 | // that doesn't seem to be case. 293 | virtual ~TDynamicWriteBuffer(void); // VTABLE[6] 294 | // Duplicate destructor that also calls operator delete. // VTABLE[7] 295 | }; 296 | 297 | #endif //TIBIA_COMMON_HH_ 298 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Tibia 7.7 Game Server 2 | This is the result of manually decompiling the leaked Tibia 7.7 server binary into a readable version of the original source code. With the exception of a few [changes](#changes), expected typos, and translation errors, everything should be as close as possible to the original. I do expect problems so any issues should be submitted to the issue tracker with the appropriate description. 3 | 4 | ## License and Legal Issues 5 | The legal status of decompiled and rewritten code is a gray area. While the project contains no original assets or code, CipSoft may still view this as a violation of their claimed intellectual property rights, due to the original binary not being willfully released. As the author, I release this code into the public domain, relinquishing all rights and claims to it (see `LICENSE.txt`). 6 | 7 | ## Changes 8 | - The most important change was fixing thread interactions. The original binary relied on some old Linux threading library, most likely LinuxThreads, which assigned different process ids to different threads, among other quirks. The fix is small and contained in commit d359c0a. 9 | 10 | - Custom RSA routines were replaced with OpenSSL's libcrypto. I didn't bother to translate the decompiled routines because it would be annoying to get right and could end up with security issues. This introduces the **ONLY** dependency required to compile but is fairly simple to get it installed on most Linux distros. For example, on Debian you can use `apt install libssl-dev`. 11 | 12 | ## Future 13 | I had a small *TODO* list with a few things to attempt after the server was up and running but I think that trying to modernize it further or get it to run on Windows wouldn't really add any value, plus it would probably require a lot of extra time. My commitment is to fix all reported bugs and perhaps string handling/building which is kind of a disaster. Exceptions are already too engraved into the codebase, to the point it would make more sense to rewrite everything without them rather than attempting to remove them. 14 | 15 | ## Compiling 16 | The server uses a few Linux specific features so it will **ONLY** compile on Linux. It's possible to port to Windows but it would require a few design changes. Originally it would have no dependencies but with the RSA routines change it now requires OpenSSL's `libcrypto` as its **ONLY** dependency. The makefile is very simple and should work as long as libcrypto is installed. You can add the `-j N` switch to make it compile across N processes. 17 | ``` 18 | make # build in release mode 19 | make DEBUG=1 # build in debug mode 20 | make clean # remove `build` directory 21 | make clean && make # full rebuild in release mode (recommended) 22 | make clean && make DEBUG=1 # full rebuild in debug mode (recommended) 23 | ``` 24 | 25 | ## Running 26 | This repository contains only the source code for the game server. After the first decompilation pass, it was clear the server would need a few supporting services. They're fairly simple but each one will have a separate *README* file with a short description on how to compile and run them. 27 | - [Query Manager](https://github.com/fusion32/tibia-querymanager) 28 | - [Login Server](https://github.com/fusion32/tibia-login) 29 | - [Web Server](https://github.com/fusion32/tibia-web) 30 | 31 | The game server won't boot up if it's not able to connect to the query manager which makes it the only real hard dependency. The login server will handle character list, and the web server will handle basic account management. 32 | 33 | It is recommended that the server is setup as a service. There is a *systemd* configuration file (`tibia-game.service`) in the repository that may be used along with the intructions below to set everything up. The steps may change depending on whether your system uses *systemd* or not. 34 | 35 | > It is also a good idea to configure a firewall and generate a new RSA private key for **security** reasons, but those aren't covered here. 36 | 37 | ## Playing 38 | Playing will require a copy of the original Tibia 7.7 client and some IP-Changer application. Since I can't trust anything out there, I also wrote a small [IP-Changer](https://github.com/fusion32/tibia-ipchanger) command line tool which should be very simple to use. As for the client itself, there is no way except to look around the internet for the old installer and hope that it doesn't contain viruses. It is always a good idea to check it with [Virus Total](https://www.virustotal.com). 39 | 40 | ### Stale data 41 | The leaked tarball contains a lot of stale data that you may want to cleanup ahead of time: 42 | - `.tibia` contains the base config and should be configured appropriately 43 | - `dat/owners.dat` contains house owner information and will cause query manager errors the first time the server is run because there is no character data in the database 44 | - `log` contains old log data 45 | - `map` contains world data which is **PERSISTENT** and should probably be replaced with a fresh copy of `origmap` 46 | - `map.bak` contains map backup data (generated from `map` by some external tool) 47 | - `usr` contains old character data 48 | - `usr.bak` contains character backup data (generated from `usr` by some external tool) 49 | - `save/game.pid` is used to prevent multiple instances of the game server from running at the same time and will linger if the process doesn't exit gracefully -- should be removed whenever **needed** to allow the server to properly startup 50 | 51 | > The server won't create sub-directories like `usr/01` which will result in saving/loading errors. You'll need to recreate them manually with `mkdir -p {00..99}`. 52 | 53 | ### Prepare game files 54 | ``` 55 | tar -xzf ./tibia-game.tarball.tar.gz ./tibia-game # extract game files 56 | cp game ./tibia-game/bin/game # replace old game binary with newly compiled one 57 | cp tibia.pem ./tibia-game/tibia.pem # copy RSA private key 58 | ``` 59 | 60 | ### Setup service user and game files 61 | The default service config will assume game files are in `/opt/tibia/game`. 62 | ``` 63 | useradd -r -M tibia-game # create user that will run the service 64 | mkdir -p /opt/tibia/game # create directory that will hold game files 65 | cp -r ./tibia-game/* /opt/tibia/game # copy game files into /opt/tibia/game 66 | chown -R tibia-game:tibia-game /opt/tibia/game # change game files ownership to newly created user 67 | ``` 68 | 69 | ### Install service 70 | ``` 71 | cp tibia-game.service /etc/systemd/system # copy service configuration into place 72 | systemctl daemon-reload # reload configuration files 73 | systemctl enable tibia-game.service # enable service 74 | systemctl start tibia-game.service # start service 75 | ``` 76 | 77 | ### Check service 78 | ``` 79 | systemctl status tibia-game.service # show service status 80 | journalctl -n 100 -f -u tibia-game.service # show last 100 log lines 81 | ``` 82 | 83 | ### Ownership and permission problems 84 | Unexpected errors may arise from invalid file ownership and permissions. You should always make sure that game files have the appropriate read-write-execute permissions and that are owned by the user running the game binary. I won't discuss this topic here but you may find plenty of information either in the manual (`man chmod`, `man chown`) or the internet. 85 | 86 | > Extra Security: Files with sensitive data like `tibia.pem` should have 0600 permissions to make sure it is only accessed by the assigned user. 87 | 88 | ## Comparison with OpenTibia 89 | ### Performance 90 | The application is well designed and I'd expect the main thread to have comparable performance to OpenTibia distros. The problem is that each connection will spawn a communication thread, each with its own stack (hardcoded to 64KB) and a few extra system resources for bookkeeping. They should have negligible runtime due to constant I/O but if we consider a high volume, CPU performance would surely be impacted with the constant context switching, nevermind the constant instruction and data cache thrashing. 91 | 92 | Making connection management asynchronous would probably allow a higher number of connections with better performance but would also require some design changes as the communication threads will also handle authentication which involves reaching out to the query manager which also performs I/O. 93 | 94 | ### Customizability 95 | If we're talking about the executable itself, then the imagination is the limit. If we're talking about external files/scripts, then you'll find that changes are strictly limited to existing game mechanics. The level of customizability of OpenTibia servers are a lot higher with custom Lua scripts, etc... 96 | 97 | Modifying original files shouldn't be too difficult with a text editor but having a specialized tool like a map editor will make a huge difference when trying to modify the map particularly. 98 | 99 | ## Why 100 | I've been thinking about different server designs lately, and at the start of the year, I had setup a decompilation project for the leaked binary to see if would give me any insights. To my surprise it had debug symbols which made all the difference and kinda got me hooked. I knew I'd want to fully decompile it at some point but mostly for introspection. 101 | 102 | I still visit OTLand every once in a while, and around May of this year, there was a thread advertising a server using some modified version of the 7.7 leak, which quickly turned into a shit show. It got me wondering why hadn't anyone released anything related to this leak **AT ALL**, and as the depressing as it may sound, there is only one answer: the OpenTibia **community** is pretty much dead. People gate-keeping or selling features/bug-fixes, abusing known bugs to extort server owners. Entitled users that can't get anything working without help while also extorting players with their low quality servers. A pathetic server list that requires advertisement fees to even display servers past a certain amount of players. It is a dread scenario all across. 103 | 104 | To break out of this shitty ass trend, I knew from the beginning that I'd release it all with no strings attached. It may not be in the best possible shape but it will give an idea on how earlier Tibia servers worked and may even serve as inspiration for future server designs. 105 | -------------------------------------------------------------------------------- /src/config.cc: -------------------------------------------------------------------------------- 1 | #include "config.hh" 2 | #include "script.hh" 3 | 4 | char BINPATH[4096]; 5 | char DATAPATH[4096]; 6 | char LOGPATH[4096]; 7 | char MAPPATH[4096]; 8 | char MONSTERPATH[4096]; 9 | char NPCPATH[4096]; 10 | char ORIGMAPPATH[4096]; 11 | char SAVEPATH[4096]; 12 | char USERPATH[4096]; 13 | 14 | int SHMKey; 15 | int AdminPort; 16 | int GamePort; 17 | int QueryManagerPort; 18 | 19 | char AdminAddress[16]; 20 | char GameAddress[16]; 21 | char QueryManagerAddress[16]; 22 | char QueryManagerAdminPW[9]; 23 | char QueryManagerGamePW[9]; 24 | char QueryManagerWebPW[9]; 25 | 26 | int DebugLevel; 27 | bool PrivateWorld; 28 | TWorldType WorldType; 29 | char WorldName[30]; 30 | int MaxPlayers; 31 | int MaxNewbies; 32 | int PremiumPlayerBuffer; 33 | int PremiumNewbieBuffer; 34 | int Beat; 35 | int RebootTime; 36 | 37 | TDatabaseSettings ADMIN_DATABASE; 38 | TDatabaseSettings VOLATILE_DATABASE; 39 | TDatabaseSettings WEB_DATABASE; 40 | TDatabaseSettings FORUM_DATABASE; 41 | TDatabaseSettings MANAGER_DATABASE; 42 | 43 | int NumberOfQueryManagers; 44 | TQueryManagerSettings QUERY_MANAGER[10]; 45 | 46 | static char PasswordKey[9] = "Pm-,o%yD"; 47 | 48 | static void DisguisePassword(char *Password, char *Key){ 49 | if(Password == NULL){ 50 | throw "password is null"; 51 | } 52 | 53 | if(Key == NULL){ 54 | throw "key for disguising password is null"; 55 | } 56 | 57 | usize KeyLen = strlen(Key); 58 | usize PasswordLen = strlen(Password); 59 | if(KeyLen < PasswordLen){ 60 | throw "key for disguising password is too short"; 61 | } 62 | 63 | for(usize Index = 0; Index < PasswordLen; Index += 1){ 64 | Password[Index] = (Key[Index] - Password[Index] + 0x5E) % 0x5E + 0x21; 65 | } 66 | } 67 | 68 | void ReadConfig(void){ 69 | // TODO(fusion): We're not properly initializing these values, specially the 70 | // `TDatabaseSettings` ones. It is probably not that big of a deal anyway since 71 | // we only call this function once at startup and all this memory should be zero 72 | // initialized. 73 | PrivateWorld = false; 74 | strncpy(GameAddress, "0.0.0.0", 8); 75 | SHMKey = 0; 76 | QueryManagerPort = 0; 77 | strncpy(QueryManagerAddress, "0.0.0.0", 8); 78 | AdminPort = 0; 79 | GamePort = 0; 80 | strncpy(AdminAddress, "0.0.0.0", 8); 81 | MaxPlayers = 0; 82 | DebugLevel = 1; 83 | WorldType = NORMAL; 84 | BINPATH[0] = 0; 85 | DATAPATH[0] = 0; 86 | LOGPATH[0] = 0; 87 | MAPPATH[0] = 0; 88 | MONSTERPATH[0] = 0; 89 | NPCPATH[0] = 0; 90 | ORIGMAPPATH[0] = 0; 91 | SAVEPATH[0] = 0; 92 | USERPATH[0] = 0; 93 | QueryManagerAdminPW[0] = 0; 94 | QueryManagerGamePW[0] = 0; 95 | QueryManagerWebPW[0] = 0; 96 | MaxNewbies = 0; 97 | PremiumPlayerBuffer = 0; 98 | PremiumNewbieBuffer = 0; 99 | NumberOfQueryManagers = 0; 100 | Beat = 200; 101 | RebootTime = 540; 102 | ADMIN_DATABASE.Database[0] = 0; 103 | VOLATILE_DATABASE.Database[0] = 0; 104 | WEB_DATABASE.Database[0] = 0; 105 | FORUM_DATABASE.Database[0] = 0; 106 | MANAGER_DATABASE.Database[0] = 0; 107 | 108 | char FileName[4096]; 109 | #if 0 110 | if(const char *Home = getenv("home")){ 111 | snprintf(FileName, sizeof(FileName), "%s/.tibia", Home); 112 | }else if(const char *Home = getenv("HOME")){ 113 | snprintf(FileName, sizeof(FileName), "%s/.tibia", Home); 114 | }else{ 115 | snprintf(FileName, sizeof(FileName), ".tibia"); 116 | } 117 | #else 118 | snprintf(FileName, sizeof(FileName), ".tibia"); 119 | #endif 120 | 121 | if(!FileExists(FileName)){ 122 | throw "cannot find config-file"; 123 | } 124 | 125 | TReadScriptFile Script; 126 | Script.open(FileName); 127 | while(true){ 128 | Script.nextToken(); 129 | if(Script.Token == ENDOFFILE){ 130 | // NOTE(fusion): This is the only graceful exit point of the function. 131 | // Errors are transmitted back through exceptions. 132 | Script.close(); 133 | return; 134 | } 135 | 136 | char Identifier[MAX_IDENT_LENGTH]; 137 | strcpy(Identifier, Script.getIdentifier()); 138 | Script.readSymbol('='); 139 | 140 | // TODO(fusion): Ughh... Get rid of all `strcpy`s. A malicious configuration 141 | // file could do a lot of damage. 142 | if(strcmp(Identifier, "binpath") == 0){ 143 | strcpy(BINPATH, Script.readString()); 144 | }else if(strcmp(Identifier, "mappath") == 0){ 145 | strcpy(MAPPATH, Script.readString()); 146 | }else if(strcmp(Identifier, "origmappath") == 0){ 147 | strcpy(ORIGMAPPATH, Script.readString()); 148 | }else if(strcmp(Identifier, "datapath") == 0){ 149 | strcpy(DATAPATH, Script.readString()); 150 | }else if(strcmp(Identifier, "monsterpath") == 0){ 151 | strcpy(MONSTERPATH, Script.readString()); 152 | }else if(strcmp(Identifier, "npcpath") == 0){ 153 | strcpy(NPCPATH, Script.readString()); 154 | }else if(strcmp(Identifier, "userpath") == 0){ 155 | strcpy(USERPATH, Script.readString()); 156 | }else if(strcmp(Identifier, "logpath") == 0){ 157 | strcpy(LOGPATH, Script.readString()); 158 | }else if(strcmp(Identifier, "savepath") == 0){ 159 | strcpy(SAVEPATH, Script.readString()); 160 | }else if(strcmp(Identifier, "shm") == 0){ 161 | SHMKey = Script.readNumber(); 162 | }else if(strcmp(Identifier, "adminport") == 0){ 163 | AdminPort = Script.readNumber(); 164 | }else if(strcmp(Identifier, "adminaddress") == 0){ 165 | strcpy(AdminAddress, Script.readString()); 166 | }else if(strcmp(Identifier, "querymanagerport") == 0){ 167 | QueryManagerPort = Script.readNumber(); 168 | }else if(strcmp(Identifier, "querymanageraddress") == 0){ 169 | strcpy(QueryManagerAddress, Script.readString()); 170 | }else if(strcmp(Identifier, "querymanageradminpw") == 0){ 171 | strcpy(QueryManagerAdminPW, Script.readString()); 172 | }else if(strcmp(Identifier, "querymanagergamepw") == 0){ 173 | strcpy(QueryManagerGamePW, Script.readString()); 174 | }else if(strcmp(Identifier, "querymanagerwebpw") == 0){ 175 | strcpy(QueryManagerWebPW, Script.readString()); 176 | }else if(strcmp(Identifier, "debuglevel") == 0){ 177 | DebugLevel = Script.readNumber(); 178 | }else if(strcmp(Identifier, "state") == 0){ 179 | PrivateWorld = (strcmp(Script.readIdentifier(), "private") == 0); 180 | }else if(strcmp(Identifier, "world") == 0){ 181 | strcpy(WorldName, Script.readString()); 182 | }else if(strcmp(Identifier, "beat") == 0){ 183 | Beat = Script.readNumber(); 184 | }else if(strcmp(Identifier, "admindatabase") == 0){ 185 | Script.readSymbol('('); 186 | strcpy(ADMIN_DATABASE.Product, Script.readIdentifier()); 187 | Script.readSymbol(','); 188 | strcpy(ADMIN_DATABASE.Database, Script.readString()); 189 | Script.readSymbol(','); 190 | strcpy(ADMIN_DATABASE.Login, Script.readString()); 191 | Script.readSymbol(','); 192 | strcpy(ADMIN_DATABASE.Password, Script.readString()); 193 | DisguisePassword(ADMIN_DATABASE.Password, PasswordKey); 194 | Script.readSymbol(','); 195 | strcpy(ADMIN_DATABASE.Host, Script.readString()); 196 | Script.readSymbol(','); 197 | strcpy(ADMIN_DATABASE.Port, Script.readString()); 198 | Script.readSymbol(')'); 199 | }else if(strcmp(Identifier, "volatiledatabase") == 0){ 200 | Script.readSymbol('('); 201 | strcpy(VOLATILE_DATABASE.Product, Script.readIdentifier()); 202 | Script.readSymbol(','); 203 | strcpy(VOLATILE_DATABASE.Database, Script.readString()); 204 | Script.readSymbol(','); 205 | strcpy(VOLATILE_DATABASE.Login, Script.readString()); 206 | Script.readSymbol(','); 207 | strcpy(VOLATILE_DATABASE.Password, Script.readString()); 208 | DisguisePassword(VOLATILE_DATABASE.Password, PasswordKey); 209 | Script.readSymbol(','); 210 | strcpy(VOLATILE_DATABASE.Host, Script.readString()); 211 | Script.readSymbol(','); 212 | strcpy(VOLATILE_DATABASE.Port, Script.readString()); 213 | Script.readSymbol(')'); 214 | }else if(strcmp(Identifier, "webdatabase") == 0){ 215 | Script.readSymbol('('); 216 | strcpy(WEB_DATABASE.Product, Script.readIdentifier()); 217 | Script.readSymbol(','); 218 | strcpy(WEB_DATABASE.Database, Script.readString()); 219 | Script.readSymbol(','); 220 | strcpy(WEB_DATABASE.Login, Script.readString()); 221 | Script.readSymbol(','); 222 | strcpy(WEB_DATABASE.Password, Script.readString()); 223 | DisguisePassword(WEB_DATABASE.Password, PasswordKey); 224 | Script.readSymbol(','); 225 | strcpy(WEB_DATABASE.Host, Script.readString()); 226 | Script.readSymbol(','); 227 | strcpy(WEB_DATABASE.Port, Script.readString()); 228 | Script.readSymbol(')'); 229 | }else if(strcmp(Identifier, "forumdatabase") == 0){ 230 | Script.readSymbol('('); 231 | strcpy(FORUM_DATABASE.Product, Script.readIdentifier()); 232 | Script.readSymbol(','); 233 | strcpy(FORUM_DATABASE.Database, Script.readString()); 234 | Script.readSymbol(','); 235 | strcpy(FORUM_DATABASE.Login, Script.readString()); 236 | Script.readSymbol(','); 237 | strcpy(FORUM_DATABASE.Password, Script.readString()); 238 | DisguisePassword(FORUM_DATABASE.Password, PasswordKey); 239 | Script.readSymbol(','); 240 | strcpy(FORUM_DATABASE.Host, Script.readString()); 241 | Script.readSymbol(','); 242 | strcpy(FORUM_DATABASE.Port, Script.readString()); 243 | Script.readSymbol(')'); 244 | }else if(strcmp(Identifier, "managerdatabase") == 0){ 245 | Script.readSymbol('('); 246 | strcpy(MANAGER_DATABASE.Product, Script.readIdentifier()); 247 | Script.readSymbol(','); 248 | strcpy(MANAGER_DATABASE.Database, Script.readString()); 249 | Script.readSymbol(','); 250 | strcpy(MANAGER_DATABASE.Login, Script.readString()); 251 | Script.readSymbol(','); 252 | strcpy(MANAGER_DATABASE.Password, Script.readString()); 253 | DisguisePassword(MANAGER_DATABASE.Password, PasswordKey); 254 | Script.readSymbol(','); 255 | strcpy(MANAGER_DATABASE.Host, Script.readString()); 256 | Script.readSymbol(','); 257 | strcpy(MANAGER_DATABASE.Port, Script.readString()); 258 | Script.readSymbol(')'); 259 | }else if(strcmp(Identifier, "querymanager") == 0){ 260 | Script.readSymbol('{'); 261 | do{ 262 | if(NumberOfQueryManagers >= NARRAY(QUERY_MANAGER)){ 263 | Script.error("Cannot handle more query managers"); 264 | } 265 | Script.readSymbol('('); 266 | strcpy(QUERY_MANAGER[NumberOfQueryManagers].Host, Script.readString()); 267 | Script.readSymbol(','); 268 | QUERY_MANAGER[NumberOfQueryManagers].Port = Script.readNumber(); 269 | Script.readSymbol(','); 270 | strcpy(QUERY_MANAGER[NumberOfQueryManagers].Password, Script.readString()); 271 | DisguisePassword(QUERY_MANAGER[NumberOfQueryManagers].Password, PasswordKey); 272 | Script.readSymbol(')'); 273 | NumberOfQueryManagers += 1; 274 | }while(Script.readSpecial() != '}'); 275 | }else{ 276 | // TODO(fusion): 277 | //error("Unknown configuration key \"%s\"", Identifier); 278 | } 279 | } 280 | } 281 | -------------------------------------------------------------------------------- /src/connections.hh: -------------------------------------------------------------------------------- 1 | #ifndef TIBIA_CONNECTIONS_HH_ 2 | #define TIBIA_CONNECTIONS_HH_ 1 3 | 4 | #include "common.hh" 5 | #include "crypto.hh" 6 | #include "enums.hh" 7 | #include "map.hh" 8 | 9 | struct TConnection; 10 | struct TPlayer; 11 | 12 | enum ClientCommand: int { 13 | CL_CMD_LOGIN_REQUEST = 10, 14 | CL_CMD_LOGIN = 11, 15 | CL_CMD_LOGOUT = 20, 16 | CL_CMD_PING = 30, 17 | CL_CMD_GO_PATH = 100, 18 | CL_CMD_GO_NORTH = 101, 19 | CL_CMD_GO_EAST = 102, 20 | CL_CMD_GO_SOUTH = 103, 21 | CL_CMD_GO_WEST = 104, 22 | CL_CMD_GO_STOP = 105, 23 | CL_CMD_GO_NORTHEAST = 106, 24 | CL_CMD_GO_SOUTHEAST = 107, 25 | CL_CMD_GO_SOUTHWEST = 108, 26 | CL_CMD_GO_NORTHWEST = 109, 27 | CL_CMD_ROTATE_NORTH = 111, 28 | CL_CMD_ROTATE_EAST = 112, 29 | CL_CMD_ROTATE_SOUTH = 113, 30 | CL_CMD_ROTATE_WEST = 114, 31 | CL_CMD_MOVE_OBJECT = 120, 32 | CL_CMD_TRADE_OBJECT = 125, 33 | CL_CMD_INSPECT_TRADE = 126, 34 | CL_CMD_ACCEPT_TRADE = 127, 35 | CL_CMD_REJECT_TRADE = 128, 36 | CL_CMD_USE_OBJECT = 130, 37 | CL_CMD_USE_TWO_OBJECTS = 131, 38 | CL_CMD_USE_ON_CREATURE = 132, 39 | CL_CMD_TURN_OBJECT = 133, 40 | CL_CMD_CLOSE_CONTAINER = 135, 41 | CL_CMD_UP_CONTAINER = 136, 42 | CL_CMD_EDIT_TEXT = 137, 43 | CL_CMD_EDIT_LIST = 138, 44 | CL_CMD_LOOK_AT_POINT = 140, 45 | CL_CMD_TALK = 150, 46 | CL_CMD_GET_CHANNELS = 151, 47 | CL_CMD_JOIN_CHANNEL = 152, 48 | CL_CMD_LEAVE_CHANNEL = 153, 49 | CL_CMD_PRIVATE_CHANNEL = 154, 50 | CL_CMD_PROCESS_REQUEST = 155, 51 | CL_CMD_REMOVE_REQUEST = 156, 52 | CL_CMD_CANCEL_REQUEST = 157, 53 | CL_CMD_SET_TACTICS = 160, 54 | CL_CMD_ATTACK = 161, 55 | CL_CMD_FOLLOW = 162, 56 | CL_CMD_INVITE_TO_PARTY = 163, 57 | CL_CMD_JOIN_PARTY = 164, 58 | CL_CMD_REVOKE_INVITATION = 165, 59 | CL_CMD_PASS_LEADERSHIP = 166, 60 | CL_CMD_LEAVE_PARTY = 167, 61 | CL_CMD_OPEN_CHANNEL = 170, 62 | CL_CMD_INVITE_TO_CHANNEL = 171, 63 | CL_CMD_EXCLUDE_FROM_CHANNEL = 172, 64 | CL_CMD_CANCEL = 190, 65 | CL_CMD_REFRESH_FIELD = 201, 66 | CL_CMD_REFRESH_CONTAINER = 202, 67 | CL_CMD_GET_OUTFIT = 210, 68 | CL_CMD_SET_OUTFIT = 211, 69 | CL_CMD_ADD_BUDDY = 220, 70 | CL_CMD_REMOVE_BUDDY = 221, 71 | CL_CMD_BUG_REPORT = 230, 72 | CL_CMD_RULE_VIOLATION = 231, 73 | CL_CMD_ERROR_FILE_ENTRY = 232, 74 | }; 75 | 76 | enum ServerCommand: int { 77 | SV_CMD_INIT_GAME = 10, 78 | SV_CMD_RIGHTS = 11, 79 | SV_CMD_LOGIN_ERROR = 20, 80 | SV_CMD_LOGIN_PREMIUM = 21, 81 | SV_CMD_LOGIN_WAITINGLIST = 22, 82 | SV_CMD_PING = 30, 83 | SV_CMD_FULLSCREEN = 100, 84 | SV_CMD_ROW_NORTH = 101, 85 | SV_CMD_ROW_EAST = 102, 86 | SV_CMD_ROW_SOUTH = 103, 87 | SV_CMD_ROW_WEST = 104, 88 | SV_CMD_FIELD_DATA = 105, 89 | SV_CMD_ADD_FIELD = 106, 90 | SV_CMD_CHANGE_FIELD = 107, 91 | SV_CMD_DELETE_FIELD = 108, 92 | SV_CMD_MOVE_CREATURE = 109, 93 | SV_CMD_CONTAINER = 110, 94 | SV_CMD_CLOSE_CONTAINER = 111, 95 | SV_CMD_CREATE_IN_CONTAINER = 112, 96 | SV_CMD_CHANGE_IN_CONTAINER = 113, 97 | SV_CMD_DELETE_IN_CONTAINER = 114, 98 | SV_CMD_SET_INVENTORY = 120, 99 | SV_CMD_DELETE_INVENTORY = 121, 100 | SV_CMD_TRADE_OFFER_OWN = 125, 101 | SV_CMD_TRADE_OFFER_PARTNER = 126, 102 | SV_CMD_CLOSE_TRADE = 127, 103 | SV_CMD_AMBIENTE = 130, 104 | SV_CMD_GRAPHICAL_EFFECT = 131, 105 | SV_CMD_TEXTUAL_EFFECT = 132, 106 | SV_CMD_MISSILE_EFFECT = 133, 107 | SV_CMD_MARK_CREATURE = 134, 108 | SV_CMD_CREATURE_HEALTH = 140, 109 | SV_CMD_CREATURE_LIGHT = 141, 110 | SV_CMD_CREATURE_OUTFIT = 142, 111 | SV_CMD_CREATURE_SPEED = 143, 112 | SV_CMD_CREATURE_SKULL = 144, 113 | SV_CMD_CREATURE_PARTY = 145, 114 | SV_CMD_EDIT_TEXT = 150, 115 | SV_CMD_EDIT_LIST = 151, 116 | SV_CMD_PLAYER_DATA = 160, 117 | SV_CMD_PLAYER_SKILLS = 161, 118 | SV_CMD_PLAYER_STATE = 162, 119 | SV_CMD_CLEAR_TARGET = 163, 120 | SV_CMD_TALK = 170, 121 | SV_CMD_CHANNELS = 171, 122 | SV_CMD_OPEN_CHANNEL = 172, 123 | SV_CMD_PRIVATE_CHANNEL = 173, 124 | SV_CMD_OPEN_REQUEST_QUEUE = 174, 125 | SV_CMD_DELETE_REQUEST = 175, 126 | SV_CMD_FINISH_REQUEST = 176, 127 | SV_CMD_CLOSE_REQUEST = 177, 128 | SV_CMD_OPEN_OWN_CHANNEL = 178, 129 | SV_CMD_CLOSE_CHANNEL = 179, 130 | SV_CMD_MESSAGE = 180, 131 | SV_CMD_SNAPBACK = 181, 132 | SV_CMD_FLOOR_UP = 190, 133 | SV_CMD_FLOOR_DOWN = 191, 134 | SV_CMD_OUTFIT = 200, 135 | SV_CMD_BUDDY_DATA = 210, 136 | SV_CMD_BUDDY_ONLINE = 211, 137 | SV_CMD_BUDDY_OFFLINE = 212, 138 | }; 139 | 140 | // TODO(fusion): The maximum number of connections should probably be kept in 141 | // sync with the maximum number of communication threads, or maybe it is the 142 | // same constant. 143 | #define MAX_CONNECTIONS 1100 144 | 145 | struct TKnownCreature { 146 | KNOWNCREATURESTATE State; 147 | uint32 CreatureID; 148 | TKnownCreature *Next; 149 | TConnection *Connection; 150 | }; 151 | 152 | struct TConnection { 153 | TConnection(void); 154 | void Process(void); 155 | void ResetTimer(int Command); 156 | void EmergencyPing(void); 157 | pid_t GetThreadID(void); 158 | bool SetLoginTimer(int Timeout); 159 | void StopLoginTimer(void); 160 | int GetSocket(void); 161 | const char *GetIPAddress(void); 162 | void Free(void); 163 | void Assign(void); 164 | void Connect(int Socket); 165 | void Login(void); 166 | bool JoinGame(TReadBuffer *Buffer); 167 | void EnterGame(void); 168 | void Die(void); 169 | void Logout(int Delay, bool StopFight); 170 | void Close(bool Delay); 171 | void Disconnect(void); 172 | TPlayer *GetPlayer(void); 173 | const char *GetName(void); 174 | void GetPosition(int *x, int *y, int *z); 175 | bool IsVisible(int x, int y, int z); 176 | KNOWNCREATURESTATE KnownCreature(uint32 ID, bool UpdateFollows); 177 | uint32 NewKnownCreature(uint32 NewID); 178 | void ClearKnownCreatureTable(bool Unchain); 179 | void UnchainKnownCreature(uint32 ID); 180 | 181 | bool InGame(void) const { 182 | return this->State == CONNECTION_GAME 183 | || this->State == CONNECTION_DEAD; 184 | } 185 | 186 | bool Live(void) const { 187 | return this->State == CONNECTION_LOGIN 188 | || this->State == CONNECTION_GAME 189 | || this->State == CONNECTION_DEAD 190 | || this->State == CONNECTION_LOGOUT; 191 | } 192 | 193 | // DATA 194 | // ================= 195 | uint8 InData[2048]; 196 | int InDataSize; 197 | bool SigIOPending; 198 | bool WaitingForACK; 199 | uint8 OutData[16384]; 200 | int NextToSend; 201 | int NextToCommit; 202 | int NextToWrite; 203 | bool Overflow; 204 | bool WillingToSend; 205 | TConnection *NextSendingConnection; 206 | uint32 RandomSeed; 207 | CONNECTIONSTATE State; 208 | pid_t ThreadID; 209 | timer_t LoginTimer; 210 | int Socket; 211 | char IPAddress[16]; 212 | TXTEASymmetricKey SymmetricKey; 213 | bool ConnectionIsOk; 214 | bool ClosingIsDelayed; 215 | uint32 TimeStamp; 216 | uint32 TimeStampAction; 217 | int TerminalType; 218 | int TerminalVersion; 219 | int TerminalOffsetX; 220 | int TerminalOffsetY; 221 | int TerminalWidth; 222 | int TerminalHeight; 223 | uint32 CharacterID; 224 | char Name[31]; 225 | TKnownCreature KnownCreatureTable[150]; 226 | }; 227 | 228 | // connections.cc 229 | TConnection *AssignFreeConnection(void); 230 | TConnection *GetFirstConnection(void); 231 | TConnection *GetNextConnection(void); 232 | void ProcessConnections(void); 233 | void InitConnections(void); 234 | void ExitConnections(void); 235 | 236 | // sending.cc 237 | void SendAll(void); 238 | bool BeginSendData(TConnection *Connection); 239 | void FinishSendData(TConnection *Connection); 240 | void SkipFlush(TConnection *Connection); 241 | void SendMapObject(TConnection *Connection, Object Obj); 242 | void SendMapPoint(TConnection *Connection, int x, int y, int z); 243 | void SendResult(TConnection *Connection, RESULT r); 244 | void SendRefresh(TConnection *Connection); 245 | void SendInitGame(TConnection *Connection, uint32 CreatureID); 246 | void SendRights(TConnection *Connection); 247 | void SendPing(TConnection *Connection); 248 | void SendFullScreen(TConnection *Connection); 249 | void SendRow(TConnection *Connection, int Direction); 250 | void SendFloors(TConnection *Connection, bool Up); 251 | void SendFieldData(TConnection *Connection, int x, int y, int z); 252 | void SendAddField(TConnection *Connection, int x, int y, int z, Object Obj); 253 | void SendChangeField(TConnection *Connection, int x, int y, int z, Object Obj); 254 | void SendDeleteField(TConnection *Connection, int x, int y, int z, Object Obj); 255 | void SendMoveCreature(TConnection *Connection, 256 | uint32 CreatureID, int DestX, int DestY, int DestZ); 257 | void SendContainer(TConnection *Connection, int ContainerNr); 258 | void SendCloseContainer(TConnection *Connection, int ContainerNr); 259 | void SendCreateInContainer(TConnection *Connection, int ContainerNr, Object Obj); 260 | void SendChangeInContainer(TConnection *Connection, int ContainerNr, Object Obj); 261 | void SendDeleteInContainer(TConnection *Connection, int ContainerNr, Object Obj); 262 | void SendBodyInventory(TConnection *Connection, uint32 CreatureID); 263 | void SendSetInventory(TConnection *Connection, int Position, Object Obj); 264 | void SendDeleteInventory(TConnection *Connection, int Position); 265 | void SendTradeOffer(TConnection *Connection, const char *Name, bool OwnOffer, Object Obj); 266 | void SendCloseTrade(TConnection *Connection); 267 | void SendAmbiente(TConnection *Connection); 268 | void SendGraphicalEffect(TConnection *Connection, int x, int y, int z, int Type); 269 | void SendTextualEffect(TConnection *Connection, int x, int y, int z, int Color, const char *Text); 270 | void SendMissileEffect(TConnection *Connection, int OrigX, int OrigY, int OrigZ, 271 | int DestX, int DestY, int DestZ, int Type); 272 | void SendMarkCreature(TConnection *Connection, uint32 CreatureID, int Color); 273 | void SendCreatureHealth(TConnection *Connection, uint32 CreatureID); 274 | void SendCreatureLight(TConnection *Connection, uint32 CreatureID); 275 | void SendCreatureOutfit(TConnection *Connection, uint32 CreatureID); 276 | void SendCreatureSpeed(TConnection *Connection, uint32 CreatureID); 277 | void SendCreatureSkull(TConnection *Connection, uint32 CreatureID); 278 | void SendCreatureParty(TConnection *Connection, uint32 CreatureID); 279 | void SendEditText(TConnection *Connection, Object Obj); 280 | void SendEditList(TConnection *Connection, uint8 Type, uint32 ID, const char *Text); 281 | void SendPlayerData(TConnection *Connection); 282 | void SendPlayerSkills(TConnection *Connection); 283 | void SendPlayerState(TConnection *Connection, uint8 State); 284 | void SendClearTarget(TConnection *Connection); 285 | void SendTalk(TConnection *Connection, uint32 StatementID, 286 | const char *Sender, int Mode, const char *Text, int Data); 287 | void SendTalk(TConnection *Connection, uint32 StatementID, 288 | const char *Sender, int Mode, int Channel, const char *Text); 289 | void SendTalk(TConnection *Connection, uint32 StatementID, 290 | const char *Sender, int Mode, int x, int y, int z, const char *Text); 291 | void SendChannels(TConnection *Connection); 292 | void SendOpenChannel(TConnection *Connection, int Channel); 293 | void SendPrivateChannel(TConnection *Connection, const char *Name); 294 | void SendOpenRequestQueue(TConnection *Connection); 295 | void SendDeleteRequest(TConnection *Connection, const char *Name); 296 | void SendFinishRequest(TConnection *Connection, const char *Name); 297 | void SendCloseRequest(TConnection *Connection); 298 | void SendOpenOwnChannel(TConnection *Connection, int Channel); 299 | void SendCloseChannel(TConnection *Connection, int Channel); 300 | void SendMessage(TConnection *Connection, int Mode, const char *Text, ...) ATTR_PRINTF(3, 4); 301 | void SendSnapback(TConnection *Connection); 302 | void SendOutfit(TConnection *Connection); 303 | void SendBuddyData(TConnection *Connection, uint32 CharacterID, const char *Name, bool Online); 304 | void SendBuddyStatus(TConnection *Connection, uint32 CharacterID, bool Online); 305 | void BroadcastMessage(int Mode, const char *Text, ...) ATTR_PRINTF(2, 3); 306 | void CreateGamemasterRequest(const char *Name, const char *Text); 307 | void DeleteGamemasterRequest(const char *Name); 308 | void InitSending(void); 309 | void ExitSending(void); 310 | 311 | // receiving.cc 312 | void ReceiveData(TConnection *Connection); 313 | void ReceiveData(void); 314 | void InitReceiving(void); 315 | void ExitReceiving(void); 316 | 317 | #endif // TIBIA_CONNECTIONS_HH_ 318 | -------------------------------------------------------------------------------- /src/utils.cc: -------------------------------------------------------------------------------- 1 | #include "common.hh" 2 | 3 | #include 4 | 5 | static TErrorFunction *ErrorFunction; 6 | static TPrintFunction *PrintFunction; 7 | static char StandardLogFile[4096]; 8 | 9 | void SetErrorFunction(TErrorFunction *Function){ 10 | ErrorFunction = Function; 11 | } 12 | 13 | void SetPrintFunction(TPrintFunction *Function){ 14 | PrintFunction = Function; 15 | } 16 | 17 | void SetStandardLogFile(const char *FileName){ 18 | strcpy(StandardLogFile, FileName); 19 | } 20 | 21 | void SilentHandler(const char *Text){ 22 | // no-op 23 | } 24 | 25 | void SilentHandler(int Level, const char *Text){ 26 | // no-op 27 | } 28 | 29 | void LogFileHandler(const char *Text){ 30 | if(StandardLogFile[0] == 0){ 31 | return; 32 | } 33 | 34 | FILE *File = fopen(StandardLogFile, "at"); 35 | if(File != NULL){ 36 | fprintf(File, "%s", Text); 37 | if(fclose(File) != 0){ 38 | error("LogfileHandler: Fehler %d beim Schließen der Datei.\n", errno); 39 | } 40 | } 41 | } 42 | 43 | void LogFileHandler(int Level, const char *Text){ 44 | LogFileHandler(Text); 45 | } 46 | 47 | void error(const char *Text, ...){ 48 | char s[1024]; 49 | 50 | va_list ap; 51 | va_start(ap, Text); 52 | vsnprintf(s, sizeof(s), Text, ap); 53 | va_end(ap); 54 | 55 | if(ErrorFunction){ 56 | ErrorFunction(s); 57 | }else{ 58 | printf("%s", s); 59 | } 60 | } 61 | 62 | void print(int Level, const char *Text, ...){ 63 | char s[1024]; 64 | 65 | va_list ap; 66 | va_start(ap, Text); 67 | vsnprintf(s, sizeof(s), Text, ap); 68 | va_end(ap); 69 | 70 | if(PrintFunction){ 71 | PrintFunction(Level, s); 72 | }else{ 73 | printf("%s", s); 74 | } 75 | } 76 | 77 | int random(int Min, int Max){ 78 | int Range = (Max - Min) + 1; 79 | int Result = Min; 80 | if(Range > 0){ 81 | Result += rand() % Range; 82 | } 83 | return Result; 84 | } 85 | 86 | bool FileExists(const char *FileName){ 87 | struct stat Buffer; 88 | bool Result = true; 89 | if(stat(FileName, &Buffer) != 0){ 90 | if(errno != ENOENT){ 91 | error("FileExists: Unerwarteter Fehlercode %d.\n", errno); 92 | } 93 | Result = false; 94 | } 95 | return Result; 96 | } 97 | 98 | // String Utility 99 | // ============================================================================= 100 | bool isSpace(int c){ 101 | return c == ' ' 102 | || c == '\t' 103 | || c == '\n' 104 | || c == '\r' 105 | || c == '\v' 106 | || c == '\f'; 107 | } 108 | 109 | bool isAlpha(int c){ 110 | // TODO(fusion): This is most likely wrong! We're assuming a direct conversion 111 | // from `char` to `int` which will cause sign extension for negative values. This 112 | // wouldn't be a problem if we expected to parse only streams of `char[]` but can 113 | // be problematic for the output of `getc` which returns bytes as `unsigned char` 114 | // converted to `int`. 115 | // TLDR: The parameter `c` should be `uint8`. 116 | return ('A' <= c && c <= 'Z') 117 | || ('a' <= c && c <= 'z') 118 | || c == -0x1C // E4 => ä 119 | || c == -0x0A // F6 => ö 120 | || c == -0x04 // FC => ü 121 | || c == -0x3C // C4 => Ä 122 | || c == -0x2A // D6 => Ö 123 | || c == -0x24 // DC => Ü 124 | || c == -0x21; // DF => ß 125 | } 126 | 127 | bool isEngAlpha(int c){ 128 | return ('A' <= c && c <= 'Z') 129 | || ('a' <= c && c <= 'z'); 130 | } 131 | 132 | bool isDigit(int c){ 133 | return ('0' <= c && c <= '9'); 134 | } 135 | 136 | int toLower(int c){ 137 | // TODO(fusion): Same problem as `isAlpha`. 138 | if(('A' <= c && c <= 'Z') || (0xC0 <= c && c <= 0xDE && c != 0xD7)){ 139 | c += 32; 140 | } 141 | return c; 142 | } 143 | 144 | int toUpper(int c){ 145 | // TODO(fusion): Same problem as `isAlpha`. 146 | if(('a' <= c && c <= 'z') || (0xE0 <= c && c <= 0xFE && c != 0xF7)){ 147 | c -= 32; 148 | } 149 | return c; 150 | } 151 | 152 | char *strLower(char *s){ 153 | for(int i = 0; s[i] != 0; i += 1){ 154 | s[i] = (char)toLower(s[i]); 155 | } 156 | return s; 157 | } 158 | 159 | char *strUpper(char *s){ 160 | for(int i = 0; s[i] != 0; i += 1){ 161 | s[i] = (char)toUpper(s[i]); 162 | } 163 | return s; 164 | } 165 | 166 | int stricmp(const char *s1, const char *s2, int Max /*= INT_MAX*/){ 167 | for(int i = 0; i < Max; i += 1){ 168 | int c1 = toLower(s1[i]); 169 | int c2 = toLower(s2[i]); 170 | if(c1 > c2){ 171 | return 1; 172 | }else if(c1 < c2){ 173 | return -1; 174 | }else{ 175 | ASSERT(c1 == c2); 176 | if(c1 == 0){ 177 | break; 178 | } 179 | } 180 | } 181 | return 0; 182 | } 183 | 184 | char *findFirst(char *s, char c){ 185 | return strchr(s, (int)c); 186 | } 187 | 188 | char *findLast(char *s, char c){ 189 | char *Current = s; 190 | char *Last = NULL; 191 | while(true){ 192 | Current = strchr(Current, (int)c); 193 | if(Current == NULL) 194 | break; 195 | Last = Current; 196 | Current += 1; // skip character 197 | } 198 | return Last; 199 | } 200 | 201 | // BitSet Utility 202 | // ============================================================================= 203 | bool CheckBitIndex(int BitSetBytes, int Index){ 204 | return Index >= 0 && Index < (BitSetBytes * 8); 205 | } 206 | 207 | bool CheckBit(uint8 *BitSet, int Index){ 208 | int ByteIndex = (int)(Index / 8); 209 | uint8 BitMask = (uint8)(1 << (Index % 8)); 210 | return (BitSet[ByteIndex] & BitMask) != 0; 211 | } 212 | 213 | void SetBit(uint8 *BitSet, int Index){ 214 | int ByteIndex = (int)(Index / 8); 215 | uint8 BitMask = (uint8)(1 << (Index % 8)); 216 | BitSet[ByteIndex] |= BitMask; 217 | } 218 | 219 | void ClearBit(uint8 *BitSet, int Index){ 220 | int ByteIndex = (int)(Index / 8); 221 | uint8 BitMask = (uint8)(1 << (Index % 8)); 222 | BitSet[ByteIndex] &= ~BitMask; 223 | } 224 | 225 | 226 | // TReadStream 227 | // ============================================================================= 228 | bool TReadStream::readFlag(void){ 229 | return this->readByte() != 0; 230 | } 231 | 232 | uint16 TReadStream::readWord(void){ 233 | // NOTE(fusion): Data is encoded in little endian. 234 | uint8 Byte0 = this->readByte(); 235 | uint8 Byte1 = this->readByte(); 236 | return ((uint16)Byte1 << 8) | (uint16)Byte0; 237 | } 238 | 239 | uint32 TReadStream::readQuad(void){ 240 | // NOTE(fusion): Data is encoded in little endian. 241 | uint8 Byte0 = this->readByte(); 242 | uint8 Byte1 = this->readByte(); 243 | uint8 Byte2 = this->readByte(); 244 | uint8 Byte3 = this->readByte(); 245 | return ((uint32)Byte3 << 24) | ((uint32)Byte2 << 16) 246 | | ((uint32)Byte1 << 8) | (uint32)Byte0; 247 | } 248 | 249 | void TReadStream::readString(char *Buffer, int MaxLength){ 250 | if(Buffer == NULL || MaxLength == 0){ 251 | error("TReadStream::readString: Übergebener Puffer existiert nicht.\n"); 252 | throw "internal error"; 253 | } 254 | 255 | int Length = (int)this->readWord(); 256 | if(Length == 0xFFFF){ 257 | Length = (int)this->readQuad(); 258 | } 259 | 260 | if(Length > 0){ 261 | if(MaxLength < 0 || MaxLength > Length){ 262 | this->readBytes((uint8*)Buffer, Length); 263 | Buffer[Length] = 0; 264 | }else{ 265 | this->readBytes((uint8*)Buffer, MaxLength - 1); 266 | this->skip(Length - MaxLength + 1); 267 | Buffer[MaxLength - 1] = 0; 268 | } 269 | }else{ 270 | Buffer[0] = 0; 271 | } 272 | } 273 | 274 | void TReadStream::readBytes(uint8 *Buffer, int Count){ 275 | if(Buffer == NULL){ 276 | error("TReadStream::readBytes: Übergebener Puffer existiert nicht.\n"); 277 | throw "internal error"; 278 | } 279 | 280 | for(int i = 0; i < Count; i += 1){ 281 | Buffer[i] = this->readByte(); 282 | } 283 | } 284 | 285 | // TReadBuffer 286 | // ============================================================================= 287 | TReadBuffer::TReadBuffer(const uint8 *Data, int Size){ 288 | if(Data == NULL){ 289 | error("TReadBuffer::TReadBuffer: data ist NULL.\n"); 290 | Size = 0; 291 | }else if(Size < 0){ 292 | error("TReadBuffer::TReadBuffer: Ungültige Datengröße %d.\n", Size); 293 | Size = 0; 294 | } 295 | 296 | this->Data = Data; 297 | this->Size = Size; 298 | this->Position = 0; 299 | } 300 | 301 | uint8 TReadBuffer::readByte(void){ 302 | if((this->Size - this->Position) < 1){ 303 | throw "buffer empty"; 304 | } 305 | 306 | uint8 Byte = this->Data[this->Position]; 307 | this->Position += 1; 308 | return Byte; 309 | } 310 | 311 | uint16 TReadBuffer::readWord(void){ 312 | if((this->Size - this->Position) < 2){ 313 | throw "buffer empty"; 314 | } 315 | 316 | uint8 Byte0 = this->Data[this->Position]; 317 | uint8 Byte1 = this->Data[this->Position + 1]; 318 | this->Position += 2; 319 | return ((uint16)Byte1 << 8) | (uint16)Byte0; 320 | } 321 | 322 | uint32 TReadBuffer::readQuad(void){ 323 | if((this->Size - this->Position) < 4){ 324 | throw "buffer empty"; 325 | } 326 | 327 | uint8 Byte0 = this->Data[this->Position]; 328 | uint8 Byte1 = this->Data[this->Position + 1]; 329 | uint8 Byte2 = this->Data[this->Position + 2]; 330 | uint8 Byte3 = this->Data[this->Position + 3]; 331 | this->Position += 4; 332 | return ((uint32)Byte3 << 24) | ((uint32)Byte2 << 16) 333 | | ((uint32)Byte1 << 8) | (uint32)Byte0; 334 | } 335 | 336 | void TReadBuffer::readBytes(uint8 *Buffer, int Count){ 337 | if(Buffer == NULL || Count <= 0){ 338 | error("TReadBuffer::readBytes: Übergebener Puffer existiert nicht.\n"); 339 | throw "buffer not existing"; 340 | } 341 | 342 | if((this->Size - this->Position) < Count){ 343 | throw "buffer empty"; 344 | } 345 | 346 | memcpy(Buffer, &this->Data[this->Position], Count); 347 | this->Position += Count; 348 | } 349 | 350 | bool TReadBuffer::eof(void){ 351 | return this->Size <= this->Position; 352 | } 353 | 354 | void TReadBuffer::skip(int Count){ 355 | if((this->Size - this->Position) < Count){ 356 | throw "buffer empty"; 357 | } 358 | 359 | this->Position += Count; 360 | } 361 | 362 | // TWriteStream 363 | // ============================================================================= 364 | void TWriteStream::writeFlag(bool Flag){ 365 | this->writeByte((uint8)Flag); 366 | } 367 | 368 | void TWriteStream::writeWord(uint16 Word){ 369 | this->writeByte((uint8)(Word)); 370 | this->writeByte((uint8)(Word >> 8)); 371 | } 372 | 373 | void TWriteStream::writeQuad(uint32 Quad){ 374 | this->writeByte((uint8)(Quad)); 375 | this->writeByte((uint8)(Quad >> 8)); 376 | this->writeByte((uint8)(Quad >> 16)); 377 | this->writeByte((uint8)(Quad >> 24)); 378 | } 379 | 380 | void TWriteStream::writeString(const char *String){ 381 | if(String == NULL){ 382 | this->writeWord(0); 383 | return; 384 | } 385 | 386 | int StringLength = (int)strlen(String); 387 | ASSERT(StringLength >= 0); 388 | if(StringLength < 0xFFFF){ 389 | this->writeWord((uint16)StringLength); 390 | }else{ 391 | this->writeWord(0xFFFF); 392 | this->writeQuad((uint32)StringLength); 393 | } 394 | 395 | if(StringLength > 0){ 396 | this->writeBytes((const uint8*)String, StringLength); 397 | } 398 | } 399 | 400 | void TWriteStream::writeBytes(const uint8 *Buffer, int Count){ 401 | if(Buffer == NULL){ 402 | error("TWriteStream::writeBytes: Übergebener Puffer existiert nicht.\n"); 403 | throw "internal error"; 404 | } 405 | 406 | for(int i = 0; i < Count; i += 1){ 407 | this->writeByte(Buffer[i]); 408 | } 409 | } 410 | 411 | // TWriteBuffer 412 | // ============================================================================= 413 | TWriteBuffer::TWriteBuffer(uint8 *Data, int Size){ 414 | if(Data == NULL){ 415 | error("TWriteBuffer::TWriteBuffer: data ist NULL.\n"); 416 | Size = 0; 417 | }else if(Size < 0){ 418 | error("TWriteBuffer::TWriteBuffer: Ungültige Datengröße %d.\n", Size); 419 | Size = 0; 420 | } 421 | 422 | this->Data = Data; 423 | this->Size = Size; 424 | this->Position = 0; 425 | } 426 | 427 | void TWriteBuffer::writeByte(uint8 Byte){ 428 | if((this->Size - this->Position) < 1){ 429 | throw "buffer full"; 430 | } 431 | 432 | this->Data[this->Position] = Byte; 433 | this->Position += 1; 434 | } 435 | 436 | void TWriteBuffer::writeWord(uint16 Word){ 437 | if((this->Size - this->Position) < 2){ 438 | throw "buffer full"; 439 | } 440 | 441 | this->Data[this->Position] = (uint8)(Word); 442 | this->Data[this->Position + 1] = (uint8)(Word >> 8); 443 | this->Position += 2; 444 | } 445 | 446 | void TWriteBuffer::writeQuad(uint32 Quad){ 447 | if((this->Size - this->Position) < 4){ 448 | throw "buffer full"; 449 | } 450 | 451 | this->Data[this->Position] = (uint8)(Quad); 452 | this->Data[this->Position + 1] = (uint8)(Quad >> 8); 453 | this->Data[this->Position + 2] = (uint8)(Quad >> 16); 454 | this->Data[this->Position + 3] = (uint8)(Quad >> 24); 455 | this->Position += 4; 456 | } 457 | 458 | void TWriteBuffer::writeBytes(const uint8 *Buffer, int Count){ 459 | if((this->Size - this->Position) < Count){ 460 | throw "buffer full"; 461 | } 462 | 463 | memcpy(&this->Data[this->Position], Buffer, Count); 464 | this->Position += Count; 465 | } 466 | 467 | // TDynamicWriteBuffer 468 | // ============================================================================= 469 | TDynamicWriteBuffer::TDynamicWriteBuffer(int InitialSize) 470 | : TWriteBuffer(new uint8[InitialSize], InitialSize) 471 | { 472 | // no-op 473 | } 474 | 475 | void TDynamicWriteBuffer::resizeBuffer(void){ 476 | ASSERT(this->Size > 0); 477 | int Size = this->Size * 2; 478 | uint8 *Data = new uint8[Size]; 479 | if(this->Data != NULL){ 480 | memcpy(Data, this->Data, this->Size); 481 | delete[] this->Data; 482 | } 483 | 484 | this->Data = Data; 485 | this->Size = Size; 486 | } 487 | 488 | void TDynamicWriteBuffer::writeByte(uint8 Byte){ 489 | while((this->Size - this->Position) < 1){ 490 | this->resizeBuffer(); 491 | } 492 | 493 | this->Data[this->Position] = Byte; 494 | this->Position += 1; 495 | } 496 | 497 | void TDynamicWriteBuffer::writeWord(uint16 Word){ 498 | while((this->Size - this->Position) < 2){ 499 | this->resizeBuffer(); 500 | } 501 | 502 | this->Data[this->Position] = (uint8)(Word); 503 | this->Data[this->Position + 1] = (uint8)(Word >> 8); 504 | this->Position += 2; 505 | } 506 | 507 | void TDynamicWriteBuffer::writeQuad(uint32 Quad){ 508 | while((this->Size - this->Position) < 4){ 509 | this->resizeBuffer(); 510 | } 511 | 512 | this->Data[this->Position] = (uint8)(Quad); 513 | this->Data[this->Position + 1] = (uint8)(Quad >> 8); 514 | this->Data[this->Position + 2] = (uint8)(Quad >> 16); 515 | this->Data[this->Position + 3] = (uint8)(Quad >> 24); 516 | this->Position += 4; 517 | } 518 | 519 | void TDynamicWriteBuffer::writeBytes(const uint8 *Buffer, int Count){ 520 | while((this->Size - this->Position) < Count){ 521 | this->resizeBuffer(); 522 | } 523 | 524 | memcpy(&this->Data[this->Position], Buffer, Count); 525 | this->Position += Count; 526 | } 527 | 528 | TDynamicWriteBuffer::~TDynamicWriteBuffer(void){ 529 | if(this->Data != NULL){ 530 | delete[] this->Data; 531 | } 532 | } 533 | -------------------------------------------------------------------------------- /src/containers.hh: -------------------------------------------------------------------------------- 1 | #ifndef TIBIA_CONTAINERS_HH_ 2 | #define TIBIA_CONTAINERS_HH_ 1 3 | 4 | #include "common.hh" 5 | 6 | // NOTE(fusion): All containers are automatically managed by their constructors 7 | // and destructors, meaning raw copies could result in memory corruption through 8 | // double frees, use after frees, etc... To avoid that, we need to make them all 9 | // non copyable. 10 | #define NONCOPYABLE(Type) \ 11 | Type(const Type &Other) = delete; \ 12 | void operator=(const Type &Other) = delete; 13 | 14 | // NOTE(fusion): What the actual fuck. This is an ever growing dynamic array 15 | // with each access through `operator()` growing it to make sure the requested 16 | // index is valid, even for negative indices. 17 | template 18 | struct vector{ 19 | NONCOPYABLE(vector) 20 | 21 | vector(int min, int max, int block){ 22 | int space = (max - min) + 1; 23 | if(space < 1){ 24 | error("vector: Ungueltige Feldgroesse %d bis %d.\n", min, max); 25 | space = 1; 26 | } 27 | 28 | if(block < 0){ 29 | error("vector: Ungueltige Blockgroesse %d.\n", block); 30 | block = 0; 31 | } 32 | 33 | this->min = min; 34 | this->max = max; 35 | this->start = min; 36 | this->space = space; 37 | this->block = block; 38 | this->initialized = false; 39 | this->entry = new T[this->space]; 40 | } 41 | 42 | vector(int min, int max, int block, T init) : vector(min, max, block) { 43 | this->initialized = true; 44 | this->init = init; 45 | for(int i = 0; i < this->space; i += 1){ 46 | this->entry[i] = init; 47 | } 48 | } 49 | 50 | ~vector(void){ 51 | delete[] this->entry; 52 | } 53 | 54 | T *at(int index){ 55 | // TODO(fusion): This is probably not the best way to achieve this. 56 | 57 | while(index < this->start){ 58 | int increment = this->block; 59 | if(increment == 0){ 60 | increment = this->space; 61 | } 62 | 63 | T *entry = new T[this->space + increment]; 64 | for(int i = this->min; i <= this->max; i += 1){ 65 | int old_index = i - this->start; 66 | int new_index = old_index + increment; 67 | 68 | // TODO(fusion): Do we actually need to swap elements here? I'm 69 | // assuming some non-trivial structures that would invoke their 70 | // destructors when `this->entry` gets deleted just below. 71 | std::swap(entry[new_index], this->entry[old_index]); 72 | } 73 | 74 | if(this->entry != NULL){ 75 | delete[] this->entry; 76 | } 77 | this->entry = entry; 78 | this->start -= increment; 79 | this->space += increment; 80 | } 81 | 82 | while(index >= (this->start + this->space)){ 83 | int increment = this->block; 84 | if(increment == 0){ 85 | increment = this->space; 86 | } 87 | 88 | T *entry = new T[this->space + increment]; 89 | for(int i = this->min; i <= this->max; i += 1){ 90 | int old_index = i - this->start; 91 | int new_index = old_index; 92 | 93 | // TODO(fusion): Same as above. 94 | std::swap(entry[new_index], this->entry[old_index]); 95 | } 96 | 97 | if(this->entry != NULL){ 98 | delete[] this->entry; 99 | } 100 | this->entry = entry; 101 | this->space += increment; 102 | } 103 | 104 | while(index < this->min){ 105 | this->min -= 1; 106 | if(this->initialized){ 107 | this->entry[this->min - this->start] = this->init; 108 | } 109 | } 110 | 111 | while(index > this->max){ 112 | this->max += 1; 113 | if(this->initialized){ 114 | this->entry[this->max - this->start] = this->init; 115 | } 116 | } 117 | 118 | return &this->entry[index - this->start]; 119 | } 120 | 121 | T copyAt(int index) const { 122 | T Result = {}; 123 | if(index >= this->start && index < (this->start + this->space)){ 124 | Result = this->entry[index - this->start]; 125 | }else if(this->initialized){ 126 | Result = this->init; 127 | } 128 | return Result; 129 | } 130 | 131 | // DATA 132 | // ================= 133 | int min; 134 | int max; 135 | int start; 136 | int space; 137 | int block; 138 | bool initialized; 139 | T init; 140 | T *entry; 141 | }; 142 | 143 | template 144 | struct priority_queue_entry{ 145 | K Key; 146 | T Data; 147 | }; 148 | 149 | template 150 | struct priority_queue{ 151 | NONCOPYABLE(priority_queue) 152 | 153 | priority_queue(int capacity, int increment){ 154 | Entry = new vector>(1, capacity, increment); 155 | Entries = 0; 156 | } 157 | 158 | ~priority_queue(void){ 159 | delete Entry; 160 | } 161 | 162 | void insert(K Key, T Data){ 163 | this->Entries += 1; 164 | int CurrentIndex = this->Entries; 165 | *this->Entry->at(CurrentIndex) = {Key, Data}; 166 | while(CurrentIndex > 1){ 167 | int ParentIndex = CurrentIndex / 2; 168 | priority_queue_entry *Current = this->Entry->at(CurrentIndex); 169 | priority_queue_entry *Parent = this->Entry->at(ParentIndex); 170 | if(Parent->Key <= Current->Key) 171 | break; 172 | std::swap(*Current, *Parent); 173 | CurrentIndex = ParentIndex; 174 | } 175 | } 176 | 177 | void deleteMin(void){ 178 | if(this->Entries < 1){ 179 | error("priority_queue::deleteMin: Warteschlange ist leer.\n"); 180 | return; 181 | } 182 | 183 | if(this->Entries > 1){ 184 | int CurrentIndex = 1; 185 | int LastIndex = this->Entries; 186 | std::swap(*this->Entry->at(CurrentIndex), 187 | *this->Entry->at(LastIndex)); 188 | 189 | // TODO(fusion): This may be an oversight but the decompiled version 190 | // checks in the loop below would INCLUDE `LastIndex`, which I assume 191 | // is a bug? The first and last elements were just swapped and what 192 | // was the first element in the queue is now at the end and should be 193 | // considered "removed". 194 | while(1){ 195 | int SmallestIndex = CurrentIndex * 2; 196 | if(SmallestIndex >= LastIndex){ 197 | break; 198 | } 199 | 200 | priority_queue_entry *Smallest = this->Entry->at(SmallestIndex); 201 | if((SmallestIndex + 1) < LastIndex){ 202 | priority_queue_entry *Other = this->Entry->at(SmallestIndex + 1); 203 | if(Other->Key < Smallest->Key){ 204 | Smallest = Other; 205 | SmallestIndex += 1; 206 | } 207 | } 208 | 209 | priority_queue_entry *Current = this->Entry->at(CurrentIndex); 210 | if(Current->Key <= Smallest->Key){ 211 | break; 212 | } 213 | 214 | std::swap(*Current, *Smallest); 215 | CurrentIndex = SmallestIndex; 216 | } 217 | } 218 | 219 | this->Entries -= 1; 220 | } 221 | 222 | // DATA 223 | // ================= 224 | vector> *Entry; 225 | int Entries; 226 | }; 227 | 228 | template 229 | struct matrix{ 230 | NONCOPYABLE(matrix) 231 | 232 | matrix(int xmin, int xmax, int ymin, int ymax){ 233 | int dx = (xmax - xmin) + 1; 234 | int dy = (ymax - ymin) + 1; 235 | 236 | if(dx < 1 || dy < 1){ 237 | error("matrix: Ungueltige Feldgroesse %d..%d, %d..%d.\n", xmin, xmax, ymin, ymax); 238 | 239 | if(dx < 1){ 240 | dx = 1; 241 | } 242 | 243 | if(dy < 1){ 244 | dy = 1; 245 | } 246 | } 247 | 248 | this->xmin = xmin; 249 | this->ymin = ymin; 250 | this->dx = dx; 251 | this->dy = dy; 252 | this->entry = new T[dx * dy]; 253 | } 254 | 255 | matrix(int xmin, int xmax, int ymin, int ymax, T init) : matrix(xmin, xmax, ymin, ymax) { 256 | int count = this->dx * this->dy; 257 | for(int i = 0; i < count; i += 1){ 258 | this->entry[i] = init; 259 | } 260 | } 261 | 262 | ~matrix(void){ 263 | delete[] this->entry; 264 | } 265 | 266 | // NOTE(fusion): Same as `at` but returns NULL on out of bounds coordinates. 267 | T *boundedAt(int x, int y){ 268 | int xoffset = x - this->xmin; 269 | int yoffset = y - this->ymin; 270 | if(xoffset < 0 || xoffset >= this->dx || yoffset < 0 || yoffset >= this->dy){ 271 | return NULL; 272 | }else{ 273 | return &this->entry[yoffset * this->dx + xoffset]; 274 | } 275 | } 276 | 277 | T *at(int x, int y){ 278 | int xoffset = x - this->xmin; 279 | int yoffset = y - this->ymin; 280 | if(xoffset < 0 || xoffset >= this->dx || yoffset < 0 || yoffset >= this->dy){ 281 | error("matrix::operator(): Ungueltiger Index %d/%d.\n", x, y); 282 | return &this->entry[0]; 283 | }else{ 284 | // TODO(fusion): Are we really storing this in row major order? 285 | return &this->entry[yoffset * this->dx + xoffset]; 286 | } 287 | } 288 | 289 | // DATA 290 | // ================= 291 | int xmin; 292 | int ymin; 293 | int dx; 294 | int dy; 295 | T *entry; 296 | }; 297 | 298 | template 299 | struct matrix3d{ 300 | NONCOPYABLE(matrix3d) 301 | 302 | matrix3d(int xmin, int xmax, int ymin, int ymax, int zmin, int zmax){ 303 | int dx = (xmax - xmin) + 1; 304 | int dy = (ymax - ymin) + 1; 305 | int dz = (zmax - zmin) + 1; 306 | 307 | if(dx < 1 || dy < 1 || dz < 1){ 308 | error("matrix3d: Ungueltige Feldgroesse %d..%d, %d..%d, %d..%d.\n", 309 | xmin, xmax, ymin, ymax, zmin, zmax); 310 | 311 | if(dx < 1){ 312 | dx = 1; 313 | } 314 | 315 | if(dy < 1){ 316 | dy = 1; 317 | } 318 | 319 | if(dz < 1){ 320 | dz = 1; 321 | } 322 | } 323 | 324 | this->xmin = xmin; 325 | this->ymin = ymin; 326 | this->zmin = zmin; 327 | this->dx = dx; 328 | this->dy = dy; 329 | this->dz = dz; 330 | this->entry = new T[dx * dy * dz]; 331 | } 332 | 333 | matrix3d(int xmin, int xmax, int ymin, int ymax, int zmin, int zmax, T init) 334 | : matrix3d(xmin, xmax, ymin, ymax, zmin, zmax) { 335 | int count = this->dx * this->dy * this->dz; 336 | for(int i = 0; i < count; i += 1){ 337 | this->entry[i] = init; 338 | } 339 | } 340 | 341 | ~matrix3d(void){ 342 | delete[] this->entry; 343 | } 344 | 345 | T *at(int x, int y, int z){ 346 | int xoffset = x - this->xmin; 347 | int yoffset = y - this->ymin; 348 | int zoffset = z - this->zmin; 349 | if(xoffset < 0 || xoffset >= this->dx 350 | || yoffset < 0 || yoffset >= this->dy 351 | || zoffset < 0 || zoffset >= this->dz){ 352 | error("matrix3d::operator(): Ungueltiger Index %d/%d/%d.\n", x, y, z); 353 | return &this->entry[0]; 354 | }else{ 355 | // TODO(fusion): Same as `matrix::at` on the XY plane. 356 | return &this->entry[zoffset * this->dx * this->dy 357 | + yoffset * this->dx 358 | + xoffset]; 359 | } 360 | } 361 | 362 | // DATA 363 | // ================= 364 | int xmin; 365 | int ymin; 366 | int zmin; 367 | int dx; 368 | int dy; 369 | int dz; 370 | T *entry; 371 | }; 372 | 373 | template 374 | struct listnode{ 375 | listnode *next; 376 | listnode *prev; 377 | T data; 378 | }; 379 | 380 | template 381 | struct list{ 382 | NONCOPYABLE(list) 383 | 384 | list(void){ 385 | firstNode = NULL; 386 | lastNode = NULL; 387 | } 388 | 389 | ~list(void){ 390 | while(this->firstNode != NULL){ 391 | this->remove(this->firstNode); 392 | } 393 | } 394 | 395 | listnode *append(void){ 396 | listnode *node = new listnode; 397 | node->next = NULL; 398 | node->prev = NULL; 399 | if(this->firstNode == NULL){ 400 | ASSERT(this->lastNode == NULL); 401 | this->firstNode = node; 402 | }else{ 403 | ASSERT(this->lastNode != NULL); 404 | this->lastNode->next = node; 405 | node->prev = this->lastNode; 406 | } 407 | this->lastNode = node; 408 | return node; 409 | } 410 | 411 | void remove(listnode *node){ 412 | if(node == NULL){ 413 | error("list::remove: node ist NULL.\n"); 414 | return; 415 | } 416 | 417 | if(node->prev == NULL){ 418 | ASSERT(this->firstNode == node); 419 | this->firstNode = node->next; 420 | }else{ 421 | node->prev->next = node->next; 422 | } 423 | 424 | if(node->next == NULL){ 425 | ASSERT(this->lastNode == node); 426 | this->lastNode = node->prev; 427 | }else{ 428 | node->next->prev = node->prev; 429 | } 430 | 431 | delete node; 432 | } 433 | 434 | // DATA 435 | // ================= 436 | listnode *firstNode; 437 | listnode *lastNode; 438 | }; 439 | 440 | template 441 | struct fifo{ 442 | NONCOPYABLE(fifo) 443 | 444 | fifo(int InitialSize){ 445 | ASSERT(InitialSize > 0); 446 | this->Entry = new T[InitialSize]; 447 | this->Size = InitialSize; 448 | this->Head = -1; 449 | this->Tail = 0; 450 | } 451 | 452 | ~fifo(void){ 453 | delete[] this->Entry; 454 | } 455 | 456 | T *next(void){ 457 | T *Next = NULL; 458 | if(this->Tail <= this->Head){ 459 | Next = &this->Entry[this->Tail % this->Size]; 460 | } 461 | return Next; 462 | } 463 | 464 | T *append(void){ 465 | if((this->Head - this->Tail + 1) == this->Size){ 466 | int NewSize = this->Size * 2; 467 | T *NewEntry = new T[NewSize]; 468 | // TODO(fusion): Is it even possible to have `this->Entry == NULL`? 469 | if(this->Entry != NULL){ 470 | for(int Index = this->Tail; Index <= this->Head; Index += 1){ 471 | NewEntry[Index % NewSize] = this->Entry[Index % this->Size]; 472 | } 473 | delete[] this->Entry; 474 | } 475 | this->Entry = NewEntry; 476 | this->Size = NewSize; 477 | } 478 | 479 | // TODO(fusion): We don't consider integer overflow at all. 480 | this->Head += 1; 481 | return &this->Entry[this->Head % this->Size]; 482 | } 483 | 484 | void remove(void){ 485 | if(this->Tail > this->Head){ 486 | error("fifo::remove: Fifo ist leer.\n"); 487 | return; 488 | } 489 | 490 | this->Tail += 1; 491 | } 492 | 493 | int iterFirst(void){ 494 | return this->Head; 495 | } 496 | 497 | int iterLast(void){ 498 | return this->Tail; 499 | } 500 | 501 | T *iterNext(int *Position){ 502 | if(*Position < this->Tail || this->Head < *Position){ 503 | return NULL; 504 | } 505 | T *Result = &this->Entry[*Position % this->Size]; 506 | *Position -= 1; 507 | return Result; 508 | } 509 | 510 | T *iterPrev(int *Position){ 511 | if(*Position < this->Tail || this->Head < *Position){ 512 | return NULL; 513 | } 514 | 515 | T *Result = &this->Entry[*Position % this->Size]; 516 | *Position += 1; 517 | return Result; 518 | } 519 | 520 | // TODO(fusion): There is also a `fifoIterator` used a few times and it is 521 | // essentially iterating from `this->Head` towards `this->Tail`. All its 522 | // functions were inlined so I'm not sure it is needed. 523 | 524 | // DATA 525 | // ================= 526 | T *Entry; 527 | int Size; 528 | int Head; 529 | int Tail; 530 | }; 531 | 532 | template 533 | union storeitem{ 534 | // IMPORTANT(fusion): This will only work properly with POD structures. We 535 | // could also manually handle `data` construction and destruction but I don't 536 | // think we need it. 537 | STATIC_ASSERT(std::is_trivially_default_constructible::value 538 | && std::is_trivially_destructible::value 539 | && std::is_trivially_copyable::value); 540 | storeitem *next; 541 | T data; 542 | }; 543 | 544 | template 545 | struct storeunit{ 546 | STATIC_ASSERT(N > 0); 547 | storeitem item[N]; 548 | }; 549 | 550 | // NOTE(fusion): The `store` container is an allocator that manages a single type. 551 | // It is also known as a slab allocator. 552 | template 553 | struct store{ 554 | NONCOPYABLE(store) 555 | 556 | store(void){ 557 | this->Units = new list>; 558 | this->firstFreeItem = NULL; 559 | } 560 | 561 | ~store(void){ 562 | delete this->Units; 563 | } 564 | 565 | T *getFreeItem(void){ 566 | if(this->firstFreeItem == NULL){ 567 | storeunit *Unit = &this->Units->append()->data; 568 | for(usize i = 0; i < (N - 1); i += 1){ 569 | Unit->item[i].next = &Unit->item[i + 1]; 570 | } 571 | Unit->item[N - 1].next = NULL; 572 | this->firstFreeItem = &Unit->item[0]; 573 | } 574 | 575 | storeitem *Item = this->firstFreeItem; 576 | this->firstFreeItem = Item->next; 577 | return &Item->data; 578 | } 579 | 580 | void putFreeItem(T *Item){ 581 | // TODO(fusion): Not the safest thing to do. 582 | ASSERT(Item != NULL); 583 | ((storeitem*)Item)->next = this->firstFreeItem; 584 | this->firstFreeItem = (storeitem*)Item; 585 | } 586 | 587 | // DATA 588 | // ================= 589 | list> *Units; 590 | storeitem *firstFreeItem; 591 | }; 592 | 593 | #endif //TIBIA_CONTAINERS_HH_ -------------------------------------------------------------------------------- /src/main.cc: -------------------------------------------------------------------------------- 1 | #include "common.hh" 2 | #include "communication.hh" 3 | #include "config.hh" 4 | #include "houses.hh" 5 | #include "info.hh" 6 | #include "map.hh" 7 | #include "magic.hh" 8 | #include "moveuse.hh" 9 | #include "objects.hh" 10 | #include "operate.hh" 11 | #include "query.hh" 12 | #include "reader.hh" 13 | #include "writer.hh" 14 | 15 | #include 16 | #include 17 | #include 18 | 19 | static bool BeADaemon = false; 20 | static bool Reboot = false; 21 | static bool SaveMapOn = false; 22 | 23 | static timer_t BeatTimer; 24 | static int SigAlarmCounter = 0; 25 | static int SigUsr1Counter = 0; 26 | 27 | static sighandler_t SigHandler(int SigNr, sighandler_t Handler){ 28 | struct sigaction Action; 29 | struct sigaction OldAction; 30 | 31 | Action.sa_handler = Handler; 32 | 33 | // TODO(fusion): I feel we should probably use `sigfillset` specially for 34 | // signals that share the same handler. 35 | sigemptyset(&Action.sa_mask); 36 | 37 | if(SigNr == SIGALRM){ 38 | Action.sa_flags = SA_INTERRUPT; 39 | }else{ 40 | Action.sa_flags = SA_RESTART; 41 | } 42 | 43 | if(sigaction(SigNr, &Action, &OldAction) == 0){ 44 | return OldAction.sa_handler; 45 | }else{ 46 | return SIG_ERR; 47 | } 48 | } 49 | 50 | static void SigBlock(int SigNr){ 51 | sigset_t Set; 52 | sigemptyset(&Set); 53 | sigaddset(&Set, SigNr); 54 | if(sigprocmask(SIG_BLOCK, &Set, NULL) == -1){ 55 | error("SigBlock: Failed to block signal %d (%s): (%d) %s\n", 56 | SigNr, sigdescr_np(SigNr), errno, strerrordesc_np(errno)); 57 | } 58 | } 59 | 60 | static void SigWaitAny(void){ 61 | sigset_t Set; 62 | sigemptyset(&Set); 63 | sigsuspend(&Set); 64 | } 65 | 66 | static void SigHupHandler(int signr){ 67 | // no-op (?) 68 | } 69 | 70 | static void SigAbortHandler(int signr){ 71 | print(1, "SigAbortHandler: schalte Writer-Thread ab.\n"); 72 | AbortWriter(); 73 | } 74 | 75 | static void DefaultHandler(int signr){ 76 | print(1, "DefaultHandler: Beende Game-Server (SigNr. %d: %s).\n", 77 | signr, sigdescr_np(signr)); 78 | 79 | SigHandler(SIGINT, SIG_IGN); 80 | SigHandler(SIGQUIT, SIG_IGN); 81 | SigHandler(SIGTERM, SIG_IGN); 82 | SigHandler(SIGXCPU, SIG_IGN); 83 | SigHandler(SIGXFSZ, SIG_IGN); 84 | SigHandler(SIGPWR, SIG_IGN); 85 | 86 | SaveMapOn = (signr == SIGQUIT) || (signr == SIGTERM) || (signr == SIGPWR); 87 | if(signr == SIGTERM){ 88 | int Hour, Minute; 89 | GetRealTime(&Hour, &Minute); 90 | RebootTime = (Hour * 60 + Minute + 6) % 1440; 91 | CloseGame(); 92 | }else{ 93 | EndGame(); 94 | } 95 | 96 | Reboot = false; 97 | } 98 | 99 | #if 0 100 | // TODO(fusion): This function was exported in the binary but not referenced anywhere. 101 | static void ErrorHandler(int signr){ 102 | error("ErrorHandler: SigNr. %d: %s\n", signr, sigdescr_np(signr)); 103 | EndGame(); 104 | LogoutAllPlayers(); 105 | exit(EXIT_FAILURE); 106 | } 107 | #endif 108 | 109 | static void InitSignalHandler(void){ 110 | int Count = 0; 111 | Count += (SigHandler(SIGHUP, SigHupHandler) != SIG_ERR); 112 | Count += (SigHandler(SIGINT, DefaultHandler) != SIG_ERR); 113 | Count += (SigHandler(SIGQUIT, DefaultHandler) != SIG_ERR); 114 | Count += (SigHandler(SIGABRT, SigAbortHandler) != SIG_ERR); 115 | Count += (SigHandler(SIGUSR1, SIG_IGN) != SIG_ERR); 116 | Count += (SigHandler(SIGUSR2, SIG_IGN) != SIG_ERR); 117 | Count += (SigHandler(SIGPIPE, SIG_IGN) != SIG_ERR); 118 | Count += (SigHandler(SIGALRM, SIG_IGN) != SIG_ERR); 119 | Count += (SigHandler(SIGTERM, DefaultHandler) != SIG_ERR); 120 | Count += (SigHandler(SIGSTKFLT, SIG_IGN) != SIG_ERR); 121 | Count += (SigHandler(SIGCHLD, SIG_IGN) != SIG_ERR); 122 | Count += (SigHandler(SIGTSTP, SIG_IGN) != SIG_ERR); 123 | Count += (SigHandler(SIGTTIN, SIG_IGN) != SIG_ERR); 124 | Count += (SigHandler(SIGTTOU, SIG_IGN) != SIG_ERR); 125 | Count += (SigHandler(SIGURG, SIG_IGN) != SIG_ERR); 126 | Count += (SigHandler(SIGXCPU, DefaultHandler) != SIG_ERR); 127 | Count += (SigHandler(SIGXFSZ, DefaultHandler) != SIG_ERR); 128 | Count += (SigHandler(SIGVTALRM, SIG_IGN) != SIG_ERR); 129 | Count += (SigHandler(SIGWINCH, SIG_IGN) != SIG_ERR); 130 | Count += (SigHandler(SIGPOLL, SIG_IGN) != SIG_ERR); 131 | Count += (SigHandler(SIGPWR, DefaultHandler) != SIG_ERR); 132 | print(1, "InitSignalHandler: %d Signalhandler eingerichtet (Soll=%d)\n", Count, 0x1c); 133 | } 134 | 135 | static void ExitSignalHandler(void){ 136 | // no-op 137 | } 138 | 139 | static void SigAlarmHandler(int SigNr){ 140 | SigAlarmCounter += (1 + timer_getoverrun(BeatTimer)); 141 | } 142 | 143 | static void InitTime(void){ 144 | ASSERT(Beat > 0); 145 | SigAlarmCounter = 0; 146 | SigHandler(SIGALRM, SigAlarmHandler); 147 | 148 | struct sigevent SigEvent = {}; 149 | SigEvent.sigev_notify = SIGEV_THREAD_ID; 150 | SigEvent.sigev_signo = SIGALRM; 151 | SigEvent.sigev_notify_thread_id = gettid(); 152 | if(timer_create(CLOCK_MONOTONIC, &SigEvent, &BeatTimer) == -1){ 153 | error("InitTime: Failed to create beat timer: (%d) %s\n", 154 | errno, strerrordesc_np(errno)); 155 | throw "cannot create beat timer"; 156 | } 157 | 158 | struct itimerspec TimerSpec = {}; 159 | TimerSpec.it_interval.tv_sec = Beat / 1000; 160 | TimerSpec.it_interval.tv_nsec = (Beat % 1000) * 1000000; 161 | TimerSpec.it_value = TimerSpec.it_interval; 162 | if(timer_settime(BeatTimer, 0, &TimerSpec, NULL) == -1){ 163 | error("InitTime: Failed to start beat timer: (%d) %s\n", 164 | errno, strerrordesc_np(errno)); 165 | throw "cannot start beat timer"; 166 | } 167 | } 168 | 169 | static void ExitTime(void){ 170 | if(timer_delete(BeatTimer) == -1){ 171 | error("ExitTime: Failed to delete beat timer: (%d) %s\n", 172 | errno, strerrordesc_np(errno)); 173 | } 174 | 175 | SigHandler(SIGALRM, SIG_IGN); 176 | } 177 | 178 | static void UnlockGame(void){ 179 | // TODO(fusion): Probably use snprintf to format file name? 180 | char FileName[4096]; 181 | strcpy(FileName, SAVEPATH); 182 | strcat(FileName, "/game.pid"); 183 | 184 | std::ifstream InputFile(FileName, std::ios_base::in); 185 | if(!InputFile.fail()){ 186 | int Pid; 187 | InputFile >> Pid; 188 | 189 | if(Pid == getpid()){ 190 | unlink(FileName); 191 | } 192 | } 193 | } 194 | 195 | static void LockGame(void){ 196 | // TODO(fusion): Probably use snprintf to format file name? 197 | char FileName[4096]; 198 | strcpy(FileName, SAVEPATH); 199 | strcat(FileName, "/game.pid"); 200 | 201 | { 202 | std::ifstream InputFile(FileName, std::ios_base::in); 203 | if(!InputFile.fail()){ 204 | int Pid; 205 | InputFile >> Pid; 206 | if(Pid != 0){ 207 | throw "Game-Server is already running, PID file exists."; 208 | } 209 | } 210 | } 211 | 212 | { 213 | std::ofstream OutputFile(FileName, std::ios_base::out | std::ios_base::trunc); 214 | OutputFile << getpid(); 215 | } 216 | 217 | atexit(UnlockGame); 218 | } 219 | 220 | void LoadWorldConfig(void){ 221 | TQueryManagerConnection Connection(KB(16)); 222 | if(!Connection.isConnected()){ 223 | error("LoadWorldConfig: Kann nicht zum Query-Manager verbinden.\n"); 224 | throw "cannot connect to querymanager"; 225 | } 226 | 227 | int HelpWorldType; 228 | int HelpGameAddress[4]; 229 | int Ret = Connection.loadWorldConfig(&HelpWorldType, &RebootTime, 230 | HelpGameAddress, &GamePort, 231 | &MaxPlayers, &PremiumPlayerBuffer, 232 | &MaxNewbies, &PremiumNewbieBuffer); 233 | if(Ret != 0){ 234 | error("LoadWorldConfig: Kann Konfigurationsdaten nicht holen.\n"); 235 | throw "cannot load world config"; 236 | } 237 | 238 | WorldType = (TWorldType)HelpWorldType; 239 | snprintf(GameAddress, sizeof(GameAddress), "%d.%d.%d.%d", 240 | HelpGameAddress[0], HelpGameAddress[1], 241 | HelpGameAddress[2], HelpGameAddress[3]); 242 | } 243 | 244 | static void InitAll(void){ 245 | try{ 246 | ReadConfig(); 247 | SetQueryManagerLoginData(1, WorldName); 248 | LoadWorldConfig(); 249 | InitSHM(!BeADaemon); 250 | LockGame(); 251 | InitLog("game"); 252 | srand(time(NULL)); 253 | InitSignalHandler(); 254 | InitConnections(); 255 | InitCommunication(); 256 | InitStrings(); 257 | InitWriter(); 258 | InitReader(); 259 | InitObjects(); 260 | InitMap(); 261 | InitInfo(); 262 | InitMoveUse(); 263 | InitMagic(); 264 | InitCr(); 265 | InitHouses(); 266 | InitTime(); 267 | ApplyPatches(); 268 | }catch(const char *str){ 269 | error("Initialisierungsfehler: %s\n", str); 270 | exit(EXIT_FAILURE); 271 | } 272 | } 273 | 274 | static void ExitAll(void){ 275 | EndGame(); 276 | ExitTime(); 277 | ExitCr(); 278 | ExitMagic(); 279 | ExitMoveUse(); 280 | ExitInfo(); 281 | ExitHouses(); 282 | ExitMap(SaveMapOn); 283 | ExitObjects(); 284 | ExitReader(); 285 | ExitWriter(); 286 | ExitStrings(); 287 | ExitCommunication(); 288 | ExitConnections(); 289 | ExitSignalHandler(); 290 | ExitSHM(); 291 | } 292 | 293 | static void ProcessCommand(void){ 294 | int Command = GetCommand(); 295 | if(Command != 0){ 296 | char *Buffer = GetCommandBuffer(); 297 | if(Command == 1){ 298 | if(Buffer != NULL){ 299 | BroadcastMessage(TALK_ADMIN_MESSAGE, "%s", Buffer); 300 | }else{ 301 | error("ProcessCommand: Text für Broadcast ist NULL.\n"); 302 | } 303 | }else{ 304 | error("ProcessCommand: Unbekanntes Kommando %d.\n", Command); 305 | } 306 | 307 | SetCommand(0, NULL); 308 | } 309 | } 310 | 311 | static void AdvanceGame(int Delay){ 312 | static int CreatureTimeCounter = 0; 313 | static int CronTimeCounter = 0; 314 | static int SkillTimeCounter = 0; 315 | static int OtherTimeCounter = 0; 316 | static int OldAmbiente = -1; 317 | static uint32 NextMinute = 30; 318 | static bool Lag = false; 319 | 320 | CreatureTimeCounter += Delay; 321 | CronTimeCounter += Delay; 322 | SkillTimeCounter += Delay; 323 | OtherTimeCounter += Delay; 324 | 325 | if(CreatureTimeCounter >= 1750){ 326 | CreatureTimeCounter -= 1000; 327 | ProcessCreatures(); 328 | } 329 | 330 | if(CronTimeCounter >= 1500){ 331 | CronTimeCounter -= 1000; 332 | ProcessCronSystem(); 333 | } 334 | 335 | if(SkillTimeCounter >= 1250){ 336 | SkillTimeCounter -= 1000; 337 | ProcessSkills(); 338 | } 339 | 340 | if(OtherTimeCounter >= 1000){ 341 | OtherTimeCounter -= 1000; 342 | 343 | RoundNr += 1; 344 | SetRoundNr(RoundNr); 345 | 346 | ProcessConnections(); 347 | ProcessMonsterhomes(); 348 | ProcessMonsterRaids(); 349 | ProcessCommunicationControl(); 350 | ProcessReaderThreadReplies(RefreshSector, SendMails); 351 | ProcessWriterThreadReplies(); 352 | ProcessCommand(); 353 | 354 | // TODO(fusion): Shouldn't we be checking both brightness and color? 355 | int Brightness, Color; 356 | GetAmbiente(&Brightness, &Color); 357 | if(OldAmbiente != Brightness){ 358 | OldAmbiente = Brightness; 359 | TConnection *Connection = GetFirstConnection(); 360 | while(Connection != NULL){ 361 | if(Connection->Live()){ 362 | SendAmbiente(Connection); 363 | } 364 | Connection = GetNextConnection(); 365 | } 366 | } 367 | 368 | if(RoundNr % 10 == 0){ 369 | NetLoadCheck(); 370 | } 371 | 372 | if(RoundNr >= NextMinute){ 373 | int Hour, Minute; 374 | GetRealTime(&Hour, &Minute); 375 | 376 | RefreshCylinders(); 377 | if(Minute % 5 == 0){ 378 | CreatePlayerList(true); 379 | } 380 | if(Minute % 15 == 0){ 381 | SavePlayerDataOrder(); 382 | } 383 | if(Minute == 0){ 384 | NetLoadSummary(); 385 | } 386 | if(Minute == 55){ 387 | WriteKillStatistics(); 388 | } 389 | 390 | int RealTime = Minute + Hour * 60; 391 | if((RealTime + 5) % 1440 == RebootTime){ 392 | if(Reboot){ 393 | BroadcastMessage(TALK_ADMIN_MESSAGE, 394 | "Server is saving game in 5 minutes.\nPlease come back in 10 minutes."); 395 | }else{ 396 | BroadcastMessage(TALK_ADMIN_MESSAGE, 397 | "Server is going down in 5 minutes.\nPlease log out."); 398 | } 399 | CloseGame(); 400 | }else if((RealTime + 3) % 1440 == RebootTime){ 401 | if(Reboot){ 402 | BroadcastMessage(TALK_ADMIN_MESSAGE, 403 | "Server is saving game in 3 minutes.\nPlease come back in 10 minutes."); 404 | }else{ 405 | BroadcastMessage(TALK_ADMIN_MESSAGE, 406 | "Server is going down in 3 minutes.\nPlease log out."); 407 | } 408 | }else if((RealTime + 1) % 1440 == RebootTime){ 409 | if(Reboot){ 410 | BroadcastMessage(TALK_ADMIN_MESSAGE, 411 | "Server is saving game in one minute.\nPlease log out."); 412 | }else{ 413 | BroadcastMessage(TALK_ADMIN_MESSAGE, 414 | "Server is going down in one minute.\nPlease log out."); 415 | } 416 | }else if(RealTime == RebootTime){ 417 | CloseGame(); 418 | LogoutAllPlayers(); 419 | SendAll(); 420 | if(Reboot){ 421 | RefreshMap(); 422 | } 423 | SaveMap(); 424 | SaveMapOn = false; 425 | EndGame(); 426 | } 427 | 428 | NextMinute = GetRoundForNextMinute(); 429 | } 430 | CleanupDynamicStrings(); 431 | } 432 | 433 | if(Delay > Beat){ 434 | Log("lag", "Verzögerung %d msec.\n", Delay); 435 | } 436 | 437 | // TODO(fusion): Why would we delay creature movement yet another beat? 438 | if(Delay < 1000){ 439 | MoveCreatures(Delay); 440 | Lag = false; 441 | }else{ 442 | if(!Lag && RoundNr > 10){ 443 | error("AdvanceGame: Keine Kreaturbewegung wegen Lag (Verzögerung: %d msec).\n", Delay); 444 | } 445 | Lag = true; 446 | } 447 | 448 | SendAll(); 449 | } 450 | 451 | static void SigUsr1Handler(int signr){ 452 | SigUsr1Counter += 1; 453 | } 454 | 455 | static void LaunchGame(void){ 456 | SaveMapOn = true; 457 | SigUsr1Counter = 0; 458 | SigAlarmCounter = 0; 459 | 460 | SigBlock(SIGUSR1); 461 | SigHandler(SIGUSR1, SigUsr1Handler); 462 | StartGame(); 463 | 464 | print(1, "LaunchGame: Game-Server ist bereit (Pid=%d, Tid=%d).\n", getpid(), gettid()); 465 | 466 | // IMPORTANT(fusion): In general signal handlers can execute on any thread in 467 | // the process group but the design of the server is to use signals directed 468 | // at different threads to communicate certain events (see `CommunicationThread` 469 | // for example). 470 | // Even if that wasn't the case, loads/stores on x86 are always ATOMIC and when 471 | // there is a single writer (signal handlers), even a read-modify-write will be 472 | // atomic. 473 | // This is to say, there should be no problem with reading from `SigUsr1Counter`, 474 | // `SigAlarmCounter`, or `SaveMapOn`, which may be modified from signal handlers. 475 | 476 | while(GameRunning()){ 477 | while(SigUsr1Counter == 0 && SigAlarmCounter == 0){ 478 | SigWaitAny(); 479 | } 480 | 481 | if(SigUsr1Counter > 0){ 482 | SigUsr1Counter = 0; 483 | ReceiveData(); 484 | } 485 | 486 | int NumBeats = SigAlarmCounter; 487 | if(NumBeats > 0){ 488 | SigAlarmCounter = 0; 489 | AdvanceGame(NumBeats * Beat); 490 | } 491 | } 492 | 493 | LogoutAllPlayers(); 494 | } 495 | 496 | static bool DaemonInit(bool NoFork){ 497 | if(!NoFork){ 498 | pid_t Pid = fork(); 499 | if(Pid < 0){ 500 | return true; 501 | } 502 | 503 | if(Pid != 0){ 504 | exit(EXIT_SUCCESS); 505 | } 506 | 507 | setsid(); 508 | } 509 | 510 | umask(0177); 511 | chdir(SAVEPATH); 512 | 513 | int OpenMax = sysconf(_SC_OPEN_MAX); 514 | if(OpenMax < 0){ 515 | OpenMax = 1024; 516 | } 517 | 518 | for(int fd = 0; fd < OpenMax; fd += 1){ 519 | close(fd); 520 | } 521 | 522 | return false; 523 | } 524 | 525 | int main(int argc, char **argv){ 526 | bool NoFork = false; 527 | BeADaemon = false; 528 | Reboot = true; 529 | 530 | for(int i = 1; i < argc; i += 1){ 531 | if(strcmp(argv[i], "daemon") == 0){ 532 | BeADaemon = true; 533 | }else if(strcmp(argv[i], "nofork") == 0){ 534 | NoFork = true; 535 | } 536 | } 537 | 538 | // TODO(fusion): It doesn't make sense for `DaemonInit` to even return here. 539 | // It either exits the parent or child process, or let it run. 540 | if(BeADaemon && DaemonInit(NoFork)){ 541 | return 2; 542 | } 543 | 544 | puts("Tibia Game-Server\n(c) by CIP Productions, 2003.\n"); 545 | 546 | InitAll(); 547 | atexit(ExitAll); 548 | 549 | // TODO(fusion): The original binary does use exceptions but identifying 550 | // try..catch blocks are not as straightforward as throw statements. I'll 551 | // leave this one at the top level but we should come back to this problem 552 | // once we identify all throw statements and how to roughly handle them. 553 | try{ 554 | LaunchGame(); 555 | }catch(RESULT r){ 556 | error("main: Nicht abgefangene Exception %d.\n", r); 557 | }catch(const char *str){ 558 | error("main: Nicht abgefangene Exception \"%s\".\n", str); 559 | }catch(const std::exception &e){ 560 | error("main: Nicht abgefangene Exception %s.\n", e.what()); 561 | }catch(...){ 562 | error("main: Nicht abgefangene Exception unbekannten Typs.\n"); 563 | } 564 | 565 | if(!Reboot){ 566 | print(1, "Beende Game-Server...\n"); 567 | }else{ 568 | UnlockGame(); 569 | 570 | char FileName[4096]; 571 | snprintf(FileName, sizeof(FileName), "%s/reboot-daily", BINPATH); 572 | if(FileExists(FileName)){ 573 | ExitAll(); 574 | print(1, "Starte Game-Server neu...\n"); 575 | execv(FileName, argv); 576 | }else{ 577 | print(1, "Reboot-Skript existiert nicht.\n"); 578 | } 579 | } 580 | 581 | return 0; 582 | } 583 | -------------------------------------------------------------------------------- /src/connections.cc: -------------------------------------------------------------------------------- 1 | #include "connections.hh" 2 | #include "cr.hh" 3 | #include "info.hh" 4 | #include "threads.hh" 5 | #include "writer.hh" 6 | 7 | #include 8 | #include 9 | #include 10 | 11 | static Semaphore ConnectionMutex(1); 12 | static int ConnectionIterator; 13 | static TConnection Connections[MAX_CONNECTIONS]; 14 | 15 | // TConnection 16 | // ============================================================================= 17 | TConnection::TConnection(void){ 18 | this->State = CONNECTION_FREE; 19 | } 20 | 21 | void TConnection::Process(void){ 22 | if(this->InGame()){ 23 | uint32 LastCommand = (RoundNr - this->TimeStamp); 24 | if(LastCommand == 30 || LastCommand == 60){ 25 | SendPing(this); 26 | } 27 | 28 | uint32 LastAction = (RoundNr - this->TimeStampAction); 29 | if(LastAction == 900 && !CheckRight(this->CharacterID, NO_LOGOUT_BLOCK)){ 30 | SendMessage(this, TALK_ADMIN_MESSAGE, 31 | "You have been idle for %d minutes. You will be disconnected" 32 | " in one minute if you are still idle then.", 15); 33 | } 34 | 35 | if(LastAction >= 960 && !CheckRight(this->CharacterID, NO_LOGOUT_BLOCK)){ 36 | this->Logout(0, true); 37 | }else if(!GameRunning() || !this->ConnectionIsOk || (LastCommand >= 90)){ 38 | this->Logout(0, false); 39 | } 40 | }else if(this->State == CONNECTION_LOGIN){ 41 | if(!GameRunning() || !this->ConnectionIsOk){ 42 | this->Disconnect(); 43 | } 44 | }else if(this->State == CONNECTION_LOGOUT){ 45 | // NOTE(fusion): `TimeStamp` has the logout round which is set by 46 | // `TConnection::Logout`. 47 | if(!GameRunning() || !this->ConnectionIsOk || this->TimeStamp <= RoundNr){ 48 | this->Disconnect(); 49 | } 50 | } 51 | } 52 | 53 | void TConnection::ResetTimer(int Command){ 54 | if(this->InGame()){ 55 | this->TimeStamp = RoundNr; 56 | if(Command != CL_CMD_PING 57 | && Command != CL_CMD_GO_STOP 58 | && Command != CL_CMD_CANCEL 59 | && Command != CL_CMD_REFRESH_FIELD 60 | && Command != CL_CMD_REFRESH_CONTAINER){ 61 | this->TimeStampAction = RoundNr; 62 | } 63 | } 64 | } 65 | 66 | void TConnection::EmergencyPing(void){ 67 | if(this->InGame()){ 68 | uint32 LastCommand = (RoundNr - this->TimeStamp); 69 | if(LastCommand < 80){ 70 | // TODO(fusion): This is only called by `NetLoadCheck`, when it detects 71 | // lag, which can only happen after some set number of rounds, usually 72 | // higher than 100. This is all to say this subtraction below is very 73 | // unlikely to wrap. Nevertheless, we should also have some helper 74 | // functions to do saturating addition or subtraction for both signed 75 | // and unsigned integers. 76 | this->TimeStamp = RoundNr - 100; 77 | } 78 | SendPing(this); 79 | } 80 | } 81 | 82 | pid_t TConnection::GetThreadID(void){ 83 | if(this->State == CONNECTION_FREE){ 84 | error("TConnection::GetThreadID: Verbindung ist nicht zugewiesen.\n"); 85 | return 0; 86 | } 87 | 88 | return this->ThreadID; 89 | } 90 | 91 | bool TConnection::SetLoginTimer(int Timeout){ 92 | if(this->State == CONNECTION_FREE){ 93 | error("TConnection::SetLoginTimer: Verbindung ist nicht zugewiesen.\n"); 94 | return false; 95 | } 96 | 97 | if(this->LoginTimer != 0){ 98 | error("TConnection::SetLoginTimer: Timer already set.\n"); 99 | return false; 100 | } 101 | 102 | struct sigevent SigEvent = {}; 103 | SigEvent.sigev_notify = SIGEV_THREAD_ID; 104 | SigEvent.sigev_signo = SIGALRM; 105 | SigEvent.sigev_notify_thread_id = this->ThreadID; 106 | if(timer_create(CLOCK_MONOTONIC, &SigEvent, &this->LoginTimer) == -1){ 107 | error("TConnection::SetLoginTimer: Failed to create timer: (%d) %s\n", 108 | errno, strerrordesc_np(errno)); 109 | return false; 110 | } 111 | 112 | struct itimerspec TimerSpec = {}; 113 | TimerSpec.it_value.tv_sec = Timeout; 114 | if(timer_settime(this->LoginTimer, 0, &TimerSpec, NULL) == -1){ 115 | error("TConnection::SetLoginTimer: Failed to start timer: (%d) %s\n", 116 | errno, strerrordesc_np(errno)); 117 | return false; 118 | } 119 | 120 | return true; 121 | } 122 | 123 | void TConnection::StopLoginTimer(void){ 124 | if(this->State == CONNECTION_FREE){ 125 | error("TConnection::SetLoginTimer: Verbindung ist nicht zugewiesen.\n"); 126 | return; 127 | } 128 | 129 | if(this->LoginTimer == 0){ 130 | error("TConnection::StopLoginTimer: Timer not set.\n"); 131 | return; 132 | } 133 | 134 | if(timer_delete(this->LoginTimer) == -1){ 135 | error("TConnection::StopLoginTimer: Failed to delete timer: (%d) %s\n", 136 | errno, strerrordesc_np(errno)); 137 | } 138 | 139 | this->LoginTimer = 0; 140 | } 141 | 142 | int TConnection::GetSocket(void){ 143 | if(this->State == CONNECTION_FREE || this->State == CONNECTION_ASSIGNED){ 144 | error("TConnection::GetSocket: Verbindung ist nicht angeschlossen.\n"); 145 | return -1; 146 | } 147 | 148 | return this->Socket; 149 | } 150 | 151 | const char *TConnection::GetIPAddress(void){ 152 | if(this->State == CONNECTION_FREE || this->State == CONNECTION_ASSIGNED){ 153 | error("TConnection::GetIPAddress: Verbindung ist nicht angeschlossen.\n"); 154 | return "Unknown"; 155 | } 156 | 157 | return this->IPAddress; 158 | } 159 | 160 | void TConnection::Free(void){ 161 | this->State = CONNECTION_FREE; 162 | } 163 | 164 | void TConnection::Assign(void){ 165 | if(this->State != CONNECTION_FREE){ 166 | error("TConnection::Assign: Verbindung ist nicht frei.\n"); 167 | } 168 | 169 | this->State = CONNECTION_ASSIGNED; 170 | this->ThreadID = gettid(); 171 | this->LoginTimer = 0; 172 | } 173 | 174 | void TConnection::Connect(int Socket){ 175 | if(this->State != CONNECTION_ASSIGNED){ 176 | error("TConnection::Connect: Verbindung ist keinem Thread zugewiesen.\n"); 177 | } 178 | 179 | this->State = CONNECTION_CONNECTED; 180 | this->Socket = Socket; 181 | this->ConnectionIsOk = true; 182 | this->ClosingIsDelayed = true; 183 | this->RandomSeed = rand(); 184 | 185 | struct sockaddr_in RemoteAddr; 186 | socklen_t RemoteAddrLen = sizeof(RemoteAddr); 187 | getpeername(Socket, (struct sockaddr*)&RemoteAddr, &RemoteAddrLen); 188 | strcpy(this->IPAddress, inet_ntoa(RemoteAddr.sin_addr)); 189 | } 190 | 191 | void TConnection::Login(void){ 192 | if(this->State != CONNECTION_CONNECTED){ 193 | error("TConnection::Connect: Ungültiger Verbindungs-Zustand %d.\n", this->State); 194 | } 195 | 196 | this->State = CONNECTION_LOGIN; 197 | } 198 | 199 | bool TConnection::JoinGame(TReadBuffer *Buffer){ 200 | if(this->State != CONNECTION_LOGIN){ 201 | error("TConnection::JoinGame: Ungültiger Verbindungszustand %d.\n", this->State); 202 | } 203 | 204 | this->ClearKnownCreatureTable(false); 205 | 206 | try{ 207 | this->TerminalType = (int)Buffer->readWord(); 208 | this->TerminalVersion = (int)Buffer->readWord(); 209 | this->CharacterID = Buffer->readQuad(); 210 | }catch(const char *str){ 211 | error("TConnection::JoinGame: Fehler beim Auslesen des Puffers (%s).\n", str); 212 | } 213 | 214 | if(this->TerminalType != 1 && this->TerminalType != 2){ 215 | error("TConnection::JoinGame: Unbekannter Terminal-Typ %d.\n", this->TerminalType); 216 | return false; 217 | } 218 | 219 | this->TerminalOffsetX = 8; 220 | this->TerminalOffsetY = 6; 221 | this->TerminalWidth = 18; 222 | this->TerminalHeight = 14; 223 | 224 | TPlayer *Player = ::GetPlayer(this->CharacterID); 225 | if(Player == NULL){ 226 | Player = new TPlayer(this, this->CharacterID); 227 | if(Player->ConstructError != NOERROR){ 228 | delete Player; 229 | return false; 230 | } 231 | }else{ 232 | if(Player->IsDead){ 233 | Log("game", "Spieler %s ist gerade am Sterben - Einloggen gescheitert.\n", Player->Name); 234 | DecreasePlayerPoolSlotSticky(this->CharacterID); 235 | return false; 236 | } 237 | 238 | if(Player->LoggingOut && Player->LogoutPossible() == 0){ 239 | Log("game", "Spieler %s loggt gerade aus - Einloggen gescheitert.\n", Player->Name); 240 | DecreasePlayerPoolSlotSticky(this->CharacterID); 241 | return false; 242 | } 243 | 244 | TConnection *OldConnection = Player->Connection; 245 | Player->ClearConnection(); 246 | if(OldConnection != NULL){ 247 | OldConnection->CharacterID = 0; 248 | OldConnection->Logout(0, true); 249 | } 250 | 251 | DecrementIsOnlineOrder(this->CharacterID); 252 | Player->TakeOver(this); 253 | } 254 | 255 | strcpy(this->Name, Player->Name); 256 | this->TimeStamp = RoundNr; 257 | this->TimeStampAction = RoundNr; 258 | return true; 259 | } 260 | 261 | void TConnection::EnterGame(void){ 262 | if(this->State != CONNECTION_LOGIN){ 263 | error("TConnection::EnterGame: Ungültiger Verbindungszustand %d.\n", this->State); 264 | } 265 | 266 | this->State = CONNECTION_GAME; 267 | } 268 | 269 | void TConnection::Die(void){ 270 | if(this->State == CONNECTION_GAME){ 271 | this->State = CONNECTION_DEAD; 272 | } 273 | } 274 | 275 | void TConnection::Logout(int Delay, bool StopFight){ 276 | if(!this->InGame() && this->State != CONNECTION_LOGOUT){ 277 | error("TConnection::Logout: Ungültiger Verbindungszustand %d.\n", this->State); 278 | } 279 | 280 | this->State = CONNECTION_LOGOUT; 281 | if(this->CharacterID != 0){ 282 | TPlayer *Player = ::GetPlayer(this->CharacterID); 283 | if(Player != NULL){ 284 | Player->ClearConnection(); 285 | Player->StartLogout(false, StopFight); 286 | } 287 | } 288 | 289 | if(Delay < 0){ 290 | Delay = 0; 291 | } 292 | 293 | this->CharacterID = 0; 294 | this->TimeStamp = RoundNr + (uint32)Delay; 295 | this->ClosingIsDelayed = false; 296 | } 297 | 298 | void TConnection::Close(bool Delay){ 299 | if(this->State == CONNECTION_FREE || this->State == CONNECTION_ASSIGNED){ 300 | error("TConnection::Close: Ungültiger Verbindungszustand %d.\n", this->State); 301 | } 302 | 303 | if(this->State == CONNECTION_CONNECTED){ 304 | this->State = CONNECTION_DISCONNECTED; 305 | } 306 | 307 | this->ConnectionIsOk = false; 308 | this->ClosingIsDelayed = Delay; 309 | } 310 | 311 | void TConnection::Disconnect(void){ 312 | if(this->State == CONNECTION_FREE || this->State == CONNECTION_ASSIGNED){ 313 | error("TConnection::Close: Ungültiger Verbindungszustand %d.\n", this->State); 314 | } 315 | 316 | this->ClearKnownCreatureTable(true); 317 | this->ConnectionIsOk = false; 318 | this->State = CONNECTION_DISCONNECTED; 319 | tgkill(GetGameProcessID(), this->ThreadID, SIGHUP); 320 | } 321 | 322 | TPlayer *TConnection::GetPlayer(void){ 323 | if(!this->Live()){ 324 | error("TConnection::GetPlayer: Ungültiger Verbindungszustand %d.\n", this->State); 325 | return NULL; 326 | } 327 | 328 | TPlayer *Player = NULL; 329 | if(this->CharacterID != 0){ 330 | Player = ::GetPlayer(this->CharacterID); 331 | } 332 | return Player; 333 | } 334 | 335 | const char *TConnection::GetName(void){ 336 | if(!this->Live()){ 337 | error("TConnection::GetName: Ungültiger Verbindungszustand %d.\n", this->State); 338 | return ""; 339 | } 340 | 341 | return this->Name; 342 | } 343 | 344 | void TConnection::GetPosition(int *x, int *y, int *z){ 345 | TPlayer *Player = this->GetPlayer(); 346 | if(Player != NULL){ 347 | *x = Player->posx; 348 | *y = Player->posy; 349 | *z = Player->posz; 350 | }else{ 351 | *x = 0; 352 | *y = 0; 353 | *z = 0; 354 | } 355 | } 356 | 357 | bool TConnection::IsVisible(int x, int y, int z){ 358 | int PlayerX, PlayerY, PlayerZ; 359 | this->GetPosition(&PlayerX, &PlayerY, &PlayerZ); 360 | 361 | // TODO(fusion): Have a standalone version of `TCreature::CanSeeFloor`? 362 | if(PlayerZ <= 7){ 363 | if(z > 7){ 364 | return false; 365 | } 366 | }else{ 367 | if(std::abs(PlayerZ - z) > 2){ 368 | return false; 369 | } 370 | } 371 | 372 | int MinX = (PlayerX - this->TerminalOffsetX) + (PlayerZ - z); 373 | int MinY = (PlayerY - this->TerminalOffsetY) + (PlayerZ - z); 374 | int MaxX = MinX + this->TerminalWidth - 1; 375 | int MaxY = MinY + this->TerminalHeight - 1; 376 | return x >= MinX && x <= MaxX 377 | && y >= MinY && y <= MaxY; 378 | } 379 | 380 | KNOWNCREATURESTATE TConnection::KnownCreature(uint32 ID, bool UpdateFollows){ 381 | int EntryIndex = -1; 382 | for(int i = 0; i < NARRAY(this->KnownCreatureTable); i += 1){ 383 | if(this->KnownCreatureTable[i].CreatureID == ID){ 384 | EntryIndex = i; 385 | break; 386 | } 387 | } 388 | 389 | if(EntryIndex == -1){ 390 | return KNOWNCREATURE_FREE; 391 | } 392 | 393 | KNOWNCREATURESTATE Result = this->KnownCreatureTable[EntryIndex].State; 394 | if(Result == KNOWNCREATURE_OUTDATED && UpdateFollows){ 395 | this->KnownCreatureTable[EntryIndex].State = KNOWNCREATURE_UPTODATE; 396 | } 397 | return Result; 398 | } 399 | 400 | uint32 TConnection::NewKnownCreature(uint32 NewID){ 401 | uint32 OldID = 0; 402 | int EntryIndex = -1; 403 | for(int i = 0; i < NARRAY(this->KnownCreatureTable); i += 1){ 404 | if(this->KnownCreatureTable[i].CreatureID == NewID){ 405 | OldID = NewID; 406 | EntryIndex = i; 407 | break; 408 | } 409 | } 410 | 411 | if(EntryIndex == -1){ 412 | for(int i = 0; i < NARRAY(this->KnownCreatureTable); i += 1){ 413 | if(this->KnownCreatureTable[i].State == KNOWNCREATURE_FREE){ 414 | OldID = this->KnownCreatureTable[i].CreatureID; 415 | EntryIndex = i; 416 | break; 417 | } 418 | } 419 | } 420 | 421 | if(EntryIndex == -1){ 422 | for(int i = 0; i < NARRAY(this->KnownCreatureTable); i += 1){ 423 | TCreature *Creature = GetCreature(this->KnownCreatureTable[i].CreatureID); 424 | if(Creature == NULL || !this->IsVisible(Creature->posx, Creature->posy, Creature->posz)){ 425 | OldID = this->KnownCreatureTable[i].CreatureID; 426 | EntryIndex = i; 427 | this->UnchainKnownCreature(OldID); 428 | break; 429 | } 430 | } 431 | } 432 | 433 | if(EntryIndex == -1){ 434 | print(3, "KnownCreatureTable ausgelastet.\n"); 435 | return 0; 436 | } 437 | 438 | if(this->KnownCreatureTable[EntryIndex].State != KNOWNCREATURE_FREE){ 439 | error("TUserCom::NewKnownCreature: Slot ist nicht gelöscht.\n"); 440 | } 441 | 442 | this->KnownCreatureTable[EntryIndex].State = KNOWNCREATURE_UPTODATE; 443 | this->KnownCreatureTable[EntryIndex].CreatureID = NewID; 444 | 445 | TCreature *Creature = GetCreature(NewID); 446 | if(Creature != NULL){ 447 | this->KnownCreatureTable[EntryIndex].Next = Creature->FirstKnowingConnection; 448 | Creature->FirstKnowingConnection = &this->KnownCreatureTable[EntryIndex]; 449 | }else{ 450 | error("TUserCom::NewKnownCreature: Kreatur %u existiert nicht.\n", NewID); 451 | } 452 | 453 | return OldID; 454 | } 455 | 456 | void TConnection::ClearKnownCreatureTable(bool Unchain){ 457 | for(int i = 0; i < NARRAY(this->KnownCreatureTable); i += 1){ 458 | if(Unchain && this->KnownCreatureTable[i].State != KNOWNCREATURE_FREE){ 459 | this->UnchainKnownCreature(this->KnownCreatureTable[i].CreatureID); 460 | } 461 | this->KnownCreatureTable[i].State = KNOWNCREATURE_FREE; 462 | this->KnownCreatureTable[i].CreatureID = 0; 463 | this->KnownCreatureTable[i].Connection = this; 464 | } 465 | } 466 | 467 | void TConnection::UnchainKnownCreature(uint32 ID){ 468 | TCreature *Creature = GetCreature(ID); 469 | if(Creature == NULL){ 470 | error("TUserCom::UnchainKnownCreature: Kreatur %u existiert nicht.\n", ID); 471 | return; 472 | } 473 | 474 | if(Creature->FirstKnowingConnection == NULL){ 475 | error("TUserCom::UnchainKnownCreature: Kreatur %u kennt niemand.\n", ID); 476 | return; 477 | } 478 | 479 | TKnownCreature *Prev = NULL; 480 | TKnownCreature *Cur = Creature->FirstKnowingConnection; 481 | while(Cur != NULL){ 482 | if(Cur->Connection == this){ 483 | break; 484 | } 485 | Prev = Cur; 486 | Cur = Cur->Next; 487 | } 488 | 489 | if(Cur == NULL){ 490 | error("TUserCom::UnchainKnownCreature: Kreatur %u ist nicht bekannt.\n", ID); 491 | return; 492 | } 493 | 494 | Cur->State = KNOWNCREATURE_FREE; 495 | if(Prev == NULL){ 496 | Creature->FirstKnowingConnection = Cur->Next; 497 | }else{ 498 | Prev->Next = Cur->Next; 499 | } 500 | } 501 | 502 | // Connection Utility 503 | // ============================================================================= 504 | TConnection *AssignFreeConnection(void){ 505 | TConnection *Connection = NULL; 506 | ConnectionMutex.down(); 507 | for(int i = 0; i < MAX_CONNECTIONS; i += 1){ 508 | if(Connections[i].State == CONNECTION_FREE){ 509 | Connection = &Connections[i]; 510 | Connection->Assign(); 511 | break; 512 | } 513 | } 514 | ConnectionMutex.up(); 515 | return Connection; 516 | } 517 | 518 | TConnection *GetFirstConnection(void){ 519 | ConnectionIterator = 0; 520 | return GetNextConnection(); 521 | } 522 | 523 | TConnection *GetNextConnection(void){ 524 | TConnection *NextConnection = NULL; 525 | while(ConnectionIterator < MAX_CONNECTIONS){ 526 | TConnection *Connection = &Connections[ConnectionIterator]; 527 | ConnectionIterator += 1; 528 | if(Connection->State != CONNECTION_FREE){ 529 | NextConnection = Connection; 530 | break; 531 | } 532 | } 533 | return NextConnection; 534 | } 535 | 536 | void ProcessConnections(void){ 537 | TConnection *Connection = GetFirstConnection(); 538 | while(Connection != NULL){ 539 | Connection->Process(); 540 | Connection = GetNextConnection(); 541 | } 542 | } 543 | 544 | void InitConnections(void){ 545 | InitSending(); 546 | InitReceiving(); 547 | 548 | ConnectionIterator = 0; 549 | for(int i = 0; i < MAX_CONNECTIONS; i += 1){ 550 | Connections[i].State = CONNECTION_FREE; 551 | } 552 | } 553 | 554 | void ExitConnections(void){ 555 | ExitSending(); 556 | ExitReceiving(); 557 | } 558 | --------------------------------------------------------------------------------