diff options
| author | Darkelarious <darkelarious@333networks.com> | 2017-05-13 14:18:28 +0200 |
|---|---|---|
| committer | Darkelarious <darkelarious@333networks.com> | 2017-05-13 14:20:49 +0200 |
| commit | 34a2c7390ea9662d33258d384e72fff1912343ff (patch) | |
| tree | d96ea33c0107e4906a152aa1de4b5c75b81ba0a8 | |
| parent | 84af66aba26d2088d5d95c240d176f3edaf17b58 (diff) | |
| download | MasterServer-Perl-2.3.0.tar.gz MasterServer-Perl-2.3.0.zip | |
revised synchronization methods, config settings and bug fixesv2.3.0
46 files changed, 1224 insertions, 549 deletions
diff --git a/CHANGELOG b/CHANGELOG new file mode 100644 index 0000000..81a1b51 --- /dev/null +++ b/CHANGELOG @@ -0,0 +1,30 @@ +AUTHOR + Darkelarious + http://333networks.com + darkelarious@333networks.com + +CHANGELOG + +2.3.0 - 13 May 2017 + * maintaining a changelog + * added/changed configuration options + - db_dump (database dump interval) + - hex_format (removed, deprecated) + - master_applet (syntax changed, more arguments) + * revised uplinking method between 333networks-based masterservers + * load applet configuration into the database on startup + * load synchronization servers into the database on startup + * automatic masterserver selection for synchronization + * sharing of synchronization servers + * applet updating/deleting based on success rate + * scheduled database dumping/backup + * configurable time-outs for slow connections + * added experts tools in the repository for debugging + * reduce sync time with smarter database queries + * wide variety of bugfixes and code improvements + +2.2.5 - 19 Nov 2016 + * Reference point for changelog + * improved error logging + * hotfix for missing gamenames + * restored SQLite3 support @@ -1,4 +1,4 @@ -Copyright (c) 2005-2016 Darkelarious & 333networks.com +Copyright (c) 2005-2017 Darkelarious & 333networks.com Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the @@ -1,15 +1,15 @@ ========= DESCRIPTION - This repository contains the source code which was written by Darkelarious to - soften the effects of GameSpy (GameSpy Industries, Inc.) shutting down their - services. Among this service was a so-called "masterserver". - + A masterserver is a program that maintains a list of online game servers and presents this list to clients (gamers, players) who request the list of game addresses. The 333networks Masterserver is a software framework that allows - gamers/players to browse for online games in the same manner as GameSpy - provided this service. + gamers/players to browse online games. + + This repository contains the source code which was written by Darkelarious to + soften the effects of GameSpy (GameSpy Industries, Inc.) shutting down their + services. Among this service was a so-called "masterserver". More information about the masterserver and variations on the protocol by 333networks can be found online at @@ -17,11 +17,13 @@ DESCRIPTION http://wiki.333networks.com/index.php/MasterServer AUTHOR + Darkelarious http://333networks.com darkelarious@333networks.com REQUIREMENTS + - Postgresql, MySQL or SQLite3 - Perl 5.10 or above - The following CPAN modules: @@ -31,6 +33,7 @@ REQUIREMENTS AnyEvent::Handle::UDP IP::Country::Fast Switch + JSON - screen (or another terminal multiplexer, optional) INSTALL @@ -38,33 +41,37 @@ INSTALL THE MASTER SERVER IS WRITTEN ON LINUX. IF YOU WANT TO RUN THE SOFTWARE IN MICROSOFT WINDOWS OR APPLE OSX, IT MAY NOT WORK WITHOUT MODIFICATIONS. - This repository consists of Perl modules and is run in terminal. The software - utilizes UDP and TCP connections to receive and send information about game - servers. Carefully read through the code and documentation before you start - randomly querying other game servers and/or masterservers. + Download the masterserver files from our GIT repository (or zip) and extract + them to your favorite server directory. The masterserver stores server addresses in the database. This database must be created manually. SQLite3 users should not forget to chmod the database file with read/write access. The tables to be created can be found in the - "data/database" folder in the tables-Pg/SQLite/mysql.sql file. Choose your - prefered database driver and created the tables as described below. + "data/sql" folder in the tables-Pg/SQLite/mysql.sql file. Choose your + preferred database driver and create the tables by importing the SQL file or + by manually creating the tables with your SQL shell. + + This repository consists of Perl modules and is run in terminal. The software + utilizes UDP and TCP connections to receive and send information about game + servers. Carefully read through the code and documentation before you start + randomly querying other game servers and/or masterservers. CONFIGURATION The 333networks masterserver comes with options. These options are found in - configuration file "data/masterserver-config.pl". Comments in this file give - a brief description. Below, the configuration is discussed in further detail. + configuration file "data/masterserver-config.pl". Comments in that file give + a brief description. Here, the configuration is discussed in further detail. Masterserver HOST information Fill in your contact details here to be able to synchronize with other - masterservers. Though the synchronization process also works without your - contact details, it is preferred and appreciated if you allow other - masterservers to synchronize with you too. In addition, third parties may - want to retrieve the status of your masterserver. When remote servers query - for your contact information, they can see who hosts this masterserver, which - build you are running and which games you currently support. This is highly - recommended and much appreciated. + masterservers. These settings are displayed on the web interfaces and are + shown to others in order to connect to your masterserver. + + This information is also shown when others use the \status\ query on your + server: they can see who hosts this masterserver, which build you are running + and which games you currently support. Filling in this information is highly + recommended and much appreciated by the community. Database login information @@ -72,22 +79,28 @@ CONFIGURATION "lib/MasterServer/Databases" you can see which types are currently supporting out of the box. To choose a database type, specify the "dblogin" variable: - # Postgresql - dblogin => ['dbi:Pg:dbname=databasename', 'user', 'password'], + # Postgresql + dblogin => ['dbi:Pg:dbname=databasename', 'user', 'password'], - # SQLite - dblogin => ["dbi:SQLite:dbname=$ROOT/data/databasename.db",'',''], + # SQLite + dblogin => ["dbi:SQLite:dbname=$ROOT/data/databasename.db",'',''], - # MySQL - dblogin => ["dbi:mysql:database=databasename;host=localhost;port=3306", + # MySQL + dblogin => ["dbi:mysql:database=databasename;host=localhost;port=3306", 'user','password'], Keep in mind that the database needs to be created manually. That means that you first have to create your postgres/mysql user and grant permissions as described in the proprietary manuals that come with your database installation. After that, you have to insert the tables manually, which are - provided in the "data/database" folder. The masterserver script requires - read-and-write permissions on the SQLite database file. + provided in the "data/sql" folder. The masterserver script requires + read-and-write permissions on the SQL database file(s). + + dump_db => "daily", + + It is useful to dump the database every once in a while for backup purposes. + This can be done by specifying the "dump_db" option as above, with the options + every day, week, month, year or not at all. Logging @@ -98,14 +111,14 @@ CONFIGURATION option takes the message type, separated by spaces. To suppress a message type, use the identifier between brackets. Example: - [2015-05-19 17:31:47] [load] > Connected to the Postgres database. + [2017-05-13 17:31:47] [debug] > Connected to the Postgres database. In this message, the timestamp of the message is shown first, followed by the - identifier "Success". To suppress this type of message, the following + identifier "debug". To suppress this type of message, the following parameter can be set: - suppress => " load " - suppress => " load beacon secure hostname " + suppress => "debug" + suppress => "debug beacon secure stat" More message types can be suppressed, where the types are separated by spaces as shown in the second example. If you want to log a lot of events, you could @@ -116,11 +129,13 @@ CONFIGURATION The masterserver uses UDP and TCP networking. The default port numbers are 27900 for server beacons (UDP) and 28900 for serverlists (TCP). Home-hosters - have to open those firewall ports. - Not all games request data in the same format. Until we established how the - request for a different datatype are made, we also provide the ability to - send data in the alternate (byte) form manually. More games can be added, - separated by spaces. + have to open those firewall ports. + + timeout_time => 5, + + Some servers and clients have slow connections or a lot of latency. If you + experience issues with this, increase the time-out time for connections. + Recommended: 5 seconds. Supported Games & Secure/Validate @@ -138,9 +153,10 @@ CONFIGURATION If you use the proper authentication ciphers, keep in mind that some games, like Deus Ex, have more than one cipher, or do not support the challenge at all. Other games may have been modified to counter the vulnerability against - long queries. Some UT server have been observed to respond with "Orange" + long queries. Some UT servers have been observed to respond with "Orange" instead of the correct response. Both situations can be in- or excluded in - the "ignore_key" options. + the "ignore_key" options. Contact 333networks for more information about + obtaining ciphers. Enable settings @@ -153,6 +169,10 @@ CONFIGURATION the masterserver can query the addresses individually, to determine whether they are valid game servers. Change with "beacon_checker_enabled". + This also scans servers periodically to see whether they are still online, + what game they are and other server information such as version/compatibility + and server name. + Query UCC Applets In addition, other UCC masterserver applets can be queried for more server @@ -170,11 +190,27 @@ CONFIGURATION your list. Your masterserver is then queried in the same way as if it were an ordinary game server. This also shares the information that you provided earlier in the HOST information section. If you do not wish this to happen, - you should disable synchronization entirely. + you should disable synchronization entirely. Change with "sync_enabled". + Newly received servers are added to the "pending" list as described above. - Change with "sync_enabled". Please note that this will not prevent others - from obtaining the server lists. Attempting to disable this is hypocrite and - ambiguous, as "regular" clients do the same to get the serverlist. + Please note that this will not prevent others from obtaining the server + lists. Attempting to disable this is ambiguous, as "regular" clients do the + same to get the serverlist. + + BY ENABLING SYNCHRONIZATION, YOU AGREE TO BE QUERIED BY OTHER 333NETWORKS + BASED MASTERSERVERS AND SYNCHRONIZATION TOOLS. To us, this sounds perfectly + reasonable and logical, but some newlings actually started complaining that + when they leeched off masterservers, others actually queried them too. If you + do not want other people querying your masterserver, then why are you running + a masterserver in the first place? + + We do not want authorized people mass-spamming our (or other's) masterservers + with sync request to determine whether the server is online (yes, this + actually happens -- use the \status\ query for that!) or to keep the list + unnecessary updated. Sync requests should not be executed more than 1-2 times + per hour. + + If you want to sync with us, please email us on info@333networks.com RUNNING @@ -208,32 +244,41 @@ TOOLS not necessary to use these tools. While debugging and writing new features for the masterserver, these scripts have been very handy. Therefore, they are included in the repository. Poor documentation is included in the scripts and - required files themselves. + in the required files themselves. Do not forget to change database settings + per tool. Expert knowledge required! KNOWN ISSUES + There are a few known issues that will be resolved in future versions. The following issues are listed and do not need to be reported. - MySQL does not work with this masterserver version. When any database type - other than Postgresql or SQLite3 is selected, the script either crashes. An - update where this is fixed will follow late 2016 or early 2017. + MySQL has not been tested with this masterserver version. When any database + other than Postgresql or SQLite3 is selected, the masterserver may crash. + Support for MySQL will follow at some point, hopefully late 2017. + + Differences in statistics compared to other 333networks-based masterservers. + Some other masterserver interfaces may show different statistics than your + own masterserver. This may have multiple reasons: servers could have gone + offline before your masterserver established whether these addresses were + online; servers may have missed the bi-hourly update moment and show offline, + but may get updated in the next cycle; servers send beacons to the other + masterserver directly, but have incorrect firewall settings that prevent them + from being checked after synchronization. These factors can not be compensated + by the 333networks masterservers. Slow database: currently, database requests are blocking. On slow hardware, these requests may take enough time to miss/deny incoming beacons and list - requests. No solution available in this version. Proposed solution: AnyEvent's - asynchronous database driver(s). The current configuration for 333networks and - affiliated servers do not suffer from this problem. + requests. No solution available in this version. The current configuration + for 333networks and affiliated servers do not suffer from this problem. No servers found after syncing with 333networks or others: the syncing protocol also requires authentication. We do not want authorized people mass- - spamming our masterservers with sync request to determine whether the server - is online (yes, this actually happens -- use the \about\ query for that!) or - to keep the list unnecessary updated. Sync requests should not be executed - more than 1-2 times per hour! - If you want to sync with us, please email 333networks on info@333networks.com + spamming our (or other's) masterservers with unnecessarily fast sync request. + If you want to sync with us, please email us on info@333networks.com COPYING - Copyright (c) 2005-2016 Darkelarious & 333networks.com + + Copyright (c) 2005-2017 Darkelarious & 333networks.com Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the diff --git a/data/dumps/dumpfile.txt b/data/dumps/dumpfile.txt new file mode 100644 index 0000000..03d9604 --- /dev/null +++ b/data/dumps/dumpfile.txt @@ -0,0 +1 @@ +dumpfiles go here diff --git a/data/masterserver-config.pl b/data/masterserver-config.pl index 8af455a..7cae5de 100755 --- a/data/masterserver-config.pl +++ b/data/masterserver-config.pl @@ -1,7 +1,7 @@ package MasterServer; # -# Last update: Sun 20 Nov 2016 19:14 GMT+1 +# Last update: Sat 13 May 2017 13:37 GMT+1 # our (%S, $ROOT); @@ -32,7 +32,7 @@ our %S = ( # # # Login credentials for the database that was created manually before. # # Yes, that means that you need to create the database and tables on your own. # -# Use only one option: Postgresql, SQLite, MySQL (only tested with Postgresql) # +# Use only one option: Postgresql, SQLite, MySQL (not tested with MySQL) # # # ################################################################################ @@ -40,11 +40,15 @@ our %S = ( dblogin => ['dbi:Pg:dbname=masterserver', 'user', 'password'], # SQLite - #dblogin => ["dbi:SQLite:dbname=$ROOT/data/testdatabase.db",'',''], + #dblogin => ["dbi:SQLite:dbname=$ROOT/data/masterserver.db",'',''], # MySQL - #dblogin => ["dbi:mysql:database=database_name;host=localhost;port=3306",'user','password'], - + #dblogin => ["dbi:mysql:database=masterserver;host=localhost;port=3306",'user','password'], + + # backup database dump + # new backup for every period of time? options: daily, weekly, monthly, yearly, none + dump_db => "daily", + ################################################################################ # Logging configuration # # # @@ -60,20 +64,22 @@ our %S = ( log_rotate => "weekly", # print both to screen and log (1=screen+log, 0=only log) - printlog => 0, + printlog => 1, # which messages do you NOT want to see in the logs (and screen)? # show all entries #suppress => "none", - # disable most messages, except for important events - suppress => "udp add update tcp udp delete uplink stat beacon secure utserver hostname kfstat debug", + # show only important events + suppress => "debug beacon uplink secure tcp add update delete", + + # more keywords that can be suppressed: + # applet-rx error info kfstat stat sync-rx sync-tx list ignore dump support ################################################################################ # Network settings # # # # Beacon UDP port (beacons) and Browser TCP port (serverlist) # -# Settings for games that require different data formats # # # ################################################################################ @@ -81,9 +87,9 @@ our %S = ( listen_port => 28900, # default 28900 beacon_port => 27900, # default 27900 - # these games require a special hex format instead of \ip\ip:port\ - # if the current protocol is correct, you don't need to touch this ever. - hex_format => "", + # Timeout time for connections. Some clients are on slow connections + # or are queued for a relatively long time. Recommended: 5s + timeout_time => 10, ################################################################################ # Secure/Validate configuration # @@ -118,7 +124,7 @@ our %S = ( # Beacon Checker query all addresses in the database, requesting "basic" and # "info". Execute at least twice per hour, to avoid time-outs in own data. - # disabling breaks support for certain games [citation needed]. + # disabling breaks support for certain games [like tribesv]. beacon_checker_enabled => 1, # Collect server information for the 333networks main site. Identical @@ -130,18 +136,17 @@ our %S = ( ################################################################################ # Synchronization settings # # # -# Request the masterlist for selected or all games from other 333networks- # -# based masterservers. Also uplinks to these servers in return. # -# # +# Send beacons to the following selected masterservers. This joins us in the # +# 333networks network and makes two-way synchronization possible for all games # +# or only selected games. Requires at least one entry to a live masterserver. # ################################################################################ - # additional masters to sync with (in addition to db-entries) + # default masterservers to uplink to sync_masters => [ - { address => "master.333networks.com", port => 28900, beacon => 27900 }, # default + { address => "master.333networks.com", port => 28900, beacon => 27900 }, { address => "master.noccer.de", port => 28900, beacon => 27900 }, { address => "master.oldunreal.com", port => 28900, beacon => 27900 }, { address => "master.errorist.tk", port => 28900, beacon => 27900 }, -# { address => "master.333networks.com", port => 28905, beacon => 28906 }, # if available, devmaster ], # sync all or selected games? @@ -157,10 +162,13 @@ our %S = ( # Request the masterlist for single games from the remote UCC applet or # # equivalent. # # # +# Arguments: domain/ip, tcp port, array of gamenames # ################################################################################ master_applet => [ - {ip => "utmaster.epicgames.com", port => 28900, game => "ut"}, - {ip => "master.newbiesplayground.net", port => 28900, game => "unreal"}, + {address => "utmaster.epicgames.com", port => 28900, games => [qw|ut unreal|]}, + {address => "master.hypercoop.tk", port => 28900, games => [qw|unreal|]}, + {address => "sof1master.megalag.org", port => 28900, games => [qw|sofretail|]}, + {address => "master.deusexnetwork.com", port => 28900, games => [qw|deusex|]}, ], ################################################################################ @@ -169,12 +177,12 @@ our %S = ( # Read player statistics from the KFstats file in the UT2004 configuration. # # Applies to 333networks Killing Floor Server only! # ################################################################################ - - #kfstats.ini file location - kfstats_file => "/home/darkelarious/ut2004/System/KFStats.ini", # Collect kfstats info kfstats_enabled => 0, + + #kfstats.ini file location + kfstats_file => "/UT2004/System/KFStats.ini", ); #end configuration %S diff --git a/data/sql/tables-Pg.sql b/data/sql/tables-Pg.sql index f5663ff..b4ddd4d 100755 --- a/data/sql/tables-Pg.sql +++ b/data/sql/tables-Pg.sql @@ -1,10 +1,19 @@ +CREATE TABLE appletlist( + id SERIAL UNIQUE NOT NULL PRIMARY KEY, + ip inet NOT NULL DEFAULT '0.0.0.0', + port INTEGER NOT NULL DEFAULT 0, + gamename VARCHAR(50) NOT NULL DEFAULT ' ', + added timestamptz NOT NULL DEFAULT NOW(), + updated timestamptz NOT NULL DEFAULT NOW() +); + CREATE TABLE serverlist( id SERIAL UNIQUE NOT NULL PRIMARY KEY, ip inet NOT NULL DEFAULT '0.0.0.0', port INTEGER NOT NULL DEFAULT 0, gamename VARCHAR(50) NOT NULL DEFAULT ' ', gamever VARCHAR(50) NOT NULL DEFAULT ' ', - hostname VARCHAR(100) NOT NULL DEFAULT ' ', + hostname VARCHAR(200) NOT NULL DEFAULT ' ', hostport INTEGER NOT NULL DEFAULT 0, country VARCHAR(5), b333ms BOOLEAN NOT NULL DEFAULT FALSE, @@ -14,22 +23,84 @@ CREATE TABLE serverlist( updated timestamptz NOT NULL DEFAULT NOW() ); +CREATE TABLE games( + gamename VARCHAR(50) NOT NULL, + cipher VARCHAR(10) NOT NULL DEFAULT ' ', + description VARCHAR(200) NOT NULL DEFAULT ' ', + default_qport INTEGER NOT NULL DEFAULT 0, + num_uplink INTEGER NOT NULL DEFAULT 0, + num_total INTEGER NOT NULL DEFAULT 0 +); + CREATE TABLE pending( id SERIAL UNIQUE NOT NULL PRIMARY KEY, ip inet NOT NULL DEFAULT '0.0.0.0', beaconport INTEGER NOT NULL DEFAULT 0, heartbeat INTEGER NOT NULL DEFAULT 0, gamename VARCHAR(25) NOT NULL DEFAULT ' ', - secure VARCHAR(12) NOT NULL DEFAULT ' ', + secure VARCHAR(12) NOT NULL DEFAULT 'wookie', enctype INTEGER NOT NULL DEFAULT 0, added timestamptz NOT NULL DEFAULT NOW() ); -CREATE TABLE games( - gamename VARCHAR(50) NOT NULL, - cipher VARCHAR(10) NOT NULL DEFAULT ' ', - description VARCHAR(200) NOT NULL DEFAULT ' ', - default_qport INTEGER NOT NULL DEFAULT 0, - num_uplink INTEGER NOT NULL DEFAULT 0, - num_total INTEGER NOT NULL DEFAULT 0 +CREATE TABLE utserver_info( + server_id SERIAL REFERENCES serverlist(id), + minnetver INTEGER NOT NULL DEFAULT 400, + gamever INTEGER NOT NULL DEFAULT 400, + location INTEGER NOT NULL DEFAULT 0, + listenserver BOOLEAN NOT NULL DEFAULT TRUE, + hostport INTEGER NOT NULL DEFAULT 7777, + hostname varchar(200) NOT NULL DEFAULT 'Another UT server', + adminname varchar(200) NOT NULL DEFAULT '', + adminemail varchar(300) NOT NULL DEFAULT '', + password BOOLEAN NOT NULL DEFAULT FALSE, + gametype varchar(50) NOT NULL DEFAULT '', + gamestyle varchar(50) NOT NULL DEFAULT 'Normal', + changelevels BOOLEAN NOT NULL DEFAULT FALSE, + maptitle varchar(100) NOT NULL DEFAULT 'Unknown', + mapname varchar(100) NOT NULL DEFAULT '', + numplayers INTEGER NOT NULL DEFAULT 0, + maxplayers INTEGER NOT NULL DEFAULT 0, + minplayers INTEGER NOT NULL DEFAULT 0, + botskill varchar(30) NOT NULL DEFAULT 'Novice', + balanceteams BOOLEAN NOT NULL DEFAULT FALSE, + playersbalanceteams BOOLEAN NOT NULL DEFAULT FALSE, + friendlyfire varchar(10) NOT NULL DEFAULT '0%', + maxteams INTEGER NOT NULL DEFAULT 4, + timelimit INTEGER NOT NULL DEFAULT 0, + goalteamscore INTEGER NOT NULL DEFAULT 0, + fraglimit INTEGER NOT NULL DEFAULT 0, + mutators TEXT NOT NULL DEFAULT 'None', + updated timestamptz NOT NULL DEFAULT NOW() +); + +CREATE TABLE utplayer_info( + server_id SERIAL NOT NULL, + player varchar(40) NOT NULL DEFAULT 'Player', + team INTEGER NOT NULL DEFAULT 255, + frags INTEGER NOT NULL DEFAULT 0, + mesh varchar(100) NOT NULL DEFAULT '', + skin varchar(100) NOT NULL DEFAULT '', + face varchar(100) NOT NULL DEFAULT '', + ping INTEGER NOT NULL DEFAULT 0, + ngsecret varchar(10) NOT NULL DEFAULT 'false', + updated timestamptz NOT NULL DEFAULT NOW() +); + +CREATE TABLE kfstats( + UTkey varchar(34) NOT NULL, + Username varchar(80) NOT NULL DEFAULT '', + CurrentVeterancy varchar(80) DEFAULT 'None', + TotalKills INTEGER NOT NULL DEFAULT 0, + DecaptedKills INTEGER NOT NULL DEFAULT 0, + TotalMeleeDamage INTEGER NOT NULL DEFAULT 0, + MeleeKills INTEGER NOT NULL DEFAULT 0, + PowerWpnKills INTEGER NOT NULL DEFAULT 0, + BullpupDamage INTEGER NOT NULL DEFAULT 0, + StalkerKills INTEGER NOT NULL DEFAULT 0, + TotalWelded INTEGER NOT NULL DEFAULT 0, + TotalHealed INTEGER NOT NULL DEFAULT 0, + TotalPlaytime INTEGER NOT NULL DEFAULT 0, + GamesWon INTEGER NOT NULL DEFAULT 0, + GamesLost INTEGER NOT NULL DEFAULT 0 ); diff --git a/data/sql/tables-SQLite.sql b/data/sql/tables-SQLite.sql index 7d68786..6050acc 100755 --- a/data/sql/tables-SQLite.sql +++ b/data/sql/tables-SQLite.sql @@ -1,3 +1,12 @@ +CREATE TABLE appletlist( + id INTEGER PRIMARY KEY AUTOINCREMENT, + ip VARCHAR(15) NOT NULL DEFAULT '0.0.0.0', + port INTEGER NOT NULL DEFAULT 0, + gamename VARCHAR(50) NOT NULL DEFAULT ' ', + added timestamptz NOT NULL DEFAULT CURRENT_TIMESTAMP, + updated timestamptz NOT NULL DEFAULT CURRENT_TIMESTAMP +); + CREATE TABLE serverlist( id INTEGER PRIMARY KEY AUTOINCREMENT, ip VARCHAR(15) NOT NULL DEFAULT '0.0.0.0', @@ -14,22 +23,85 @@ CREATE TABLE serverlist( updated timestamptz NOT NULL DEFAULT CURRENT_TIMESTAMP ); +CREATE TABLE games( + gamename VARCHAR(50) NOT NULL, + cipher VARCHAR(10) NOT NULL DEFAULT ' ', + description VARCHAR(200) NOT NULL DEFAULT ' ', + default_qport INTEGER NOT NULL DEFAULT 0, + num_uplink INTEGER NOT NULL DEFAULT 0, + num_total INTEGER NOT NULL DEFAULT 0 +); + CREATE TABLE pending( id INTEGER PRIMARY KEY AUTOINCREMENT, ip VARCHAR(15) NOT NULL DEFAULT '0.0.0.0', beaconport INTEGER NOT NULL DEFAULT 0, heartbeat INTEGER NOT NULL DEFAULT 0, gamename VARCHAR(25) NOT NULL DEFAULT ' ', - secure VARCHAR(12) NOT NULL DEFAULT ' ', + secure VARCHAR(12) NOT NULL DEFAULT 'wookie', enctype INTEGER NOT NULL DEFAULT 0, added timestamptz NOT NULL DEFAULT CURRENT_TIMESTAMP ); -CREATE TABLE games( - gamename VARCHAR(50) NOT NULL, - cipher VARCHAR(10) NOT NULL DEFAULT ' ', - description VARCHAR(200) NOT NULL DEFAULT ' ', - default_qport INTEGER NOT NULL DEFAULT 0, - num_uplink INTEGER NOT NULL DEFAULT 0, - num_total INTEGER NOT NULL DEFAULT 0 +CREATE TABLE utserver_info( + server_id INTEGER PRIMARY KEY AUTOINCREMENT, + minnetver INTEGER NOT NULL DEFAULT 400, + gamever INTEGER NOT NULL DEFAULT 400, + location INTEGER NOT NULL DEFAULT 0, + listenserver BOOLEAN NOT NULL DEFAULT TRUE, + hostport INTEGER NOT NULL DEFAULT 7777, + hostname varchar(200) NOT NULL DEFAULT 'Another UT server', + adminname varchar(200) NOT NULL DEFAULT '', + adminemail varchar(300) NOT NULL DEFAULT '', + password BOOLEAN NOT NULL DEFAULT 0, + gametype varchar(50) NOT NULL DEFAULT '', + gamestyle varchar(50) NOT NULL DEFAULT 'Normal', + changelevels BOOLEAN NOT NULL DEFAULT 0, + maptitle varchar(100) NOT NULL DEFAULT 'Unknown', + mapname varchar(100) NOT NULL DEFAULT '', + numplayers INTEGER NOT NULL DEFAULT 0, + maxplayers INTEGER NOT NULL DEFAULT 0, + minplayers INTEGER NOT NULL DEFAULT 0, + botskill varchar(30) NOT NULL DEFAULT 'Novice', + balanceteams BOOLEAN NOT NULL DEFAULT 0, + playersbalanceteams BOOLEAN NOT NULL DEFAULT 0, + friendlyfire varchar(10) NOT NULL DEFAULT '0%', + maxteams INTEGER NOT NULL DEFAULT 4, + timelimit INTEGER NOT NULL DEFAULT 0, + goalteamscore INTEGER NOT NULL DEFAULT 0, + fraglimit INTEGER NOT NULL DEFAULT 0, + mutators TEXT NOT NULL DEFAULT 'None', + updated timestamptz NOT NULL DEFAULT CURRENT_TIMESTAMP, + FOREIGN KEY(server_id) REFERENCES serverlist(id) +); + +CREATE TABLE utplayer_info( + server_id INTEGER, + player varchar(40) NOT NULL DEFAULT 'Player', + team INTEGER NOT NULL DEFAULT 255, + frags INTEGER NOT NULL DEFAULT 0, + mesh varchar(100) NOT NULL DEFAULT '', + skin varchar(100) NOT NULL DEFAULT '', + face varchar(100) NOT NULL DEFAULT '', + ping INTEGER NOT NULL DEFAULT 0, + ngsecret varchar(10) NOT NULL DEFAULT 'false', + updated timestamptz NOT NULL DEFAULT CURRENT_TIMESTAMP +); + +CREATE TABLE kfstats( + UTkey varchar(34) NOT NULL, + Username varchar(80) NOT NULL DEFAULT '', + CurrentVeterancy varchar(80) DEFAULT 'None', + TotalKills INTEGER NOT NULL DEFAULT 0, + DecaptedKills INTEGER NOT NULL DEFAULT 0, + TotalMeleeDamage INTEGER NOT NULL DEFAULT 0, + MeleeKills INTEGER NOT NULL DEFAULT 0, + PowerWpnKills INTEGER NOT NULL DEFAULT 0, + BullpupDamage INTEGER NOT NULL DEFAULT 0, + StalkerKills INTEGER NOT NULL DEFAULT 0, + TotalWelded INTEGER NOT NULL DEFAULT 0, + TotalHealed INTEGER NOT NULL DEFAULT 0, + TotalPlaytime INTEGER NOT NULL DEFAULT 0, + GamesWon INTEGER NOT NULL DEFAULT 0, + GamesLost INTEGER NOT NULL DEFAULT 0 ); diff --git a/data/sql/tables-mysql.sql b/data/sql/tables-mysql.sql index c460b1b..0bdfc02 100755 --- a/data/sql/tables-mysql.sql +++ b/data/sql/tables-mysql.sql @@ -1,8 +1,18 @@ +CREATE TABLE appletlist( + id INTEGER NOT NULL AUTO_INCREMENT, + ip VARCHAR(15) NOT NULL DEFAULT '0.0.0.0', + port INTEGER NOT NULL DEFAULT 0, + gamename VARCHAR(50) NOT NULL DEFAULT ' ', + added timestamptz NOT NULL DEFAULT NOW(), + updated timestamptz NOT NULL DEFAULT NOW(), + PRIMARY KEY (id) +); + CREATE TABLE serverlist( id INTEGER NOT NULL AUTO_INCREMENT, ip VARCHAR(15) NOT NULL DEFAULT '0.0.0.0', port INTEGER NOT NULL DEFAULT 0, - gamename VARCHAR(100) NOT NULL DEFAULT ' ', + gamename VARCHAR(50) NOT NULL DEFAULT ' ', gamever VARCHAR(50) NOT NULL DEFAULT ' ', hostname VARCHAR(100) NOT NULL DEFAULT ' ', hostport INTEGER NOT NULL DEFAULT 0, diff --git a/lib/MasterServer/Core/Core.pm b/lib/MasterServer/Core/Core.pm index be0646b..4d1e47b 100755 --- a/lib/MasterServer/Core/Core.pm +++ b/lib/MasterServer/Core/Core.pm @@ -1,4 +1,3 @@ - package MasterServer::Core::Core; use strict; @@ -6,6 +5,7 @@ use warnings; use AnyEvent; use Exporter 'import'; use DBI; +$|++; our @EXPORT = qw | halt select_database_type main |; @@ -47,7 +47,7 @@ sub select_database_type { if ( "Pg SQLite mysql" =~ m/$db_type[1]/i) { # inform us what DB we try to load - $self->log("load","Loading $db_type[1] database module."); + $self->log("debug","Loading $db_type[1] database module."); # load dbd and tables/queries for this db type MasterServer::load_recursive("MasterServer::Database::$db_type[1]"); @@ -74,11 +74,11 @@ sub main { # condition var prevents or allows the program from ending $self->{must_halt} = AnyEvent->condvar; - # force version info + # load version info $self->version(); # print startup - print "Running...\n"; + print "Running 333networks Master Server Application...\n"; # keep several objects alive outside their original scope $self->{scope} = (); @@ -96,14 +96,24 @@ sub main { # determine the type of database and load the appropriate module $self->select_database_type(); + ### # - # Prepare necessary tasks for running the masterserver + # execute necessary tasks for running the masterserver # + ### + + # load the list with ciphers from the config file if no ciphers were detected + # update manually with util/tools/db_load_ciphers.pl + # then unload the game variables from masterserver memory + $self->load_ciphers() unless $self->check_cipher_count(); + $self->{game} = undef; - # (re)load the list with ciphers from the config file, into the database - $self->load_ciphers(); + # (re)load the list with masterservers and master applets from config + # does not clear out old entries, but resets "last_updated" to now + $self->load_sync_masters(); + $self->load_applet_masters(); - # set first run flag to avoid ignoring servers after downtime + # set first run flag to avoid ignoring/deleting servers after downtime $self->{firstrun} = undef; $self->{firstruntime} = time; @@ -134,10 +144,14 @@ sub main { # provide server lists to clients with the browser host server $self->{scope}->{browser_host} = $self->browser_host(); + ### + # # all modules loaded. Running... + # + ### $self->log("info", "All modules loaded. Masterserver is now running."); - # prevent main program from ending prematurely + # prevent main program from ending as long as no fatal errors occur $self->{must_halt}->recv; } diff --git a/lib/MasterServer/Core/LoadConfig.pm b/lib/MasterServer/Core/LoadConfig.pm new file mode 100755 index 0000000..e209848 --- /dev/null +++ b/lib/MasterServer/Core/LoadConfig.pm @@ -0,0 +1,150 @@ +package MasterServer::Core::LoadConfig; + +use strict; +use warnings; +use AnyEvent; +use POSIX qw/strftime/; +use Exporter 'import'; +use DBI; + +our @EXPORT = qw | load_applet_masters + load_sync_masters + add_sync_master |; + +################################################################################ +## Load configuration variables to the database, helper functions +################################################################################ +sub load_applet_masters { + my $self = shift; + + # loop through config entries + foreach my $master_applet (@{$self->{master_applet}}) { + # master_applet contains + # address --> domain + # port --> tcp port + # games --> array of gamenames + + # iterate through all games per entry + for my $gamename (@{$master_applet->{games}}) { + + # resolve domain names + my $applet_ip = $self->host2ip($master_applet->{address}); + + # check if all credentials are valid + if ($applet_ip && + $master_applet->{port} && + $gamename) + { + # add to database + $self->add_master_applet( + ip => $applet_ip, + port => $master_applet->{port}, + gamename => $gamename, + ); + + #log + $self->log("add", "added applet $master_applet->{address}:$master_applet->{port} for $gamename"); + + } # else: insufficient info available + else { + $self->log("fail", "Could not add master applet: ". + ($applet_ip || "unknown ip"). ", ". + ($master_applet->{port} || "0"). ", ". + ($gamename || "game"). "." + ); + } + } # end gamename + } # end master_applet + + # reset added/updated time to last current time + $self->reset_master_applets(); + + # clear out the original variable, we don't use it anymore + $self->{master_applet} = (); + + # report + $self->log("info", "Applet database successfully updated!"); + +} + +################################################################################ +## There are three ways to load new masterservers to sync with. +## 1: from the config file; address, port and beaconport are provided +## 2: from a heartbeat; this automatically parses like all other servers +## 3: from another sync request. Add if sufficient info is available +################################################################################ +sub load_sync_masters { + my $self = shift; + + # loop through config entries + foreach my $sync_host (@{$self->{sync_masters}}) { + + # add them to database + $self->add_sync_master($sync_host); + } + + # clear out the original variable, we don't use it anymore + $self->{sync_masters} = (); + + # report + $self->log("info", "Sync server database successfully updated!"); + +} + +################################################################################ +## Add a sync master according to cases 1 and 3. +## Check for valid IP, port and/or beaconport +################################################################################ +sub add_sync_master { + my ($self, $sync_host) = @_; + + # sync_host contains + # address --> domain + # port --> tcp port + # beacon --> udp port + + # resolve domain names + my $sync_ip = $self->host2ip($sync_host->{address}); + + # check if all credentials are valid + if ($sync_ip && + $sync_host->{beacon} && + $sync_host->{port}) + { + # select sync master from serverlist + my $entry = $self->get_server(ip => $sync_ip, + port => $sync_host->{beacon})->[0]; + + # was found, update the entry + if (defined $entry) { + # update the serverlist with + my $sa = $self->update_server_list( + ip => $sync_ip, + port => $sync_host->{beacon}, + hostport => $sync_host->{port}, + gamename => "333networks", + ); + } + # was not found, insert clean entry + else { + my $sa = $self->add_server_list( + ip => $sync_ip, + port => $sync_host->{beacon}, + hostport => $sync_host->{port}, + gamename => "333networks", + ); + + #log + $self->log("add", "added sync $sync_host->{address}:$sync_host->{port},$sync_host->{beacon}"); + } + } # else: insufficient info available + else { + $self->log("fail", "Could not add sync master: ". + ($sync_ip || "ip"). ", ". + ($sync_host->{beacon} || "0"). ", ". + ($sync_host->{port} || "0"). "." + ); + } +} + +1; diff --git a/lib/MasterServer/Core/Logging.pm b/lib/MasterServer/Core/Logging.pm index e8631de..416a97f 100755 --- a/lib/MasterServer/Core/Logging.pm +++ b/lib/MasterServer/Core/Logging.pm @@ -1,4 +1,3 @@ - package MasterServer::Core::Logging; use strict; @@ -6,6 +5,7 @@ use warnings; use Switch; use POSIX qw/strftime/; use Exporter 'import'; +$|++; our @EXPORT = qw| log error |; @@ -46,41 +46,34 @@ sub error { } } - ################################################################################ ## Log to file and print to screen. ## args: $self, message_type, message ################################################################################ sub log { my ($self, $type, $msg) = @_; - - # flush - $| = 1; - - # parse time of log entry and prep for rotating log - my $time = strftime('%Y-%m-%d %H:%M:%S',localtime); - my $yearly = strftime('-%Y',localtime); - my $monthly = strftime('-%Y-%m',localtime); - my $weekly = strftime('-%Y-week%U',localtime); - my $daily = strftime('-%Y-%m-%d',localtime); - + # is the message suppressed in config? return if (defined $type && $self->{suppress} =~ m/$type/i); + + # parse time of log entry and prep for rotating log + my $time = strftime('%Y-%m-%d %H:%M:%S',localtime); # determine filename my $f = "MasterServer"; # rotate log filename according to config - $f .= $daily if ($self->{log_rotate} =~ /^daily$/i ); - $f .= $weekly if ($self->{log_rotate} =~ /^weekly$/i ); - $f .= $monthly if ($self->{log_rotate} =~ /^monthly$/i ); - $f .= $yearly if ($self->{log_rotate} =~ /^yearly$/i ); + $f .= strftime('-%Y-%m-%d',localtime) if ($self->{log_rotate} =~ /^daily$/i ); + $f .= strftime('-%Y-week%U',localtime) if ($self->{log_rotate} =~ /^weekly$/i ); + $f .= strftime('-%Y-%m',localtime) if ($self->{log_rotate} =~ /^monthly$/i); + $f .= strftime('-%Y',localtime) if ($self->{log_rotate} =~ /^yearly$/i ); $f .= ".log"; # put log filename together my $logfile = $self->{log_dir}.((substr($self->{log_dir},-1) eq "/")?"":"/").$f; - print "[$time] [$type] > $msg\n" if $self->{printlog}; + # print to stdout if enabled + print "[$time]\t[$type]\t$msg\n" if $self->{printlog}; # temporarily disable the warnings-to-log, to avoid infinite recursion if # this function throws a warning. @@ -99,5 +92,4 @@ sub log { $SIG{__WARN__} = $old; } - 1; diff --git a/lib/MasterServer/Core/Schedulers.pm b/lib/MasterServer/Core/Schedulers.pm index 97e45a5..cee4e5c 100755 --- a/lib/MasterServer/Core/Schedulers.pm +++ b/lib/MasterServer/Core/Schedulers.pm @@ -1,4 +1,3 @@ - package MasterServer::Core::Schedulers; use strict; @@ -18,75 +17,90 @@ our @EXPORT = qw | ################################################################################ sub long_periodic_tasks { my $self = shift; - my $num = 0; + my $prev = 0; return AnyEvent->timer ( - after => 300, # 5 minutes grace time - interval => 1800, # execute every half hour + after => 30, # 30 seconds grace time + interval => 3600, # execute every hour cb => sub { - ## update Killing Floor stats - $self->read_kfstats(); + # update Killing Floor stats + $self->read_kfstats() if $self->{kfstats_enabled}; + + # delete old masterserver applets that have been unresponsive for a while now + $self->remove_unresponsive_applets() if (defined $self->{firstrun}); # time spacer my $t = 0; # clean out handles from the previous round (executed or not) $self->{scope}->{sync} = (); - - ## Query Epic Games'-based UCC applets periodically to get an additional - ## list of online UT, Unreal (or other) game servers. - if ($self->{master_applet_enabled}) { - for my $ms (@{$self->{master_applet}}) { + + # Synchronize with all other 333networks masterservers that are uplinking, + # added by synchronization or manually listed. + if ($self->{sync_enabled}) { + + # get serverlist + my $masterserverlist = $self->get_server( + updated => 3600, + gamename => "333networks", + ); - # add 3 second delay to spread network/server load + foreach my $ms (@{$masterserverlist}) { + # add 5 second delay to spread network/server load $self->{scope}->{sync}->{$t} = AnyEvent->timer( - after => 3*$t++, - cb => sub{$self->query_applet($ms)} - ); + after => 5*$t++, + cb => sub{$self->sync_with_master($ms)} + ) if ($ms->{hostport} > 0); } } + + # do NOT reset $t, keep padding time -- you should not have more than 300 + # entries in applets/syncer in total anyway. - # do NOT reset $t, keep padding time -- you should not have more than 600 - # entries in applets/syncer in total. - - ## Request the masterlist for selected or all games from other - ## 333networks-based masterservers that uplinked to us and otherwise made - ## our list (config, manual entry, etc) - if ($self->{sync_enabled}) { - foreach my $ms (values %{$self->masterserver_list()}) { + # Query Epic Games-based UCC applets periodically to get an additional + # list of online UT, Unreal and other game servers. + if ($self->{master_applet_enabled}) { - # add 3 second delay to spread network/server load + # get applet list + my $appletlist = $self->get_masterserver_applets(); + + for my $ms (@{$appletlist}) { + + # add 5 second delay to spread network/server load $self->{scope}->{sync}->{$t} = AnyEvent->timer( - after => 3*$t++, - cb => sub{$self->sync_with_master($ms) if ($ms->{tcp} > 0)} + after => 5*$t++, + cb => sub{$self->query_applet($ms)} ); } } # - # Also very long-running tasks, like once per day: + # very long-running tasks, like database dumps + # interval from config # - if ($num++ >= 47) { - # reset counter - $num = 0; - - # - # do database dump - # - my $time = strftime('%Y-%m-%d-%H-%M',localtime); - - # read db type from db login - my @db_type = split(':', $self->{dblogin}->[0]); - $db_type[2] =~ s/dbname=//; + my $curr = 0; + $curr = strftime('%d',localtime) if ($self->{dump_db} =~ /^daily$/i ); + $curr = strftime('%U',localtime) if ($self->{dump_db} =~ /^weekly$/i ); + $curr = strftime('%m',localtime) if ($self->{dump_db} =~ /^monthly$/i); + $curr = strftime('%Y',localtime) if ($self->{dump_db} =~ /^yearly$/i ); + + # on change, execute + if ($prev < $curr) { - if ($db_type[1] eq "Pg") { - # use pg_dump to dump Postgresql databases - system("pg_dump $db_type[2] -U $self->{dblogin}->[1] > $self->{root}/data/dumps/$db_type[1]-$time.db"); - $self->log("dump", "Dumping database to /data/dumps/$db_type[1]-$time.db"); + # skip on first run + if ($prev == 0) { + # update timer and loop + $prev = $curr; + return; } + + # dump db + $self->dump_database(); + + # update timekeeper + $prev = $curr; } - }, ); } @@ -102,22 +116,19 @@ sub short_periodic_tasks { interval => 120, cb => sub { - ## update stats on direct beacons and total number of servers + # update stats on direct beacons and total number of servers $self->update_stats(); - ## determine whether servers are still uplinking to us. If not, toggle. + # determine whether servers are still uplinking to us. If not, toggle. $self->write_direct_beacons() if (defined $self->{firstrun}); - ## delete old servers from the "pending" list (except for the first run) + # delete old servers from the "pending" list (except for the first run) $self->delete_old_pending() if (defined $self->{firstrun}); - ## uplink to other 333networks masterservers with heartbeats, - ## that way we can index other masterservers too + # uplink to other 333networks masterservers with heartbeats, so other + # masterservers can find us too $self->send_heartbeats(); - - # - # more short tasks? - # + }, ); } diff --git a/lib/MasterServer/Core/Secure.pm b/lib/MasterServer/Core/Secure.pm index 51d1832..6d05f82 100755 --- a/lib/MasterServer/Core/Secure.pm +++ b/lib/MasterServer/Core/Secure.pm @@ -1,4 +1,3 @@ - package MasterServer::Core::Secure; use strict; @@ -25,7 +24,7 @@ sub load_ciphers { # first delete the old cipher database $self->clear_ciphers(); - # start inserting ciphers (lots of 'em) + # start inserting ciphers (use transactions for slow systems) $self->{dbh}->begin_work; # iterate through the game list @@ -36,10 +35,11 @@ sub load_ciphers { $opt{gamename} = lc $_; $opt{cipher} = $self->{game}->{$_}->{key}; $opt{description} = $self->{game}->{$_}->{label} || 'Unknown Game'; - $opt{default_qport} = $self->{game}->{$_}->{port} || 0; + $opt{default_qport} = $self->{game}->{$_}->{port} || 0; # insert the game/cipher in the db or halt on error if ($self->insert_cipher(%opt) < 0) { + # failure causes a fatal error and exits $self->{dbh}->rollback; $self->halt(); } @@ -48,19 +48,14 @@ sub load_ciphers { # commit $self->{dbh}->commit; $self->log("info", "Cipher database successfully updated!"); - - # unload the game variables from masterserver memory - $self->{game} = undef; - } - ################################################################################ # generate a random string of 6 characters long for the \secure\ challenge # returns string ################################################################################ sub secure_string { - # spit out a random string, only uppercase characters + # generate a random string, only uppercase characters my @c = ('A'..'Z'); my $s = ""; $s .= $c[rand @c] for 1..6; @@ -82,23 +77,21 @@ sub compare_challenge { # secure string too long? (because vulnerable in UE) return 0 if (length $o{secure} > 16); - # additional conditions to skip checking provided? - $o{ignore} = "" unless $o{ignore}; - # ignore this game if asked to do so - if ($o{ignore} =~ m/$o{gamename}/i){ - $self->log("secure", "ignored beacon validation for $o{gamename}"); + if ($self->{ignore_browser_key} =~ m/$o{gamename}/i){ + $self->log("ignore", "ignored beacon validation for $o{gamename}"); return 1; } # enctype given? $o{enctype} = 0 unless $o{enctype}; - - # get cipher corresponding with the gamename - my $cip = $self->get_game_props($o{gamename})->{cipher}; - + # calculate validate string - my $val = get_validate_string($cip, $o{secure}, $o{enctype}); + my $val = get_validate_string( + $self->get_game_props($o{gamename})->{cipher}, + $o{secure}, + $o{enctype} + ); # return whether or not they match return ($val eq $o{validate}); @@ -136,33 +129,33 @@ sub validate_string { # conversion and modification of the algorithm by Darkelarious, June 2014 with # explicit, written permission of Luigi Auriemma. # +# use pre-built rotations for enctype +# -- see GSMSALG 0.3.3 reference for copyright and more information +my @enc_chars = ( qw | + 001 186 250 178 081 000 084 128 117 022 142 142 002 008 054 165 + 045 005 013 022 082 007 180 034 140 233 009 214 185 038 000 004 + 006 005 000 019 024 196 030 091 029 118 116 252 080 081 006 022 + 000 081 040 000 004 010 041 120 081 000 001 017 082 022 006 074 + 032 132 001 162 030 022 071 022 050 081 154 196 003 042 115 225 + 045 079 024 075 147 076 015 057 010 000 004 192 018 012 154 094 + 002 179 024 184 007 012 205 033 005 192 169 065 067 004 060 082 + 117 236 152 128 029 008 002 029 088 132 001 078 059 106 083 122 + 085 086 087 030 127 236 184 173 000 112 031 130 216 252 151 139 + 240 131 254 014 118 003 190 057 041 119 048 224 043 255 183 158 + 001 004 248 001 014 232 083 255 148 012 178 069 158 010 199 006 + 024 001 100 176 003 152 001 235 002 176 001 180 018 073 007 031 + 095 094 093 160 079 091 160 090 089 088 207 082 084 208 184 052 + 002 252 014 066 041 184 218 000 186 177 240 018 253 035 174 182 + 069 169 187 006 184 136 020 036 169 000 020 203 036 018 174 204 + 087 086 238 253 008 048 217 253 139 062 010 132 070 250 119 184 +|); +# # args: game cipher, 6-char challenge string, encryption type # returns: validate string (usually 8 characters long) # !! requires cipher hash to be configured in config! (imported or otherwise) ################################################################################ sub get_validate_string { my ($cipher_string, $secure_string, $enctype) = @_; - - # use pre-built rotations for enctype - # -- see GSMSALG 0.3.3 reference for copyright and more information - my @enc_chars = ( qw | - 001 186 250 178 081 000 084 128 117 022 142 142 002 008 054 165 - 045 005 013 022 082 007 180 034 140 233 009 214 185 038 000 004 - 006 005 000 019 024 196 030 091 029 118 116 252 080 081 006 022 - 000 081 040 000 004 010 041 120 081 000 001 017 082 022 006 074 - 032 132 001 162 030 022 071 022 050 081 154 196 003 042 115 225 - 045 079 024 075 147 076 015 057 010 000 004 192 018 012 154 094 - 002 179 024 184 007 012 205 033 005 192 169 065 067 004 060 082 - 117 236 152 128 029 008 002 029 088 132 001 078 059 106 083 122 - 085 086 087 030 127 236 184 173 000 112 031 130 216 252 151 139 - 240 131 254 014 118 003 190 057 041 119 048 224 043 255 183 158 - 001 004 248 001 014 232 083 255 148 012 178 069 158 010 199 006 - 024 001 100 176 003 152 001 235 002 176 001 180 018 073 007 031 - 095 094 093 160 079 091 160 090 089 088 207 082 084 208 184 052 - 002 252 014 066 041 184 218 000 186 177 240 018 253 035 174 182 - 069 169 187 006 184 136 020 036 169 000 020 203 036 018 174 204 - 087 086 238 253 008 048 217 253 139 062 010 132 070 250 119 184 - |), # convert to array of characters my @cip = split "", $cipher_string; diff --git a/lib/MasterServer/Core/Stats.pm b/lib/MasterServer/Core/Stats.pm index 25044e8..8e9eb95 100755 --- a/lib/MasterServer/Core/Stats.pm +++ b/lib/MasterServer/Core/Stats.pm @@ -1,4 +1,3 @@ - package MasterServer::Core::Stats; use strict; @@ -31,9 +30,8 @@ sub update_stats { $self->write_stat(%opt); } - #done + # done $self->log("stat", "Updated all game statistics."); - } 1; diff --git a/lib/MasterServer/Core/Util.pm b/lib/MasterServer/Core/Util.pm index 4f64fe1..682335c 100755 --- a/lib/MasterServer/Core/Util.pm +++ b/lib/MasterServer/Core/Util.pm @@ -1,4 +1,3 @@ - package MasterServer::Core::Util; use strict; @@ -24,10 +23,10 @@ sub ip2country { ################################################################################ sub host2ip { my ($self, $name) = @_; - return inet_ntoa(inet_aton($name)) if $name; + my $unpack = inet_aton($name) if $name; + return inet_ntoa($unpack) if $unpack; } - ################################################################################ ## Verify whether a given domain name or IP address and port are valid. ## returns 1/0 if valid/invalid ip + port. IPv4 ONLY! @@ -36,8 +35,8 @@ sub valid_address { my ($self, $a, $p) = @_; # check if ip and port are in valid range - my $val_addr = ($a =~ '\b(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\b') if $a; - my $val_port = (0 < $p && $p <= 65535) if $p; + my $val_addr = ($a =~ '^(?:(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)\.){3}(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)$') if $a; + my $val_port = ($p =~ m/^\d+$/ && 0 < $p && $p <= 65535) if $p; # exclude addresses where we don't want people sniffing for (qw|192.168.(.\d*).(.\d*) 127.0.(.\d*).(.\d*) 10.0.(.\d*).(.\d*)|){$val_addr = 0 if ($a =~ m/$_/)} @@ -72,7 +71,7 @@ sub db_all { } ################################################################################ -# sqlprint (TUWF): +# sqlprint (TUWF, Yorhel): # ? normal placeholder # !l list of placeholders, expects arrayref # !H list of SET-items, expects hashref or arrayref: format => (bind_value || \@bind_values) diff --git a/lib/MasterServer/Core/Version.pm b/lib/MasterServer/Core/Version.pm index 718b8c6..f87ea8d 100755 --- a/lib/MasterServer/Core/Version.pm +++ b/lib/MasterServer/Core/Version.pm @@ -1,4 +1,3 @@ - package MasterServer::Core::Version; use strict; @@ -27,16 +26,16 @@ sub version { # # master type - $self->{build_type} = "333networks Masterserver-Perl pre-release"; + $self->{build_type} = "333networks Masterserver-Perl Multidb"; # version - $self->{build_version} = "2.2.5"; + $self->{build_version} = "2.3.0"; # short version for uplinks $self->{short_version} = "MS-perl $self->{build_version}"; # date yyyy-mm-dd - $self->{build_date} = "2016-11-19"; + $self->{build_date} = "2017-05-13"; #author, email $self->{build_author} = "Darkelarious, darkelarious\@333networks.com"; diff --git a/lib/MasterServer/Database/Pg/dbAddServers.pm b/lib/MasterServer/Database/Pg/dbAddServers.pm index 33785da..31deb08 100755 --- a/lib/MasterServer/Database/Pg/dbAddServers.pm +++ b/lib/MasterServer/Database/Pg/dbAddServers.pm @@ -1,4 +1,3 @@ - package MasterServer::Database::Pg::dbAddServers; use strict; @@ -26,7 +25,10 @@ sub add_server_new { $o{direct} ? ( 'b333ms = CAST(? AS BOOLEAN)' => $o{direct}) : (), $o{updated} ? ( 'updated = to_timestamp(?)' => $o{updated}) : (), $o{beacon} ? ( 'beacon = to_timestamp(?)' => $o{beacon}) : (), - $o{gamename} ? ('gamename = ?' => lc $o{gamename}) : (), + + # some applets have incorrect gamename lists, let udpticker update this + # entry instead. this way, applets don't overwrite with incorrect data + #$o{gamename} ? ('gamename = ?' => lc $o{gamename}) : (), ); my($q, @p) = sqlprint("UPDATE serverlist !H @@ -37,12 +39,10 @@ sub add_server_new { # if serverlist was updated return 0 if ($n > 0); - # try updating it in pending %H = ( $o{added} ? ( 'added = ?' => $o{added}) : (), $o{secure} ? ( 'secure = ?' => $o{secure}) : (), - $o{gamename} ? ( 'gamename = ?' => lc $o{gamename}) : (), $o{beaconport} ? ('beaconport = ?' => $o{beaconport}) : (), ); @@ -119,7 +119,7 @@ sub syncer_add { # if address is in the list AND up to date, # acknowledge its existance but don't do anything with it my $u = $self->{dbh}->do( - "SELECT * FROM serverlist + "SELECT count(*) FROM serverlist WHERE ip = ? AND port = ? AND updated > to_timestamp(?)", @@ -136,8 +136,8 @@ sub syncer_add { AND heartbeat = ?", undef, $secure, $ip, $port); - # notify - $self->log("update","$ip:$port was updated by syncer") if ($u > 0); + # notify (debug) + #$self->log("update","$ip:$port was updated by syncer") if ($u > 0); # return 1 if found return 1 if ($u > 0); @@ -148,8 +148,8 @@ sub syncer_add { SELECT ?, ?, ?, ?", undef, $ip, $port, lc $gamename, $secure); - # notify - $self->log("add","beacon: $ip:$port was added for $gamename after sync") if ($u > 0); + # notify (debug) + #$self->log("add","beacon: $ip:$port was added for $gamename after sync") if ($u > 0); # return 2 if added new return 2 if ($u > 0); diff --git a/lib/MasterServer/Database/Pg/dbAppletActions.pm b/lib/MasterServer/Database/Pg/dbAppletActions.pm new file mode 100755 index 0000000..dc7d941 --- /dev/null +++ b/lib/MasterServer/Database/Pg/dbAppletActions.pm @@ -0,0 +1,92 @@ +package MasterServer::Database::Pg::dbAppletActions; + +use strict; +use warnings; +use Exporter 'import'; + +our @EXPORT = qw| add_master_applet + update_master_applet + reset_master_applets + get_masterserver_applets + remove_unresponsive_applets |; + +################################################################################ +## Add a remote master server applet +################################################################################ +sub add_master_applet { + my $self = shift; + my %o = @_; + + my $u = $self->{dbh}->do( + "SELECT * FROM appletlist + WHERE ip = ? + AND port = ? + AND gamename = ?", + undef, $o{ip}, $o{port}, lc $o{gamename}); + + # return if found + return if ($u > 0); + + # insert applet data + return $self->{dbh}->do("INSERT INTO appletlist (ip, port, gamename) + SELECT ?, ?, ?", undef, + $o{ip}, $o{port}, lc $o{gamename}); +} + +################################################################################ +## reset added/updated time after restart +################################################################################ +sub reset_master_applets { + my $self = shift; + return $self->{dbh}->do("UPDATE appletlist + SET added = to_timestamp(?), + updated = to_timestamp(?)", + undef, time, time); +} + +################################################################################ +## update time on master applet +################################################################################ +sub update_master_applet { + my ($self, %o) = @_; + + return $self->{dbh}->do("UPDATE appletlist + SET updated = to_timestamp(?) + WHERE ip = ? + AND port = ? + AND gamename = ?", + undef, time, $o{ip}, $o{port}, lc $o{gamename}); +} + +################################################################################ +## get a list of master server applets that were online in the past week +## +################################################################################ +sub get_masterserver_applets { + my $self = shift; + + return $self->db_all( + "SELECT * + FROM appletlist + WHERE updated > to_timestamp(?)", + time-604800); +} + +################################################################################ +## Clear out applet entries that have been unresponsive or without servers for +## more than a week. Servers with multiple entries only have the entry of this +## specific gamename removed. +################################################################################ +sub remove_unresponsive_applets { + my $self = shift; + + # remove entries + my $u = $self->{dbh}->do( + "DELETE FROM appletlist + WHERE updated < to_timestamp(?)", undef, time-604800); + + # notify + $self->log("delete", "Removed $u entries from applet list.") if ($u > 0); +} + +1; diff --git a/lib/MasterServer/Database/Pg/dbCiphers.pm b/lib/MasterServer/Database/Pg/dbCiphers.pm index e2a5784..e099f88 100755 --- a/lib/MasterServer/Database/Pg/dbCiphers.pm +++ b/lib/MasterServer/Database/Pg/dbCiphers.pm @@ -1,15 +1,23 @@ - package MasterServer::Database::Pg::dbCiphers; use strict; use warnings; use Exporter 'import'; -our @EXPORT = qw| clear_ciphers +our @EXPORT = qw| check_cipher_count + clear_ciphers insert_cipher get_game_props |; ################################################################################ +## Check if ciphers exist +################################################################################ +sub check_cipher_count { + my $self = shift; + return $self->db_all('SELECT count(*) as num from games')->[0]->{num}; +} + +################################################################################ ## Clear all existing ciphers from the database ################################################################################ sub clear_ciphers { @@ -43,15 +51,13 @@ sub insert_cipher { ################################################################################ ## get the cipher, description and default port that goes with given gamename +## returns only the first item if multiple items ################################################################################ sub get_game_props { my ($self, $gn) = @_; # get cipher from db if gamename exists - return $self->{dbh}->selectall_arrayref( - 'SELECT * FROM games WHERE gamename = ?', - {Slice=>{}}, - lc $gn)->[0]; + return $self->db_all('SELECT * FROM games WHERE gamename = ?', lc $gn)->[0]; } 1; diff --git a/lib/MasterServer/Database/Pg/dbCore.pm b/lib/MasterServer/Database/Pg/dbCore.pm index 90899f7..0891012 100755 --- a/lib/MasterServer/Database/Pg/dbCore.pm +++ b/lib/MasterServer/Database/Pg/dbCore.pm @@ -1,11 +1,11 @@ - package MasterServer::Database::Pg::dbCore; use strict; use warnings; +use POSIX qw/strftime/; use Exporter 'import'; -our @EXPORT = qw| database_login |; +our @EXPORT = qw| database_login dump_database |; ################################################################################ ## login to the database with credentials provided in the config file. @@ -19,6 +19,9 @@ sub database_login { # get db info my @db_type = split(':', $self->{dblogin}->[0]); + + # inform what db we try to load + $self->log("info","Database: $db_type[1], $db_type[2]"); # create the dbi object my $dbh = DBI->connect(@{$self->{dblogin}}, {PrintError => 1}); @@ -47,4 +50,30 @@ sub database_login { return undef; } +################################################################################ +## Dump the database in the data/dump folder. +## useful for backups, historical data +################################################################################ +sub dump_database { + my $self = shift; + + # filename / time + my $time = strftime('%Y-%m-%d-%H-%M',localtime); + + # FIXME + # separate absolute path and relative path, + # split database filename for dump filename. + + # read db credentials from db login + my @db_type = split(':', $self->{dblogin}->[0]); + $db_type[2] =~ s/dbname=//; + + # use pg_dump to dump Postgresql databases + system("pg_dump $db_type[2] -U $self->{dblogin}->[1] > $self->{root}/data/dumps/Pg-$time-$db_type[2].db"); + + # log + $self->log("dump", "Dumping database to /data/dumps/$db_type[1]-$time.db"); +} + + 1; diff --git a/lib/MasterServer/Database/Pg/dbMaintenance.pm b/lib/MasterServer/Database/Pg/dbMaintenance.pm index 90e4d34..7a4fc23 100755 --- a/lib/MasterServer/Database/Pg/dbMaintenance.pm +++ b/lib/MasterServer/Database/Pg/dbMaintenance.pm @@ -5,7 +5,7 @@ use warnings; use Exporter 'import'; our @EXPORT = qw| delete_old_pending - remove_pending |; + remove_pending |; ################################################################################ ## delete unresponsive servers from the pending list diff --git a/lib/MasterServer/Database/Pg/dbUTServerInfo.pm b/lib/MasterServer/Database/Pg/dbUTServerInfo.pm index 12ba05f..0bf005e 100755 --- a/lib/MasterServer/Database/Pg/dbUTServerInfo.pm +++ b/lib/MasterServer/Database/Pg/dbUTServerInfo.pm @@ -1,4 +1,3 @@ - package MasterServer::Database::Pg::dbUTServerInfo; use strict; diff --git a/lib/MasterServer/Database/SQLite/dbAddServers.pm b/lib/MasterServer/Database/SQLite/dbAddServers.pm index 9fccead..592ab7b 100755 --- a/lib/MasterServer/Database/SQLite/dbAddServers.pm +++ b/lib/MasterServer/Database/SQLite/dbAddServers.pm @@ -26,7 +26,10 @@ sub add_server_new { $o{direct} ? ( 'b333ms = CAST(? AS BOOLEAN)' => $o{direct}) : (), $o{updated} ? ( 'updated = datetime(?, \'unixepoch\')' => $o{updated}) : (), $o{beacon} ? ( 'beacon = datetime(?, \'unixepoch\')' => $o{beacon}) : (), - $o{gamename} ? ('gamename = ?' => lc $o{gamename}) : (), + + # some applets have incorrect gamename lists, let udpticker update this + # entry instead. this way, applets don't overwrite with incorrect data + #$o{gamename} ? ('gamename = ?' => lc $o{gamename}) : (), ); my($q, @p) = sqlprint("UPDATE serverlist !H @@ -37,12 +40,10 @@ sub add_server_new { # if serverlist was updated return 0 if ($n > 0); - # try updating it in pending %H = ( $o{added} ? ( 'added = ?' => $o{added}) : (), $o{secure} ? ( 'secure = ?' => $o{secure}) : (), - $o{gamename} ? ( 'gamename = ?' => lc $o{gamename}) : (), $o{beaconport} ? ('beaconport = ?' => $o{beaconport}) : (), ); @@ -119,7 +120,7 @@ sub syncer_add { # if address is in the list AND up to date, # acknowledge its existance but don't do anything with it my $u = $self->{dbh}->do( - "SELECT * FROM serverlist + "SELECT count(*) FROM serverlist WHERE ip = ? AND port = ? AND updated > datetime(?, 'unixepoch')", @@ -137,7 +138,7 @@ sub syncer_add { undef, $secure, $ip, $port); # notify - $self->log("update","$ip:$port was updated by syncer") if ($u > 0); + #$self->log("update","$ip:$port was updated by syncer") if ($u > 0); # return 1 if found return 1 if ($u > 0); @@ -149,7 +150,7 @@ sub syncer_add { undef, $ip, $port, lc $gamename, $secure); # notify - $self->log("add","beacon: $ip:$port was added for $gamename after sync") if ($u > 0); + #$self->log("add","beacon: $ip:$port was added for $gamename after sync") if ($u > 0); # return 2 if added new return 2 if ($u > 0); diff --git a/lib/MasterServer/Database/SQLite/dbAppletActions.pm b/lib/MasterServer/Database/SQLite/dbAppletActions.pm new file mode 100755 index 0000000..d2421fc --- /dev/null +++ b/lib/MasterServer/Database/SQLite/dbAppletActions.pm @@ -0,0 +1,92 @@ +package MasterServer::Database::SQLite::dbAppletActions; + +use strict; +use warnings; +use Exporter 'import'; + +our @EXPORT = qw| add_master_applet + update_master_applet + reset_master_applets + get_masterserver_applets + remove_unresponsive_applets |; + +################################################################################ +## Add a remote master server applet +################################################################################ +sub add_master_applet { + my $self = shift; + my %o = @_; + + my $u = $self->{dbh}->do( + "SELECT * FROM appletlist + WHERE ip = ? + AND port = ? + AND gamename = ?", + undef, $o{ip}, $o{port}, lc $o{gamename}); + + # return if found + return if ($u > 0); + + # insert applet data + return $self->{dbh}->do("INSERT INTO appletlist (ip, port, gamename) + SELECT ?, ?, ?", undef, + $o{ip}, $o{port}, lc $o{gamename}); +} + +################################################################################ +## reset added/updated time after restart +################################################################################ +sub reset_master_applets { + my $self = shift; + return $self->{dbh}->do("UPDATE appletlist + SET added = datetime(?, \'unixepoch\'), + updated = datetime(?, \'unixepoch\')", + undef, time, time); +} + +################################################################################ +## update time on master applet +################################################################################ +sub update_master_applet { + my ($self, %o) = @_; + + return $self->{dbh}->do("UPDATE appletlist + SET updated = datetime(?, \'unixepoch\') + WHERE ip = ? + AND port = ? + AND gamename = ?", + undef, time, $o{ip}, $o{port}, lc $o{gamename}); +} + +################################################################################ +## get a list of master server applets that were online in the past week +## +################################################################################ +sub get_masterserver_applets { + my $self = shift; + + return $self->db_all( + "SELECT * + FROM appletlist + WHERE updated > datetime(?, \'unixepoch\')", + time-604800); +} + +################################################################################ +## Clear out applet entries that have been unresponsive or without servers for +## more than a week. Servers with multiple entries only have the entry of this +## specific gamename removed. +################################################################################ +sub remove_unresponsive_applets { + my $self = shift; + + # remove entries + my $u = $self->{dbh}->do( + "DELETE FROM appletlist + WHERE updated < datetime(?, \'unixepoch\')", undef, time-604800); + + # notify + $self->log("delete", "Removed $u entries from applet list.") if ($u > 0); +} + +1; diff --git a/lib/MasterServer/Database/SQLite/dbCiphers.pm b/lib/MasterServer/Database/SQLite/dbCiphers.pm index c98eb05..5b88944 100755 --- a/lib/MasterServer/Database/SQLite/dbCiphers.pm +++ b/lib/MasterServer/Database/SQLite/dbCiphers.pm @@ -1,15 +1,23 @@ - package MasterServer::Database::SQLite::dbCiphers; use strict; use warnings; use Exporter 'import'; -our @EXPORT = qw| clear_ciphers +our @EXPORT = qw| check_cipher_count + clear_ciphers insert_cipher get_game_props |; ################################################################################ +## Check if ciphers exist +################################################################################ +sub check_cipher_count { + my $self = shift; + return $self->db_all('SELECT count(*) as num from games')->[0]->{num}; +} + +################################################################################ ## Clear all existing ciphers from the database ################################################################################ sub clear_ciphers { @@ -43,15 +51,13 @@ sub insert_cipher { ################################################################################ ## get the cipher, description and default port that goes with given gamename +## returns only the first item if multiple items ################################################################################ sub get_game_props { my ($self, $gn) = @_; # get cipher from db if gamename exists - return $self->{dbh}->selectall_arrayref( - 'SELECT * FROM games WHERE gamename = ?', - {Slice=>{}}, - lc $gn)->[0]; + return $self->db_all('SELECT * FROM games WHERE gamename = ?', lc $gn)->[0]; } 1; diff --git a/lib/MasterServer/Database/SQLite/dbCore.pm b/lib/MasterServer/Database/SQLite/dbCore.pm index f58d535..0772a4e 100755 --- a/lib/MasterServer/Database/SQLite/dbCore.pm +++ b/lib/MasterServer/Database/SQLite/dbCore.pm @@ -1,11 +1,11 @@ - package MasterServer::Database::SQLite::dbCore; use strict; use warnings; +use POSIX qw/strftime/; use Exporter 'import'; -our @EXPORT = qw| database_login |; +our @EXPORT = qw| database_login dump_database |; ################################################################################ ## login to the database with credentials provided in the config file. @@ -19,7 +19,10 @@ sub database_login { # get db info my @db_type = split(':', $self->{dblogin}->[0]); - + + # inform what db we try to load + $self->log("info","Database: $db_type[1], $db_type[2]"); + # check if database file exists my $db_file = [split(':', $self->{dblogin}->[0])]->[2]; $db_file =~ s/dbname=//i; @@ -31,10 +34,7 @@ sub database_login { # end program $self->halt(); } - - # inform what DB we try to load - # $self->log("info","Database: $db_type[1]"); - + # create the dbi object my $dbh = DBI->connect(@{$self->{dblogin}}, {PrintError => 1}); @@ -72,4 +72,28 @@ sub database_login { return undef; } +################################################################################ +## Dump the database in the data/dump folder. +## useful for backups, historical data +################################################################################ +sub dump_database { + my $self = shift; + + # filename / time + my $time = strftime('%Y-%m-%d-%H-%M',localtime); + + # read db credentials from db login + my @db_type = split ':', $self->{dblogin}->[0]; + $db_type[2] =~ s/dbname=//; + + # split db path + my @db_path = split '/', $db_type[2]; + + # use pg_dump to dump Postgresql databases + system("cp $db_type[2] $self->{root}/data/dumps/SQLite-$time-$db_path[-1]"); + + # log + $self->log("dump", "Dumping database to /data/dumps/SQLite-$time-$db_path[-1]"); +} + 1; diff --git a/lib/MasterServer/Database/SQLite/dbMaintenance.pm b/lib/MasterServer/Database/SQLite/dbMaintenance.pm index 06a8db4..019f31f 100755 --- a/lib/MasterServer/Database/SQLite/dbMaintenance.pm +++ b/lib/MasterServer/Database/SQLite/dbMaintenance.pm @@ -5,7 +5,7 @@ use warnings; use Exporter 'import'; our @EXPORT = qw| delete_old_pending - remove_pending |; + remove_pending |; ################################################################################ ## delete unresponsive servers from the pending list diff --git a/lib/MasterServer/Database/SQLite/dbUTServerInfo.pm b/lib/MasterServer/Database/SQLite/dbUTServerInfo.pm index f1577b2..119900b 100755 --- a/lib/MasterServer/Database/SQLite/dbUTServerInfo.pm +++ b/lib/MasterServer/Database/SQLite/dbUTServerInfo.pm @@ -1,4 +1,3 @@ - package MasterServer::Database::SQLite::dbUTServerInfo; use strict; diff --git a/lib/MasterServer/TCP/BrowserHost.pm b/lib/MasterServer/TCP/BrowserHost.pm index 855b2c0..c53ae42 100755 --- a/lib/MasterServer/TCP/BrowserHost.pm +++ b/lib/MasterServer/TCP/BrowserHost.pm @@ -1,4 +1,3 @@ - package MasterServer::TCP::BrowserHost; use strict; @@ -24,7 +23,7 @@ sub browser_host { my $auth = 0; # debug -- new connection opened - $self->log("tcp","New connection from $a:$p"); + #$self->log("tcp","New connection from $a:$p"); # prep a challenge my $secure = $self->secure_string(); @@ -33,9 +32,9 @@ sub browser_host { my $h; $h = AnyEvent::Handle->new( fh => $fh, poll => 'r', - timeout => 5, - on_eof => sub {$self->clean_tcp_handle(@_)}, - on_error => sub {$self->clean_tcp_handle(@_)}, + timeout => $self->{timeout_time}, + on_eof => sub {$self->log("tcp","eof on $a:$p" ); $self->clean_tcp_handle(@_)}, + on_error => sub {$self->error($!, "browser $a:$p"); $self->clean_tcp_handle(@_)}, on_read => sub {$self->read_tcp_handle($h, $a, $p, $secure, @_)}, ); diff --git a/lib/MasterServer/TCP/Handler.pm b/lib/MasterServer/TCP/Handler.pm index 1a075bf..4e174c2 100755 --- a/lib/MasterServer/TCP/Handler.pm +++ b/lib/MasterServer/TCP/Handler.pm @@ -1,4 +1,3 @@ - package MasterServer::TCP::Handler; use strict; @@ -28,14 +27,12 @@ sub read_tcp_handle { # did the client validate already? my $val = $self->{browser_clients}->{$h}[1]; - # in case of errors, save the original message + # in case of errors, log the original message my $rxbuf = $m; + #$self->log("debug","$a:$p sent $rxbuf"); # allow multiple blocks to add to the response string my $response = ""; - - # print debug values - $self->log("debug","$a:$p sent $rxbuf"); # replace empty values for the string "undef" and replace line endings from netcatters # parse the received data and extrapolate all the query commands found @@ -75,14 +72,13 @@ sub read_tcp_handle { "Contact us via 333networks.com\\final\\"); # and log it - $self->log("error","invalid request from Browser $a:$p with unknown message \"$rxbuf\"."); + $self->log("error","invalid request from browser $a:$p with unknown message \"$rxbuf\"."); } # end if weird query else { $c->push_write($response . "\\final\\") if ($response ne ""); } } - ################################################################################ ## The master server opens the connection with the \secure\ challenge. The ## client should respond with basic information about itself and the @@ -95,7 +91,8 @@ sub handle_validate { my $val = 0; # pass or fail the secure challenge - if (exists $r->{gamename} && length $self->get_game_props(lc $r->{gamename})->{cipher} > 1 ) { + if (exists $r->{gamename} && $self->get_game_props($r->{gamename})) { + # game exists and we have the key to verify the response $val = $self->compare_challenge( gamename => $r->{gamename}, @@ -113,7 +110,7 @@ sub handle_validate { $self->log("support", "received unknown gamename request \"$r->{gamename}\" from $a:$p."); } - # log (the spam!) + # log (debug) #$self->log("secure","$a:$p validated with $val for $r->{gamename}, $secure, $r->{validate}"); # return auth status @@ -132,6 +129,7 @@ sub handle_validate { ## ## NOTE: client does not need to validate to be allowed to perform this ## query. +## TODO: deprecate about query -- info will now be in udp query! ################################################################################ sub handle_about { my ($self, $about, $a, $p) = @_; @@ -197,7 +195,7 @@ sub handle_list { my $data = ""; # determine the return format - if ($self->{hex_format} =~ m/$r->{gamename}/i or $r->{list} =~ /^cmp$/i) { + if ($r->{list} =~ /^cmp$/i) { # return addresses as byte format (ip=ABCD port=EF) $data .= $self->compile_list_cmp($r->{gamename}); } @@ -216,6 +214,7 @@ sub handle_list { $self->log("list","$a:$p successfully retrieved the list for $r->{gamename}."); # clean and close the connection + #$self->log("tcp","closing $a:$p"); $self->clean_tcp_handle($c); } @@ -229,6 +228,7 @@ sub handle_list { $self->log("error","browser $a:$p failed validation for $r->{gamename}"); # clean and close the connection + #$self->log("tcp","closing $a:$p"); $self->clean_tcp_handle($c); } } @@ -256,6 +256,7 @@ sub handle_sync { $self->log("sync-tx","$a:$p successfully synced."); # clean and close the connection + #$self->log("tcp","closing $a:$p"); $self->clean_tcp_handle($c); } @@ -270,6 +271,7 @@ sub handle_sync { $self->log("error","$a:$p failed synchronization."); # clean and close the connection + #$self->log("tcp","closing $a:$p"); $self->clean_tcp_handle($c); } } diff --git a/lib/MasterServer/TCP/ListCompiler.pm b/lib/MasterServer/TCP/ListCompiler.pm index a5571d0..97ef541 100755 --- a/lib/MasterServer/TCP/ListCompiler.pm +++ b/lib/MasterServer/TCP/ListCompiler.pm @@ -1,9 +1,7 @@ - package MasterServer::TCP::ListCompiler; use strict; use warnings; - use Exporter 'import'; our @EXPORT = qw| compile_list compile_list_cmp compile_sync |; diff --git a/lib/MasterServer/TCP/Syncer.pm b/lib/MasterServer/TCP/Syncer.pm index 3e903e0..d890f00 100755 --- a/lib/MasterServer/TCP/Syncer.pm +++ b/lib/MasterServer/TCP/Syncer.pm @@ -1,4 +1,3 @@ - package MasterServer::TCP::Syncer; use strict; @@ -8,8 +7,7 @@ use AnyEvent::Handle; use Exporter 'import'; our @EXPORT = qw| sync_with_master - process_sync_list - masterserver_list |; + process_sync_list |; ################################################################################ ## Sends synchronization request to another 333networks based master server and @@ -27,18 +25,20 @@ sub sync_with_master { # connection handle my $handle; $handle = new AnyEvent::Handle( - connect => [$ms->{ip} => $ms->{tcp}], - timeout => 4, + connect => [$ms->{ip} => $ms->{hostport}], + timeout => $self->{timeout_time}, poll => 'r', - on_error => sub {$self->error($!, "$ms->{ip}:$ms->{port}"); $handle->destroy;}, - on_eof => sub {$self->process_sync_list($sync_list, $ms); $handle->destroy;}, + on_error => sub {$self->error($!, "$ms->{ip}:$ms->{hostport}"); $handle->destroy;}, + on_eof => sub {$self->process_sync_list($sync_list, $ms); $handle->destroy;}, on_read => sub { + # receive and clear buffer my $m = $_[0]->rbuf; $_[0]->rbuf = ""; - # remove string terminator: sometimes trailing slashes are added or - # forgotten by sender, so \secure\abcdef is actually \secure\abcdef{\0} + # remove string terminator: sometimes trailing slashes, line endings or + # string terminators are added or forgotten by sender, so \secure\abcdef + # is actually \secure\abcdef{\0} chop $m if $m =~ m/secure/; # part 1: receive \basic\\secure\$key @@ -63,7 +63,7 @@ sub sync_with_master { # part 3: request the list \sync\gamenames consisting of space-seperated game names or "all" # compatibility note: old queries use "new", instead treat them as "all". my $request = "\\sync\\" - . (($self->{sync_games}[0] == 0) ? ("all" or "new") : $self->{sync_games}[1]) + . (($self->{sync_games}[0] == 0) ? ("all") : $self->{sync_games}[1]) . "\\final\\"; # push the request to remote host @@ -99,8 +99,7 @@ sub process_sync_list { if (exists $r{echo}) { # remote address says... - $self->log("error", "$ms->{ip} replied: $r{echo}"); - + $self->log("echo", "$ms->{ip} replied: $r{echo}"); } # iterate through the gamenames and addresses @@ -127,8 +126,8 @@ sub process_sync_list { # add server $self->syncer_add($a, $p, $gn, $self->secure_string()); - # print address - $self->log("add", "syncer added $gn\t$a\t$p"); + # print address (debug) + # $self->log("add", "syncer added $gn\t$a\t$p"); } else { # invalid address, log @@ -144,42 +143,14 @@ sub process_sync_list { } # end defined $gn } # end while - # end message - $self->log("sync-rx", "received $c addresses after syncing from $ms->{ip}:$ms->{tcp}"); -} - -################################################################################ -## Determine a list of all unique 333networks-compatible masterservers -## and return this list. Join the brotherhood! -################################################################################ -sub masterserver_list { - my $self = shift; - my %brotherhood; + # update this sync master in the gamelist with lastseen time + $self->update_server_list( + ip => $ms->{ip}, + port => $ms->{port}, + ) if ($c > 0); - # start with the masterservers defined in our configuration file - for my $ms (@{$self->{sync_masters}}) { - my $ip = $self->host2ip($ms->{address}); - $brotherhood{"$ip:$ms->{port}"} = {ip => $ip, tcp => $ms->{port}, udp => $ms->{beacon}} if $ip; - } - - # get the list of uplinking masterservers - my $serverlist = $self->get_server( - updated => 3600, - gamename => "333networks", - limit => 50, # more would be ridiculous.. right?.. - ); - - # overwrite existing entries, add new - for my $ms (@{$serverlist}) { - $brotherhood{"$ms->{ip}:$ms->{hostport}"} = {ip => $ms->{ip}, tcp => $ms->{hostport}, udp => $ms->{port}}; - } - - # masterservers that sync with us can not be derived directly, but by reading - # the server log we can add them manually. Lot of work, little gain, as those - # syncing masterservers will most likely be uplinking as well between now and - # a few weeks/months. - - return \%brotherhood; + # end message + $self->log("sync-rx", "received $c addresses after syncing from $ms->{ip}:$ms->{hostport}"); } 1; diff --git a/lib/MasterServer/TCP/UCCAppletQuery.pm b/lib/MasterServer/TCP/UCCAppletQuery.pm index 7637e9f..2c32de9 100755 --- a/lib/MasterServer/TCP/UCCAppletQuery.pm +++ b/lib/MasterServer/TCP/UCCAppletQuery.pm @@ -1,4 +1,3 @@ - package MasterServer::TCP::UCCAppletQuery; use strict; @@ -18,7 +17,7 @@ sub query_applet { my ($self, $ms) = @_; # be nice to notify - $self->log("tcp","start querying $ms->{ip}:$ms->{port} for '$ms->{game}' games"); + $self->log("tcp","start querying $ms->{ip}:$ms->{port} for '$ms->{gamename}' games"); # list to store all IPs in. my $master_list = ""; @@ -27,7 +26,7 @@ sub query_applet { my $handle; $handle = new AnyEvent::Handle( connect => [$ms->{ip} => $ms->{port}], - timeout => 5, + timeout => $self->{timeout_time}, poll => 'r', on_error => sub {$self->error($!, "$ms->{ip}:$ms->{port}"); $handle->destroy;}, on_eof => sub {$self->process_ucc_applet_query($master_list, $ms); $handle->destroy;}, @@ -36,7 +35,7 @@ sub query_applet { # receive and clear buffer my $m = $_[0]->rbuf; $_[0]->rbuf = ""; - + # remove string terminator chop $m if $m =~ m/secure/; @@ -48,20 +47,21 @@ sub query_applet { $m =~ s/\\([^\\]+)\\([^\\]+)/$r{$1}=$2/eg; # respond to challenge - my $validate = $self->validate_string(gamename => $ms->{game}, + my $validate = $self->validate_string(gamename => $ms->{gamename}, enctype => $r{enctype}||0, secure => $r{secure}); # send response - $handle->push_write("\\gamename\\$ms->{game}\\location\\0\\validate\\$validate\\final\\"); + $handle->push_write("\\gamename\\$ms->{gamename}\\location\\0\\validate\\$validate\\final\\"); # part 3: also request the list \list\gamename\ut -- skipped in UCC applets - $handle->push_write("\\list\\\\gamename\\$ms->{game}\\final\\"); + $handle->push_write("\\list\\\\gamename\\$ms->{gamename}\\final\\"); } # part 3b: receive the entire list in multiple steps. - if ($m =~ m/\\ip\\/) { + # $m contains \ip\ or part of that string + else { # add buffer to the list $master_list .= $m; } diff --git a/lib/MasterServer/UDP/BeaconCatcher.pm b/lib/MasterServer/UDP/BeaconCatcher.pm index 06fd38a..5c4ff5f 100755 --- a/lib/MasterServer/UDP/BeaconCatcher.pm +++ b/lib/MasterServer/UDP/BeaconCatcher.pm @@ -1,4 +1,3 @@ - package MasterServer::UDP::BeaconCatcher; use strict; @@ -45,8 +44,7 @@ sub on_beacon_receive { my ($port, $iaddr) = sockaddr_in($pa); my $peer_addr = inet_ntoa($iaddr); - # if the beacon has a length longer than a certain amount, assume it is - # a fraud or crash attempt + # assume fraud/crash attempt if response too long if (length $b > 64) { # log $self->log("attack","length exceeded in beacon: $peer_addr:$port sent $b"); @@ -55,17 +53,20 @@ sub on_beacon_receive { $b = substr $b, 0, 64; } - # if a heartbeat format was detected... - $self->process_udp_beacon($udp, $pa, $b, $peer_addr, $port) - if ($b =~ m/\\heartbeat\\/ && $b =~ m/\\gamename\\/); + # FIXME: note to self: order is important when having combined queries! + # TODO: find a more elegant and long-time solution for this. - # or if this is a secure response, verify the response + # if this is a secure response, verify the response $self->process_udp_validate($b, $peer_addr, $port, undef) if ($b =~ m/\\validate\\/); + + # if a heartbeat format was detected... + $self->process_udp_beacon($udp, $pa, $b, $peer_addr, $port) + if ($b =~ m/\\heartbeat\\/ && $b =~ m/\\gamename\\/); - # or if other masterservers check if we're still alive - $self->process_udp_basic($udp, $pa, $b, $peer_addr) - if ($b =~ m/\\basic\\/ || $b =~ m/\\status\\/ || $b =~ m/\\info\\/); + # if other masterservers check if we're still alive + $self->process_udp_secure($udp, $pa, $b, $peer_addr) + if ($b =~ m/\\secure\\/ || $b =~ m/\\basic\\/ || $b =~ m/\\status\\/ || $b =~ m/\\info\\/); } 1; diff --git a/lib/MasterServer/UDP/BeaconChecker.pm b/lib/MasterServer/UDP/BeaconChecker.pm index f74378d..73220cf 100755 --- a/lib/MasterServer/UDP/BeaconChecker.pm +++ b/lib/MasterServer/UDP/BeaconChecker.pm @@ -1,4 +1,3 @@ - package MasterServer::UDP::BeaconChecker; use strict; @@ -6,7 +5,7 @@ use warnings; use AnyEvent::Handle::UDP; use Exporter 'import'; -our @EXPORT = qw| query_udp_server|; +our @EXPORT = qw| query_udp_server |; ################################################################################ ## Get the server status from any server over UDP and store the received @@ -17,20 +16,28 @@ sub query_udp_server { my ($self, $id, $ip, $port, $secure, $message_type) = @_; my $buf = ""; - # debug spamming - $self->log("udp", "Query server $id ($ip:$port)"); + # debug logging + # $self->log("debug", "Query server $id ($ip:$port)"); # connect with UDP server my $udp_client; $udp_client = AnyEvent::Handle::UDP->new( - # Bind to this host and port connect => [$ip, $port], - timeout => 1, - on_timeout => sub {$udp_client->destroy();}, # don't bother reporting timeouts + timeout => $self->{timeout_time}, + on_timeout => sub {$udp_client->destroy();}, # do not report timeouts on_error => sub {$udp_client->destroy();}, # or errors - on_recv => sub { + on_recv => sub { # add packet to buffer $buf .= $_[0]; + + # FIXME: note to self: order is important when having combined queries! + # TODO: find a more elegant and long-time solution for this. + + # message type 1: \basic\\secure\wookie + # if validate, assume that we sent a \basic\secure request. + if ($buf =~ m/\\validate\\/){ + $self->process_udp_validate($buf, $ip, undef, $port); + } # message type 0: \basic\\info\ # if gamename, ver, hostname and hostport are available, but NOT the value @@ -42,12 +49,6 @@ sub query_udp_server { $self->process_query_response($buf, $ip, $port); } - # message type 1: \basic\\secure\wookie - # if validate, assume that we sent a \basic\secure request. - if ($buf =~ m/\\validate\\/){ - $self->process_udp_validate($buf, $ip, undef, $port); - } - # message type 2: \status\ # contains same info as \basic\\info, but also "listenserver". Only for UT. if ($buf =~ m/\\gamename\\ut/ && diff --git a/lib/MasterServer/UDP/DatagramProcessor.pm b/lib/MasterServer/UDP/DatagramProcessor.pm index d2a3333..2a26763 100755 --- a/lib/MasterServer/UDP/DatagramProcessor.pm +++ b/lib/MasterServer/UDP/DatagramProcessor.pm @@ -38,18 +38,18 @@ sub process_udp_beacon { # if no entry exists, report error. if (defined $game_props) { - - # some games (like bcommander) have a default port and don't send a heartbeat port. - $r{heartbeat} = $game_props->{heartbeat} if ($r{heartbeat} == 0); + + # validate heartbeat data + my $heartbeat = ($r{heartbeat} || $game_props->{default_qport}); # # verify valid server address (ip+port) - if ($self->valid_address($peer_addr,$r{heartbeat})) { + if ($self->valid_address($peer_addr,$heartbeat)) { # check if the entry already was not added within the last 5 seconds, throttle otherwise my $throttle = $self->get_pending( ip => $peer_addr, - heartbeat => $r{heartbeat}, + heartbeat => $heartbeat, gamename => $r{gamename}, after => 5, sort => "added", @@ -64,7 +64,7 @@ sub process_udp_beacon { # or add to pending with new secure string. my $auth = $self->add_server_new(ip => $peer_addr, beaconport => $port, - heartbeat => $r{heartbeat}, + heartbeat => $heartbeat, gamename => $r{gamename}, secure => $secure, direct => 1, @@ -77,8 +77,8 @@ sub process_udp_beacon { # verify that this is a legitimate client by sending the "secure" query $udp->push_send("\\secure\\$secure\\final\\", $pa); - # log this as a new beacon - $self->log("secure", "challenged new beacon $peer_addr:$port with $secure."); + # log this as a new beacon (debug) + #$self->log("secure", "challenged new beacon $peer_addr:$port with $secure."); } } @@ -91,7 +91,7 @@ sub process_udp_beacon { } # unknown game else { - $self->log("invalid","$peer_addr tries to identify as unknown game \"$r{gamename}\"."); + $self->log("support","$peer_addr tries to identify as unknown game \"$r{gamename}\"."); } } @@ -100,13 +100,7 @@ sub process_udp_beacon { # be extrapolated from the heartbeat else { # log - $self->log("support", "received unknown beacon from $peer_addr --> $raw"); - # - # TODO: more practical way to log this to the database: new table - # named "unsupported" where messages are logged by ip, port, gamename (if - # applicable) and TEXT raw message. - # - + $self->log("support", "received unknown beacon from $peer_addr --> '$raw'"); } } @@ -138,7 +132,7 @@ sub process_udp_validate { # was either removed by the BeaconChecker or cleaned out in maintenance (after X hours). if (defined $pending) { - #determine if it uses any enctype + # determine if it uses any enctype my $enc = (defined $r{enctype}) ? $r{enctype} : 0; # database may not contain the correct gamename (ucc applet, incomplete beacon, other game) @@ -169,7 +163,7 @@ sub process_udp_validate { # remove the entry from pending if successfully added $self->remove_pending($pending->{id}) if ( $sa >= 0); } - # was not found, insert clean and remove from pending + # was not found in serverlist, insert clean and remove from pending else { my $sa = $self->add_server_list( ip => $pending->{ip}, @@ -183,37 +177,51 @@ sub process_udp_validate { else { # else failed validation # calculate expected result for log - my $validate_string = $self->validate_string( - gamename => $pending->{gamename}, - secure => $pending->{secure} + + my $validate_string = ""; + if ($pending->{gamename} && $pending->{secure}) { + $validate_string = $self->validate_string( + gamename => $pending->{gamename}, + secure => $pending->{secure} + ); + } + $self->log("secure","$pending->{id} for ". + ($pending->{gamename} || "empty_p_gamename") + ." sent: '". ($pending->{secure} || "empty_p_secure") + ."', expected '". ($validate_string || "empty_v_string") + ."', got '". ($r{validate} || "empty_r_validate") + ."'" ); - $self->log("secure","$pending->{id} for $pending->{gamename} sent: $pending->{secure}, got $r{validate}, expected $validate_string"); } } # if no entry found in pending list else { - # 404 not found - $self->log("error","server not found in pending for $peer_addr:", - ($heartbeat ? $heartbeat : "" ), ($port ? $port : "" ), " !"); + # not found + $self->log("error","server not found in pending for ". + ($peer_addr || "ip") .":". + ($heartbeat || "0") .",". + ($port || "0") ." !"); } } ################################################################################ ## Process query data that was obtained with \basic\ and/or \info\ from the ## beacon checker module. +## FIXME: error checking and data processing. ($_ || "default") instead. ################################################################################ sub process_query_response { # $self, udp data, ip, port my ($self, $buf, $ip, $port) = @_; - #process datastream - my %s; + # process datastream + my %s = (); $buf = encode('UTF-8', $buf); $buf =~ s/\\([^\\]+)\\([^\\]+)/$s{$1}=$2/eg; + + # check whether the gamename is supported in our db - if (defined $s{gamename} && - length $self->get_game_props($s{gamename})->{cipher} > 1) { + if (exists $s{gamename} && $self->get_game_props($s{gamename})) { # parse variables my %nfo = (); @@ -224,19 +232,22 @@ sub process_query_response { # some mor0ns have hostnames longer than 200 characters $nfo{hostname} = substr $nfo{hostname}, 0, 199 if (length $nfo{hostname} >= 199); - # log results - $self->log("hostname", "$ip:$port is now known as $nfo{hostname}"); + # log results (debug) + # $self->log("hostname", "$ip:$port is now known as $nfo{hostname}"); # add or update in serverlist (assuming validation is complete) - $self->update_server_list( + my $result = $self->update_server_list( ip => $ip, port => $port, gamename => $s{gamename}, %nfo); - + # if address is in pending list, remove it my $pen = $self->get_pending(ip => $ip, heartbeat => $port)->[0]; $self->remove_pending($pen->{id}) if $pen; + + # log potential error + $self->log("support", "no entries were updated for $ip:$port ($s{gamename}), but it was still removed from pending!") if ($result == 0 && $pen); } } @@ -248,7 +259,7 @@ sub process_status_response { # $self, udp data, ip, port my ($self, $buf, $ip, $port) = @_; - #process datastream + # process datastream my %s; $buf = encode('UTF-8', $buf); $buf =~ s/\\([^\\]+)\\([^\\]+)/$s{$1}=$2/eg; @@ -312,16 +323,15 @@ sub process_status_response { # write to db $self->insert_utplayer($serverlist_id->{id}, %player); } - - # - # Prevent null concatenation in logging - $s{numplayers} ||= 0; - $s{maxplayers} ||= 0; - $s{mapname} ||= "Unknown map"; - $s{hostname} ||= "Unknown hostname"; - - # log results - $self->log("utserver", "$serverlist_id->{id}, $ip:$port,\t $s{numplayers}/$s{maxplayers} players, $s{mapname}, $s{hostname}"); + + # log results (debug) + #$self->log("utserver", + # "$serverlist_id->{id}, $ip:$port,\t". + # ($s{numplayers} || "0") ."/". + # ($s{maxplayers} || "0") ."players, ". + # ($s{mapname} || "mapname") .",". + # ($s{hostname} || "hostname") + #); } } @@ -351,28 +361,34 @@ sub process_ucc_applet_query { # count number of valid addresses $c++; - # print address - $self->log("add", "applet query added $ms->{game}\t$a\t$p"); + # print address (debug) + # $self->log("add", "applet query added $ms->{gamename}\t$a\t$p"); # add server $self->add_server_new(ip => $a, beaconport => $p, heartbeat => $p, - gamename => $ms->{game}, + gamename => $ms->{gamename}, secure => $self->secure_string(), updated => time); } # invalid address, log - else {$self->log("error", "invalid address found at master applet $ms->{ip}: $l!");} + else {$self->log("error", "invalid address found at master applet $ms->{ip}, $l!");} } } # end transaction, commit $self->{dbh}->commit; + + # update time if successful applet query + $self->update_master_applet( + ip => $ms->{ip}, + port => $ms->{port}, + gamename => $ms->{gamename}, + ) if ($c > 0); # print findings - $self->log("applet-rx","found $c addresses at $ms->{ip} for $ms->{game}."); - + $self->log("applet-rx","found $c addresses at $ms->{ip} for $ms->{gamename}."); } 1; diff --git a/lib/MasterServer/UDP/UDPTicker.pm b/lib/MasterServer/UDP/UDPTicker.pm index 5a34a8f..6b3a681 100755 --- a/lib/MasterServer/UDP/UDPTicker.pm +++ b/lib/MasterServer/UDP/UDPTicker.pm @@ -1,10 +1,10 @@ - package MasterServer::UDP::UDPTicker; use strict; use warnings; use AnyEvent::Handle::UDP; use Exporter 'import'; +use Data::Dumper 'Dumper'; our @EXPORT = qw| udp_ticker |; @@ -45,7 +45,7 @@ sub udp_ticker { # go through all servers that need querying my $server_info = AnyEvent->timer ( - after => 75, # first give beacons a chance to uplink + after => 120, # first give beacons a chance to uplink interval => 0.2, # 5 addresses per second is fast enough cb => sub { @@ -71,7 +71,7 @@ sub udp_ticker { if (time - $ut_serv{start} > $ut_serv{limit}) { if ($ut_serv{c} > 0) { # done within defined time, reset - %ut_serv = (%ut_serv, %reset) + %ut_serv = (%ut_serv, %reset); } } @@ -89,48 +89,6 @@ sub udp_ticker { %oldserv = (%oldserv, %reset); } } - - # - # else { print "Making overtime!" } - -=pod - # FIXME remove this if above works - - # debug: detect premature resets - if (time - $pending{start} > $pending{limit}) { - if ($pending{c} == 0) { - print "Premature pending reset\n" ; - } - else{$pending{c} = 0;} - } - - if (time - $updater{start} > $updater{limit}) { - if ($updater{c} == 0) { - print "Premature updater reset\n" ; - } - else{$updater{c} = 0;} - } - - if (time - $ut_serv{start} > $ut_serv{limit}) { - if ($ut_serv{c} == 0) { - print "Premature ut_serv reset\n" ; - } - else{$ut_serv{c} = 0;} - } - - if (time - $oldserv{start} > $oldserv{limit}) { - if ($oldserv{c} == 0) { - print "Premature oldserv reset\n" ; - } - else{$oldserv{c} = 0;} - } - - # are we making overtime on any of the counters yet? - %pending = (%pending, %reset) if (time - $pending{start} > $pending{limit}); - %updater = (%updater, %reset) if (time - $updater{start} > $updater{limit}); - %ut_serv = (%ut_serv, %reset) if (time - $ut_serv{start} > $ut_serv{limit}); - %oldserv = (%oldserv, %reset) if (time - $oldserv{start} > $oldserv{limit}); -=cut } # @@ -242,6 +200,7 @@ sub udp_ticker { $n = $self->get_server( next_id => $oldserv{id}, before => 7200, + (defined $self->{firstrun}) ? () : (updated => 86400), # FIXME long firstrun time fixed now? sort => "id", limit => 1, )->[0] if $self->{beacon_checker_enabled}; @@ -271,7 +230,7 @@ sub udp_ticker { if (!defined $self->{firstrun}) { # inform that first run is completed my $t = time-$self->{firstruntime}; - my $t_readable = ($t > 60) ? (($t/60). ":". ($t%60). "minutes") : ($t. "seconds"); + my $t_readable = ($t > 60) ? (int($t/60). " minutes ". ($t%60). " seconds") : ($t. " seconds"); $self->log("info", "First run completed after $t_readable."); $self->{firstrun} = 0; diff --git a/lib/MasterServer/UDP/UpLink.pm b/lib/MasterServer/UDP/UpLink.pm index e5c703b..63cbefb 100755 --- a/lib/MasterServer/UDP/UpLink.pm +++ b/lib/MasterServer/UDP/UpLink.pm @@ -10,8 +10,7 @@ use Exporter 'import'; our @EXPORT = qw| send_heartbeats do_uplink process_uplink_response - process_udp_secure - process_udp_basic |; + process_udp_secure |; ################################################################################ ## Broadcast heartbeats to other masterservers @@ -23,16 +22,21 @@ sub send_heartbeats { # in order to be permitted to sync, you need to share your address too so # others can sync from you too. if ($self->{sync_enabled}) { - - # uplink to every entry of the masterserver brotherhood list - foreach my $uplink (values %{$self->masterserver_list()}) { + + # get serverlist + my $masterserverlist = $self->get_server( + updated => 3600, + gamename => "333networks", + ); + + # uplink to every 333networks-based masterserver + foreach my $ms (@{$masterserverlist}) { # send uplink - $self->do_uplink($uplink->{ip}, $uplink->{udp}); + $self->do_uplink($ms->{ip}, $ms->{port}); } - } + } } - ################################################################################ ## Do an uplink to other 333networks-based masterservers so we can be shared ## along the 333networks synchronization protocol. Other 333networks-based @@ -44,16 +48,15 @@ sub do_uplink { # do not proceed if not all information is available return unless (defined $ip && defined $port && $port > 0); - # debug spamming + # report uplinks to log $self->log("uplink", "Uplink to Masterserver $ip:$port"); # connect with UDP server my $udp_client; $udp_client = AnyEvent::Handle::UDP->new( - # Bind to this host and port connect => [$ip, $port], - timeout => 5, - on_timeout => sub {$udp_client->destroy();}, # don't bother reporting timeouts - on_error => sub {$udp_client->destroy();}, # or errors + timeout => $self->{timeout_time}, + on_timeout => sub {$udp_client->destroy()}, + on_error => sub {$udp_client->destroy()}, on_recv => sub {$self->process_uplink_response(@_)}, ); @@ -73,8 +76,7 @@ sub process_uplink_response { my ($port, $iaddr) = sockaddr_in($pa); my $peer_addr = inet_ntoa($iaddr); - # if the beacon has a length longer than a certain amount, assume it is - # a fraud or crash attempt + # assume fraud/crash attempt if response too long if (length $b > 64) { # log $self->log("attack","length exceeded in uplink response: $peer_addr:$port sent $b"); @@ -91,27 +93,28 @@ sub process_uplink_response { ################################################################################ ## Process the received secure query and respond with the correct response -## +## TODO: expand queries with support for info, rules, players, status, etc ################################################################################ sub process_udp_secure { # $self, handle, packed address, udp data, peer ip address, $port my ($self, $udp, $pa, $buf, $peer_addr) = @_; - - # received secure in $buf: \basic\secure\l8jfVy + + # received secure in $buf: \basic\\secure\wookie my %r; - my $raw = $buf; # raw buffer for logging if necessary + $buf = encode('UTF-8', $buf); $buf =~ s/\\\\/\\undef\\/; $buf =~ s/\n//; $buf =~ s/\\([^\\]+)\\([^\\]+)/$r{$1}=$2/eg; - # scope + # response string my $response = ""; - # provide basic information if asked for (not uncommon) - if (defined $r{basic}) { - # compile basic string (identical to process_udp_basic) - + # compile basic string + + # provide basic information if asked for + if (defined $r{basic} || defined $r{status} || defined $r{info}) { + # format: \gamename\ut\gamever\348\minnetver\348\location\0\final\\queryid\16.1 $response .= "\\gamename\\333networks" . "\\gamever\\$self->{short_version}" @@ -119,51 +122,21 @@ sub process_udp_secure { . "\\hostname\\$self->{masterserver_hostname}" . "\\hostport\\$self->{listen_port}"; } + + # TODO: add queryid -- not because it's useful, but because protocol compliant - # we only respond with gamename = 333networks + # support for secure/validate if (defined $r{secure}) { - # get response + # generate response + $response .= "\\validate\\" . $self->validate_string(gamename => "333networks", enctype => 0, secure => $r{secure}); } - # send the response to the \basic\\secure\wookie query + # send the response $udp->push_send("$response\\final\\", $pa); } -################################################################################ -## Respond to basic or status queries -## TODO: abstract function for this -- otherwise these functions pile up. -################################################################################ -sub process_udp_basic { - # $self, handle, packed address, udp data, peer ip address, $port - my ($self, $udp, $pa, $buf, $peer_addr) = @_; - - # received basic or status in $buf: \basic\ or \status\ - my %r; - $buf = encode('UTF-8', $buf); - $buf =~ s/\\\\/\\undef\\/; - $buf =~ s/\n//; - $buf =~ s/\\([^\\]+)\\([^\\]+)/$r{$1}=$2/eg; - - # scope - my $basic = ""; - # provide basic information - - if (defined $r{basic} || defined $r{status} || defined $r{info}) { - # compile basic string (identical to process_udp_basic) - - # format: \gamename\ut\gamever\348\minnetver\348\location\0\final\\queryid\16.1 - $basic = "\\gamename\\333networks" - . "\\gamever\\$self->{short_version}" - . "\\location\\0" - . "\\hostname\\$self->{masterserver_hostname}" - . "\\hostport\\$self->{listen_port}"; - } - - # send the response to the \basic\ or \status\ - $udp->push_send("$basic\\final\\", $pa); -} 1; diff --git a/lib/MasterServer/Util/KFStatsWatcher.pm b/lib/MasterServer/Util/KFStatsWatcher.pm index 916446e..6601aa3 100755 --- a/lib/MasterServer/Util/KFStatsWatcher.pm +++ b/lib/MasterServer/Util/KFStatsWatcher.pm @@ -1,4 +1,3 @@ - package MasterServer::Util::KFStatsWatcher; use strict; @@ -49,11 +48,10 @@ sub read_kfstats { } } - #notify + # notify $self->log("kfstat", "Updated Killing Floor player stats."); } ); } - 1; diff --git a/util/masterserver.pl b/util/masterserver.pl index 8fda04b..d684001 100755 --- a/util/masterserver.pl +++ b/util/masterserver.pl @@ -20,7 +20,6 @@ $MasterServer::OBJ->{$_} = $S{$_} for (keys %S); # load MasterServer core libs MasterServer::load_recursive('MasterServer::Core', - 'MasterServer::Database', 'MasterServer::UDP', 'MasterServer::TCP', 'MasterServer::Util'); diff --git a/util/tools/db_load_applets.pl b/util/tools/db_load_applets.pl new file mode 100755 index 0000000..9707231 --- /dev/null +++ b/util/tools/db_load_applets.pl @@ -0,0 +1,66 @@ +#!/usr/bin/perl + +################################################################################ +## Load configuration variables to the database +## +## Normally the masterserver loads a number of masterserver applets via the +## configuration file. This tool allows to load a number of masterserver applets +## directly into the database without restarting the masterserver. +## +## It is generally not necessary to run this script at all. Normally, the +## masterserver performs the same action on startup. +## +## Use with care! +## +## Note: set database name / user / password manually in the r_database.pl file! +################################################################################ + +use strict; +use warnings; +use DBI; + +require "r_database.pl"; +require "r_functions.pl"; + +# open db +our $dbh; + +my @data = ( + {address => "utmaster.epicgames.com", port => 28900, games => [qw|ut unreal|]}, + {address => "master.hypercoop.tk", port => 28900, games => [qw|unreal|]}, + {address => "sof1master.megalag.org", port => 28900, games => [qw|sofretail|]}, + {address => "master.deusexnetwork.com", port => 28900, games => [qw|deusex|]}, +); + +# iterate through all entries +for my $ms (@data) { + + # iterate through all games per entry + for my $g (@{$ms->{games}}) { + + # resolve domain names + my $applet_ip = host2ip($ms->{address}); + + # check if all credentials are valid + if ($applet_ip && + $ms->{port} && + $g) + { + # add to database + add_master_applet( + ip => $applet_ip, + port => $ms->{port}, + gamename => $g, + ); + } # else: insufficient info available + else { + print "fail: could not add master applet: ". + ($applet_ip || "ip") .", ". + ($ms->{port} || "0") .", ". + ($g || "game")."."; + } + } +} + +# close db +$dbh->disconnect(); diff --git a/util/tools/db_load_ciphers.pl b/util/tools/db_load_ciphers.pl index b047b77..5e0bc8d 100755 --- a/util/tools/db_load_ciphers.pl +++ b/util/tools/db_load_ciphers.pl @@ -30,7 +30,7 @@ use Data::Dumper 'Dumper'; use Cwd 'abs_path'; our $ROOT; -BEGIN { ($ROOT = abs_path $0) =~ s{/util/ciphers\.pl$}{}; } +BEGIN { ($ROOT = abs_path $0) =~ s{/util/tools/db_load_ciphers\.pl$}{}; } use lib $ROOT.'/lib'; use MasterServer; @@ -40,7 +40,7 @@ require "$ROOT/data/supportedgames.pl"; # open db connection -my $dbh = DBI->connect('dbi:Pg:dbname=masterserver', 'user', 'password') +my $dbh = DBI->connect('dbi:Pg:dbname=database', 'user', 'password') or die "Cannot connect: $DBI::errstr\n"; # intro diff --git a/util/tools/db_query_master_applet.pl b/util/tools/db_query_master_applet.pl index 628e03c..9af8b73 100755 --- a/util/tools/db_query_master_applet.pl +++ b/util/tools/db_query_master_applet.pl @@ -26,11 +26,15 @@ require "r_lists.pl"; our $dbh; my @data = ( - {ip => "dev.333networks.com", port => 28905, games => [qw|ut unreal deusex rune|]}, + {ip => "master.hypercoop.tk", port => 28900, games => [qw|ut unreal|]}, + {ip => "utmaster.epicgames.com", port => 28900, games => [qw|ut unreal|]}, + {ip => "master.deusexnetwork.com",port => 28900, games => [qw|deusex|]}, ); for my $ms (@data) { sleep 1; + + print "\n\n$ms->{ip}, $ms->{port}\n"; query_master($ms); } diff --git a/util/tools/r_database.pl b/util/tools/r_database.pl index 601b1ae..2875844 100755 --- a/util/tools/r_database.pl +++ b/util/tools/r_database.pl @@ -6,7 +6,7 @@ use DBI; our $dbh = open_database(); sub open_database { - my $dbh = DBI->connect('dbi:Pg:dbname=masterserver', 'user', 'password') + my $dbh = DBI->connect('dbi:Pg:dbname=database', 'user', 'password') or die "Cannot connect: $DBI::errstr\n"; # don't forget at end! @@ -19,7 +19,7 @@ sub open_database { ## If the address already exists in the database, it will be ignored. ################################################################################ sub db_add_server { - my %o = (@_); + my %o = @_; # Try to get the address out of the database. my $exists = $dbh->selectall_arrayref( @@ -32,9 +32,49 @@ sub db_add_server { undef, $o{ip}, $o{port}); return if (defined $exists->[0]); + # generate random "secure" string + my @c = ('A'..'Z'); + my $s = ""; + $s .= $c[rand @c] for 1..6; + #print "Add $o{ip}\t $o{port}\n"; - $exists = $dbh->do("INSERT INTO pending (ip, heartbeat) VALUES(?, ?)", - undef, $o{ip}, $o{port}); + $exists = $dbh->do("INSERT INTO pending (ip, heartbeat, secure) VALUES(?, ?, ?)", + undef, $o{ip}, $o{port}, $s); +} + +################################################################################ +## Add a remote master server applet +################################################################################ +sub add_master_applet { + my %o = @_; + + my $u = $dbh->do( + "SELECT * FROM appletlist + WHERE ip = ? + AND port = ? + AND gamename = ?", + undef, $o{ip}, $o{port}, lc $o{gamename}); + + # return if found + return if ($u > 0); + + # insert applet data + return $dbh->do("INSERT INTO appletlist (ip, port, gamename) + SELECT ?, ?, ?", undef, + $o{ip}, $o{port}, lc $o{gamename}); +} + +################################################################################ +## update time on master applet +################################################################################ +sub update_master_applet { + my %o = @_; + return $dbh->do("UPDATE appletlist + SET updated = to_timestamp(?) + WHERE ip = ? + AND port = ? + AND gamename = ?", + undef, time, $o{ip}, $o{port}, lc $o{gamename}); } 1; diff --git a/util/tools/r_functions.pl b/util/tools/r_functions.pl index f4834c6..e197e9f 100755 --- a/util/tools/r_functions.pl +++ b/util/tools/r_functions.pl @@ -5,6 +5,9 @@ use warnings; use Encode; use AnyEvent; use AnyEvent::Handle; +use IP::Country::Fast; +use Socket; +use POSIX qw/strftime/; our %S; @@ -19,13 +22,6 @@ sub valid_address { my ($a, $p) = ($h =~ m/:/) ? $h =~ /(.*):(.*)/ : ($h,0); return (undef,undef) unless ($a && $p); - # resolve hostname when needed -- shouldn't even be in the list! FIXME - #if($a =~ /[a-zA-Z]/g) { - # my $raw_addr = (gethostbyname($a))[4]; - # my @octets = unpack("C4", $raw_addr); - # $a = join(".", @octets); - #} - # check if IP and port are in valid range $a = ($a =~ '\b(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\b') ? $a : 0; $p = (0 < $p && $p <= 65535) ? $p : 0; @@ -36,6 +32,12 @@ sub valid_address { return ($a, $p); } +sub host2ip { + my $name = shift; + my $unpack = inet_aton($name) if $name; + return inet_ntoa($unpack) if $unpack; +} + sub query_master { my $ms = shift; @@ -48,7 +50,7 @@ sub query_master { timeout => 15, poll => 'r', on_error => sub { - print "($ms->{ip}, $g) $!\n"; + #print "($ms->{ip}, $g) $!\n"; $handle->destroy; $cv->send; }, @@ -105,7 +107,12 @@ sub process_received_data { } } - print "found $c \t$g \taddresses at $ms->{ip}.\n" if ($c > 0 ); + # log addresses per game per master + #print "found $c \t$g \taddresses at $ms->{ip}.\n" if ($c > 0 ); + + # print only responsive gamenames (useful for making selections) + print "$g " if ($c > 0 ); + } 1; diff --git a/util/tools/r_lists.pl b/util/tools/r_lists.pl index 73a4203..40346c1 100755 --- a/util/tools/r_lists.pl +++ b/util/tools/r_lists.pl @@ -4,6 +4,6 @@ use strict; use warnings; sub get_gamename_list { - return qw| thecombatwii magmay2d sbk09usps3d kohanag hotwheels2ps2 bfield1942ps2am jbondmv2ds crysis2ps3am draculagolds coteaglessp railsam rfactory2ds steeltide timeshift blade acrossingdsam atlas_samples thps3pc ravenshieldas splintcellchaos winters3nawii superv8pcd cnoutbreakd bldragonNAdsam sptouzokuds crysis2pc ufc10ps3am spartan disfriendsds dragonbzUSwii rtlwspor11wii crysis2pcam ut3ps3am ballarenaps3d cellfacttwpcam bllrs2005ps2d hunterdanwii momotarodends redalert3pcbmb megamansfeuds qsolace greconawf2bam swat4xp1 robolypsewii hd2d swordotnw micchannelwii jkmosith1 bgatetales slugfest06ps2 civ4wrldmac dungeonr digimonsleds mcmaddemo velocitypc AmMcGeeGrimm03 pbfqm tribesv Customrobods cohofbeta blitz2004ps2e gtacwiphoned ta cc3tibwarsmb gorese gta4ps3test xenocellpcam maxpayne3ps3am bfield1942rtr homeworld2d giants omfbattleb bcm heretic2 raw2009wii digisunmoonds irl2000 wormspspam riseofnationsam famfishwii mototrax pba2001 cellfactorpcam flyinghero rontp h2cdigitalps3 ravenshield mohpad rdr2ps3am destruction taking mcmania motogp09ps3d beateratoripham itycoon2 ultibandwii greconawf2b smackdn2ps2pal scourgepc voiceapp raymanrr2wii commandos2 exigobam kaosmpram mmadness2 rally empiresd mclub4ps3 necrovisionpcam bldragonNAds kingtigerspc section8x360 empires civconps3 section8pcbd banditsd powerslide facesofward besieger smackdnps2kor biahhPOLps3am metalmax3ds tpfolps3 bbobblewii blitz08ps3am wcpool2004ps2 bstormps3 painkilleroddam nitrobikeps2am s_cssource dqmonjkr2plds segaracingwii ludicrousmac wmarkofchaos hrollerzds NN2Simple pokedungeonds supv8ncusps3 actofwarht nitrobikeps2 monopolyty doraemonds mini4wdds 50centjpnps3d cc3kwmb obiwon biahhJPps3d tetrisppwii scrbnateu2ds anno1701 chessrevpc girlsds hotrod2 painkresurrpcam arkaUSEUds homeworld2 bderlandsps3am ufcfitwii civ4b chessrevpcam pokedngnwii armymen ra2 bluemarspc dogsofwar conquestfwd bang rtlwspor11wiiam drainworks othellods bleachds blitz2005ps2 gta4xam ghero4wii janefightpcam dtrscdmo whammermocd ragonlinenads mekurucawii slclothingdsam srow2ps3 whammer40kwa superv8ncpc worldshiftpcb mclub4ps3am draglade2ds cnpanzers2cwbam siextremeds sawpc moo3 sshafricawii vietnamso swbfront3pcCam cmmwcpoker titanquestit blitz2004ps2b daoc fakk2 roguewarpcd celtickings fairyfightps3 blic2007 civrevasiaps3 axis tsfirestorm cstaisends arkanoidds conflictsopc cc3xp1 megaman9wii scotttest taprace kacademy bllrs2004ps2d ufc10ps3DEV survivor casinotourwiiam demonforgepcd section8pcb uchaosrrps2 marvlegps3pam warlordsb2 MotoGP08PS3 civ4wrldam rockmanwds mcmad exigob crysis2x360am kenteitvwii painkillerd sonoatestam redalert3pcam sanitybeta petz09ds bldragonfds pbellumr1 dh2004 populoustb gshearts pocketwrldds tmntds parabellumps3 mxvsatvutps2 simsraceEUds decsprt3euwii scribnaut2ds dh4 reloadtdwiiam srow2pcam strategistpc dv quakewarset mtruckm2 ageofsail2 puzzleqt2ds blindpointpcd civ4btsjpam poriginpcjpam cnpanzers2cwam E3_2003 shadowforce redline srow2xb360am fbackgammon hotncoldds AncientQofSaq mfcoachwii smrailroads demonforgepcam swbfront2pcb menofwarpcdam metprime3wii globalopsd tpfolpcBam mechcomm2 powerkoushds dinokingEUds sonicrushads roguewarpcam cskiesdemo ut3jpps3am cc3xp1am necrovisionpd sumofallfears racedriverd dmhand gtacwarspspam halomac worldshiftpcam riskingdoms sakatsukuds puzquestds wkingsb gamespy1pc srgakuendsam appletestd THPGds bf2142 smgettysbu demonforgeps3d mmadnessexps3am srow2ps3am cnoutbreak sbkUSpc tcendwar FlockPCd winelev11wii oldscrabble monhuntergwii nplusds DeathtoSpies xwingtie dominion rrt2scnd diablo2 cmr5pcd phylon serioussamps2 cod5wii SF3XboxPSrv riskps2dis mafia2ps3 actofwarhtam rulesotg svsr10x360am ZumaDeluxe jetfighter4 quake4 spellforce lanoirex360 woosc topspin2pc cskies cityofheroes fsw10hps2pal jk cc3dev charcollectds trackfieldds terrortkdwn2d gta4ps3 fearxp1 tpfolEUpcam treadmarks bg2bhaal aowsm bandits cc3arenapcam tribesvb nights2wii cruciformam syachi2ds contractjack rmth2003 motogp3 mafia2pc mohaasmac thps5pc konductrads legofwreps3 bbarenaJPps3d cod7ds ut3pcam rfactory3ds mua2wii segarally2 wiibombmanwii woforam 50ctsndlvps3am civ4wrld kissdc whamdowfrbam kohankow worldshiftpc motogp08ps3 blitzkriegrt mohaa nanost2EUds quake2 beateratoriph digichampds superv8ps3am avp2demo quake1 trkmaniawii tablegamestds hd2b beatrunnerwii vietkong scribnautjpds matrixproxy ufc10x360d sukashikds cc3tibwars lanoirex360d chessrevmac fxtrainingds biahhPRps3d ffvsttrd renegadebf srow2pc actofwarhtdam haegemonia overturnwii marvlegps2p parabellumpcam metalcrush3 jikkyonextwii neopetspapcam survivorm lotr3 dh2003 tgmasterds bokujyomonds robotarena2 genesisr empiresam mohaamac SF3XboxPSrvam crysis2pcd mtxmototrax section8pcd rsblackthornd black9ps2 dimensitypcam sbkUSps3am rftbomb cc3xp1mb jissenpachwii scsdwd medarotds ronbam bderlandspcam conduit2wii vtennisacewii fuelpc stnw mysecretsds dungeonsiege stlegacy aoe3wcd gspyweb cardherodsam mech3 cc3arenapcd dtr2d fwarriorps2 3DUltraMinigolf nba2k11wiiam marveltcardds ufc10ps3 harbunkods nthunder2004 msgolf99 streetracer lostmagicwii legendaryps3 mrpantsqm monlabwii motogp08pc bleach2wii testam rofam suddenstrike2 rfgrlaonliveam roguewarps3 prismgs realwar gulfwarham 3celsiuswii srgakuends mariosprtwii armedass midmaddemo stronghold SF3PS3PSrvam dawnheroesds redalert3pcbam pbellumr2 sweawfocd legendsmmbeta ufcfitwiiam penginhimids wsc2007ps2 propocket12ds tpfolpcd marveltcard ludicrousmacam unowii FlockPSNd ritesofwar rdpoker nindev marvlegps2pam iwdale bots darkstone cardgamesds decasport2wii 3dpicrosseuds uchaosrrps2am facesofwarxp1 sawps3d greconfswii strikefighters1 tycoonnyc facesofwarxp1am vietcong wiilinkwii arma2pcam dtrsc bumperwars nomansland dirt2onlive mukoubuchids mclub2ps2 biahhPOLps3d rallytrophy dimensitypcd redalert3ps3 ufc09x360 menofwarpcb ioftheenemy fury horserace TG09PCam redorchestra chesk ufc10x360 quizmagicds fuelps3ptchdam fifa09ds bgate2 marvlegpcd tpfolpcB fstarzerods s_tf2 civrevoasiads sinpunish2wii yakumands midmad airhockeywii racedriver mfatigue gamespy2 sbkxpcdemo assaultheroes spectrobes2ds etforces demonforgepc 3dpicrossUSds dsakurads checkers moutlawne sawpcd bfield1942ps2b bfield1942 takameijinwii railty3 crysis2x360 sinmac axisallies wooscd bc3k swbfrontpc links2001 disneydevam worldshiftpcd bokujomonods streetjam poriginpcjpd medieval2am cfs2 Majesty2PCam TG09PS3 ffantasy3ds maxpayne3x360 motogp09ps3am scourgepcam nwnxp2 pbfqm2 maxpayne3ps3d testdriveud nvchess karajoy3wii battlemages acejokerUSds swat4d wormsow2ds winx2010ds dragonbzwii bldragonds sbkxpcam marvlegps2 ee3alpha birhhps3am legofwreps3am timeshiftb assimilation rebellion cueballworld monkmayhemwii rman2blkredds na2runpc stellad jyankenparwii codwawbeta jk3 takoronUSwii gauntletps2 bandw fargate section8ps3d runefactoryds picrossEUds ludicrousmacd wormswiiwaream msecurity closecomftfmac simsracingds roadwars brigades wh40kdow2crolam proyakyuds reloadpcam plunderps3 mkvsdcps3bam supcommb lotrbme wcsnkr2004pc roguespeard nakedbrbndds mkarmpalps2 elemonsterds close4bb janefightps3 stlprinEUds hokutokenwii sbk08pcam terminus hotrodwii MotoGP08PCam newgamenameam maxpayne3x360d pnomads risk2 cc3arenapc eearth2xp1 wrldgoowii spcell3coop fstreetv3ds eawar wrcps3 conquestfw blkuzushiwii superv8usps3 digistoryds links2001dmo biahhPRps3 wsoppsp devastation civ4jp section8pcbam bldragonfdsam eearth3b expertpool digistorydsam racedriver3pc avp2ph uotd mkvsdcps3 decsprt3nawii knelynch2ddolam genetrooperps2 fushigidun5ds pangmagmichds aoex neopetspapc seafarmwii tetrisds bstormps3am figlandds amfbowlingds menofwarnam beijing08ps3am painkresurrpcd superv8USpc luchalibrepc railty2 redalert3pc thps5ps2 sbkxps3demo ut2004 vietcong2d boardgamesds chessrevmacam tokyoparkwii ballers3ps3 strategistwii strategistpsnam starwrsfrc il2sturmovikfb mprimeds mxvsatvutwii gunman FlockPSN blahmasterid sfc2dv olvps2 im1pcam puzzshangwii codmw2ds gwgalaxiesds upwords hd mechcomm picrossds dukes gtacwarspspd ysstrategyds foxtrotps3 xenocellpc gta4pcdevam mxvsatvutps2am il2sturmovik codedarmspsp greconawfd dragladeds bombermanslds anno1602ad riskingdomsam ben10bbam cc3tibwarscdam bgate poriginps3jpd rafcivatwart gsspades WSWeleven07ds bonkwii fearobsc gta3pc crysisd svsr11x360 section8ps3 tpfolEUps3am rune crysis2x360d castlestrike rainbowsixv fireemblemds pokemondpds yugiohgx2ds keuthendev section8ps3am whammermoct dundfniphoneam exigo wrcpcam moutlawned greconawf2am wz2100demo silenthunter2 ut2004d whammermocbmd harmoon2ds superv8ncpcd rallychamp SampAppTestam heistpc projecteden dh2005 group links99 hlwarriors springwidgetsam risingeaglepc elevenkords lithdevam ufc09x360d avpnotgold fifa08ds chocobombds sacrifice earth2150 topspin3euds gh4metalwii qlione rachelwood bfield1942rtrm whammer40kdcam penginhimidsam sparta2pcam acrossingds sforces quake3 warlordsb2d poriginps3jpam rof sporearenads starpballwii motogp2 bf2142d fherjwkk trivialppalps2 aoe2tcdemo swat4xp1_tmp superv8ncps3am sbk09pc ut3ps3 cohof bballarenaps3am serioussamse nfsprostds lionheart shogo contactds fswps2kor s_cstrikecz stbotf warcraft2bne gotchad roguewarpc prey mt2003 TEST1 wingsofward ut2d dqmonjokerds famista2010ds 2kboxingds nitrosample descent3 ardinokingds patchtest yugioh5dds fuelps3am mk9test dental2dsam wcpoker2pc sengo3wii ubisoftdevam ccrenegadedemo winelev10wii wracing2 risingeagleg motogp09pc guinnesswrds risk mleatingJPwiiam blitz2004ps2 gh4vhalenwiiam mfightbbukds gsTiaKreisDS sneeziesdsw gloftpokerwii civ4xp3am tankbeatEUds chspades civ4mac duelfield test071806 wsoppcam kentomashods cmanager3 thps6ps2 bf2sttest furfighters furdemo mohaasd dundefndps3am marvlegpcdam gp500 finertiaps3am molecontrolpcam firearmsevopc robotech2 armymenrts leadfoot crysis2ps3d tpfolpcam mkvsdcps3b warmonger sbk08ps3 maxpayne3pc aowfull ut3pcd bleach2EUds legendaryps3am civ4 section8pc ingenious runefantasyds ecolisEUds weleplay09wii civ4jpam codblackopspc axallirnb mk9testam neopetspapcd okirakuwii poriginps3jp namcotestd socelevends netathlon2 mxvatvutEUwii nobuyabou2ds dynaztrialwii janesattack marvlegjpps3am tempunavail cellfactorpsnam opflashd hoodzps2 tataitemogwii blahblahtest luminarcUSds menofvalor gspylite elebitsds wordzap reloadtdwii thps7ps2 fatedragon superv8ps3d yugiohWC07ds kaosmprd simsraceJPNds menofwarasr subcommand mcdcrewds greconawf2d revolt starsiege swrcommando dundefndps3 smackdn2ps2kor sofretail crashnburnps2b GameSpy.com strategistpcd mxun05ps2am h2cdigitalps3d supruler2010 wracing1 sbubpop foreverbl2wii pente swine ufc10ps3DEVd strfltcmd2 callofduty legofwrex360am panzergen2 koshien2ds stitandemo gspoker powerpinconds spartaawd wofor cnpanzers2cwb redalert3pcdmb tmntsmashwii swbfespspd quakeworld hotrodwiiam le_projectx masterrally facesofwaram lotr3b treasurewldds hustleps2am swgbd tacticalops kingclubsds otonatrainds ufc2010iphone mcomm2 iwd2 insanedmo rdriver3ps2d beateratoram bballarenaps3 entente menofwar:asam MenofWar nitrofamily narutorpg3ds thesactionwii hail2chimps3r bstrikeotspcam monsterfarmds afrikakorpsd moonproject gta4pcdev aliencrashwii fuelps3ptchd ddozenptd dirt2onliveam dtracing mechamotedsi AmMcGeeGrimm04 blurdsam racko ut3pc appletest civ2gold fear birhhpcam codwaw aarts pb4 puyobomberds narutor3euwii linksext sbk09usps3am ace sin psyintdevpcam linksds tenchu4wii battlerealmsbBA scsdws wcsnkr2005 diplomacy tpfolps3am swg gangsters2 ubisoftdev ptacticsds takoronKRwii whamdowfrb saturdaynsd simplejudowii emperorbfd st_rank wormsarm originalwar ddayd guinnesswrwii sharpshooter fordvchevyps2 mfightbbukdsam etherlordsd actofwarhtd katekyohitds ageofconanb SF3PS3PSrvmb dragonthrone biahhRUSpc stormrisepcd texasholdwii il2sturmovikd lotrbfme2 livewire sbkUSpcam bldragonids yugioh5dwii close5 demonforgeps3am legionarena whammer40000 cfs jbnightfire combatzonepcd trivialppcsp nolf gslive ut3jppc tetriskords sniperelps2 poriginpcam sonicrkords perimeter blindpointpcam sbk09ps3am chesschalwii midmad2dmo mk9testd drakan spaceremixds idraculawii rock rdgridds FlockPC wcpokerpalps2 tankbeat2ds suddenstrike black9pc fsxaam alphacent pkodgermand heistps3 outlaws mclub4xboxdev chesschalwiiam wofordam shogiwii 4x4retail saspc parcheesi callofduty5 scrabbledel mswinterwii necrovision lanoirepcd civ3con millebourn cvania08ds bsmidwaypc 50centjpnps3 motogp2007 cc3kwcdam superv8ncpcam DaggerdalePS3 civ4wrldcnam prismgsd deusex omfbattled PowaPPocketds spinvgewii fearob sakuraTDDds parabellumps3am janefightpc mxun05pc medarotkuds mkvsdcxbox ufc10x360devd im1pc carnivores3 praetorians worldshiftpcbam mafia2ps3am yetisportswiiam serioussam callofdutyps2 roguespear srow2ps3dam darkplanet tpfolEUpcBd werewolf legendsmm codblackopspcam mobileforces cardherods swrcommandoj echelon serioussam2d olg2PS2 doom3 tribesvd evolva trivialppalpc dsnattest2 tron20d bz2 wwkuzushiwii tiumeshiftu civ4coljpam dod dungeonlords scrbnatot2ds fightclubps2 AliensCMPS3d mechamotedsiam preyd civ4btsam 50centsandps3am gravitronwii 7kingdoms pubdartswii mostwanted bluemarspcam pariahpc rtlwsportswii reloadpc sshafricawiiam clubgameKORds dartspartywii hail2chimps3d dtr2 mta hotpacnadps2 rockstarsclubam swsnow2wii diablo supcomfabeta wlclashpc sonoatest trkmaniads utdc luminarc2ds trivialppcit coh2pcam marvlegpc fairyfightpcd fswps2jp avponliveam svsr09x360 mysimsflyerds armymenspc civ4ruam f1teamdriver orb fatedragond wsopps2am 50centsandps3 battlefield2 ut mooncommander swbfront2pcd cmr5pc kohanexp swbfrontps2p ffvsttr roguewarps3d topspin swbfront2ps2j rdr2x360am betonsoldier omfbattle halo beijing08ps3d bangdemo bfield1942mac allegiance cultures ds9dominion fsw10hps2kor slancerdc agrome betonsoldierd whammermocbm spectro2wii thps3pcr nwnxp1 starcraftexp themark majesty smackdnps2 sballrevwii mmadexps3d MSolympicds influencepcam aquanox sfcdemo thps3ps2 sbkUSps3 nolf2d gamevoice cod5victoryds hearts rbeaverdefwii fblackjack indycarps2 ragunonlineds crmgdntdr2k superv8pcam combat bleach2ds famista09ds hotrod scribnaut2wii suddenstrike3 civ4am dundfniphone trivialppcgr crystalw2wii strifeshadow bokutwinvilds beijing08pcd MotoGP08PC bgeverwii duke4 madeinoreds mxravenpsp dsnattest 8ballstarsds mohaabd globalops plunderpc taisends area51pcb zeroGds marvlegps3am fuelps3 tpfolpcBd ghostraw redalert3pcb dwctest metalfight3ds koinudewii SF3XboxPSrvmb forsaken bldragonddsam rtcw snightxds thps4pc armaas swbf3pspam evosoc08USds eearth2 cribbage hotwheels2pcd mech4 s_hl2dm smashbrosxwii custoboeuds greconawf battlefield2d dinokingUSds dsiege2am biahhJPps3am motogp09ps3 goreAVd cardheroesds jikkyopprowii sbk08pcd topspinps2 ccgeneralsb janesusaf DSwars2ds timeshiftx SF3RemiXboxam popwii lostmagicds gopetsvids mk9ps3 devastationd sof2 sbk09ps3 destructionam evaspacewii cc3devam menofwarpcd racedriver3pcd sonic2010wii cnpanzers2cw venomworld sweawfoc CVjudgmentwii pbellumr3 MOHADemo vietcong2pd tiberiansun ufc10x360am racedriver2d ffcryschronds scribnaut2pcam mmvdkds stalkercoppc poriginpc vietcong2 thps4ps2 rdriver3ps2 abominatio whammermok biahhPOLps3 Jyotrainwii f1comp girlssecEUds twoods08ds wormswiiware trivialpps2 Digidwndskds callofduty2 testdriveuak backgammon tcounterwii gts4xdev tapraceam scratchdjpc rockmanBSDds bfield1942t mohaad buckmaster soccerjamds whammermoctam dexplorerds medarotkudsam epochwarspc AliensCMPCd mtmdemo s_darkmmm damnationps3am condemned2bs acrossingwii wordjongFRds bldragonsds tribes test rockstarsclub konsportswii AGON ginrummy redbaronps3 scsdw bboarderswii mmessagesds pokebattlewii mleatingJPwii mplayer ironstorm hustleps2 whammer40kb strategistpcam sekainodokods simspartywii ghpballps2 source santietam harvfishEUds amworldwar poriginps3d timeshiftg orderofwarpcd rpgtkooldsi falloutbos chesswii plunderps3d rainbowislwii stef1 fswps2pal appletestam megaman10wii harmooniohds bioshockd painkillert blood2 potbs gh4ghitswii ccombat3 saspcam statesmen exigor cc3tibwarsam shatteredunion hotpacificpcd kidslearnwii marvlegjpps3 thps3media avp2lv swempiremacam lazgo2demo facesow teamfactor darkreign2 nhl2k10wii spoilsofwaram painkresurrpc testdriveub ninjagaidends jbondmv2dsam armada2beta opfor actofwaram parabellumpcd secondlife velocityps2 menofwarpc pandeponds area51pc lozphourds kaihatsuds sdamigowii mmartracewii puyopuyo7wii mcommgold strfltcmd2d stalkercs vietcongd motogp09pcd pssake blockrushwii gokuidsi iwar2 kott2pc ganglandd srally2dmo ghostrecon legendarypc shootantowii chat haegemoniaxp stormrisepc orbb st_highscore rdr2ps3 bejeweled2wii fallout3 ufc09ps3d gmtestcd janesf18 birhhpc sakwcha2010ds opflashr ihraracing crttestdead sparta2pc tenchuds sonic2010ds links98 sumofallfearsd lonposwii damnationpcam escviruswii beijing08pcam civ4macam srow2ps3d kingpin heistpcd mageknightd scratchdjpcam s_l4d glracerwii ghostsquadwii scotttestam heavygear2 judgedredddi motoracer3 dragoncrwnwii scrabble3 sbkxpcdemoam MotoGP08PS3am lanoirepc smball2iphd kodawar2010ds marvlegpcam civ3conb svsr10x360 nwn lanoirepcam bombls2ds winel10jpnwiiam atlantis redsteel2wii halod smackdnps2palr oishiids timeshiftps3d drmariowii memansf2EUDS stylelabds uprising2 ironstormd lanoirex360am nanostray2ds beateratoriphd gta4ps3devam foxtrotps3d armedassd gtasaps2 bderlandsx360d nwn2 mahjongkcds luchalibrewii ufc09ps3am swempiream aoe2tc civ4wrldjpam momoden16wii blkhwkdnps2 blockoutwii TG09360am svsr11ps3 jk2 swbfront3pc nitrobikewii simsflyerswii hobbitdsam kohankowd batwars2wii scrabble girlssecretds projectigi2r cossacks lumark3eyesds strifeshadowd q3tademo segatennisps3am superpower2 aartsd culdceptds tolmamods cpenguin2wii furfiighters gamebot mclub3ps2 medieval2d rfactoryKRds flashanzands daikatana falloutbosd bspiritsdsam gangland serioussam2 marvlegps3 terrortkdwn2 gsttestgame phoenix ffantasy3usds shirenUSEUds realwarrs painkillerodam Majesty2PC SF3RemiXbox gsyarn actofward locomotion tf15 hail2chimps3ram champgamesps3 nerfarena xmenleg2psp mxvatvuPALps2am and1sballps2 wormsforts zax eearth3dam psyintdevpc combatzonepc digichampKRds CM_Testing aow2 tothrainbowds tqexp1 buccaneerpcd moo3a wmarkofchaosdam menofwarasam ut3onliveam starraiders rtcwtest stronghold2 zdoom mageknight waterloo c5 rometw hsmusicalds cpenguin2ds cheetah3ds SF3RemixPS3am mxun05pcam bderlands360am keenracerswii strongholdcd sawps3am freepark phybaltraiwii sanitydemo gta4xgrmam tron20mac wotrb stalkercsam kiss scourgeps3d firecapbay swbfront3pcam plunderpcd bfield1942sw winters3nawiiam rsblackthorn Decathletesds projectigi2d mcm2demo halom mclub4xboxam maxpayne3pcd gamespy2pc onslaughtpc metalfight3dsam dragquestsds gta4pcam dmania necrovisionpcd strongholdc myth3 smball2ipham spellforced simcitywii ffurtdriftps2am swtakoronwii hardtruck mk9ps3am digiwrldds sbkxusps3am swbfespspam tecmoblkickds turok2 fmasterwtwii kqmateDS midmad2 bderlandsps3 mech4merc bleach1EUds vanguardsoh scribnaut2pc buckshotwii bandbrosds lithdev tpfolpc swbfffpsp derbydogwii trivialppcfr memansf2USDS gta4xgrm fswpc getmede eternalforcesam rmth3 superv8usps3am kkhrebornwii lotrbme2 bsmidwayps2am jbond08wii conan cstrike beateratorpsp wrcps3d sbkxps3d gunnylamacwii aliencross startrekmacam cityofvl evosoc08EUwii contrads krabbitpcmac justsingds Narutonin2ds strategistpsn ut3jppcam marvlegnpspam sbkxpcd radiohitzwii ascensionpcd biahhJPps3 blademasters im1pcd slclothingds commandpcam topspin3usds fullautops3 FlockPSNam olg2ps2 guinnesswriph kohan commandpc motogp4ps2 gta4ps3grmam valknightswii wingsofwar area51ps2 freedomforce nwn2mac lanoireps3d atlantica civ4xp3d opblitz nhl2k11wiiam trivialppc djangosabds fairyfightps3d toribashwii sbkUSps3d bewarewii peoplesgen bfvietnam ffantasy3euds carnivalkwii genetrooperpc redalert3pccdam celtickingsdemo maxpayne3pcam luchalibrepcd crysis2ps3 fuusuibands sengo3wiijp mohaabmac gta4ps3dev pachgundamwii bbarenaJPNps3 netathlon swbfront2ps2 na2rowpc gtacwarspsp blockade TG09PS3am battlerealms groundcontrol2 tetrisworlds redbaronww1 asobids impglory crimson madden09ds svsr10ps3am swat4 beijing08pc tpfolEUpcB migalley openseasonds wosinmac foxtrotpcam myth3demo viper blandsjpnps3am coh2pc nwn2macam scribnauteuds whtacticspspam harleywof scourgepcd spades rfberlin influencepc swbfront2pc cueballworldd hotpacificpc ddayxp1 startreklegacy regimentps2 ryantestam elecraildsam nfs6 hotwheels2pc protocolwii nsr0405 bfield1942ps2 buccaneer nthunder2003 chocotokiwii bderlandspcd truecrime fear2olam rtrooperpcam hobbitds swgb armada2 tribes2 specialforces sonicdlwii rb6 dfriendsEUds lonposUSwii luminarc2USds mkvsdcEUps3am actofwardam namcotest puffinsds ragonlineKRds whammer40kwaam whammermocam playgrounddsam sawpcam cellfactorpsn whamdowfram wrcps3am bomberman20ds bodarkness Rabgohomewii hitz2004ps2 chaserd gauntletds close5dmo homeworld2b bleach1USds bsmidwaypcam marvlegpspam rockstardev sonicbkwii MSolympicwii boggle arkwarriorsam motogp09pcam DrnWrk(iphon)am warfronttp puzzlernumds chess rnconsole hhbball2001 tpfolEUpc afrikakorps cnpanzers2cwd thawpc jacknick6 starcraftdmo plunderps3am narutorev3wii exigoam crysisspd commandos3 stitans mlb2k11wii sanity actofwar gta4x serioussamsed ejammingpc bomberfunt Majesty2PCd fltsim98 SF3RemiXboxmb unbballswii suparobods cmr4pcd yakumanwii gicombat1 gh4vhalenwii evosoc08USwii svsr11ps3am touchpanicds DaggerdalePCam 4x4evo wlclashpcam necrolcpc spyvsspyps2 gschess ddozenpt gamepopulator frontlinesfow surfsupds ra3 knightsoh dh3 mobileforcesd cuesportswii gamespy2pcam sbkxusps3 wildwings itadakistwii sangotends hd2ss jnglspeedwii anarchyonline marvlegnpsp iwdalehow blurds Superslamhapcam civ4wrldcn necrovisionpdam soa suitelifeds wicb TG09360 ludicrouspcd ellipticpc AliensCMPCam svsr11x360d arkwarriors dinerdashwii mkarmps2 fairyfightspcd quakewarsetb heroes3 wsc2007ps3 kingtigerspcam gettysburg redbaronps3am ufc10x360devam blitzkrieg tomenasawii gticsfestwii keuthendevam casinotourwii etherlords hhbball2000 majestyx cricket2007 sof2demo soad tankbeatds mfightbbueuds starcraft ffowbeta orderofwarpc nfsmwucoverds mkvsdcps3am whammermocbmam AliensCMPS3 ucardgamesds ufc2010iphoneam motogp3d gc2demo aoe2 puyopuyo7ds superv8USpcam callofdutyuo janefightps3am svsr10ps3d sof 50centjpnps3am megamansfds 4x4evodemo newgamename officersgwupcam section8pcam bderlandruspc lanoireps3am worms3 agentps3am rogerwilco rtrooperpc talesofgrawii mohpa supcomm terrortkdwn2am madden08ds janesww2 gsbgammon ufc09ps3 harley3 postal2d rtcwet mclub4ps3dev mcmaniadmo mariokartds ut3jpps3 fearxp2 sbkxps3 tzar painkillerodd maxpayne3ps3 xmenlegps2pals biahhPCHpcam cheuchre bsmidway whammermoc bfield1942swd gsreversi aoemythds scourgeps3 swbf3psp othellowii swempire pitcrewwii wwpuzzlewii ufc10ps3DEVam civ4cham wordjongeuds foreverbwii avp2 ccrenegade yetisportswii hotpaceudps2 lotrbme2r cneagle ascensionpc smrailroadsjp xmenlegps2pal scourgerpcam mohairborne ilrosso facesofwar startrekmac metalfightds fsx arma2pc girlskoreads crysiswars cnpanzers sniperelpc mech4bkexp snackdsi virtualpool3 cusrobousds ffurtdriftps2 actval1 dirtdemo wh40kdow2crol marvlegpsp tankbattlesds aoe2demo gtacwarsds hwbasharena poriginpcd svsr09ps3 whammer40kt mclub2pc theracewii menofwar:as 333networks bbladeds celtickingspu menofvalord scribnautsds mkdeceppalps2 monhunter3wii nba2k11wii dow heistps3am sbk08ps3am saadtest wtrwarfarewii links2004 wicd luchalibrepcam goreAVam jkmots kott2ps2 slugfestps2 psyintdevpcd fifasoc10ds bllrs2004ps2 civ4wrldjp takeda wot ufc2010iphoam echelonwwd eearth3am SF3RemixPS3mb jumpsstars2ds arma2oapcam spbobbleds condemned2bsd civ4coljp jikkyoprowii paraworldd civrevods cardiowrk2wii tcghostreconaw mkvsdcEUps3 dreamchronwii wlclashpcd revolution conduitwii halor fairyfightspc wincircleds fileplanet mdamiiwalkds otonazenshuds projectigi2 ufc10ps3d simsportswii smball2iph damnationps3 beateratorpspam mech3pm swbfront3wii section8x360am kingtigerspcd hawxpc evosoccer08ds rtcwett banburadxds civ2tot maxpayne3x360am wcpool2004pc q3tafull blahtest sbk08pc crystalw1wii halomacd hhball2003 chasspart5 ppkpocket11ds spacepod planetside sparta2pcd cc3tibwarsd scourgerpc reichps3 swbfront3ps3 pokemonplatds cellfactorpc quizmagic2ds ufc09x360am captsubasads namcotestam gscheckers epochwarspcam armygame mohaab laserarenad preystarsds civ4colpcd ballers3ps3d rook rfactoryEUds fsw10hps2 jbond2009ds quakewarsetd fantcubewii fltsim2002 bldragonsdsam starlancer closecomftf wptps2pal unreal blandsonlive idolmasterds aoe suitelifeEUds freessbalpha bbarenaEUps3am test1 luchalibreps3 contractjackd GunMahjongZds tetrisdeluxds TG09PC bfvietnamt dtr medieval2 fsxa contractjackpr superv8ps3 heiseikyods liightwii boyvgirlcwii mclub4xboxdevam blandsjpnps3 capitalism2 sonriders2wii FieldOps cavestorywii ikaropcam tribes2demo hexenworld callofduty4 mogumonwii ee3beta gwgalaxieswii dshardam wcsnkr2005ps2 krissxpc railsamd nrs2003 tdubeta dogsrunamock ellipticpcam bderlandsps3d cadZ2JPwii foxtrotpc redalert3pccd medievalvi parabellumpcdam decasport3wii kororinpa2wii archlord gta4pc nobunagapktds greconawf2 tvshwking2wii civrevasips3d legouniverse ghostrecond bmbermanexdsi mmtestam redalert2exp rdr2x360 arma2oapc cruciform bbarenaEUps3d aow3 and1sballps2am gtacwiphone blitz08ps3 lanoireps3 ut3onlive svsr10ps3 redalert2 topspin4wii nthunder2004d ballers3ps3am fullautops3d surkatamarwii ludicrouspc beateratord gta4ps3am asbball2005ps2 gts4xdevam ssamdemo thps4pcr guitarh3wii pkodgermanam drainworksam stefdemo MLBallstarsds bbarenaEUps3 bderlandsx360 spoilsofwar stella ronb scourgeps3am Nushizurids superv8ncps3d svsr11ps3d demoderby whammer40kbam ww2frontline anno1503b mohaabdm sinpun2NAwii breed metalmax3dsam bderlandruspcd tpfolEUps3 crysisb mtgbgrounds unodsi bf2142e supv8ncusps3am cc3kwcd usingwii xmenlegps2 sbkUSpcd elecrailds bstrikeotspcd stef2 gmtest supcommdemo Asonpartywii buckshotwiiam wptps2 orderofwarpcam mebiuswii dogalo postal2 baldursg Superslamhapc infectedpspam imagineartds everquest2 timeshiftps3 puzzlemojiwii 12irondsam ccgenerals lovegolfwii fairyfightspcam wkingsbd swempiremac famstadiumwii gradiusrbwii tsurimasterds topspinps2am cb2ds civ4ch fairstrike menofwaras wh40kwap tpfolEUpcBam touchmast4dsi marvlegps3p specops xboxtunnel fearcb superv8usps3d s_GalacticBowli mmadnesswii ww2btanks whammermocdam itadakistds ut3pcdam f12002 CMwrldkitwii monopoly3 powprokundsiam birhhps3 bridgebaron14 crashnburnps2 na2rowpcam arc st_ladder machines dstallionds trackmania2ds mkdeceptionps2 blzrdriverds heistpcam civconps3am reichpcam magmay2 tetpartywii ecorisds musicmakerwii bf2ddostest paradisecity medieval section8x360d buccaneerpc bleach2USds yugiohwc08ds SF3RemixXboxam infectedpsp necrolcpcam chessworlds condemned2bsam sonic2010wiiam runedemo blitz08ps3d dominos finertiaps3 winel10jpnwii ufc10x360dev stef1exp svsr11x360am farcry poolshark2ps2 biahhRUSpcam bejeweled2wiiam titanquest bllrs2005ps2 americax atlantispre roguewarps3am saadtestam paintball mxravenpspam guinnesswriphd springwidgets parabellumpc molecontrolpc gta4ps3grm ben10bb avponlive officersgwupc kateifestds svsr11x360dev monracersds bderlandspc eforcesr wiinat narutoex4wii wosin civconps3d dsiege2bw twc2 empiresdam codbigredps2 wofps2 bokujyods leadfootd SampAppTest menofwarpcam riskingdomsd regimentpc kohanagdemo svsr11ps3dev tarlawdartwiiam momo2010wii blandsonliveam fear2ol biahhPCHpc wrcpcd codedarmspspam poriginpcjp conflictsops2 outlawsdem ras wz2100 mafia rafcivatwar firearmsevopcam srsyndps2 mfightbbunads sfc3 gamepopulatoram puyopuyods marveltcardps pbfqmv heroesmanads bfield1942d stlprinKORds wsc2007pc gmtestcdam planecrazy terminator3d tstgme colcourseds racedriver2 biahhPRps3am reichpc stalkercoppcam startopia bderlandruspcam redlinenet octoEUwii foxtrotps3am motogp2d chocmbeuds dragladeEUds themarkam bldragoneudsam dqmonjoker2ds racedriver2ps2 12ironds tqexp1am janesf15 flatout2ps2 mclub4ps3devam beateratorpspd rafcivatwaram whtacticspsp spacepodd civ5 nolf2 jeopardyps2 exitds bldragondds darkheaven hail2chimps3am bomberman2wiid goredemo rscovertops snooker2003 swrcommandod wormsasowii 50ctsndlvps3 svsr11ps3devam ccgenzh SF3PS3PSrv links2000 sbkxps3demoam cc3tibwarscd hail2chimps3 civ4colpcam mech4st mmtest poriginps3am necrolcpcd wrcpc warlordsb plandmajinds worms2 swbfespsp shikagariwii crashnitro claw fuelpcd whammer40000am civ4colpc krissxpcam ekorisu2ds dow_dc powprokundsi wordjongds riseofnations thps6pc hhbball2002 kurikurimixds sonic2010dsam goreAV armada2d damnationpcd redalert3pcd por2 xar etherlordsbeta swinedemo disciples2 hookedJPNwii ut3ps3d starfoxds praetoriansd mkvsdcxboxam fuelps3d scompany bspiritsds whammer40ktds dental2ds dshard rfgrlaonlive postpetds omfbattlecp wormspsp realwarrsd cmanager bsmidwayps2 mschargedwii beijing08ps3 redalert3pcmb smackdn2ps2 mh3uswii monfarm2ds menofwar:nam ascensionpcam cnpanzers2 bandbrosEUds fwarriorpc AliensCMPC superv8pc sbk09pcam scribnaut2dsam sneezieswiiw fushigidunds swordots nameneverds harmoon2kords ut2 painkillerod resevildrkwii RKMvalleyds knelynch2ddol onslaughtpcam beaterator wh40kp fairyfightpc pkodgerman stormrisepcam nhl2k11wii exigoram flatout swgbcc sbk09usps3 topanglerwii redalert3pcdam wotr redalert legendsmmbeta2 tankbeatusds paraworld pariahpcd tacore monopoly kumawar guitarh3xpwii ut3 disciples wsoppspam smackdnps2r bllrs2004pal foxtrotpcd tron20 dimensitypc rsurbanops hookedEUwii SF3RemixXbox poriginps3 civ4xp3 sandbags FlockPCam dhunterps2dis imaginejdds hookedfishwii chocotokids tongaribouids testdriveu wsoppc thps4pcram civ4btsjp scrabbleo worms4 ms2012wii rafcivatwartam spartaaw ikaropc sfc tongaribouidsam cod7dsam timeshiftd ppirates knightsohd rubikguidewii coteagles marvlegps2am genesisrbeta aarmy3 jefftest saturdayns guinnesswripham chhearts ninsake takingdoms agentps3 flatout2pc naruto5ds callofduty4d2d fairstriked wcsnkr2004ps2 assaultheroesam raymanRR3wii whamdowfr aow2d bldragondfdsam anno1503 ecocreatureds sbkxps3am avp austerlitz AssaultHeroesD2 fswps2 witcher fltsim2k ludicrouspcam demonstar dh5 menofwarnamam legendarypcd echelonww sporeds bf1942swmac cmr5ps2 worms4d luchalibreps3am yugiohwc11ds segatennisps3 dkracingds combatzonepcam aowdemo mmadnessexps3 ubraingamesds bldragoneuds civ4ru incomingforces kaiwanowads swbfrontps2 hastpaint2wii yugiohwc10ds reichps3am srsyndpc ironstrategy thetsuriwii ryantest racedriverds xmenlegpc kingbeetlesds xcomenforcer DaggerdalePS3am 3dpicrossds nwnmac bbarenaJPNps3am gtacwiphoneam mparty1ds mdungeonds tatvscapwii arma2pcd buccaneerpcam redalert3ps3am na2runpcam wsc2007 mfightbbultds mvsdk25ds mariokartwii luchalibreps3d arma2oapcd segaracingds eearth3d swrcommandot mswinterds assultwii Doragureidods rontpam kurikinds zsteel necrovisionpc locksquestds hoopworldwii sneeziesdswam rtrooperps2 exciteracewii onslaughtpcd mech4bwexpd comrade vanguardbeta punchoutwii hd2 anno1701d gore opflash cc3kw eearth2d thdhilljamds batmanaa2ps3am gmtestam whammer40kdc perimeterd dh2004d superv8ncps3 simsportsds mariokartkods mysimsflyEUds spartand unavailable ffccechods slavezero owar smrailroadsjpam rallychampx vietnamsod bbangminids stlprincessds ducatimotods trivialppcuk swempirexp1 sawps3 greconawf2g ssoldierrwii bomberman2wii luchalibrewiiam nexttetris wmarkofchaosd rockstardevam bldragonidsam furaishi3wii bcommander painkiller momotaro20ds dh2005d gored ee3 wic playgroundds wcpokerps2 disneydev motogp2007am connect4 dsiege2 fsw10hpc sfc3dv demonforgeps3 homm4 fuelpcam AliensCMPS3am dynamiczanwii mxun05ps2 th2003d laststorywii civ4wrldmacam mphearts dday Happinuds armygamemac tgstadiumwii krabbitpcmacam woford mohaas twc mafia2pcam kaosmpr hinterland menofwarpcbam civ3ptw hookagainwii bfield2xp1 stalkercoppcd ssmahjongwii skateitds sfc2opdv mxvatvuPALps2 laserarena ffwcbeta blindpointpc halflife AmMcGeeGrimm02 redfactionwii damnationpc wsopps2 DaggerdalePC plunderpcam warriorkings soldiersww2 strongholdce heroeswii cellfacttwpc mezasetm2wii bstrikeotspc ut3demo strongholdd Frogger mariokartdsam smackdnps2pal mclub4xbox conflictzone noahprods dundefndpc insane mlb2k9ds southpark harmoon2kordsam svsr11x360devam chaser strategistpsnd breedd ultimateMKds contactusds druidking cmr4pc stalinsub eearth3bam TerroristT2 bioshock pokerangerds crysis merchant2 bf2142b uno armymen2 fairyfightpcam igowii SF3RemixPS3 gsiphonefw empireearth eternalforces altitude botbattles afllive05ps2 luckystar2ds sbkxpc luminarc2EUds moritashogids srow2xb360 eearth3 shirends2ds ghostreconds celebdm fifasoc11ds virtuaten4wii airwingsds gruntz legendarypcam batmanaa2ps3 WSWelevenwii flatoutps2 mrwtour castles dundefndpcam warnbriads callofdutyps2d tpfolEUpcd gcracing fullmatcgds poolshark2pc nwnlinux lotrbme2wk eq fxtrainlvds ikaropcd warlordsdr civ4bts potco janesfa legofwrex360 hotpacificps2 strongholdl nba2k10wii terminator3 fairyfightps3am heroes3arm godzilla2ps2 excessive digichampUSds lbookofbigsds mleatingwii amairtac sfc2op feard coteaglesam paraworldam oltps2 stalkercsd bestfriendds menofwar:namam tarlawdartwii ejammingmac kohandemo hooploopwii rfts svsr10x360d bangler2003 virtuaten4wiiam krabbitpcmacd mkvsdcEUps3b gotcha |; + return qw| ut unreal serioussamse serioussam bfield1942 rune postal2 descent3 sofretail deusex heretic2 ut2004 turok2 333networks wot ironstorm mohaas mohaab legendsmm f1comp nerfarena sfc3dv blood2 mohaa iwar2 |; } 1; |
