aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--README.md22
-rw-r--r--src/communication.cc61
-rw-r--r--src/crnonpl.cc18
-rw-r--r--src/crplayer.cc2
-rw-r--r--tibia-game.service2
5 files changed, 93 insertions, 12 deletions
diff --git a/README.md b/README.md
index 05c0f78..a66e673 100644
--- a/README.md
+++ b/README.md
@@ -15,13 +15,25 @@ I had a small *TODO* list with a few things to attempt after the server was up a
## Compiling
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.
```
-make # build in release mode
-make DEBUG=1 # build in debug mode
-make clean # remove `build` directory
-make clean && make # full rebuild in release mode (recommended)
-make clean && make DEBUG=1 # full rebuild in debug mode (recommended)
+make # build in release mode
+make DEBUG=1 # build in debug mode
+make clean # remove `build` directory
+make -B # full rebuild in release mode (recommended)
+make -B DEBUG=1 # full rebuild in debug mode (recommended)
```
+### Extra Features (Advanced)
+There are also some extra features that can be enabled by appending the following definitions to the `CFLAGS` variable in your `Makefile`. They're completely optional but can be useful in specific scenarios.
+
+- `-DTIBIA772=1`
+ - Modify the protocol to match Tibia 7.72. It's not a 1:1 relation so 7.7 clients won't be able to connect.
+
+- `-DALLOW_LOCAL_PROXY=1`
+ - Allow **local** connections to send a proxy header. Ideally we'd have a list of trusted proxies to prevent clients from spoofing their addresses. In reality, it is almost always simpler to have a **local** relay. This is also the only way to get a multi-route proxy without having to drastically change the way connections are handled.
+
+- `-DBIND_ACCEPTOR_TO_GAME_ADDRESS=1`
+ - Make the acceptor socket bind to the interface specified by the game address, rather than all available network interfaces. This is the same address stored in the database and may cause startup issues if no such interface exists, or connection issues if the address is not publicly reachable.
+
## Running
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.
- [Query Manager](https://github.com/fusion32/tibia-querymanager)
diff --git a/src/communication.cc b/src/communication.cc
index 37223d2..2bd727b 100644
--- a/src/communication.cc
+++ b/src/communication.cc
@@ -1130,7 +1130,12 @@ bool ReceiveCommand(TConnection *Connection){
}
while(!Connection->WaitingForACK){
+#if ALLOW_LOCAL_PROXY
+ uint8 Help[32];
+#else
uint8 Help[2];
+#endif
+
int BytesRead = ReadFromSocket(Connection, Help, 2);
if(BytesRead == 0){
// NOTE(fusion): Peer has closed the connection and there was no
@@ -1153,6 +1158,62 @@ bool ReceiveCommand(TConnection *Connection){
return false;
}
+#if ALLOW_LOCAL_PROXY
+ if(Connection->State == CONNECTION_CONNECTED){
+ uint8_t *SourceAddr = NULL;
+ if(Help[0] == 0x0D && Help[1] == 0x0A){
+ // NOTE(fusion): HAProxy-V2 header.
+ static const uint8_t HAProxyV2Signature[] = {
+ 0x0D, 0x0A, 0x0D, 0x0A, 0x00, 0x0D, 0x0A, 0x51,
+ 0x55, 0x49, 0x54, 0x0A,
+ };
+
+ if(ReadFromSocket(Connection, Help + 2, 26) != 26){
+ error("Unable to read proxy header from %s (Socket=%d)\n",
+ Connection->IPAddress, Connection->GetSocket());
+ return false;
+ }
+
+ if(memcmp(Help, HAProxyV2Signature, 12) != 0){
+ error("Invalid proxy header from %s (Socket=%d)\n",
+ Connection->IPAddress, Connection->GetSocket());
+ return false;
+ }
+
+ SourceAddr = Help + 16;
+ }else if(Help[0] == 0xD9 && Help[1] == 0xFF){
+ // NOTE(fusion): Our custom proxy header.
+ if(ReadFromSocket(Connection, Help + 2, 4) != 4){
+ error("Unable to read proxy header from %s (Socket=%d)\n",
+ Connection->IPAddress, Connection->GetSocket());
+ return false;
+ }
+
+ SourceAddr = Help + 2;
+ }
+
+ if(SourceAddr != NULL){
+ // NOTE(fusion): Ideally we'd have a list of trusted proxies to
+ // prevent clients from spoofing their addresses. In reality, it
+ // is almost always simpler to have a localhost "receiver". This
+ // is also the only way to get a multi-route proxy without having
+ // to drastically change the way connections are handled.
+ if(strcmp(Connection->IPAddress, "127.0.0.1") != 0){
+ error("Received proxy header (%02X, %02X) from unknown host %s\n",
+ Help[0], Help[1], Connection->IPAddress);
+ return false;
+ }
+
+ // NOTE(fusion): Update connection IP-Address and resume reading.
+ snprintf(Connection->IPAddress, sizeof(Connection->IPAddress),
+ "%d.%d.%d.%d",
+ (int)SourceAddr[0], (int)SourceAddr[1],
+ (int)SourceAddr[2], (int)SourceAddr[3]);
+ continue;
+ }
+ }
+#endif
+
// TODO(fusion): Size is encoded as a little endian uint16. We should
// have a few helper functions to assist with buffer reading.
int Size = ((uint16)Help[0] | ((uint16)Help[1] << 8));
diff --git a/src/crnonpl.cc b/src/crnonpl.cc
index 3a6c053..1522cab 100644
--- a/src/crnonpl.cc
+++ b/src/crnonpl.cc
@@ -1760,9 +1760,17 @@ void TNPC::IdleStimulus(void){
case 3: DestY += 1; break;
}
- if(this->MovePossible(DestX, DestY, DestZ, true, false)){
- DestFound = true;
- break;
+ // NOTE(fusion): TNPC::MovePossible in particular doesn't throw
+ // exceptions so we don't need a try-catch block here like in the
+ // case of TMonster::IdleStimulus, nor does the original function
+ // has it. That said, it's still better to be safe.
+ try{
+ if(this->MovePossible(DestX, DestY, DestZ, true, false)){
+ DestFound = true;
+ break;
+ }
+ }catch(RESULT r){
+ // no-op
}
}
@@ -2702,7 +2710,7 @@ void TMonster::IdleStimulus(void){
// TODO(fusion): We're doing something similar to `TCombat::CanToDoAttack`
// with added random steps around the attacking position. This function is
// too convoluted and we should definitely split it into smaller ones. I
- // don't have a problem with large functions but this it too much.
+ // don't have a problem with large functions but this is too much.
// NOTE(fusion): Max distance.
int Distance = std::max<int>(
@@ -2936,7 +2944,7 @@ bool TMonster::KickCreature(TCreature *Creature){
print(3, "%s verschiebt %s.\n", this->Name, Creature->Name);
- // TODO(fusion): Declare these here so they can be used within the catch
+ // NOTE(fusion): Declare these here so they can be used within the catch
// block to print out what's on the last position we tried to move the
// creature to.
int DestX = Creature->posx;
diff --git a/src/crplayer.cc b/src/crplayer.cc
index 4934966..8878bda 100644
--- a/src/crplayer.cc
+++ b/src/crplayer.cc
@@ -2845,7 +2845,7 @@ void AttachPlayerPoolSlot(TPlayerData *Slot, bool DontWait){
}
PlayerDataPoolMutex.up();
- DelayThread(0,100);
+ DelayThread(0, 100);
}
}
diff --git a/tibia-game.service b/tibia-game.service
index 3c5e702..7a20c2d 100644
--- a/tibia-game.service
+++ b/tibia-game.service
@@ -19,7 +19,7 @@ RestartSec=10
# After a SIGTERM, the server will issue shutdown warnings for 5 minutes and
# take another minute or two to save everything and exit. Not waiting for this
# process to complete would cause data to not be properly saved.
-TimeoutStopSec=600
+TimeoutStopSec=900
LimitCORE=infinity
StandardOutput=journal
StandardError=journal