forked from Insprill/dv-multiplayer
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Improved servers and server browser code
Updated API spec to include private_key requirements Modularised the Rust server and compliance to new spec Updated the PHP server to comply with new spec, additional config to allow flatfile and MySQL databases. Added ReadMe. ServerBrowser major refactor. Now loads data from the lobby server
- Loading branch information
Showing
30 changed files
with
1,397 additions
and
545 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,10 @@ | ||
<?php | ||
|
||
interface DatabaseInterface { | ||
public function addGameServer($data); | ||
public function updateGameServer($data); | ||
public function removeGameServer($data); | ||
public function listGameServers(); | ||
} | ||
|
||
?> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,96 @@ | ||
<?php | ||
|
||
class FlatfileDatabase implements DatabaseInterface { | ||
private $filePath; | ||
|
||
public function __construct($dbConfig) { | ||
$this->filePath = $dbConfig['flatfile_path']; | ||
} | ||
|
||
private function readData() { | ||
if (!file_exists($this->filePath)) { | ||
return []; | ||
} | ||
return json_decode(file_get_contents($this->filePath), true) ?? []; | ||
} | ||
|
||
private function writeData($data) { | ||
file_put_contents($this->filePath, json_encode($data, JSON_PRETTY_PRINT)); | ||
} | ||
|
||
public function addGameServer($data) { | ||
$data['last_update'] = time(); // Set current time as last_update | ||
|
||
$servers = $this->readData(); | ||
$servers[] = $data; | ||
$this->writeData($servers); | ||
|
||
return json_encode(["game_server_id" => $data['game_server_id']]); | ||
} | ||
|
||
public function updateGameServer($data) { | ||
$servers = $this->readData(); | ||
$updated = false; | ||
|
||
foreach ($servers as &$server) { | ||
if ($server['game_server_id'] === $data['game_server_id']) { | ||
$server['current_players'] = $data['current_players']; | ||
$server['time_passed'] = $data['time_passed']; | ||
$server['last_update'] = time(); // Update with current time | ||
$updated = true; | ||
break; | ||
} | ||
} | ||
|
||
if ($updated) { | ||
$this->writeData($servers); | ||
return json_encode(["message" => "Server updated"]); | ||
} else { | ||
return json_encode(["error" => "Failed to update server"]); | ||
} | ||
} | ||
|
||
public function removeGameServer($data) { | ||
$servers = $this->readData(); | ||
$servers = array_filter($servers, function($server) use ($data) { | ||
return $server['game_server_id'] !== $data['game_server_id']; | ||
}); | ||
$this->writeData(array_values($servers)); | ||
return json_encode(["message" => "Server removed"]); | ||
} | ||
|
||
public function listGameServers() { | ||
$servers = $this->readData(); | ||
$current_time = time(); | ||
$active_servers = []; | ||
$changed = false; | ||
|
||
foreach ($servers as $key => $server) { | ||
if ($current_time - $server['last_update'] <= TIMEOUT) { | ||
$active_servers[] = $server; | ||
} else { | ||
$changed = true; // Indicates there's a change if any server is removed | ||
} | ||
} | ||
|
||
if ($changed) { | ||
$this->writeData($active_servers); // Write back only if there are changes | ||
} | ||
|
||
return json_encode($active_servers); | ||
} | ||
|
||
|
||
|
||
public function getGameServer($game_server_id) { | ||
$servers = $this->readData(); | ||
foreach ($servers as $server) { | ||
if ($server['game_server_id'] === $game_server_id) { | ||
return json_encode($server); | ||
} | ||
} | ||
return json_encode(null); | ||
} | ||
} | ||
|
||
?> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,74 @@ | ||
<?php | ||
|
||
class MySQLDatabase implements DatabaseInterface { | ||
private $pdo; | ||
|
||
public function __construct($dbConfig) { | ||
$this->pdo = new PDO("mysql:host={$dbConfig['host']};dbname={$dbConfig['dbname']}", $dbConfig['username'], $dbConfig['password']); | ||
$this->pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION); | ||
} | ||
|
||
public function addGameServer($data) { | ||
$stmt = $this->pdo->prepare("INSERT INTO game_servers (game_server_id, private_key, ip, port, server_name, password_protected, game_mode, difficulty, time_passed, current_players, max_players, required_mods, game_version, multiplayer_version, server_info, last_update) | ||
VALUES (:game_server_id, :private_key, :ip, :port, :server_name, :password_protected, :game_mode, :difficulty, :time_passed, :current_players, :max_players, :required_mods, :game_version, :multiplayer_version, :server_info, :last_update)"); | ||
$stmt->execute([ | ||
':game_server_id' => $data['game_server_id'], | ||
':private_key' => $data['private_key'], | ||
':ip' => $data['ip'], | ||
':port' => $data['port'], | ||
':server_name' => $data['server_name'], | ||
':password_protected' => $data['password_protected'], | ||
':game_mode' => $data['game_mode'], | ||
':difficulty' => $data['difficulty'], | ||
':time_passed' => $data['time_passed'], | ||
':current_players' => $data['current_players'], | ||
':max_players' => $data['max_players'], | ||
':required_mods' => $data['required_mods'], | ||
':game_version' => $data['game_version'], | ||
':multiplayer_version' => $data['multiplayer_version'], | ||
':server_info' => $data['server_info'], | ||
':last_update' => time() //use current time | ||
]); | ||
return json_encode(["game_server_id" => $data['game_server_id']]); | ||
} | ||
|
||
public function updateGameServer($data) { | ||
$stmt = $this->pdo->prepare("UPDATE game_servers | ||
SET current_players = :current_players, time_passed = :time_passed, last_update = :last_update | ||
WHERE game_server_id = :game_server_id"); | ||
$stmt->execute([ | ||
':current_players' => $data['current_players'], | ||
':time_passed' => $data['time_passed'], | ||
':last_update' => time(), // Update with current time | ||
':game_server_id' => $data['game_server_id'] | ||
]); | ||
|
||
return $stmt->rowCount() > 0 ? json_encode(["message" => "Server updated"]) : json_encode(["error" => "Failed to update server"]); | ||
} | ||
|
||
public function removeGameServer($data) { | ||
$stmt = $this->pdo->prepare("DELETE FROM game_servers WHERE game_server_id = :game_server_id"); | ||
$stmt->execute([':game_server_id' => $data['game_server_id']]); | ||
return $stmt->rowCount() > 0 ? json_encode(["message" => "Server removed"]) : json_encode(["error" => "Failed to remove server"]); | ||
} | ||
|
||
public function listGameServers() { | ||
// Remove servers that exceed TIMEOUT directly in the SQL query | ||
$stmt = $this->pdo->prepare("DELETE FROM game_servers WHERE last_update < :timeout"); | ||
$stmt->execute([':timeout' => time() - TIMEOUT]); | ||
|
||
// Fetch remaining servers | ||
$stmt = $this->pdo->query("SELECT * FROM game_servers"); | ||
$servers = $stmt->fetchAll(PDO::FETCH_ASSOC); | ||
|
||
return json_encode($servers); | ||
} | ||
|
||
public function getGameServer($game_server_id) { | ||
$stmt = $this->pdo->prepare("SELECT * FROM game_servers WHERE game_server_id = :game_server_id"); | ||
$stmt->execute([':game_server_id' => $game_server_id]); | ||
return json_encode($stmt->fetch(PDO::FETCH_ASSOC)); | ||
} | ||
} | ||
|
||
?> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,146 @@ | ||
# Lobby Server - PHP | ||
|
||
This is a PHP implementation of the Derail Valley Lobby Server REST API service. It is designed to run on any standard web hosting and does not rely on long-running/persistent behaviour. | ||
HTTPS support depends on the configuration of the hosting environment. | ||
|
||
As this implementation is not persistent in memory, a database is used to store server information. Two options are available for the database engine - a JSON based flatfile or a MySQL database. | ||
|
||
## Installing | ||
|
||
1. Copy the following files to your public html folder (consult your web server/web host's documentation) | ||
``` | ||
index.php | ||
DatabaseInterface.php | ||
FlatfileDatabase.php | ||
MySQLDatabase.php | ||
``` | ||
2. Copy `config.php` to a secure location outside of your public html directory | ||
3. Edit `index.php` and update the path to the config file on line 2: | ||
```php | ||
<?php | ||
include '/path/to/config.php'; | ||
... | ||
``` | ||
|
||
### Setup the database | ||
|
||
Choose between a MySQL database and a flatfile database. | ||
|
||
#### MySQL | ||
|
||
1. Edit `config.php` and set the `type` parameter to `mysql` | ||
2. Update the `host`, `dbname`, `username` and `password` parameters for your MySQL server | ||
3. Copy the `install.php` file to a location you can execute it | ||
4. Edit `install.php` and update the path to the config file on line 2: | ||
```php | ||
<?php | ||
include '/path/to/config.php'; | ||
... | ||
``` | ||
5. Run `install.php` | ||
6. If `install.php` is in a publically visible location, remove it | ||
|
||
|
||
#### Flatfile | ||
1. In a secure location (outside of your public html directory), create a directory to store the database | ||
2. Edit `config.php` and set the `type` parameter to `flatfile` | ||
3. Update `flatfile_path` to the directory created in step 1 | ||
|
||
|
||
## Configuration Parameters | ||
The server can be configured using the `config.php` file. | ||
|
||
Below are the available parameters along with their defaults: | ||
- `TIMEOUT` (u64): The time-out period in seconds for server removal. | ||
-- Default: `120` | ||
-- `dbConfig`.`type` (string): Type of database for persisting data. | ||
-- Options: `mysql`, `flatfile` | ||
-- Default: `mysql` | ||
- `dbConfig`.`host` (string): hostname of MySQLServer. | ||
-- Required when: `dbConfig`.`type` is `mysql` | ||
-- Default: `localhost` | ||
- `dbConfig`.`dbname` (string): Database name. | ||
- Required when: `dbConfig`.`type` is `mysql` | ||
-- Default: N/A | ||
- `dbConfig`.`username` (string): Username for connecting to the MySQLServer. | ||
-- Required when: `dbConfig`.`type` is `mysql` | ||
-- Default: N/A | ||
- `dbConfig`.`password` (string): Password for connecting to the MySQLServer. | ||
-- Required when: `dbConfig`.`type` is `mysql` | ||
-- Default: N/A | ||
- `dbConfig`.`flatfile_path` (string): Path to secure directory. | ||
-- Required when: `dbConfig`.`type` is `flatfile` | ||
-- Default: N/A | ||
|
||
Example `config.php` using MySQL: | ||
```php | ||
<?php | ||
|
||
// Timeout value (in seconds) | ||
define('TIMEOUT', 120); | ||
|
||
// Database configuration | ||
$dbConfig = [ | ||
'type' => 'mysql', | ||
'host' => 'localhost', | ||
'dbname' => 'dv_lobby', | ||
'username' => 'dv_lobby_server', | ||
'password' => 'n16O5+LMpeqI`{E', | ||
'flatfile_path' => '' // Path to store the flatfile database | ||
]; | ||
?> | ||
``` | ||
|
||
Example `config.php` using Flatfile: | ||
```php | ||
<?php | ||
|
||
// Timeout value (in seconds) | ||
define('TIMEOUT', 120); | ||
|
||
// Database configuration | ||
$dbConfig = [ | ||
'type' => 'flatfile', | ||
'host' => '', | ||
'dbname' => '', | ||
'username' => '', | ||
'password' => '', | ||
'flatfile_path' => '/dv_lobby/flatfile.db' // Path to store the flatfile database | ||
]; | ||
?> | ||
``` | ||
|
||
## Security Considerations | ||
This is a non-comprehensive overview of security considerations. You should always use up-to-date best practices and seek professional advice where required. | ||
|
||
### Environment variables | ||
Consider using environment variables to store sensitive database credentials (e.g. `dbConfig`.`host`, `dbConfig`.`dbname`, `dbConfig`.`username`, `dbConfig`.`password`) instead of hardcoding them in config.php. | ||
Your `config.php` can be updated to reference the environment variables. | ||
|
||
Example: | ||
```php | ||
$dbConfig = [ | ||
'type' => 'mysql', | ||
'host' => getenv('DB_HOST'), | ||
'dbname' => getenv('DB_NAME'), | ||
'username' => getenv('DB_USER'), | ||
'password' => getenv('DB_PASSWORD'), | ||
'flatfile_path' => '/path/to/flatfile.db' | ||
]; | ||
``` | ||
|
||
|
||
### File Permissions | ||
Ensure that `config.php` and any other sensitive files outside the web root are only readable by the web server user (chmod 600). | ||
For directories containing flatfile databases, restrict permissions (chmod 700 or 750) to prevent unauthorised access. | ||
|
||
### HTTPS (SSL) | ||
Configure your server to use https. Many web hosts provide free SSL certificates via Let's Encrypt. | ||
Consider forcing https via server config/`.httaccess`. | ||
|
||
Example: | ||
```apacheconf | ||
RewriteEngine On | ||
RewriteCond %{HTTPS} off | ||
RewriteRule ^(.*)$ https://%{HTTP_HOST}%{REQUEST_URI} [L,R=301] | ||
``` |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.