From cc84f739cad9c45d85f1b3048a3294612c4fac49 Mon Sep 17 00:00:00 2001 From: payet_s Date: Thu, 30 Jul 2015 20:29:47 +0200 Subject: [PATCH 01/21] Upgraded: Visual Studio solution Changed: module comment style --- admin_page_server.py | 36 ++++++++------- dwc_network_server_emulator.pyproj | 13 ++++-- dwc_network_server_emulator.sln | 7 +-- gamespy/gs_database.py | 36 ++++++++------- gamespy/gs_query.py | 36 ++++++++------- gamespy/gs_utility.py | 38 ++++++++-------- gamespy_backend_server.py | 72 +++++++++++++++--------------- gamespy_gamestats_server.py | 38 ++++++++-------- gamespy_natneg_server.py | 40 +++++++++-------- gamespy_player_search_server.py | 34 +++++++------- gamespy_profile_server.py | 38 ++++++++-------- gamespy_qr_server.py | 40 +++++++++-------- gamespy_server_browser_server.py | 40 +++++++++-------- gamestats_server_http.py | 38 ++++++++-------- internal_stats_server.py | 34 +++++++------- master_server.py | 38 ++++++++-------- nas_server.py | 38 ++++++++-------- other/utils.py | 34 +++++++------- storage_server.py | 36 ++++++++------- tools/import_wiimm_data.py | 12 ++--- 20 files changed, 371 insertions(+), 327 deletions(-) diff --git a/admin_page_server.py b/admin_page_server.py index decf0c5..ff8aa95 100644 --- a/admin_page_server.py +++ b/admin_page_server.py @@ -1,20 +1,22 @@ -# DWC Network Server Emulator -# Copyright (C) 2014 SMTDDR -# Copyright (C) 2014 kyle95wm -# Copyright (C) 2014 AdmiralCurtiss -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU Affero General Public License as -# published by the Free Software Foundation, either version 3 of the -# License, or (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU Affero General Public License for more details. -# -# You should have received a copy of the GNU Affero General Public License -# along with this program. If not, see . +"""DWC Network Server Emulator + + Copyright (C) 2014 SMTDDR + Copyright (C) 2014 kyle95wm + Copyright (C) 2014 AdmiralCurtiss + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU Affero General Public License as + published by the Free Software Foundation, either version 3 of the + License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License + along with this program. If not, see . +""" from twisted.web import server, resource from twisted.internet import reactor diff --git a/dwc_network_server_emulator.pyproj b/dwc_network_server_emulator.pyproj index 2c970c9..12d0a08 100644 --- a/dwc_network_server_emulator.pyproj +++ b/dwc_network_server_emulator.pyproj @@ -1,5 +1,5 @@ - - + + Debug 2.0 @@ -24,7 +24,12 @@ true false + + 10.0 + $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)\Python Tools\Microsoft.PythonTools.targets + + @@ -32,6 +37,7 @@ + @@ -56,5 +62,6 @@ - + + \ No newline at end of file diff --git a/dwc_network_server_emulator.sln b/dwc_network_server_emulator.sln index f35768c..9480861 100644 --- a/dwc_network_server_emulator.sln +++ b/dwc_network_server_emulator.sln @@ -1,6 +1,7 @@ - -Microsoft Visual Studio Solution File, Format Version 11.00 -# Visual Studio 2010 +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio 14 +VisualStudioVersion = 14.0.23107.0 +MinimumVisualStudioVersion = 10.0.40219.1 Project("{888888A0-9F3D-457C-B088-3A5042F75D52}") = "dwc_network_server_emulator", "dwc_network_server_emulator.pyproj", "{1A9E8A26-6CEA-4E78-835E-B043356CE360}" EndProject Global diff --git a/gamespy/gs_database.py b/gamespy/gs_database.py index 46a6ce9..cb39abb 100644 --- a/gamespy/gs_database.py +++ b/gamespy/gs_database.py @@ -1,20 +1,22 @@ -# DWC Network Server Emulator -# Copyright (C) 2014 polaris- -# Copyright (C) 2014 ToadKing -# Copyright (C) 2014 AdmiralCurtiss -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU Affero General Public License as -# published by the Free Software Foundation, either version 3 of the -# License, or (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU Affero General Public License for more details. -# -# You should have received a copy of the GNU Affero General Public License -# along with this program. If not, see . +"""DWC Network Server Emulator + + Copyright (C) 2014 polaris- + Copyright (C) 2014 ToadKing + Copyright (C) 2014 AdmiralCurtiss + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU Affero General Public License as + published by the Free Software Foundation, either version 3 of the + License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License + along with this program. If not, see . +""" import sqlite3 import hashlib diff --git a/gamespy/gs_query.py b/gamespy/gs_query.py index 885af5f..9abec2c 100644 --- a/gamespy/gs_query.py +++ b/gamespy/gs_query.py @@ -1,20 +1,22 @@ -# DWC Network Server Emulator -# Copyright (C) 2014 polaris- -# Copyright (C) 2014 ToadKing -# Copyright (C) 2014 AdmiralCurtiss -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU Affero General Public License as -# published by the Free Software Foundation, either version 3 of the -# License, or (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU Affero General Public License for more details. -# -# You should have received a copy of the GNU Affero General Public License -# along with this program. If not, see . +"""DWC Network Server Emulator + + Copyright (C) 2014 polaris- + Copyright (C) 2014 ToadKing + Copyright (C) 2014 AdmiralCurtiss + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU Affero General Public License as + published by the Free Software Foundation, either version 3 of the + License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License + along with this program. If not, see . +""" import copy diff --git a/gamespy/gs_utility.py b/gamespy/gs_utility.py index d7b91f3..f3e6732 100644 --- a/gamespy/gs_utility.py +++ b/gamespy/gs_utility.py @@ -1,21 +1,23 @@ -# DWC Network Server Emulator -# Copyright (C) 2014 polaris- -# Copyright (C) 2014 ToadKing -# Copyright (C) 2014 AdmiralCurtiss -# Copyright (C) 2014 msoucy -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU Affero General Public License as -# published by the Free Software Foundation, either version 3 of the -# License, or (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU Affero General Public License for more details. -# -# You should have received a copy of the GNU Affero General Public License -# along with this program. If not, see . +"""DWC Network Server Emulator + + Copyright (C) 2014 polaris- + Copyright (C) 2014 ToadKing + Copyright (C) 2014 AdmiralCurtiss + Copyright (C) 2014 msoucy + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU Affero General Public License as + published by the Free Software Foundation, either version 3 of the + License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License + along with this program. If not, see . +""" import base64 import hashlib diff --git a/gamespy_backend_server.py b/gamespy_backend_server.py index 7eae43c..6aa4c77 100644 --- a/gamespy_backend_server.py +++ b/gamespy_backend_server.py @@ -1,38 +1,40 @@ -# DWC Network Server Emulator -# Copyright (C) 2014 polaris- -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU Affero General Public License as -# published by the Free Software Foundation, either version 3 of the -# License, or (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU Affero General Public License for more details. -# -# You should have received a copy of the GNU Affero General Public License -# along with this program. If not, see . -# -# Master server list server -# -# Basic idea: -# The server listing does not need to be persistent, and it must be easily searchable for any unknown parameters. -# So instead of using a SQL database, I've opted to create a server list database server which communicates between the -# server browser and the qr server. The server list database will be stored in dictionaries as to allow dynamic columns -# that can be easily searched. The main reason for this configuration is because it cannot be guaranteed what data -# a game's server will required. For example, in addition to the common fields such as publicip, numplayers, dwc_pid, etc, -# Lost Magic also uses fields such as LMname, LMsecN, LMrating, LMbtmode, and LMversion. -# -# It would be possible to create game-specific databases but this would be more of a hassle and less universal. It would -# also be possible pickle a dictionary containing all of the fields and store it in a SQL database instead, but that -# would require unpickling every server each time you want to match search queries which would cause overhead if there -# are a lot of running servers. One trade off here is that we'll be using more memory by storing each server as a -# dictionary in the memory instead of storing it in a SQL database. -# -# qr_server and server_browser both will act as clients to gs_server_database. -# qr_server will send an add and/or delete to add or remove servers from the server list. -# server_browser will send a request with the game name followed by optional search parameters to get a list of servers. +"""DWC Network Server Emulator + + Copyright (C) 2014 polaris- + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU Affero General Public License as + published by the Free Software Foundation, either version 3 of the + License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License + along with this program. If not, see . + + Master server list server + + Basic idea: + The server listing does not need to be persistent, and it must be easily searchable for any unknown parameters. + So instead of using a SQL database, I've opted to create a server list database server which communicates between the + server browser and the qr server. The server list database will be stored in dictionaries as to allow dynamic columns + that can be easily searched. The main reason for this configuration is because it cannot be guaranteed what data + a game's server will required. For example, in addition to the common fields such as publicip, numplayers, dwc_pid, etc, + Lost Magic also uses fields such as LMname, LMsecN, LMrating, LMbtmode, and LMversion. + + It would be possible to create game-specific databases but this would be more of a hassle and less universal. It would + also be possible pickle a dictionary containing all of the fields and store it in a SQL database instead, but that + would require unpickling every server each time you want to match search queries which would cause overhead if there + are a lot of running servers. One trade off here is that we'll be using more memory by storing each server as a + dictionary in the memory instead of storing it in a SQL database. + + qr_server and server_browser both will act as clients to gs_server_database. + qr_server will send an add and/or delete to add or remove servers from the server list. + server_browser will send a request with the game name followed by optional search parameters to get a list of servers. +""" import logging import time diff --git a/gamespy_gamestats_server.py b/gamespy_gamestats_server.py index 4f534be..2981a3c 100644 --- a/gamespy_gamestats_server.py +++ b/gamespy_gamestats_server.py @@ -1,21 +1,23 @@ -# DWC Network Server Emulator -# Copyright (C) 2014 polaris- -# Copyright (C) 2014 ToadKing -# Copyright (C) 2014 AdmiralCurtiss -# Copyright (C) 2014 msoucy -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU Affero General Public License as -# published by the Free Software Foundation, either version 3 of the -# License, or (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU Affero General Public License for more details. -# -# You should have received a copy of the GNU Affero General Public License -# along with this program. If not, see . +"""DWC Network Server Emulator + + Copyright (C) 2014 polaris- + Copyright (C) 2014 ToadKing + Copyright (C) 2014 AdmiralCurtiss + Copyright (C) 2014 msoucy + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU Affero General Public License as + published by the Free Software Foundation, either version 3 of the + License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License + along with this program. If not, see . +""" import logging import time diff --git a/gamespy_natneg_server.py b/gamespy_natneg_server.py index bd3ddb2..1165c20 100644 --- a/gamespy_natneg_server.py +++ b/gamespy_natneg_server.py @@ -1,22 +1,24 @@ -# DWC Network Server Emulator -# Copyright (C) 2014 polaris- -# Copyright (C) 2014 ToadKing -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU Affero General Public License as -# published by the Free Software Foundation, either version 3 of the -# License, or (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU Affero General Public License for more details. -# -# You should have received a copy of the GNU Affero General Public License -# along with this program. If not, see . -# -# Server emulator for *.available.gs.nintendowifi.net and *.master.gs.nintendowifi.net -# Query and Reporting: http://docs.poweredbygamespy.com/wiki/Query_and_Reporting_Overview +"""DWC Network Server Emulator + + Copyright (C) 2014 polaris- + Copyright (C) 2014 ToadKing + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU Affero General Public License as + published by the Free Software Foundation, either version 3 of the + License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License + along with this program. If not, see . + + Server emulator for *.available.gs.nintendowifi.net and *.master.gs.nintendowifi.net + Query and Reporting: http://docs.poweredbygamespy.com/wiki/Query_and_Reporting_Overview +""" import logging import socket diff --git a/gamespy_player_search_server.py b/gamespy_player_search_server.py index b95e362..f15057c 100644 --- a/gamespy_player_search_server.py +++ b/gamespy_player_search_server.py @@ -1,19 +1,21 @@ -# DWC Network Server Emulator -# Copyright (C) 2014 polaris- -# Copyright (C) 2014 msoucy -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU Affero General Public License as -# published by the Free Software Foundation, either version 3 of the -# License, or (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU Affero General Public License for more details. -# -# You should have received a copy of the GNU Affero General Public License -# along with this program. If not, see . +"""DWC Network Server Emulator + + Copyright (C) 2014 polaris- + Copyright (C) 2014 msoucy + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU Affero General Public License as + published by the Free Software Foundation, either version 3 of the + License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License + along with this program. If not, see . +""" import logging import traceback diff --git a/gamespy_profile_server.py b/gamespy_profile_server.py index b6dd2b6..c3a9b10 100644 --- a/gamespy_profile_server.py +++ b/gamespy_profile_server.py @@ -1,21 +1,23 @@ -# DWC Network Server Emulator -# Copyright (C) 2014 polaris- -# Copyright (C) 2014 ToadKing -# Copyright (C) 2014 AdmiralCurtiss -# Copyright (C) 2014 msoucy -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU Affero General Public License as -# published by the Free Software Foundation, either version 3 of the -# License, or (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU Affero General Public License for more details. -# -# You should have received a copy of the GNU Affero General Public License -# along with this program. If not, see . +"""DWC Network Server Emulator + + Copyright (C) 2014 polaris- + Copyright (C) 2014 ToadKing + Copyright (C) 2014 AdmiralCurtiss + Copyright (C) 2014 msoucy + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU Affero General Public License as + published by the Free Software Foundation, either version 3 of the + License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License + along with this program. If not, see . +""" import logging import time diff --git a/gamespy_qr_server.py b/gamespy_qr_server.py index 7aeb3d9..8f5addc 100644 --- a/gamespy_qr_server.py +++ b/gamespy_qr_server.py @@ -1,23 +1,25 @@ -# DWC Network Server Emulator -# Copyright (C) 2014 polaris- -# Copyright (C) 2014 ToadKing -# Copyright (C) 2014 AdmiralCurtiss -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU Affero General Public License as -# published by the Free Software Foundation, either version 3 of the -# License, or (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU Affero General Public License for more details. -# -# You should have received a copy of the GNU Affero General Public License -# along with this program. If not, see . +"""DWC Network Server Emulator -# Server emulator for *.available.gs.nintendowifi.net and *.master.gs.nintendowifi.net -# Query and Reporting: http://docs.poweredbygamespy.com/wiki/Query_and_Reporting_Overview + Copyright (C) 2014 polaris- + Copyright (C) 2014 ToadKing + Copyright (C) 2014 AdmiralCurtiss + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU Affero General Public License as + published by the Free Software Foundation, either version 3 of the + License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License + along with this program. If not, see . + + Server emulator for *.available.gs.nintendowifi.net and *.master.gs.nintendowifi.net + Query and Reporting: http://docs.poweredbygamespy.com/wiki/Query_and_Reporting_Overview +""" import logging import select diff --git a/gamespy_server_browser_server.py b/gamespy_server_browser_server.py index 11849d2..3392c42 100644 --- a/gamespy_server_browser_server.py +++ b/gamespy_server_browser_server.py @@ -1,22 +1,24 @@ -# DWC Network Server Emulator -# Copyright (C) 2014 polaris- -# Copyright (C) 2014 msoucy -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU Affero General Public License as -# published by the Free Software Foundation, either version 3 of the -# License, or (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU Affero General Public License for more details. -# -# You should have received a copy of the GNU Affero General Public License -# along with this program. If not, see . -# -# I found an open source implemention of this exact server I'm trying to emulate here: https://github.com/sfcspanky/Openspy-Core/blob/master/serverbrowsing/ -# Use as reference later. +"""DWC Network Server Emulator + + Copyright (C) 2014 polaris- + Copyright (C) 2014 msoucy + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU Affero General Public License as + published by the Free Software Foundation, either version 3 of the + License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License + along with this program. If not, see . + + I found an open source implemention of this exact server I'm trying to emulate here: https://github.com/sfcspanky/Openspy-Core/blob/master/serverbrowsing/ + Use as reference later. +""" import logging import socket diff --git a/gamestats_server_http.py b/gamestats_server_http.py index cfb63ed..e5b42d3 100644 --- a/gamestats_server_http.py +++ b/gamestats_server_http.py @@ -1,21 +1,23 @@ -# DWC Network Server Emulator -# Copyright (C) 2014 polaris- -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU Affero General Public License as -# published by the Free Software Foundation, either version 3 of the -# License, or (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU Affero General Public License for more details. -# -# You should have received a copy of the GNU Affero General Public License -# along with this program. If not, see . -# -# TODO: Seperate gamestats.gs.nintendowifi.net and gamestats2.gs.nintendowifi.net -# TODO: Move gamestats list to database? +"""DWC Network Server Emulator + + Copyright (C) 2014 polaris- + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU Affero General Public License as + published by the Free Software Foundation, either version 3 of the + License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License + along with this program. If not, see . + + TODO: Seperate gamestats.gs.nintendowifi.net and gamestats2.gs.nintendowifi.net + TODO: Move gamestats list to database? +""" import logging import urlparse diff --git a/internal_stats_server.py b/internal_stats_server.py index 369cc82..5d5b6c7 100644 --- a/internal_stats_server.py +++ b/internal_stats_server.py @@ -1,19 +1,21 @@ -# DWC Network Server Emulator -# Copyright (C) 2014 polaris- -# Copyright (C) 2014 msoucy -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU Affero General Public License as -# published by the Free Software Foundation, either version 3 of the -# License, or (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU Affero General Public License for more details. -# -# You should have received a copy of the GNU Affero General Public License -# along with this program. If not, see . +"""DWC Network Server Emulator + + Copyright (C) 2014 polaris- + Copyright (C) 2014 msoucy + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU Affero General Public License as + published by the Free Software Foundation, either version 3 of the + License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License + along with this program. If not, see . +""" from twisted.web import server, resource from twisted.internet import reactor diff --git a/master_server.py b/master_server.py index 81023dc..7096b78 100644 --- a/master_server.py +++ b/master_server.py @@ -1,21 +1,23 @@ -# DWC Network Server Emulator -# Copyright (C) 2014 polaris- -# Copyright (C) 2014 ToadKing -# Copyright (C) 2014 AdmiralCurtiss -# Copyright (C) 2014 msoucy -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU Affero General Public License as -# published by the Free Software Foundation, either version 3 of the -# License, or (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU Affero General Public License for more details. -# -# You should have received a copy of the GNU Affero General Public License -# along with this program. If not, see . +"""DWC Network Server Emulator + + Copyright (C) 2014 polaris- + Copyright (C) 2014 ToadKing + Copyright (C) 2014 AdmiralCurtiss + Copyright (C) 2014 msoucy + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU Affero General Public License as + published by the Free Software Foundation, either version 3 of the + License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License + along with this program. If not, see . +""" from gamespy_player_search_server import GameSpyPlayerSearchServer from gamespy_profile_server import GameSpyProfileServer diff --git a/nas_server.py b/nas_server.py index fdb1809..9227c40 100644 --- a/nas_server.py +++ b/nas_server.py @@ -1,21 +1,23 @@ -# DWC Network Server Emulator -# Copyright (C) 2014 polaris- -# Copyright (C) 2014 ToadKing -# Copyright (C) 2014 AdmiralCurtiss -# Copyright (C) 2014 msoucy -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU Affero General Public License as -# published by the Free Software Foundation, either version 3 of the -# License, or (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU Affero General Public License for more details. -# -# You should have received a copy of the GNU Affero General Public License -# along with this program. If not, see . +"""DWC Network Server Emulator + + Copyright (C) 2014 polaris- + Copyright (C) 2014 ToadKing + Copyright (C) 2014 AdmiralCurtiss + Copyright (C) 2014 msoucy + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU Affero General Public License as + published by the Free Software Foundation, either version 3 of the + License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License + along with this program. If not, see . +""" import base64 import json diff --git a/other/utils.py b/other/utils.py index f191b20..51f7cfc 100644 --- a/other/utils.py +++ b/other/utils.py @@ -1,19 +1,21 @@ -# DWC Network Server Emulator -# Copyright (C) 2014 polaris- -# Copyright (C) 2014 msoucy -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU Affero General Public License as -# published by the Free Software Foundation, either version 3 of the -# License, or (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU Affero General Public License for more details. -# -# You should have received a copy of the GNU Affero General Public License -# along with this program. If not, see . +"""DWC Network Server Emulator + + Copyright (C) 2014 polaris- + Copyright (C) 2014 msoucy + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU Affero General Public License as + published by the Free Software Foundation, either version 3 of the + License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License + along with this program. If not, see . +""" import logging import logging.handlers diff --git a/storage_server.py b/storage_server.py index 63c0ca0..1651c06 100644 --- a/storage_server.py +++ b/storage_server.py @@ -1,20 +1,22 @@ -# DWC Network Server Emulator -# Copyright (C) 2014 polaris- -# Copyright (C) 2014 AdmiralCurtiss -# Copyright (C) 2014 msoucy -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU Affero General Public License as -# published by the Free Software Foundation, either version 3 of the -# License, or (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU Affero General Public License for more details. -# -# You should have received a copy of the GNU Affero General Public License -# along with this program. If not, see . +"""DWC Network Server Emulator + + Copyright (C) 2014 polaris- + Copyright (C) 2014 AdmiralCurtiss + Copyright (C) 2014 msoucy + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU Affero General Public License as + published by the Free Software Foundation, either version 3 of the + License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License + along with this program. If not, see . +""" import os import random diff --git a/tools/import_wiimm_data.py b/tools/import_wiimm_data.py index 36cf49c..78f2221 100644 --- a/tools/import_wiimm_data.py +++ b/tools/import_wiimm_data.py @@ -1,8 +1,10 @@ -# Import the profile and friend data collected from the GameSpy servers by Wiimm -# Put all *-nick and *-fc files to be imported into t into the data folder and then run this program to import them -# into the database. -# -# This may take some time to import all of the data on bigger files if you check if each entry is already in the database. +"""Import the profile and friend data collected from the GameSpy servers by Wiimm + +Put all *-nick and *-fc files to be imported into t into the data folder and then run this program to import them +into the database. + +This may take some time to import all of the data on bigger files if you check if each entry is already in the database. +""" import glob import sys From 2ba65186693c7156c27b8adeb3b09a93fa886a8b Mon Sep 17 00:00:00 2001 From: payet_s Date: Fri, 31 Jul 2015 14:27:00 +0200 Subject: [PATCH 02/21] Cleaned: master_server.py, nas_server.py Massive cleanup Removed: Anoying UTF-8 BOM --- admin_page_server.py | 379 +++++++++++++++++++------------ gamespy/gs_database.py | 24 +- gamespy/gs_query.py | 7 +- gamespy/gs_utility.py | 5 +- gamespy_backend_server.py | 15 +- gamespy_gamestats_server.py | 13 +- gamespy_natneg_server.py | 21 +- gamespy_player_search_server.py | 5 +- gamespy_profile_server.py | 49 ++-- gamespy_qr_server.py | 13 +- gamespy_server_browser_server.py | 23 +- gamestats_server_http.py | 2 +- internal_stats_server.py | 7 +- master_server.py | 14 +- nas_server.py | 278 ++++++++++++++++------- tools/import_wiimm_data.py | 2 +- 16 files changed, 543 insertions(+), 314 deletions(-) diff --git a/admin_page_server.py b/admin_page_server.py index ff8aa95..6338531 100644 --- a/admin_page_server.py +++ b/admin_page_server.py @@ -3,6 +3,7 @@ Copyright (C) 2014 SMTDDR Copyright (C) 2014 kyle95wm Copyright (C) 2014 AdmiralCurtiss + Copyright (C) 2015 Sepalani This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as @@ -22,26 +23,24 @@ from twisted.web import server, resource from twisted.internet import reactor from twisted.internet.error import ReactorAlreadyRunning import base64 -import codecs +import codecs import sqlite3 import collections import json -import time -import datetime import os.path import logging import other.utils as utils -import gamespy import gamespy.gs_utility as gs_utils logger_output_to_console = True logger_output_to_file = True logger_name = "AdminPage" logger_filename = "admin_page.log" -logger = utils.create_logger(logger_name, logger_filename, -1, logger_output_to_console, logger_output_to_file) +logger = utils.create_logger(logger_name, logger_filename, -1, + logger_output_to_console, logger_output_to_file) -#Example of adminpageconf.json +# Example of adminpageconf.json # # {"username":"admin","password":"opensesame"} # @@ -55,46 +54,51 @@ admin_password = None if os.path.exists('adminpageconf.json'): try: adminpageconf = json.loads(file('adminpageconf.json').read().strip()) - admin_username = str(adminpageconf['username']) - admin_password = str(adminpageconf['password']) + admin_username = str(adminpageconf['username']) + admin_password = str(adminpageconf['password']) except Exception as e: - logger.log(logging.WARNING, "Couldn't read adminpageconf.json. Admin page will not be available.") + logger.log(logging.WARNING, + "Couldn't read adminpageconf.json. " + "Admin page will not be available.") logger.log(logging.WARNING, str(e)) adminpageconf = None admin_username = None admin_password = None else: - logger.log(logging.INFO, "adminpageconf.json not found. Admin page will not be available.") - + logger.log(logging.INFO, + "adminpageconf.json not found. " + "Admin page will not be available.") + class AdminPage(resource.Resource): isLeaf = True - def __init__(self,adminpage): + def __init__(self, adminpage): self.adminpage = adminpage - def get_header(self, title = None): + def get_header(self, title=None): if not title: title = 'AltWfc Admin Page' - s = ( - '' - '' - '' + title + '' - '' - '' - '

' - 'All Users | ' - 'Consoles | ' - 'Active Bans ' - '

' - ) + s = """ + + + %s + + +

+ %s | %s | %s +

+ """ % (title, + 'All Users', + 'Consoles', + 'Active Bans') return s - + def get_footer(self): - s = ( - '' - '' - ) + s = """ + + + """ return s def is_authorized(self, request): @@ -103,15 +107,19 @@ class AdminPage(resource.Resource): error_message = "Authorization required!" address = request.getClientIP() try: - expected_auth = base64.encodestring(admin_username+":"+admin_password).strip() - actual_auth = request.getAllHeaders()['authorization'].replace("Basic ","").strip() + expected_auth = base64.encodestring( + admin_username + ":" + admin_password + ).strip() + actual_auth = request.getAllHeaders()['authorization'] \ + .replace("Basic ", "") \ + .strip() if actual_auth == expected_auth: - logger.log(logging.INFO,address+" Auth Success") + logger.log(logging.INFO, "%s Auth Success", address) is_auth = True - except Exception,e: - logger.log(logging.INFO,address+" Auth Error: "+str(e)) + except Exception as e: + logger.log(logging.INFO, "%s Auth Error: %s", address, str(e)) if not is_auth: - logger.log(logging.INFO,address+" Auth Failure") + logger.log(logging.INFO, "%s Auth Failure", address) request.setResponseCode(response_code) request.setHeader('WWW-Authenticate', 'Basic realm="ALTWFC"') request.write(error_message) @@ -123,33 +131,43 @@ class AdminPage(resource.Resource): gameid = request.args['gameid'][0].upper().strip() ipaddr = request.args['ipaddr'][0].strip() actiontype = request.args['action'][0] - if not gameid.isalnum(): + if not gameid.isalnum(): request.setResponseCode(500) - logger.log(logging.INFO,address+" Bad data "+gameid+" "+ipaddr) + logger.log(logging.INFO, + "%s Bad data %s %s", + address, gameid, ipaddr) return "Bad data" - - # this strips the region identifier from game IDs, not sure if this actually always accurate but limited testing suggests it is + + # This strips the region identifier from game IDs, not sure if this + # actually always accurate but limited testing suggests it is if len(gameid) > 3: gameid = gameid[:-1] - + if actiontype == 'ban': - dbconn.cursor().execute('insert into banned values(?,?)',(gameid,ipaddr)) - responsedata = "Added gameid=%s, ipaddr=%s" % (gameid,ipaddr) + dbconn.cursor().execute( + 'INSERT INTO banned VALUES(?,?)', + (gameid, ipaddr) + ) + responsedata = "Added gameid=%s, ipaddr=%s" % (gameid, ipaddr) else: - dbconn.cursor().execute('delete from banned where gameid=? and ipaddr=?',(gameid,ipaddr)) - responsedata = "Removed gameid=%s, ipaddr=%s" % (gameid,ipaddr) + dbconn.cursor().execute( + 'DELETE FROM banned WHERE gameid=? AND ipaddr=?', + (gameid, ipaddr) + ) + responsedata = "Removed gameid=%s, ipaddr=%s" % (gameid, ipaddr) dbconn.commit() dbconn.close() - logger.log(logging.INFO,address+" "+responsedata) + logger.log(logging.INFO, "%s %s", address, responsedata) request.setHeader("Content-Type", "text/html; charset=utf-8") - + referer = request.getHeader('referer') if not referer: referer = "/banhammer" request.setHeader("Location", referer) - + request.setResponseCode(303) return responsedata + def update_consolelist(self, request): address = request.getClientIP() dbconn = sqlite3.connect('gpcm.db') @@ -157,22 +175,37 @@ class AdminPage(resource.Resource): actiontype = request.args['action'][0] if not macadr.isalnum(): request.setResponseCode(500) - logger.log(logging.INFO,address+" Bad data "+macadr+" ") + logger.log(logging.INFO, "%s Bad data %s", address, macadr) return "Bad data" if actiontype == 'add': - dbconn.cursor().execute('insert into pending values(?)',(macadr,)) - dbconn.cursor().execute('insert into registered values(?)',(macadr,)) + dbconn.cursor().execute( + 'INSERT INTO pending VALUES(?)', + (macadr,) + ) + dbconn.cursor().execute( + 'INSERT INTO registered VALUES(?)', + (macadr,) + ) responsedata = "Added macadr=%s" % (macadr) elif actiontype == 'activate': - dbconn.cursor().execute('insert into registered values(?)',(macadr,)) + dbconn.cursor().execute( + 'INSERT INTO registered VALUES(?)', + (macadr,) + ) responsedata = "Activated console belonging to %s" % (macadr) else: - dbconn.cursor().execute('delete from pending where macadr=?',(macadr,)) - dbconn.cursor().execute('delete from registered where macadr=?',(macadr,)) + dbconn.cursor().execute( + 'DELETE FROM pending WHERE macadr=?', + (macadr,) + ) + dbconn.cursor().execute( + 'DELETE FROM registered WHERE macadr=?', + (macadr,) + ) responsedata = "Removed macadr=%s" % (macadr) dbconn.commit() dbconn.close() - logger.log(logging.INFO,address+" "+responsedata) + logger.log(logging.INFO, "%s %s", address, responsedata) request.setHeader("Content-Type", "text/html; charset=utf-8") request.setHeader("Location", "/consoles") referer = request.getHeader('referer') @@ -187,55 +220,76 @@ class AdminPage(resource.Resource): def render_banlist(self, request): address = request.getClientIP() dbconn = sqlite3.connect('gpcm.db') - logger.log(logging.INFO,address+" Viewed banlist") - responsedata = ("" - '[CLICK HERE TO LOG OUT]' - "" - "\r\n") - for row in dbconn.cursor().execute("select * from banned"): + logger.log(logging.INFO, "%s Viewed banlist", address) + responsedata = """ + [CLICK HERE TO LOG OUT] +
gameidipAddr
+ + + + """ % (request.getHeader('host')) + + for row in dbconn.cursor().execute("SELECT * FROM banned"): gameid = str(row[0]) ipaddr = str(row[1]) - responsedata += ("" - "\r\n") - responsedata += "
gameidipAddr
"+gameid+""+ipaddr+"
" - "" - "" - "\r\n" - "
" + # TODO: Use .format()/positional arguments + responsedata += """ + + %s + %s + +
+ + + + +
+ + """ % (gameid, ipaddr, gameid, ipaddr) + + responsedata += "" dbconn.close() request.setHeader("Content-Type", "text/html; charset=utf-8") return responsedata - + def render_not_available(self, request): request.setResponseCode(403) request.setHeader('WWW-Authenticate', 'Basic realm="ALTWFC"') request.write('No admin credentials set. Admin page is not available.') - + def render_blacklist(self, request): - sqlstatement = ('' - 'select users.profileid,enabled,data,users.gameid,console,users.userid ' - 'from nas_logins ' - 'inner join users ' - 'on users.userid = nas_logins.userid ' - 'inner join ( ' - ' select max(profileid) newestpid, userid, gameid, devname ' - ' from users ' - ' group by userid,gameid) ' - 'ij on ij.userid = users.userid and ' - 'users.profileid = ij.newestpid ' - 'order by users.gameid ' - '') + sqlstatement = """ + SELECT users.profileid, enabled, data, users.gameid, console, + users.userid + FROM nas_logins + INNER JOIN users + ON users.userid = nas_logins.userid + INNER JOIN ( + SELECT max(profileid) newestpid, userid, gameid, devname + FROM users ' + GROUP BY userid, gameid + ) ij + ON ij.userid = users.userid + AND users.profileid = ij.newestpid + ORDER BY users.gameid""" dbconn = sqlite3.connect('gpcm.db') banned_list = [] for row in dbconn.cursor().execute("SELECT * FROM BANNED"): banned_list.append(str(row[0])+":"+str(row[1])) - responsedata = ("" - '[CLICK HERE TO LOG OUT]' - "

" - "" - "" - "" - "\r\n") + responsedata = """ + [CLICK HERE TO LOG OUT] +

+
ingamesn or devnamegameidEnablednewest dwc_pidgsbrcduseridipAddr
" + + + + + " + + + + """ % request.getHeader('host') + for row in dbconn.cursor().execute(sqlstatement): dwc_pid = str(row[0]) enabled = str(row[1]) @@ -258,31 +312,51 @@ class AdminPage(resource.Resource): ingamesn = codecs.utf_16_le_decode(ingamesn)[0] else: ingamesn = '[NOT AVAILABLE]' - responsedata += "" - responsedata += "" - responsedata += "" - responsedata += "" - responsedata += "" - responsedata += "" - responsedata += "" - responsedata += "" - if gameid[:-1]+":"+ipaddr in banned_list: - responsedata += ("") + responsedata += """ + + + + + + + + + """ % (ingamesn, + gameid, + enabled, + dwc_pid, + gsbrcd, + userid, + ipaddr) + if gameid[:-1] + ":" + ipaddr in banned_list: + responsedata += """ + + """ % (gameid, ipaddr) else: - responsedata += ("") - responsedata += "
ingamesn or devnamegameidEnablednewest dwc_pidgsbrcduseridipAddr
"+ingamesn+""+gameid+""+enabled+""+dwc_pid+""+gsbrcd+""+userid+""+ipaddr+"
" - "" - "" - "" - "
%s%s%s%s%s%s%s +
+ + + + +
+
" - "" - "" - "" - "
" + responsedata += """ + +
+ + + + +
+ + + """ % (gameid, ipaddr) + + responsedata += "" dbconn.close() request.setHeader("Content-Type", "text/html; charset=utf-8") return responsedata.encode('utf-8') - + def enable_disable_user(self, request, enable=True): address = request.getClientIP() responsedata = "" @@ -291,23 +365,31 @@ class AdminPage(resource.Resource): ingamesn = request.args['ingamesn'][0] if not userid.isdigit() or not gameid.isalnum(): - logger.log(logging.INFO,address+" Bad data "+userid+" "+gameid) + logger.log(logging.INFO, + "%s Bad data %s %s", + address, userid, gameid) return "Bad data" dbconn = sqlite3.connect('gpcm.db') if enable: - dbconn.cursor().execute('update users set enabled=1 ' - 'where gameid=? and userid=?',(gameid,userid)) + dbconn.cursor().execute( + 'UPDATE users SET enabled=1 ' + 'WHERE gameid=? AND userid=?', + (gameid, userid) + ) responsedata = "Enabled %s with gameid=%s, userid=%s" % \ - (ingamesn,gameid,userid) + (ingamesn, gameid, userid) else: - dbconn.cursor().execute('update users set enabled=0 ' - 'where gameid=? and userid=?',(gameid,userid)) + dbconn.cursor().execute( + 'UPDATE users SET enabled=0 ' + 'WHERE gameid=? AND userid=?', + (gameid, userid) + ) responsedata = "Disabled %s with gameid=%s, userid=%s" % \ - (ingamesn,gameid,userid) + (ingamesn, gameid, userid) dbconn.commit() dbconn.close() - logger.log(logging.INFO,address+" "+responsedata) + logger.log(logging.INFO, "%s %s", address, responsedata) request.setHeader("Content-Type", "text/html; charset=utf-8") request.setHeader("Location", "/banhammer") request.setResponseCode(303) @@ -319,29 +401,44 @@ class AdminPage(resource.Resource): active_list = [] for row in dbconn.cursor().execute("SELECT * FROM REGISTERED"): active_list.append(str(row[0])) - logger.log(logging.INFO,address+" Viewed console list") - responsedata = ("" - '[CLICK HERE TO LOG OUT]' + logger.log(logging.INFO, "%s Viewed console list", address) + responsedata = ( + '[CLICK HERE TO LOG OUT]' "
" "macadr:\r\n" "\r\n" - "
\r\n" + "" + "\r\n" "" - "\r\n") - for row in dbconn.cursor().execute("select * from pending"): + "\r\n" + ) + for row in dbconn.cursor().execute("SELECT * FROM pending"): macadr = str(row[0]) if macadr in active_list: - responsedata += ("" - "\r\n") + responsedata += """ + + + + """ % (macadr, macadr) else: - responsedata += ("" - "\r\n") + responsedata += """ + + + + """ % (macadr, macadr) responsedata += "
macadr
macadr
"+macadr+"
" - "" - "\r\n" - "
%s +
+ + + +
+
"+macadr+"
" - "" - "\r\n" - "
%s +
+ + + +
+
" dbconn.close() request.setHeader("Content-Type", "text/html; charset=utf-8") @@ -353,7 +450,7 @@ class AdminPage(resource.Resource): return "" if not self.is_authorized(request): return "" - + title = None response = '' if request.path == "/banlist": @@ -373,7 +470,7 @@ class AdminPage(resource.Resource): return "" if not self.is_authorized(request): return "" - + if request.path == "/updatebanlist": return self.update_banlist(request) if request.path == "/updateconsolelist": @@ -382,13 +479,17 @@ class AdminPage(resource.Resource): return self.get_header() + self.get_footer() port = 9009 + + class AdminPageServer(object): def start(self): site = server.Site(AdminPage(self)) reactor.listenTCP(port, site) - logger.log(logging.INFO, "Now listening for connections on port %d...", port) + logger.log(logging.INFO, + "Now listening for connections on port %d...", + port) try: - if reactor.running == False: + if not reactor.running: reactor.run(installSignalHandlers=0) except ReactorAlreadyRunning: pass diff --git a/gamespy/gs_database.py b/gamespy/gs_database.py index cb39abb..204c53c 100644 --- a/gamespy/gs_database.py +++ b/gamespy/gs_database.py @@ -3,6 +3,7 @@ Copyright (C) 2014 polaris- Copyright (C) 2014 ToadKing Copyright (C) 2014 AdmiralCurtiss + Copyright (C) 2015 Sepalani This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as @@ -101,7 +102,7 @@ class GamespyDatabase(object): self.close() def close(self): - if self.conn != None: + if self.conn is not None: self.conn.close() self.conn = None @@ -153,7 +154,7 @@ class GamespyDatabase(object): r = self.get_dict(row) profileid = 1 # Cannot be 0 or else it freezes the game. - if r != None and r['m'] != None: + if r is not None and r['m'] is not None: profileid = int(r['m']) + 1 return profileid @@ -174,7 +175,7 @@ class GamespyDatabase(object): row = tx.queryone("SELECT enabled FROM users WHERE userid = ? AND gsbrcd = ?", (userid, gsbrcd)) enabled = int(row[0]) return enabled > 0 - + def check_profile_exists(self, profileid): with Transaction(self.conn) as tx: row = tx.queryone("SELECT COUNT(*) FROM users WHERE profileid = ?", (profileid,)) @@ -200,7 +201,7 @@ class GamespyDatabase(object): r = self.get_dict(row) profileid = None # Default, user doesn't exist - if r != None: + if r is not None: #md5 = hashlib.md5() #md5.update(password) @@ -313,7 +314,7 @@ class GamespyDatabase(object): r = self.get_dict(row) profileid = -1 # Default, invalid session key - if r != None: + if r is not None: profileid = r['profileid'] return profileid @@ -355,7 +356,7 @@ class GamespyDatabase(object): tx.nonquery("DELETE FROM sessions WHERE profileid = ?", (profileid,)) def create_session(self, profileid, loginticket): - if profileid != None and self.check_profile_exists(profileid) == False: + if profileid is not None and self.check_profile_exists(profileid) is False: return None # Remove any old sessions associated with this user id @@ -371,7 +372,7 @@ class GamespyDatabase(object): def get_session_list(self, profileid=None): sessions = [] with Transaction(self.conn) as tx: - if profileid != None: + if profileid is not None: r = tx.queryall("SELECT * FROM sessions WHERE profileid = ?", (profileid,)) else: r = tx.queryall("SELECT * FROM sessions") @@ -387,7 +388,7 @@ class GamespyDatabase(object): row = tx.queryone("SELECT data FROM nas_logins WHERE authtoken = ?", (authtoken,)) r = self.get_dict(row) - if r == None: + if r is None: return None else: return json.loads(r["data"]) @@ -397,7 +398,7 @@ class GamespyDatabase(object): row = tx.queryone("SELECT data FROM nas_logins WHERE userid = ?", (userid,)) r = self.get_dict(row) - if r == None: + if r is None: return None else: return json.loads(r["data"]) @@ -419,7 +420,7 @@ class GamespyDatabase(object): with Transaction(self.conn) as tx: row = tx.queryone("SELECT max(userid) AS maxuser FROM users") r = self.get_dict(row) - if r == None or r['maxuser'] == None: + if r is None or r['maxuser'] is None: return '0000000000002'#Because all zeroes means Dolphin. Don't wanna get confused during debugging later. else: userid = str(int(r['maxuser']) + 1) @@ -455,13 +456,12 @@ class GamespyDatabase(object): data = json.dumps(data) with Transaction(self.conn) as tx: - if r == None: # no row, add it + if r is None: # no row, add it tx.nonquery("INSERT INTO nas_logins VALUES (?, ?, ?)", (userid, authtoken, data)) else: tx.nonquery("UPDATE nas_logins SET authtoken = ?, data = ? WHERE userid = ?", (authtoken, data, userid)) return authtoken - # Buddy functions def add_buddy(self, userProfileId, buddyProfileId): diff --git a/gamespy/gs_query.py b/gamespy/gs_query.py index 9abec2c..727447c 100644 --- a/gamespy/gs_query.py +++ b/gamespy/gs_query.py @@ -3,6 +3,7 @@ Copyright (C) 2014 polaris- Copyright (C) 2014 ToadKing Copyright (C) 2014 AdmiralCurtiss + Copyright (C) 2015 Sepalani This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as @@ -47,7 +48,7 @@ def parse_gamespy_message(message): else: value = msg - if found_command == False: + if found_command is False: messages['__cmd__'] = key messages['__cmd_val__'] = value found_command = True @@ -121,7 +122,7 @@ def create_gamespy_message(messages, id=None): # Check for an id if the id needs to be updated. # If it already exists in the list then update it, else add it - if id != None: + if id is not None: for message in messages: if message[0] == "id": messages.pop(messages.index(message)) @@ -131,7 +132,7 @@ def create_gamespy_message(messages, id=None): query = create_gamespy_message_from_list(messages) - if id != None: + if id is not None: query += create_gamespy_message_from_list([("id", id)]) query += "\\final\\" diff --git a/gamespy/gs_utility.py b/gamespy/gs_utility.py index f3e6732..488ff88 100644 --- a/gamespy/gs_utility.py +++ b/gamespy/gs_utility.py @@ -4,6 +4,7 @@ Copyright (C) 2014 ToadKing Copyright (C) 2014 AdmiralCurtiss Copyright (C) 2014 msoucy + Copyright (C) 2015 Sepalani This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as @@ -86,7 +87,7 @@ def rc4_encrypt(_key, _data): def prepare_rc4_base64(_key, _data): data = rc4_encrypt(_key, _data) - if data == None: + if data is None: data = bytearray() data.append(0) @@ -130,7 +131,7 @@ def login_profile_via_parsed_authtoken(authtoken_parsed, db): console = 1 valid_user = db.check_user_exists(userid, gsbrcd) - if valid_user == False: + if valid_user is False: profileid = db.create_user(userid, password, email, uniquenick, gsbrcd, console, csnum, cfc, bssid, devname, birth, gameid, macadr) else: profileid = db.perform_login(userid, password, gsbrcd) diff --git a/gamespy_backend_server.py b/gamespy_backend_server.py index 6aa4c77..b1073ec 100644 --- a/gamespy_backend_server.py +++ b/gamespy_backend_server.py @@ -1,6 +1,7 @@ """DWC Network Server Emulator Copyright (C) 2014 polaris- + Copyright (C) 2015 Sepalani This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as @@ -219,14 +220,14 @@ class GameSpyBackendServer(object): valid_node = True elif isinstance(node, ast.Str): - if num_literal_only == False: + if num_literal_only is False: valid_node = True elif isinstance(node, ast.BoolOp): for value in node.values: valid_node = self.validate_ast(value, num_literal_only) - if valid_node == False: + if valid_node is False: break elif isinstance(node, ast.BinOp): @@ -325,7 +326,7 @@ class GameSpyBackendServer(object): for node in m.body: valid_filter = self.validate_ast(node, False) - if valid_filter == False: + if valid_filter is False: # Return only anything matched up until this point. logger.log(logging.WARNING, "Invalid filter(s): %s" % (filters)) #stop_search = True @@ -419,7 +420,7 @@ class GameSpyBackendServer(object): logger.log(logging.DEBUG, "Deleted %d %s servers where session = %d" % (count, gameid, session)) def find_server_by_address(self, ip, port, gameid = None): - if gameid == None: + if gameid is None: # Search all servers for gameid in self.server_list: for server in self.server_list[gameid]: @@ -454,19 +455,19 @@ class GameSpyBackendServer(object): if server[s] == localip: best_match = server - if localport == 0 and best_match == None: + if localport == 0 and best_match is None: # Kinda hackish. This sometimes happens. # Assuming two clients aren't trying to connect from the same IP, this might be safe. # The server wasn't verified to be the *correct* server, but it's on the same IP so it # has a chance of being correct. At least make an attempt to establish the connection. best_match = server - if best_match == None: + if best_match is None: logger.log(logging.DEBUG, "Couldn't find a match for %s" % (publicip)) return best_match - if gameid == None: + if gameid is None: # Search all servers for gameid in self.server_list: return find_server(gameid) diff --git a/gamespy_gamestats_server.py b/gamespy_gamestats_server.py index 2981a3c..0b548b7 100644 --- a/gamespy_gamestats_server.py +++ b/gamespy_gamestats_server.py @@ -4,6 +4,7 @@ Copyright (C) 2014 ToadKing Copyright (C) 2014 AdmiralCurtiss Copyright (C) 2014 msoucy + Copyright (C) 2015 Sepalani This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as @@ -53,7 +54,7 @@ class GameSpyGamestatsServer(object): conn_search = endpoint_search.listen(GamestatsFactory()) try: - if reactor.running == False: + if reactor.running is False: reactor.run(installSignalHandlers=0) except ReactorAlreadyRunning: pass @@ -187,7 +188,7 @@ class Gamestats(LineReceiver): userid, profileid, gsbrcd, uniquenick = gs_utils.login_profile_via_parsed_authtoken(authtoken_parsed, self.db) - if profileid != None: + if profileid is not None: # Successfully logged in or created account, continue creating session. sesskey = self.db.create_session(profileid, '') self.sessions[profileid] = self @@ -282,7 +283,7 @@ class Gamestats(LineReceiver): pid = int(data_parsed['pid']) profile = self.db.pd_get(pid, data_parsed['dindex'], data_parsed['ptype']) - if profile == None: + if profile is None: self.log(logging.WARNING, "Could not find profile for %d %s %s" % (pid, data_parsed['dindex'], data_parsed['ptype'])) keys = data_parsed['keys'].split('\x01') @@ -291,13 +292,13 @@ class Gamestats(LineReceiver): data = "" # Someone figure out if this is actually a good way to handle this when no profile is found - if profile != None and 'data' in profile: + if profile is not None and 'data' in profile: profile_data = profile['data'] if profile_data.endswith("\\"): profile_data = profile_data[:-1] profile_data = gs_query.parse_gamespy_message("\\prof\\" + profile_data + "\\final\\") - if profile_data != None: + if profile_data is not None: profile_data = profile_data[0][0] else: self.log(logging.WARNING, "Could not get data section from profile for %d" % pid) @@ -306,7 +307,7 @@ class Gamestats(LineReceiver): for key in (key for key in keys if key not in ("__cmd__", "__cmd_val__", "")): data += "\\" + key + "\\" - if profile_data != None and key in profile_data: + if profile_data is not None and key in profile_data: data += profile_data[key] else: self.log(logging.WARNING, "No keys requested, defaulting to all keys: %s" % (profile['data'])) diff --git a/gamespy_natneg_server.py b/gamespy_natneg_server.py index 1165c20..451bc74 100644 --- a/gamespy_natneg_server.py +++ b/gamespy_natneg_server.py @@ -2,6 +2,7 @@ Copyright (C) 2014 polaris- Copyright (C) 2014 ToadKing + Copyright (C) 2015 Sepalani This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as @@ -136,13 +137,13 @@ class GameSpyNatNegServer(object): clients = len(self.session_list[session_id]) for client in self.session_list[session_id]: - if self.session_list[session_id][client]['connected'] == False: # and self.session_list[session_id][client]['localaddr'][1] != 0: + if self.session_list[session_id][client]['connected'] is False: # and self.session_list[session_id][client]['localaddr'][1] != 0: if client == client_id: continue - #if self.session_list[session_id][client]['serveraddr'] == None: + #if self.session_list[session_id][client]['serveraddr'] is None: serveraddr = self.get_server_info(gameid, session_id, client) - if serveraddr == None: + if serveraddr is None: serveraddr = self.get_server_info_alt(gameid, session_id, client) self.session_list[session_id][client]['serveraddr'] = serveraddr @@ -152,7 +153,7 @@ class GameSpyNatNegServer(object): if self.session_list[session_id][client]['localaddr'][1] != 0: publicport = self.session_list[session_id][client]['localaddr'][1] - if self.session_list[session_id][client]['serveraddr'] != None: + if self.session_list[session_id][client]['serveraddr'] is not None: publicport = int(self.session_list[session_id][client]['serveraddr']['publicport']) # Send to requesting client @@ -169,9 +170,9 @@ class GameSpyNatNegServer(object): logger.log(logging.DEBUG, utils.pretty_print_hex(output)) # Send to other client - #if self.session_list[session_id][client_id]['serveraddr'] == None: + #if self.session_list[session_id][client_id]['serveraddr'] is None: serveraddr = self.get_server_info(gameid, session_id, client_id) - if serveraddr == None: + if serveraddr is None: serveraddr = self.get_server_info_alt(gameid, session_id, client_id) self.session_list[session_id][client_id]['serveraddr'] = serveraddr @@ -181,7 +182,7 @@ class GameSpyNatNegServer(object): if self.session_list[session_id][client_id]['localaddr'][1] != 0: publicport = self.session_list[session_id][client_id]['localaddr'][1] - if self.session_list[session_id][client_id]['serveraddr'] != None: + if self.session_list[session_id][client_id]['serveraddr'] is not None: publicport = int(self.session_list[session_id][client_id]['serveraddr']['publicport']) output = bytearray(recv_data[0:12]) @@ -276,7 +277,7 @@ class GameSpyNatNegServer(object): server_info = None servers = self.server_manager.get_natneg_server(session_id)._getvalue() - if servers == None: + if servers is None: return None console = False @@ -287,7 +288,7 @@ class GameSpyNatNegServer(object): server_info = next((s for s in servers if s['publicip'] == ip), None) - if server_info == None: + if server_info is None: ip = str(utils.get_ip(bytearray([int(x) for x in ipstr.split('.')]), 0, console)) server_info = next((s for s in servers if s['publicip'] == ip), None) @@ -303,7 +304,7 @@ class GameSpyNatNegServer(object): serveraddr = self.server_manager.find_server_by_local_address(ip, self.session_list[session_id][client_id]['localaddr'], self.session_list[session_id][client_id]['gameid'])._getvalue() - if serveraddr == None: + if serveraddr is None: ip = str(utils.get_ip(bytearray([int(x) for x in ipstr.split('.')]), 0, console)) serveraddr = self.server_manager.find_server_by_local_address(ip, self.session_list[session_id][client_id]['localaddr'], diff --git a/gamespy_player_search_server.py b/gamespy_player_search_server.py index f15057c..f51b012 100644 --- a/gamespy_player_search_server.py +++ b/gamespy_player_search_server.py @@ -2,6 +2,7 @@ Copyright (C) 2014 polaris- Copyright (C) 2014 msoucy + Copyright (C) 2015 Sepalani This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as @@ -48,7 +49,7 @@ class GameSpyPlayerSearchServer(object): conn_search = endpoint_search.listen(PlayerSearchFactory()) try: - if reactor.running == False: + if reactor.running is False: reactor.run(installSignalHandlers=0) except ReactorAlreadyRunning: pass @@ -114,7 +115,7 @@ class PlayerSearch(LineReceiver): profile = self.db.get_profile_from_profileid(opid) msg_d.append(('o', opid)) - if profile != None: + if profile is not None: msg_d.append(('uniquenick', profile['uniquenick'])) else: msg_d.append(('uniquenick', '')) diff --git a/gamespy_profile_server.py b/gamespy_profile_server.py index c3a9b10..903ceef 100644 --- a/gamespy_profile_server.py +++ b/gamespy_profile_server.py @@ -4,6 +4,7 @@ Copyright (C) 2014 ToadKing Copyright (C) 2014 AdmiralCurtiss Copyright (C) 2014 msoucy + Copyright (C) 2015 Sepalani This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as @@ -52,7 +53,7 @@ class GameSpyProfileServer(object): conn = endpoint.listen(PlayerFactory()) try: - if reactor.running == False: + if reactor.running is False: reactor.run(installSignalHandlers=0) except ReactorAlreadyRunning: pass @@ -106,7 +107,7 @@ class PlayerSession(LineReceiver): def get_ip_as_int(self, address): ipaddress = 0 - if address != None: + if address is not None: for n in address.split('.'): ipaddress = (ipaddress << 8) | int(n) @@ -198,8 +199,8 @@ class PlayerSession(LineReceiver): def perform_login(self, data_parsed): authtoken_parsed = gs_utils.parse_authtoken(data_parsed['authtoken'], self.db) - - if authtoken_parsed == None: + + if authtoken_parsed is None: self.log(logging.WARNING, "Invalid Authtoken.") msg = gs_query.create_gamespy_message([ ('__cmd__', "error"), @@ -224,7 +225,7 @@ class PlayerSession(LineReceiver): userid, profileid, gsbrcd, uniquenick = gs_utils.login_profile_via_parsed_authtoken(authtoken_parsed, self.db) - if profileid != None: + if profileid is not None: # Successfully logged in or created account, continue creating session. loginticket = gs_utils.base64_encode(utils.generate_random_str(16)) self.sesskey = self.db.create_session(profileid, loginticket) @@ -298,7 +299,7 @@ class PlayerSession(LineReceiver): self.send_status_to_friends() # profile = self.db.get_profile_from_profileid(profileid) - # if profile != None: + # if profile is not None: # self.statstring = profile['stat'] # self.locstring = profile['loc'] else: @@ -484,12 +485,12 @@ class PlayerSession(LineReceiver): buddy_exists = True break - if buddy_exists == False: + if buddy_exists is False: self.db.add_buddy(self.profileid, newprofileid) if newprofileid in self.sessions: logger.log(logging.DEBUG, "User is online, sending direct request from profile id %d to profile id %d..." % (self.profileid, newprofileid)) - + # TODO: Add a way to check if a profile id is already a buddy using SQL other_player_authorized = False target_buddy_list = self.db.get_buddy_list(newprofileid) @@ -498,32 +499,32 @@ class PlayerSession(LineReceiver): if buddy['buddyProfileId'] == self.profileid and buddy['blocked'] == 0: other_player_authorized = True break - + if other_player_authorized == True: logger.log(logging.DEBUG, "Automatic authorization: %d (target) already has %d (source) as a friend." % (newprofileid, self.profileid)) - + # Force them both to add each other self.send_buddy_request(self.sessions[newprofileid], self.profileid) self.send_buddy_request(self.sessions[self.profileid], newprofileid) - + self.send_bm4(newprofileid) - + self.db.auth_buddy(newprofileid, self.profileid) self.db.auth_buddy(self.profileid, newprofileid) - + self.send_status_to_friends(newprofileid) self.get_status_from_friends(newprofileid) - + else: self.send_buddy_request(self.sessions[newprofileid], self.profileid) else: # Trying to add someone who is already a friend. Just send status updates self.send_status_to_friends(newprofileid) self.get_status_from_friends(newprofileid) - + self.buddies = self.db.get_buddy_list(self.profileid) - def send_bm4(self, playerid): + def send_bm4(self, playerid): date = int(time.time()) msg = gs_query.create_gamespy_message([ ('__cmd__', "bm"), @@ -532,7 +533,7 @@ class PlayerSession(LineReceiver): ('date', date), ('msg', ""), ]) - + self.transport.write(bytes(msg)) def perform_delbuddy(self, data_parsed): @@ -547,9 +548,9 @@ class PlayerSession(LineReceiver): self.db.auth_buddy(target_profile, self.profileid) self.get_buddy_authorized() self.buddies = self.db.get_buddy_list(self.profileid) - + self.send_bm4(target_profile) - + self.send_status_to_friends(target_profile) self.get_status_from_friends(target_profile) @@ -571,9 +572,9 @@ class PlayerSession(LineReceiver): ]) buddy_list = self.buddies - if buddy_profileid != None: + if buddy_profileid is not None: buddy_list = [{"buddyProfileId":buddy_profileid}] - + for buddy in buddy_list: if buddy['buddyProfileId'] in self.sessions: #self.log(logging.DEBUG, "Sending status to buddy id %s (%s:%d): %s" % (str(buddy['buddyProfileId']), self.sessions[buddy['buddyProfileId']].address.host, self.sessions[buddy['buddyProfileId']].address.port, msg)) @@ -585,9 +586,9 @@ class PlayerSession(LineReceiver): self.buddies = self.db.get_buddy_list(self.profileid) buddy_list = self.buddies - if buddy_profileid != None: + if buddy_profileid is not None: buddy_list = [{"buddyProfileId":buddy_profileid}] - + for buddy in self.buddies: if buddy['status'] != 1: continue @@ -632,7 +633,7 @@ class PlayerSession(LineReceiver): msg = "\r\n\r\n" msg += "|signed|" + sig - if senttime == None: + if senttime is None: senttime = int(time.time()) msg = gs_query.create_gamespy_message([ diff --git a/gamespy_qr_server.py b/gamespy_qr_server.py index 8f5addc..1c55952 100644 --- a/gamespy_qr_server.py +++ b/gamespy_qr_server.py @@ -3,6 +3,7 @@ Copyright (C) 2014 polaris- Copyright (C) 2014 ToadKing Copyright (C) 2014 AdmiralCurtiss + Copyright (C) 2015 Sepalani This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as @@ -76,10 +77,10 @@ class GameSpyQRServer(object): GameSpyServerDatabase.register("delete_server") def log(self, level, address, session_id, message): - if address == None: + if address is None: logger.log(level, "%s", message) else: - if session_id != None: + if session_id is not None: logger.log(level, "[%s:%d %08x] %s", address[0], address[1], session_id, message) else: logger.log(level, "[%s:%d] %s", address[0], address[1], message) @@ -272,7 +273,7 @@ class GameSpyQRServer(object): self.write_queue.put((packet, address)) self.log(logging.DEBUG, address, session_id, "Sent client registered to %s:%s..." % (address[0], address[1])) - if self.sessions[session_id].heartbeat_data != None: + if self.sessions[session_id].heartbeat_data is not None: self.update_server_list(session_id, self.sessions[session_id].heartbeat_data) else: @@ -296,7 +297,7 @@ class GameSpyQRServer(object): #self.log(logging.DEBUG, address, session_id, "%s = %s" % (d[i], d[i+1])) k[d[i]] = d[i+1] - if self.sessions[session_id].ingamesn != None: + if self.sessions[session_id].ingamesn is not None: if "gamename" in k and "dwc_pid" in k: try: profile = self.db.get_profile_from_profileid(k['dwc_pid']) @@ -305,7 +306,7 @@ class GameSpyQRServer(object): except Exception,e: pass # If the game doesn't have, don't worry about it. - if self.sessions[session_id].ingamesn != None and "ingamesn" not in k: + if self.sessions[session_id].ingamesn is not None and "ingamesn" not in k: k['ingamesn'] = self.sessions[session_id].ingamesn if "gamename" in k: @@ -332,7 +333,7 @@ class GameSpyQRServer(object): self.sessions[session_id].console = 1 found_console = True - if found_console == False: + if found_console is False: # Couldn't detect game, try to get it from the database # Try a 3 times before giving up for i in range(0, 3): diff --git a/gamespy_server_browser_server.py b/gamespy_server_browser_server.py index 3392c42..b62a0a3 100644 --- a/gamespy_server_browser_server.py +++ b/gamespy_server_browser_server.py @@ -2,6 +2,7 @@ Copyright (C) 2014 polaris- Copyright (C) 2014 msoucy + Copyright (C) 2015 Sepalani This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as @@ -73,7 +74,7 @@ class GameSpyServerBrowserServer(object): conn = endpoint.listen(SessionFactory(self.qr)) try: - if reactor.running == False: + if reactor.running is False: reactor.run(installSignalHandlers=0) except ReactorAlreadyRunning: pass @@ -137,7 +138,7 @@ class Session(LineReceiver): packet = self.buffer[:packet_len] self.buffer = self.buffer[packet_len:] - if packet == None: + if packet is None: # Don't have enough for the entire packet, break. break @@ -380,12 +381,12 @@ class Session(LineReceiver): # Find session id of server # Iterate through the list of servers sent to the client and match by IP and port. # Is there a better way to determine this information? - if forward_client == None or len(forward_client) != 2: + if forward_client is None or len(forward_client) != 2: return server, ip = self.find_server_in_cache(forward_client[0], forward_client[1], self.console) - if server == None: + if server is None: if self.console == 0: server, ip = self.find_server_in_cache(forward_client[0], forward_client[1], 1) # Try Wii elif self.console == 1: @@ -395,7 +396,7 @@ class Session(LineReceiver): self.log(logging.DEBUG, "Trying to send message to %s:%d..." % (forward_client[0], forward_client[1])) self.log(logging.DEBUG, utils.pretty_print_hex(bytearray(data))) - if server == None: + if server is None: return self.log(logging.DEBUG, "%s %s" % (ip, server['publicip'])) @@ -408,20 +409,20 @@ class Session(LineReceiver): cookie = int(utils.generate_random_hex_str(8), 16) # Quick and lazy way to get a random 32bit integer. Replace with something else later #if (len(data) == 24 and bytearray(data)[0:10] == bytearray([0x53, 0x42, 0x43, 0x4d, 0x03, 0x00, 0x00, 0x00, 0x01, 0x04])) or (len(data) == 40 and bytearray(data)[0:10] == bytearray([0x53, 0x42, 0x43, 0x4d, 0x0b, 0x00, 0x00, 0x00, 0x01, 0x04])): - if self.own_server == None and len(data) >= 16 and bytearray(data)[0:4] in (bytearray([0xbb, 0x49, 0xcc, 0x4d]), bytearray([0x53, 0x42, 0x43, 0x4d])): + if self.own_server is None and len(data) >= 16 and bytearray(data)[0:4] in (bytearray([0xbb, 0x49, 0xcc, 0x4d]), bytearray([0x53, 0x42, 0x43, 0x4d])): # Is the endianness the same between the DS and Wii here? It seems so but I'm not positive. self_port = utils.get_short(bytearray(data[10:12]), 0, False) # Note to self: Port is little endian here. self_ip = '.'.join(["%d" % x for x in bytearray(data[12:16])]) self.own_server, _ = self.find_server_in_cache(self_ip, self_port, self.console) - if self.own_server == None: + if self.own_server is None: if self.console == 0: self.own_server, _ = self.find_server_in_cache(self_ip, self_port, 1) # Try Wii elif self.console == 1: self.own_server, _ = self.find_server_in_cache(self_ip, self_port, 0) # Try DS - if self.own_server == None: + if self.own_server is None: self.log(logging.DEBUG, "Could not find own server: %s:%d" % (self_ip, self_port)) else: self.log(logging.DEBUG, "Found own server: %s" % (self.own_server)) @@ -432,11 +433,11 @@ class Session(LineReceiver): self.log(logging.DEBUG, "Adding %d to natneg server list: %s" % (natneg_session, server)) self.server_manager.add_natneg_server(natneg_session, server) # Store info in backend so we can get it later in natneg - if self.own_server != None: + if self.own_server is not None: self.log(logging.DEBUG, "Adding %d to natneg server list: %s (self)" % (natneg_session, self.own_server)) self.server_manager.add_natneg_server(natneg_session, self.own_server) # Store info in backend so we can get it later in natneg - # if self.qr != None: + # if self.qr is not None: # own_server = self.qr.get_own_server() # # self.log(logging.DEBUG, "Adding %d to natneg server list: %s" % (natneg_session, own_server)) @@ -447,7 +448,7 @@ class Session(LineReceiver): output += bytearray(utils.get_bytes_from_int(cookie)) output += bytearray(data) - if self.qr != None: + if self.qr is not None: self.log(logging.DEBUG, "Forwarded data to %s:%s through QR server..." % (forward_client[0], forward_client[1])) self.qr.socket.sendto(output, forward_client) else: diff --git a/gamestats_server_http.py b/gamestats_server_http.py index e5b42d3..ba94082 100644 --- a/gamestats_server_http.py +++ b/gamestats_server_http.py @@ -1,4 +1,4 @@ -"""DWC Network Server Emulator +"""DWC Network Server Emulator Copyright (C) 2014 polaris- diff --git a/internal_stats_server.py b/internal_stats_server.py index 5d5b6c7..27b991f 100644 --- a/internal_stats_server.py +++ b/internal_stats_server.py @@ -2,6 +2,7 @@ Copyright (C) 2014 polaris- Copyright (C) 2014 msoucy + Copyright (C) 2015 Sepalani This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as @@ -59,7 +60,7 @@ class StatsPage(resource.Resource): restricted = [ "publicip", "__session__", "localip0", "localip1" ] # Filter out certain fields before displaying raw data - if server_list != None: + if server_list is not None: for game in server_list: for server in server_list[game]: for r in restricted: @@ -75,7 +76,7 @@ class StatsPage(resource.Resource): output += "Game ID# Players" output += "" - if server_list != None: + if server_list is not None: for game in server_list: if not server_list[game]: continue @@ -110,7 +111,7 @@ class InternalStatsServer(object): reactor.listenTCP(9001, site) try: - if reactor.running == False: + if reactor.running is False: reactor.run(installSignalHandlers=0) except ReactorAlreadyRunning: pass diff --git a/master_server.py b/master_server.py index 7096b78..8e8c2eb 100644 --- a/master_server.py +++ b/master_server.py @@ -4,6 +4,7 @@ Copyright (C) 2014 ToadKing Copyright (C) 2014 AdmiralCurtiss Copyright (C) 2014 msoucy + Copyright (C) 2015 Sepalani This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as @@ -38,20 +39,23 @@ import threading if __name__ == "__main__": - # Let database initialize before starting any servers. - # This fixes any conflicts where two servers find an uninitialized database at the same time and both try to - # initialize it. + """Let database initialize before starting any servers. + + This fixes any conflicts where two servers find an uninitialized database + at the same time and both try to initialize it. + """ + db = gs_database.GamespyDatabase() db.initialize_database() db.close() - + servers = [ GameSpyBackendServer, GameSpyQRServer, GameSpyProfileServer, GameSpyPlayerSearchServer, GameSpyGamestatsServer, - #GameSpyServerBrowserServer, + # GameSpyServerBrowserServer, GameSpyNatNegServer, NasServer, InternalStatsServer, diff --git a/nas_server.py b/nas_server.py index 9227c40..30d39f9 100644 --- a/nas_server.py +++ b/nas_server.py @@ -4,6 +4,7 @@ Copyright (C) 2014 ToadKing Copyright (C) 2014 AdmiralCurtiss Copyright (C) 2014 msoucy + Copyright (C) 2015 Sepalani This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as @@ -20,7 +21,6 @@ """ import base64 -import json import logging import time import urlparse @@ -32,42 +32,69 @@ import random import traceback import gamespy.gs_database as gs_database -import gamespy.gs_utility as gs_utils import other.utils as utils logger_output_to_console = True logger_output_to_file = True logger_name = "NasServer" logger_filename = "nas_server.log" -logger = utils.create_logger(logger_name, logger_filename, -1, logger_output_to_console, logger_output_to_file) +logger = utils.create_logger(logger_name, logger_filename, -1, + logger_output_to_console, logger_output_to_file) -# if a game from this list requests a file listing, the server will return that only one exists and return a random one -# this is used for Mystery Gift distribution on Generation 4 Pokemon games -gamecodes_return_random_file = ['ADAD', 'ADAE', 'ADAF', 'ADAI', 'ADAJ', 'ADAK', 'ADAS', 'CPUD', 'CPUE', 'CPUF', 'CPUI', 'CPUJ', 'CPUK', 'CPUS', 'IPGD', 'IPGE', 'IPGF', 'IPGI', 'IPGJ', 'IPGK', 'IPGS'] +# If a game from this list requests a file listing, the server will return +# that only one exists and return a random one. +# This is used for Mystery Gift distribution on Generation 4 Pokemon games +gamecodes_return_random_file = [ + 'ADAD', + 'ADAE', + 'ADAF', + 'ADAI', + 'ADAJ', + 'ADAK', + 'ADAS', + 'CPUD', + 'CPUE', + 'CPUF', + 'CPUI', + 'CPUJ', + 'CPUK', + 'CPUS', + 'IPGD', + 'IPGE', + 'IPGF', + 'IPGI', + 'IPGJ', + 'IPGK', + 'IPGS' +] -#address = ("0.0.0.0", 80) address = ("127.0.0.1", 9000) + class NasServer(object): def start(self): httpd = NasHTTPServer((address[0], address[1]), NasHTTPServerHandler) t = threading.Thread(target=httpd.serve_forever) t.daemon = True t.start() - logger.log(logging.INFO, "Now listening for connections on %s:%d...", address[0], address[1]) + logger.log(logging.INFO, "Now listening for connections on %s:%d...", + address[0], address[1]) httpd.serve_forever() -class NasHTTPServer(SocketServer.ThreadingMixIn,BaseHTTPServer.HTTPServer): + +class NasHTTPServer(SocketServer.ThreadingMixIn, BaseHTTPServer.HTTPServer): def __init__(self, server_address, RequestHandlerClass): - #self.db = gs_database.GamespyDatabase() - BaseHTTPServer.HTTPServer.__init__(self, server_address, RequestHandlerClass) + # self.db = gs_database.GamespyDatabase() + BaseHTTPServer.HTTPServer.__init__(self, server_address, + RequestHandlerClass) + class NasHTTPServerHandler(BaseHTTPServer.BaseHTTPRequestHandler): def version_string(self): return "Nintendo Wii (http)" def do_GET(self): - self.server = lambda:None + self.server = lambda: None self.server.db = gs_database.GamespyDatabase() try: @@ -79,24 +106,30 @@ class NasHTTPServerHandler(BaseHTTPServer.BaseHTTPRequestHandler): self.end_headers() self.wfile.write("ok") except: - logger.log(logging.ERROR, "Unknown exception: %s" % traceback.format_exc()) + logger.log(logging.ERROR, "Unknown exception: %s", + traceback.format_exc()) def do_POST(self): - self.server = lambda:None + self.server = lambda: None self.server.db = gs_database.GamespyDatabase() try: length = int(self.headers['content-length']) post = self.str_to_dict(self.rfile.read(length)) if self.client_address[0] == '127.0.0.1': - client_address = (self.headers.get('x-forwarded-for', self.client_address[0]), self.client_address[1]) + client_address = ( + self.headers.get('x-forwarded-for', + self.client_address[0]), + self.client_address[1] + ) else: client_address = self.client_address post['ipaddr'] = client_address[0] if self.path == "/ac": - logger.log(logging.DEBUG, "Request to %s from %s", self.path, client_address) + logger.log(logging.DEBUG, "Request to %s from %s", + self.path, client_address) logger.log(logging.DEBUG, post) ret = { "datetime": time.strftime("%Y%m%d%H%M%S"), @@ -110,7 +143,9 @@ class NasHTTPServerHandler(BaseHTTPServer.BaseHTTPRequestHandler): if action == "acctcreate": # TODO: test for duplicate accounts if self.server.db.is_banned(post): - logger.log(logging.DEBUG, "acctcreate denied for banned user "+str(post)) + logger.log(logging.DEBUG, + "acctcreate denied for banned user %s", + str(post)) ret = { "datetime": time.strftime("%Y%m%d%H%M%S"), "returncd": "3913", @@ -120,16 +155,21 @@ class NasHTTPServerHandler(BaseHTTPServer.BaseHTTPRequestHandler): } else: ret["returncd"] = "002" - ret['userid'] = self.server.db.get_next_available_userid() + ret['userid'] = \ + self.server.db.get_next_available_userid() - logger.log(logging.DEBUG, "acctcreate response to %s", client_address) + logger.log(logging.DEBUG, + "acctcreate response to %s", + client_address) logger.log(logging.DEBUG, ret) ret = self.dict_to_str(ret) elif action == "login": if self.server.db.is_banned(post): - logger.log(logging.DEBUG, "login denied for banned user "+str(post)) + logger.log(logging.DEBUG, + "login denied for banned user %s", + str(post)) ret = { "datetime": time.strftime("%Y%m%d%H%M%S"), "returncd": "3914", @@ -157,8 +197,11 @@ class NasHTTPServerHandler(BaseHTTPServer.BaseHTTPRequestHandler): else: challenge = utils.generate_random_str(8) post["challenge"] = challenge - - authtoken = self.server.db.generate_authtoken(post["userid"], post) + + authtoken = self.server.db.generate_authtoken( + post["userid"], + post + ) ret.update({ "returncd": "001", "locator": "gamespy.com", @@ -166,41 +209,55 @@ class NasHTTPServerHandler(BaseHTTPServer.BaseHTTPRequestHandler): "token": authtoken, }) - logger.log(logging.DEBUG, "login response to %s", client_address) + logger.log(logging.DEBUG, "login response to %s", + client_address) logger.log(logging.DEBUG, ret) ret = self.dict_to_str(ret) - elif action == "SVCLOC" or action == "svcloc": # Get service based on service id number + elif action == "SVCLOC" or action == "svcloc": + # Get service based on service id number ret["returncd"] = "007" ret["statusdata"] = "Y" - authtoken = self.server.db.generate_authtoken(post["userid"], post) + authtoken = self.server.db.generate_authtoken( + post["userid"], + post + ) if 'svc' in post: - if post["svc"] in ("9000", "9001"): # DLC host = 9000 - ret["svchost"] = self.headers['host'] # in case the client's DNS isn't redirecting dls1.nintendowifi.net + if post["svc"] in ("9000", "9001"): + # DLC host = 9000 + # In case the client's DNS isn't redirecting to + # dls1.nintendowifi.net + ret["svchost"] = self.headers['host'] - # Brawl has 2 host headers which Apache chokes on, so only return the first one or else it won't work + # Brawl has 2 host headers which Apache chokes + # on, so only return the first one or else it + # won't work ret["svchost"] = ret["svchost"].split(',')[0] if post["svc"] == 9000: ret["token"] = authtoken else: ret["servicetoken"] = authtoken - elif post["svc"] == "0000": # Pokemon requests this for some things + elif post["svc"] == "0000": + # Pokemon requests this for some things ret["servicetoken"] = authtoken ret["svchost"] = "n/a" - logger.log(logging.DEBUG, "svcloc response to %s", client_address) + logger.log(logging.DEBUG, "svcloc response to %s", + client_address) logger.log(logging.DEBUG, ret) ret = self.dict_to_str(ret) else: - logger.log(logging.WARNING, "Unknown action request %s from %s!", self.path, client_address) - + logger.log(logging.WARNING, + "Unknown action request %s from %s!", + self.path, client_address) elif self.path == "/pr": - logger.log(logging.DEBUG, "Request to %s from %s", self.path, client_address) + logger.log(logging.DEBUG, "Request to %s from %s", + self.path, client_address) logger.log(logging.DEBUG, post) words = len(post["words"].split('\t')) wordsret = "0" * words @@ -211,7 +268,7 @@ class NasHTTPServerHandler(BaseHTTPServer.BaseHTTPRequestHandler): } for l in "ACEJKP": - ret["prwords"+l] = wordsret + ret["prwords" + l] = wordsret self.send_response(200) self.send_header("Content-type", "text/plain") @@ -223,7 +280,8 @@ class NasHTTPServerHandler(BaseHTTPServer.BaseHTTPRequestHandler): ret = self.dict_to_str(ret) elif self.path == "/download": - logger.log(logging.DEBUG, "Request to %s from %s", self.path, client_address) + logger.log(logging.DEBUG, "Request to %s from %s", + self.path, client_address) logger.log(logging.DEBUG, post) action = post["action"] @@ -234,16 +292,18 @@ class NasHTTPServerHandler(BaseHTTPServer.BaseHTTPRequestHandler): dlc_contenttype = False if os.path.commonprefix([dlcdir, dlcpath]) != dlcdir: - logging.log(logging.WARNING, 'Attempted directory traversal attack "%s", cancelling.', dlcpath) + logging.log(logging.WARNING, + 'Attempted directory traversal attack "%s",' + ' cancelling.', dlcpath) self.send_response(403) return def safeloadfi(fn, mode='rb'): - ''' - safeloadfi : string -> string + """safeloadfi : string -> string - Safely load contents of a file, given a filename, and closing the file afterward - ''' + Safely load contents of a file, given a filename, + and closing the file afterward. + """ with open(os.path.join(dlcpath, fn), mode) as fi: return fi.read() @@ -262,7 +322,8 @@ class NasHTTPServerHandler(BaseHTTPServer.BaseHTTPRequestHandler): attr3 = post.get("attr3", None) dlcfi = safeloadfi("_list.txt") - lst = self.filter_list(dlcfi, attr1, attr2, attr3) + lst = self.filter_list(dlcfi, + attr1, attr2, attr3) count = self.get_file_count(lst) ret = "%d" % count @@ -271,10 +332,10 @@ class NasHTTPServerHandler(BaseHTTPServer.BaseHTTPRequestHandler): num = post.get("num", None) offset = post.get("offset", None) - if num != None: + if num is not None: num = int(num) - if offset != None: + if offset is not None: offset = int(offset) attr1 = post.get("attr1", None) @@ -283,76 +344,120 @@ class NasHTTPServerHandler(BaseHTTPServer.BaseHTTPRequestHandler): if os.path.exists(dlcpath): # Look for a list file first. - # If the list file exists, send the entire thing back to the client. + # If the list file exists, send the entire thing back + # to the client. if os.path.isfile(os.path.join(dlcpath, "_list.txt")): - if post["gamecd"].startswith("IRA") and attr1.startswith("MYSTERY"): - # Pokemon BW Mystery Gifts, until we have a better solution for that - ret = self.filter_list(safeloadfi("_list.txt"), attr1, attr2, attr3) - ret = self.filter_list_g5_mystery_gift(ret, post["rhgamecd"]) - ret = self.filter_list_by_date(ret, post["token"]) + if post["gamecd"].startswith("IRA") and \ + attr1.startswith("MYSTERY"): + # Pokemon BW Mystery Gifts, until we have a + # better solution for that + ret = self.filter_list( + safeloadfi("_list.txt"), + attr1, attr2, attr3 + ) + ret = self.filter_list_g5_mystery_gift( + ret, + post["rhgamecd"] + ) + ret = self.filter_list_by_date( + ret, + post["token"] + ) elif post["gamecd"] in gamecodes_return_random_file: # Pokemon Gen 4 Mystery Gifts, same here - ret = self.filter_list(safeloadfi("_list.txt"), attr1, attr2, attr3) - ret = self.filter_list_by_date(ret, post["token"]) + ret = self.filter_list( + safeloadfi("_list.txt"), + attr1, attr2, attr3 + ) + ret = self.filter_list_by_date( + ret, + post["token"] + ) else: # default case for most games - ret = self.filter_list(safeloadfi("_list.txt"), attr1, attr2, attr3, num, offset) + ret = self.filter_list( + safeloadfi("_list.txt"), + attr1, attr2, attr3, + num, offset + ) if action == "contents": - # Get only the base filename just in case there is a path involved somewhere in the filename string. + # Get only the base filename just in case there is a path + # involved somewhere in the filename string. dlc_contenttype = True contents = os.path.basename(post["contents"]) ret = safeloadfi(contents) self.send_response(200) - if dlc_contenttype == True: + if dlc_contenttype is True: self.send_header("Content-type", "application/x-dsdl") - self.send_header("Content-Disposition", "attachment; filename=\"" + post["contents"] + "\"") + self.send_header("Content-Disposition", + "attachment; filename=\"" + + post["contents"] + "\"") else: self.send_header("Content-type", "text/plain") self.send_header("X-DLS-Host", "http://127.0.0.1/") - logger.log(logging.DEBUG, "download response to %s", client_address) + logger.log(logging.DEBUG, "download response to %s", + client_address) - #if dlc_contenttype == False: - # logger.log(logging.DEBUG, ret) + # if dlc_contenttype is False: + # logger.log(logging.DEBUG, ret) else: self.send_response(404) - logger.log(logging.WARNING, "Unknown path request %s from %s!", self.path, client_address) + logger.log(logging.WARNING, + "Unknown path request %s from %s!", + self.path, client_address) return self.send_header("Content-Length", str(len(ret))) self.end_headers() self.wfile.write(ret) except: - logger.log(logging.ERROR, "Unknown exception: %s" % traceback.format_exc()) + logger.log(logging.ERROR, "Unknown exception: %s", + traceback.format_exc()) def str_to_dict(self, s): ret = urlparse.parse_qs(s) for k, v in ret.iteritems(): try: - # I'm not sure about the replacement for '-', but it'll at least let it be decoded. - # For the most part it's not important since it's mostly used for the devname/ingamesn fields. - ret[k] = base64.b64decode( urlparse.unquote( v[0] ).replace("*", "=").replace("?", "/").replace(">","+").replace("-","/") ) + # I'm not sure about the replacement for '-', but it'll at + # least let it be decoded. + # For the most part it's not important since it's mostly + # used for the devname/ingamesn fields. + ret[k] = base64.b64decode(urlparse.unquote(v[0]) + .replace("*", "=") + .replace("?", "/") + .replace(">", "+") + .replace("-", "/")) except TypeError: - print "Could not decode following string: ret[%s] = %s" % (k, v[0]) + print "Could not decode following string: ret[%s] = %s" \ + % (k, v[0]) print "url: %s" % s - ret[k] = v[0] # If you don't assign it like this it'll be a list, which breaks other code. + # If you don't assign it like this it'll be a list, which + # breaks other code. + ret[k] = v[0] return ret def dict_to_str(self, dict): + """Convert dict to str. + + nas(wii).nintendowifi.net has a URL query-like format but does not + use encoding for special characters. + """ for k, v in dict.iteritems(): dict[k] = base64.b64encode(v).replace("=", "*") - # nas(wii).nintendowifi.net has a URL query-like format but does not use encoding for special characters - return "&".join("{!s}={!s}".format(k, v) for k, v in dict.items()) + "\r\n" - - # custom selection for generation 5 mystery gifts, so that the random or data-based selection still works properly + return "&".join("{!s}={!s}".format(k, v) for k, v in dict.items()) + \ + "\r\n" + def filter_list_g5_mystery_gift(self, data, rhgamecd): + """Custom selection for generation 5 mystery gifts, so that the random + or data-based selection still works properly.""" if rhgamecd[2] == 'A': filterBit = 0x100000 elif rhgamecd[2] == 'B': @@ -364,7 +469,7 @@ class NasHTTPServerHandler(BaseHTTPServer.BaseHTTPRequestHandler): else: # unknown game, can't filter return data - + output = [] for line in data.splitlines(): lineBits = int(line.split('\t')[3], 16) @@ -373,8 +478,9 @@ class NasHTTPServerHandler(BaseHTTPServer.BaseHTTPRequestHandler): return '\r\n'.join(output) + '\r\n' def filter_list_by_date(self, data, token): - # allow user to control which file to receive by setting the local date - # selected file will be the one at index (day of year) mod (file count) + """Allow user to control which file to receive by setting + the local date selected file will be the one at + index (day of year) mod (file count).""" try: userData = self.server.db.get_nas_login(token) date = time.strptime(userData['devtime'], '%y%m%d%H%M%S') @@ -383,34 +489,42 @@ class NasHTTPServerHandler(BaseHTTPServer.BaseHTTPRequestHandler): except: ret = self.filter_list_random_files(data, 1) return ret - + def filter_list_random_files(self, data, count): - # Get [count] random files from the filelist + """Get [count] random files from the filelist.""" samples = random.sample(data.splitlines(), count) return '\r\n'.join(samples) + '\r\n' - def filter_list(self, data, attr1 = None, attr2 = None, attr3 = None, num = None, offset = None): - if attr1 == None and attr2 == None and attr3 == None and num == None and offset == None: + def filter_list(self, data, attr1=None, attr2=None, attr3=None, + num=None, offset=None): + """Filter the list based on the attribute fields. + + If nothing matches, at least return a newline. + Pokemon BW at least expects this and will error without it. + """ + if attr1 is None and attr2 is None and attr3 is None and \ + num is None and offset is None: # Nothing to filter, just return the input data return data - # Filter the list based on the attribute fields nc = lambda a, b: (a is None or a == b) - attrs = lambda data: (len(data) == 6 and nc(attr1, data[2]) and nc(attr2, data[3]) and nc(attr3, data[4])) - output = filter(lambda line: attrs(line.split("\t")), data.splitlines()) + attrs = lambda data: (len(data) == 6 and nc(attr1, data[2]) and + nc(attr2, data[3]) and nc(attr3, data[4])) + output = filter(lambda line: attrs(line.split("\t")), + data.splitlines()) - if offset != None: + if offset is not None: output = output[offset:] - if num != None: + if num is not None: output = output[:num] - # if nothing matches, at least return a newline; Pokemon BW at least expects this and will error without it return '\r\n'.join(output) + '\r\n' def get_file_count(self, data): return sum(1 for line in data.splitlines() if line) + if __name__ == "__main__": nas = NasServer() nas.start() diff --git a/tools/import_wiimm_data.py b/tools/import_wiimm_data.py index 78f2221..74426cd 100644 --- a/tools/import_wiimm_data.py +++ b/tools/import_wiimm_data.py @@ -47,7 +47,7 @@ for nickfile in glob.glob("data/*-nick"): uniquenick = s[1] # Uncomment to check if the user exists before inserting, but it slows down things greatly. - #if db.check_profile_exists(profileid) != None: + #if db.check_profile_exists(profileid) is not None: # pass firstname = s[2] From cad3fd0518fbc08d179d4f86ad0f102bdcbf68a8 Mon Sep 17 00:00:00 2001 From: payet_s Date: Sat, 1 Aug 2015 13:51:03 +0200 Subject: [PATCH 03/21] Cleaned: internal_stats_server.py --- internal_stats_server.py | 86 +++++++++++++++++++++++++--------------- 1 file changed, 53 insertions(+), 33 deletions(-) diff --git a/internal_stats_server.py b/internal_stats_server.py index 27b991f..88f48d8 100644 --- a/internal_stats_server.py +++ b/internal_stats_server.py @@ -32,32 +32,56 @@ logger_output_to_console = True logger_output_to_file = True logger_name = "InternalStatsServer" logger_filename = "internal_stats_server.log" -logger = utils.create_logger(logger_name, logger_filename, -1, logger_output_to_console, logger_output_to_file) +logger = utils.create_logger(logger_name, logger_filename, -1, + logger_output_to_console, logger_output_to_file) + class GameSpyServerDatabase(BaseManager): pass GameSpyServerDatabase.register("get_server_list") + class StatsPage(resource.Resource): + """Servers statistics webpage. + + Format attributes: + - header + - row + - footer + """ isLeaf = True + header = """ + + + + """ + row = """ + + + + """ # % (game, len(server_list[game])) + footer = """
Game ID# Players
%s
%d
+
+ Last updated: %s
+ """ # % (self.stats.get_last_update_time()) def __init__(self, stats): self.stats = stats def render_GET(self, request): - raw = False - force_update = False - - if '/'.join(request.postpath) == "json": + if "/".join(request.postpath) == "json": raw = True force_update = True + else: + raw = False + force_update = False server_list = self.stats.get_server_list(force_update) - if raw == True: + if raw: # List of keys to be removed - restricted = [ "publicip", "__session__", "localip0", "localip1" ] + restricted = ["publicip", "__session__", "localip0", "localip1"] # Filter out certain fields before displaying raw data if server_list is not None: @@ -70,57 +94,52 @@ class StatsPage(resource.Resource): output = json.dumps(server_list) else: - output = "" - output += "" - output += "" - output += "" - output += "" - + output = self.header if server_list is not None: - for game in server_list: - if not server_list[game]: - continue - - output += "" - output += "" - output += "" % (len(server_list[game])) - output += "" - - output += "
Game ID# Players
" + game + "
%d
" - - output += "
" - output += "Last updated: %s
" % (self.stats.get_last_update_time()) - output += "" + output += "".join(self.row % (game, len(server_list[game])) + for game in server_list + if not server_list[game]) + output += self.footer % (self.stats.get_last_update_time()) return output + class InternalStatsServer(object): + """Internal Statistics server. + + Running on port 9001 by default: http://127.0.0.1:9001/ + Can be displayed in json format: http://127.0.0.1:9001/json""" + def __init__(self): self.last_update = 0 self.next_update = 0 self.server_list = None - self.seconds_per_update = 60 # The number of seconds to wait before updating the server list + # The number of seconds to wait before updating the server list + self.seconds_per_update = 60 def start(self): manager_address = ("127.0.0.1", 27500) manager_password = "" - self.server_manager = GameSpyServerDatabase(address = manager_address, authkey= manager_password) + self.server_manager = GameSpyServerDatabase(address=manager_address, + authkey=manager_password) self.server_manager.connect() site = server.Site(StatsPage(self)) reactor.listenTCP(9001, site) try: - if reactor.running is False: + if not reactor.running: reactor.run(installSignalHandlers=0) except ReactorAlreadyRunning: pass - def get_server_list(self, force_update = False): - if force_update == True or self.next_update == 0 or self.next_update - time.time() <= 0: + def get_server_list(self, force_update=False): + if force_update or self.next_update == 0 or \ + self.next_update - time.time() <= 0: self.last_update = time.time() self.next_update = time.time() + self.seconds_per_update - self.server_list = self.server_manager.get_server_list()._getvalue() + self.server_list = self.server_manager.get_server_list() \ + ._getvalue() logger.log(logging.DEBUG, self.server_list) @@ -129,6 +148,7 @@ class InternalStatsServer(object): def get_last_update_time(self): return str(datetime.datetime.fromtimestamp(self.last_update)) + if __name__ == "__main__": stats = InternalStatsServer() stats.start() From 3090c8089dd44f03439153d015cae70d8b36a863 Mon Sep 17 00:00:00 2001 From: payet_s Date: Sat, 1 Aug 2015 16:11:06 +0200 Subject: [PATCH 04/21] Cleaned: other/utils.py --- other/utils.py | 233 ++++++++++++++++++++++++++++++++++++------------- 1 file changed, 170 insertions(+), 63 deletions(-) diff --git a/other/utils.py b/other/utils.py index 51f7cfc..8b85fa6 100644 --- a/other/utils.py +++ b/other/utils.py @@ -2,6 +2,7 @@ Copyright (C) 2014 polaris- Copyright (C) 2014 msoucy + Copyright (C) 2015 Sepalani This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as @@ -25,42 +26,51 @@ import struct import ctypes import os + def generate_random_str_from_set(ln, chs): + """Generate a random string of size based on charset .""" return ''.join(random.choice(chs) for _ in range(ln)) def generate_random_str(ln, chs=""): - return generate_random_str_from_set(ln, chs or (string.ascii_letters + string.digits)) + """Generate a random string of size .""" + return generate_random_str_from_set( + ln, + chs or (string.ascii_letters + string.digits) + ) def generate_random_number_str(ln): + """Generate a random number string of size .""" return generate_random_str_from_set(ln, string.digits) def generate_random_hex_str(ln): + """Generate a random hexadecimal number string of size .""" return generate_random_str_from_set(ln, string.hexdigits.lower()) -# Code: Tetris DS @ 020573F4 - def calculate_crc8(inp): + """ + Code: Tetris DS @ 020573F4 + """ crc_table = [ - 0x00, 0x07, 0x0E, 0x09, 0x1C, 0x1B, 0x12, 0x15, 0x38, 0x3F, 0x36, 0x31, 0x24, 0x23, 0x2A, 0x2D, - 0x70, 0x77, 0x7E, 0x79, 0x6C, 0x6B, 0x62, 0x65, 0x48, 0x4F, 0x46, 0x41, 0x54, 0x53, 0x5A, 0x5D, - 0xE0, 0xE7, 0xEE, 0xE9, 0xFC, 0xFB, 0xF2, 0xF5, 0xD8, 0xDF, 0xD6, 0xD1, 0xC4, 0xC3, 0xCA, 0xCD, - 0x90, 0x97, 0x9E, 0x99, 0x8C, 0x8B, 0x82, 0x85, 0xA8, 0xAF, 0xA6, 0xA1, 0xB4, 0xB3, 0xBA, 0xBD, - 0xC7, 0xC0, 0xC9, 0xCE, 0xDB, 0xDC, 0xD5, 0xD2, 0xFF, 0xF8, 0xF1, 0xF6, 0xE3, 0xE4, 0xED, 0xEA, - 0xB7, 0xB0, 0xB9, 0xBE, 0xAB, 0xAC, 0xA5, 0xA2, 0x8F, 0x88, 0x81, 0x86, 0x93, 0x94, 0x9D, 0x9A, - 0x27, 0x20, 0x29, 0x2E, 0x3B, 0x3C, 0x35, 0x32, 0x1F, 0x18, 0x11, 0x16, 0x03, 0x04, 0x0D, 0x0A, - 0x57, 0x50, 0x59, 0x5E, 0x4B, 0x4C, 0x45, 0x42, 0x6F, 0x68, 0x61, 0x66, 0x73, 0x74, 0x7D, 0x7A, - 0x89, 0x8E, 0x87, 0x80, 0x95, 0x92, 0x9B, 0x9C, 0xB1, 0xB6, 0xBF, 0xB8, 0xAD, 0xAA, 0xA3, 0xA4, - 0xF9, 0xFE, 0xF7, 0xF0, 0xE5, 0xE2, 0xEB, 0xEC, 0xC1, 0xC6, 0xCF, 0xC8, 0xDD, 0xDA, 0xD3, 0xD4, - 0x69, 0x6E, 0x67, 0x60, 0x75, 0x72, 0x7B, 0x7C, 0x51, 0x56, 0x5F, 0x58, 0x4D, 0x4A, 0x43, 0x44, - 0x19, 0x1E, 0x17, 0x10, 0x05, 0x02, 0x0B, 0x0C, 0x21, 0x26, 0x2F, 0x28, 0x3D, 0x3A, 0x33, 0x34, - 0x4E, 0x49, 0x40, 0x47, 0x52, 0x55, 0x5C, 0x5B, 0x76, 0x71, 0x78, 0x7F, 0x6A, 0x6D, 0x64, 0x63, - 0x3E, 0x39, 0x30, 0x37, 0x22, 0x25, 0x2C, 0x2B, 0x06, 0x01, 0x08, 0x0F, 0x1A, 0x1D, 0x14, 0x13, - 0xAE, 0xA9, 0xA0, 0xA7, 0xB2, 0xB5, 0xBC, 0xBB, 0x96, 0x91, 0x98, 0x9F, 0x8A, 0x8D, 0x84, 0x83, - 0xDE, 0xD9, 0xD0, 0xD7, 0xC2, 0xC5, 0xCC, 0xCB, 0xE6, 0xE1, 0xE8, 0xEF, 0xFA, 0xFD, 0xF4, 0xF3 + 0x00, 0x07, 0x0E, 0x09, 0x1C, 0x1B, 0x12, 0x15, 0x38, 0x3F, 0x36, 0x31, 0x24, 0x23, 0x2A, 0x2D, + 0x70, 0x77, 0x7E, 0x79, 0x6C, 0x6B, 0x62, 0x65, 0x48, 0x4F, 0x46, 0x41, 0x54, 0x53, 0x5A, 0x5D, + 0xE0, 0xE7, 0xEE, 0xE9, 0xFC, 0xFB, 0xF2, 0xF5, 0xD8, 0xDF, 0xD6, 0xD1, 0xC4, 0xC3, 0xCA, 0xCD, + 0x90, 0x97, 0x9E, 0x99, 0x8C, 0x8B, 0x82, 0x85, 0xA8, 0xAF, 0xA6, 0xA1, 0xB4, 0xB3, 0xBA, 0xBD, + 0xC7, 0xC0, 0xC9, 0xCE, 0xDB, 0xDC, 0xD5, 0xD2, 0xFF, 0xF8, 0xF1, 0xF6, 0xE3, 0xE4, 0xED, 0xEA, + 0xB7, 0xB0, 0xB9, 0xBE, 0xAB, 0xAC, 0xA5, 0xA2, 0x8F, 0x88, 0x81, 0x86, 0x93, 0x94, 0x9D, 0x9A, + 0x27, 0x20, 0x29, 0x2E, 0x3B, 0x3C, 0x35, 0x32, 0x1F, 0x18, 0x11, 0x16, 0x03, 0x04, 0x0D, 0x0A, + 0x57, 0x50, 0x59, 0x5E, 0x4B, 0x4C, 0x45, 0x42, 0x6F, 0x68, 0x61, 0x66, 0x73, 0x74, 0x7D, 0x7A, + 0x89, 0x8E, 0x87, 0x80, 0x95, 0x92, 0x9B, 0x9C, 0xB1, 0xB6, 0xBF, 0xB8, 0xAD, 0xAA, 0xA3, 0xA4, + 0xF9, 0xFE, 0xF7, 0xF0, 0xE5, 0xE2, 0xEB, 0xEC, 0xC1, 0xC6, 0xCF, 0xC8, 0xDD, 0xDA, 0xD3, 0xD4, + 0x69, 0x6E, 0x67, 0x60, 0x75, 0x72, 0x7B, 0x7C, 0x51, 0x56, 0x5F, 0x58, 0x4D, 0x4A, 0x43, 0x44, + 0x19, 0x1E, 0x17, 0x10, 0x05, 0x02, 0x0B, 0x0C, 0x21, 0x26, 0x2F, 0x28, 0x3D, 0x3A, 0x33, 0x34, + 0x4E, 0x49, 0x40, 0x47, 0x52, 0x55, 0x5C, 0x5B, 0x76, 0x71, 0x78, 0x7F, 0x6A, 0x6D, 0x64, 0x63, + 0x3E, 0x39, 0x30, 0x37, 0x22, 0x25, 0x2C, 0x2B, 0x06, 0x01, 0x08, 0x0F, 0x1A, 0x1D, 0x14, 0x13, + 0xAE, 0xA9, 0xA0, 0xA7, 0xB2, 0xB5, 0xBC, 0xBB, 0x96, 0x91, 0x98, 0x9F, 0x8A, 0x8D, 0x84, 0x83, + 0xDE, 0xD9, 0xD0, 0xD7, 0xC2, 0xC5, 0xCC, 0xCB, 0xE6, 0xE1, 0xE8, 0xEF, 0xFA, 0xFD, 0xF4, 0xF3 ] crc = 0 @@ -71,33 +81,44 @@ def calculate_crc8(inp): def base32_encode(num, reverse=True): + """Encode a number in base 32. + + Result string is reversed by default. + """ alpha = "0123456789abcdefghijklmnopqrstuv" encoded = "" while num > 0: encoded += alpha[num & 0x1f] - num = num >> 5 + num >>= 5 - while len(encoded) < 9: - encoded += "0" + encoded.ljust(9, '0') - if reverse == True: - encoded = encoded[::-1] # Reverse string + if reverse: + encoded = encoded[::-1] return encoded def base32_decode(s, reverse=False): + """Decode a number in base 32. + + Input string is not reversed by default. + """ alpha = "0123456789abcdefghijklmnopqrstuv" - if reverse == True: - s = s[::-1] # Reverse string + if reverse: + s = s[::-1] - return reduce(lambda orig, b: ((orig<<5)|alpha.index(b)), s, 0) + return reduce(lambda orig, b: ((orig << 5) | alpha.index(b)), s, 0) # Number routines def get_num_from_bytes(data, idx, fmt, bigEndian=False): + """Get number from bytes. + + Endianness by default is little. + """ return struct.unpack_from("<>"[bigEndian] + fmt, buffer(bytearray(data)), idx)[0] # Instead of passing slices, pass the buffer and index so we can calculate @@ -105,59 +126,100 @@ def get_num_from_bytes(data, idx, fmt, bigEndian=False): def get_short_signed(data, idx, be=False): + """Get short from bytes. + + Endianness by default is little. + """ return get_num_from_bytes(data, idx, 'h', be) def get_short(data, idx, be=False): + """Get unsigned short from bytes. + + Endianness by default is little. + """ return get_num_from_bytes(data, idx, 'H', be) def get_int_signed(data, idx, be=False): + """Get int from bytes. + + Endianness by default is little. + """ return get_num_from_bytes(data, idx, 'i', be) def get_int(data, idx, be=False): + """Get unsigned int from bytes. + + Endianness by default is little. + """ return get_num_from_bytes(data, idx, 'I', be) def get_ip(data, idx, be=False): + """Get IP from bytes. + + Endianness by default is little. + """ return ctypes.c_int32(get_int(data, idx, be)).value def get_string(data, idx): + """Get string from bytes.""" data = data[idx:] end = data.index('\x00') return str(''.join(data[:end])) def get_bytes_from_num(num, fmt, bigEndian=False): + """Get bytes from number. + + Endianness by default is little. + """ return struct.pack("<>"[bigEndian] + fmt, num) def get_bytes_from_short_signed(num, be=False): + """Get bytes from short. + + Endianness by default is little. + """ return get_bytes_from_num(num, 'h', be) def get_bytes_from_short(num, be=False): + """Get bytes from unsigned short. + + Endianness by default is little. + """ return get_bytes_from_num(num, 'H', be) def get_bytes_from_int_signed(num, be=False): + """Get bytes from int. + + Endianness by default is little. + """ return get_bytes_from_num(num, 'i', be) def get_bytes_from_int(num, be=False): + """Get bytes from unsigned int. + + Endianness by default is little. + """ return get_bytes_from_num(num, 'I', be) -# For server logging def create_logger(loggername, filename, level, log_to_console, log_to_file): + """Server logging.""" log_folder = "logs" - + # Create log folder if it doesn't exist if not os.path.exists(log_folder): os.makedirs(log_folder) - + # Build full path to log file filename = os.path.join(log_folder, filename) @@ -166,55 +228,100 @@ def create_logger(loggername, filename, level, log_to_console, log_to_file): fmt = "[%(asctime)s | " + loggername + "] %(message)s" date_format = "%Y-%m-%d %H:%M:%S" - #logging.basicConfig(format=format, datefmt=date_format) + # logging.basicConfig(format=format, datefmt=date_format) logger = logging.getLogger(loggername) logger.setLevel(level) # Only needed when logging.basicConfig isn't set. - if log_to_console == True: + if log_to_console: console_logger = logging.StreamHandler() console_logger.setFormatter( - logging.Formatter(fmt, datefmt=date_format)) + logging.Formatter(fmt, datefmt=date_format) + ) logger.addHandler(console_logger) - if log_to_file == True and filename != "": - # Use a rotating log set to rotate every night at midnight with a max of 10 backups - file_logger = logging.handlers.TimedRotatingFileHandler(filename, when='midnight', backupCount=10) #logging.FileHandler(filename) - file_logger.setFormatter(logging.Formatter(fmt, datefmt=date_format)) + if log_to_file and filename: + # Use a rotating log set to rotate every night at midnight with a + # max of 10 backups + file_logger = logging.handlers.TimedRotatingFileHandler( + filename, + when='midnight', + backupCount=10) # logging.FileHandler(filename) + file_logger.setFormatter( + logging.Formatter(fmt, datefmt=date_format) + ) logger.addHandler(file_logger) return logger -def print_hex(data, cols=16): - print pretty_print_hex(data, cols) +def print_hex(data, cols=16, sep=' ', pretty=True): + """Print data in hexadecimal. + + Customizable separator and columns number. + Can be pretty printed but takes more time. + """ + if pretty: + print pretty_print_hex(data, cols, sep) + else: + print sep.join("%02x" % b for b in bytearray(data)) -def pretty_print_hex(orig_data, cols=16): +def pretty_print_hex(orig_data, cols=16, sep=' '): + """Hexadecimal pretty print. + + Takes ~1s per characters. + Customizable separator and columns number. + """ data = bytearray(orig_data) - output = "\n" + end = len(data) + line = "%08x | %-*s | %s\n" + size = cols * 3 - 1 - for i in range(len(data) / cols + 1): - output += "%08x | " % (i * 16) - - c = 0 - for x in range(cols): - if (i * cols + x + 1) > len(data): - break - - output += "%02x " % data[i * cols + x] - c += 1 - - c = cols - c - output += " " * (c * 3 + 1) - for x in range(cols): - if (i * cols + x + 1) > len(data): - break - - if data[i * cols + x] < 0x21 or data[i * cols + x] >= 0x7f: - output += "." - else: - output += "%c" % data[i * cols + x] - output += "\n" + i = 0 + output = "" + while i < end: + j = i + cols if i + cols <= end \ + else end - i + output += line % ( + i, + size, + sep.join("%02x" % c for c in data[i:j]), + "".join(chr(c) if chr(c) in string.printable else + '.' + for c in data[i:j]) + ) + i += cols return output + +# def pretty_print_hex(orig_data, cols=16): +# """Takes ~1.5s per characters""" +# +# data = bytearray(orig_data) +# output = "\n" +# +# for i in range(len(data) / cols + 1): +# output += "%08x | " % (i * 16) +# +# c = 0 +# for x in range(cols): +# if (i * cols + x + 1) > len(data): +# break +# +# output += "%02x " % data[i * cols + x] +# c += 1 +# +# c = cols - c +# output += " " * (c * 3 + 1) +# for x in range(cols): +# if (i * cols + x + 1) > len(data): +# break +# +# if not chr(data[i * cols + x]) in string.printable: +# output += "." +# else: +# output += "%c" % data[i * cols + x] +# output += "\n" +# +# return output From 73202b080ce219e31e414293a45b73b34481b8ed Mon Sep 17 00:00:00 2001 From: payet_s Date: Sun, 2 Aug 2015 14:14:05 +0200 Subject: [PATCH 05/21] Cleaned: gamespy/gs_utility.py --- gamespy/gs_utility.py | 120 ++++++++++++++++++++++++++++++------------ 1 file changed, 87 insertions(+), 33 deletions(-) diff --git a/gamespy/gs_utility.py b/gamespy/gs_utility.py index 488ff88..72ddfb1 100644 --- a/gamespy/gs_utility.py +++ b/gamespy/gs_utility.py @@ -26,11 +26,18 @@ import time import other.utils as utils + def generate_secret_keys(filename="gslist.cfg"): + """Generate list of secret keys based on a config file. + + gslist.cfg is the default config file and may be incomplete. + TODO: Parse the config file in a cleaner way. (ex: using CSV module) + """ secret_key_list = {} with open(filename) as key_file: for line in key_file.readlines(): - #name = line[:54].strip() # Probably won't do anything with the name for now. + # name = line[:54].strip() + # Probably won't do anything with the name for now. id = line[54:54+19].strip() key = line[54+19:].strip() @@ -38,23 +45,37 @@ def generate_secret_keys(filename="gslist.cfg"): return secret_key_list -# GameSpy uses a slightly modified version of base64 which replaces +/= with []_ + def base64_encode(input): - output = base64.b64encode(input).replace('+', '[').replace('/', ']').replace('=', '_') + """Encode input in base64 using GameSpy variant. + + GameSpy uses a slightly modified version of base64 which replaces + +/= with []_ + """ + output = base64.b64encode(input).replace('+', '[') \ + .replace('/', ']') \ + .replace('=', '_') return output def base64_decode(input): - output = base64.b64decode(input.replace('[', '+').replace(']', '/').replace('_', '=')) + """Decode input in base64 using GameSpy variant.""" + output = base64.b64decode(input.replace('[', '+') + .replace(']', '/') + .replace('_', '=')) return output -# Tetris DS overlay 10 @ 0216E9B8 + def rc4_encrypt(_key, _data): + """ + Tetris DS overlay 10 @ 0216E9B8 + """ key = bytearray(_key) data = bytearray(_data) if len(key) == 0: - # This shouldn't happen but it apparently can on a rare occasion. key should always be set. + # This shouldn't happen but it apparently can on a rare occasion. + # Key should always be set. return # Key-scheduling algorithm @@ -71,8 +92,9 @@ def rc4_encrypt(_key, _data): # Pseudo-random generation algorithm + encryption i = 0 j = 0 - for x in range(len(data)): - i = (i + 1 + data[x]) & 0xff # Modified RC4? What's this data[x] doing here? + for x, val in enumerate(data): + # Modified RC4? + i = (i + 1 + val) & 0xff j = (j + S[i]) & 0xff S[i], S[j] = S[j], S[i] @@ -81,10 +103,13 @@ def rc4_encrypt(_key, _data): return data -# Tetris DS overlay 10 @ 0216E9B8 -# Used by the master server to send some data between the client and server. -# This seems to be what Luigi Auriemma calls "Gsmsalg". + def prepare_rc4_base64(_key, _data): + """Tetris DS overlay 10 @ 0216E9B8 + + Used by the master server to send some data between the client and server. + This seems to be what Luigi Auriemma calls "Gsmsalg". + """ data = rc4_encrypt(_key, _data) if data is None: @@ -94,28 +119,31 @@ def prepare_rc4_base64(_key, _data): return base64.b64encode(buffer(data)) -# get the login data from nas.nintendowifi.net/ac from an authtoken + def parse_authtoken(authtoken, db): + """Get the login data from nas.nintendowifi.net/ac from an authtoken""" return db.get_nas_login(authtoken) + def login_profile_via_parsed_authtoken(authtoken_parsed, db): console = 0 userid = authtoken_parsed['userid'] - csnum = authtoken_parsed.get('csnum', '') # Wii: Serial number - cfc = authtoken_parsed.get('cfc', '') # Wii: Friend code - bssid = authtoken_parsed.get('bssid', '') # NDS: Wifi network's BSSID - devname = authtoken_parsed.get('devname', '') # NDS: Device name - birth = authtoken_parsed.get('birth', '') # NDS: User's birthday + csnum = authtoken_parsed.get('csnum', '') # Wii: Serial number + cfc = authtoken_parsed.get('cfc', '') # Wii: Friend code + bssid = authtoken_parsed.get('bssid', '') # NDS: Wifi network's BSSID + devname = authtoken_parsed.get('devname', '') # NDS: Device name + birth = authtoken_parsed.get('birth', '') # NDS: User's birthday - # The Wii does not use passwd, so take another uniquely generated string as the password. + # The Wii does not use passwd, so take another uniquely generated string + # as the password. # if "passwd" in authtoken_parsed: # password = authtoken_parsed['passwd'] # else: # password = authtoken_parsed['gsbrcd'] # console = 1 - if not "passwd" in authtoken_parsed: + if "passwd" not in authtoken_parsed: console = 1 password = authtoken_parsed['gsbrcd'] @@ -123,7 +151,7 @@ def login_profile_via_parsed_authtoken(authtoken_parsed, db): gameid = gsbrcd[:4] macadr = authtoken_parsed['macadr'] uniquenick = utils.base32_encode(int(userid)) + gsbrcd - email = uniquenick + "@nds" # The Wii also seems to use @nds. + email = uniquenick + "@nds" # The Wii also seems to use @nds. if "csnum" in authtoken_parsed: console = 1 @@ -132,13 +160,17 @@ def login_profile_via_parsed_authtoken(authtoken_parsed, db): valid_user = db.check_user_exists(userid, gsbrcd) if valid_user is False: - profileid = db.create_user(userid, password, email, uniquenick, gsbrcd, console, csnum, cfc, bssid, devname, birth, gameid, macadr) + profileid = db.create_user(userid, password, email, uniquenick, + gsbrcd, console, csnum, cfc, bssid, + devname, birth, gameid, macadr) else: profileid = db.perform_login(userid, password, gsbrcd) return userid, profileid, gsbrcd, uniquenick + def generate_response(challenge, ac_challenge, secretkey, authtoken): + """Generate a challenge response.""" md5 = hashlib.md5() md5.update(ac_challenge) @@ -155,9 +187,14 @@ def generate_response(challenge, ac_challenge, secretkey, authtoken): return md5_2.hexdigest() -# The proof is practically the same thing as the response, except it has the challenge and the secret key swapped. -# Maybe combine the two functions later? def generate_proof(challenge, ac_challenge, secretkey, authtoken): + """Generate a challenge proof. + + The proof is practically the same thing as the response, except it has + the challenge and the secret key swapped. + + Maybe combine the two functions later? + """ md5 = hashlib.md5() md5.update(ac_challenge) @@ -173,8 +210,11 @@ def generate_proof(challenge, ac_challenge, secretkey, authtoken): return md5_2.hexdigest() -# Code: Tetris DS @ 02057A14 + def get_friendcode_from_profileid(profileid, gameid): + """ + Code: Tetris DS @ 02057A14 + """ friendcode = 0 # Combine the profileid and gameid into one buffer @@ -189,14 +229,20 @@ def get_friendcode_from_profileid(profileid, gameid): return friendcode + def get_profileid_from_friendcode(friendcode): + """Return profile ID from Friend Code.""" # Get the lower 32 bits as the profile id profileid = friendcode & 0xffffffff return profileid -# Code from Luigi Auriemma's enctypex_decoder.c -# It's kind of sloppy in parts, but it works. Unless there's some issues then it'll probably not change any longer. + class EncTypeX: + """Code from Luigi Auriemma's enctypex_decoder.c + + It's kind of sloppy in parts, but it works. Unless there's some issues + then it'll probably not change any longer. + """ def __init__(self): return @@ -214,7 +260,8 @@ class EncTypeX: if not key or not validate or not data: return None - # Convert data from strings to byte arrays before use or else it'll raise an error + # Convert data from strings to byte arrays before use or else + # it'll raise an error key = bytearray(key) validate = bytearray(validate) @@ -236,15 +283,16 @@ class EncTypeX: data[2] = 0x00 data[header_len - 1] = (tmp_len - header_len) ^ 0xea - header = data[:tmp_len] # The header of the data gets chopped off in init(), so save it + # The header of the data gets chopped off in init(), so save it + header = data[:tmp_len] encxkey = bytearray([0] * 261) data = self.init(encxkey, key, validate, data) self.func6e(encxkey, data, len(data)) - # Reappend header that we saved earlier before returning to make the complete buffer + # Reappend header that we saved earlier before returning to make + # the complete buffer return header + data - def init(self, encxkey, key, validate, data): data_len = len(data) @@ -259,9 +307,15 @@ class EncTypeX: if data_len < (header_len + data_start): return None - data = self.enctypex_funcx(encxkey, bytearray(key), bytearray(validate), data[header_len:], data_start) - return data[data_start:] + data = self.enctypex_funcx( + encxkey, + bytearray(key), + bytearray(validate), + data[header_len:], + data_start + ) + return data[data_start:] def enctypex_funcx(self, encxkey, key, validate, data, datalen): keylen = len(key) @@ -281,7 +335,7 @@ class EncTypeX: n1 = 0 n2 = 0 - for i in range(255,-1,-1): + for i in range(255, -1, -1): t1, n1, n2 = self.func5(encxkey, i, id, idlen, n1, n2) t2 = encxkey[i] encxkey[i] = encxkey[t1] From 9066a5f1e58cc93577e3fb5d58aa48dc0ee93e95 Mon Sep 17 00:00:00 2001 From: payet_s Date: Sun, 2 Aug 2015 16:44:40 +0200 Subject: [PATCH 06/21] Cleaned: gamespy/gs_query.py --- gamespy/gs_query.py | 40 +++++++++++++++++++++++----------------- 1 file changed, 23 insertions(+), 17 deletions(-) diff --git a/gamespy/gs_query.py b/gamespy/gs_query.py index 727447c..bcb443c 100644 --- a/gamespy/gs_query.py +++ b/gamespy/gs_query.py @@ -21,14 +21,17 @@ import copy + def parse_gamespy_message(message): + """Parse a GameSpy message.""" stack = [] messages = {} msg = message while len(msg) > 0 and msg[0] == '\\' and "\\final\\" in msg: # Find the command - # Don't search for more commands if there isn't a \final\, save the left over for the next packet + # Don't search for more commands if there isn't a \final\, save the + # left over for the next packet found_command = False while len(msg) > 0 and msg[0] == '\\': keyEnd = msg[1:].index('\\') + 1 @@ -48,7 +51,7 @@ def parse_gamespy_message(message): else: value = msg - if found_command is False: + if not found_command: messages['__cmd__'] = key messages['__cmd_val__'] = value found_command = True @@ -62,37 +65,39 @@ def parse_gamespy_message(message): return stack, msg -# Generate a list based on the input dictionary. -# The main command must also be stored in __cmd__ for it to put the parameter at the beginning. def create_gamespy_message_from_dict(messages_orig): - # Deep copy the dictionary because we don't want the original to be modified - messages = copy.deepcopy(messages_orig) + """Generate a list based on the input dictionary. - cmd = "" - cmd_val = "" + The main command must also be stored in __cmd__ for it to put the + parameter at the beginning. + """ + # Deep copy the dictionary because we don't want the original to be + # modified + messages = copy.deepcopy(messages_orig) if "__cmd__" in messages: cmd = messages['__cmd__'] messages.pop('__cmd__', None) + else: + cmd = "" if "__cmd_val__" in messages: cmd_val = messages['__cmd_val__'] messages.pop('__cmd_val__', None) + else: + cmd_val = "" if cmd in messages: messages.pop(cmd, None) - l = [] - l.append(("__cmd__", cmd)) - l.append(("__cmd_val__", cmd_val)) - - for message in messages: - l.append((message, messages[message])) + l = [("__cmd__", cmd), ("__cmd_val__", cmd_val)] + l.extend([(message, messages[message]) for message in messages]) return l def create_gamespy_message_from_list(messages): + """Generate a string based on the input list.""" d = {} cmd = "" cmd_val = "" @@ -104,17 +109,18 @@ def create_gamespy_message_from_list(messages): elif message[0] == "__cmd_val__": cmd_val = str(message[1]).strip('\\') else: - query += "\\%s\\%s" % (str(message[0]).strip('\\'), str(message[1]).strip('\\')) + query += "\\%s\\%s" % (str(message[0]).strip('\\'), + str(message[1]).strip('\\')) - if cmd != "": + if cmd: # Prepend the main command if one was found. query = "\\%s\\%s%s" % (cmd, cmd_val, query) return query -# Create a message based on a dictionary (or list) of parameters. def create_gamespy_message(messages, id=None): + """Create a message based on a dictionary (or list) of parameters.""" query = "" if isinstance(messages, dict): From c849c8afd1717e01809c6538df8eeacc9f01979c Mon Sep 17 00:00:00 2001 From: payet_s Date: Sun, 2 Aug 2015 17:54:03 +0200 Subject: [PATCH 07/21] Cleaned: gamespy/gs_database.py --- gamespy/gs_database.py | 468 ++++++++++++++++++++++++++++------------- 1 file changed, 323 insertions(+), 145 deletions(-) diff --git a/gamespy/gs_database.py b/gamespy/gs_database.py index 204c53c..79bacce 100644 --- a/gamespy/gs_database.py +++ b/gamespy/gs_database.py @@ -36,7 +36,9 @@ logger_output_to_console = True logger_output_to_file = True logger_name = "GamespyDatabase" logger_filename = "gamespy_database.log" -logger = utils.create_logger(logger_name, logger_filename, -1, logger_output_to_console, logger_output_to_file) +logger = utils.create_logger(logger_name, logger_filename, -1, + logger_output_to_console, logger_output_to_file) + class Transaction(object): def __init__(self, connection): @@ -54,7 +56,8 @@ class Transaction(object): def _executeAndMeasure(self, cursor, statement, parameters): logTransactionId = utils.generate_random_str(8) - logger.log(SQL_LOGLEVEL, "[%s] STARTING: " % logTransactionId + statement.replace('?', '%s') % parameters) + logger.log(SQL_LOGLEVEL, "[%s] STARTING: " % logTransactionId + + statement.replace('?', '%s') % parameters) timeStart = time.time() clockStart = time.clock() @@ -65,10 +68,16 @@ class Transaction(object): timeEnd = time.time() timeDiff = timeEnd - timeStart - logger.log(SQL_LOGLEVEL, "[%s] DONE: Took %s real time / %s processor time", logTransactionId, timeDiff, clockEnd - clockStart) + logger.log(SQL_LOGLEVEL, + "[%s] DONE: Took %s real time / %s processor time", + logTransactionId, timeDiff, clockEnd - clockStart) if timeDiff > 1.0: - logger.log(logging.WARNING, "[%s] WARNING: SQL Statement took %s seconds!", logTransactionId, timeDiff) - logger.log(logging.WARNING, "[%s] " % logTransactionId + statement.replace('?', '%s') % parameters) + logger.log(logging.WARNING, + "[%s] WARNING: SQL Statement took %s seconds!", + logTransactionId, timeDiff) + logger.log(logging.WARNING, + "[%s] " % logTransactionId + + statement.replace('?', '%s') % parameters) return def queryall(self, statement, parameters=()): @@ -91,12 +100,13 @@ class Transaction(object): self.databaseAltered = True return + class GamespyDatabase(object): def __init__(self, filename='gpcm.db'): self.conn = sqlite3.connect(filename, timeout=10.0) self.conn.row_factory = sqlite3.Row - #self.initialize_database() + # self.initialize_database() def __del__(self): self.close() @@ -108,34 +118,72 @@ class GamespyDatabase(object): def initialize_database(self): with Transaction(self.conn) as tx: - # I highly doubt having everything in a database be of the type TEXT is a good practice, - # but I'm not good with databases and I'm not 100% positive that, for instance, that all - # user id's will be ints, or all passwords will be ints, etc, despite not seeing any - # evidence yet to say otherwise as far as Nintendo DS games go. + # I highly doubt having everything in a database be of the type + # TEXT is a good practice, but I'm not good with databases and + # I'm not 100% positive that, for instance, that all user id's + # will be ints, or all passwords will be ints, etc, despite not + # seeing any evidence yet to say otherwise as far as Nintendo + # DS games go. - tx.nonquery("CREATE TABLE IF NOT EXISTS users (profileid INT, userid TEXT, password TEXT, gsbrcd TEXT, email TEXT, uniquenick TEXT, pid TEXT, lon TEXT, lat TEXT, loc TEXT, firstname TEXT, lastname TEXT, stat TEXT, partnerid TEXT, console INT, csnum TEXT, cfc TEXT, bssid TEXT, devname BLOB, birth TEXT, gameid TEXT, enabled INT, zipcode TEXT, aim TEXT)") - tx.nonquery("CREATE TABLE IF NOT EXISTS sessions (session TEXT, profileid INT, loginticket TEXT)") - tx.nonquery("CREATE TABLE IF NOT EXISTS buddies (userProfileId INT, buddyProfileId INT, time INT, status INT, notified INT, gameid TEXT, blocked INT)") - tx.nonquery("CREATE TABLE IF NOT EXISTS pending_messages (sourceid INT, targetid INT, msg TEXT)") - tx.nonquery("CREATE TABLE IF NOT EXISTS gamestat_profile (profileid INT, dindex TEXT, ptype TEXT, data TEXT)") - tx.nonquery("CREATE TABLE IF NOT EXISTS gameinfo (profileid INT, dindex TEXT, ptype TEXT, data TEXT)") - tx.nonquery("CREATE TABLE IF NOT EXISTS nas_logins (userid TEXT, authtoken TEXT, data TEXT)") - tx.nonquery("CREATE TABLE IF NOT EXISTS banned (gameid TEXT, ipaddr TEXT)") + tx.nonquery("CREATE TABLE IF NOT EXISTS users" + " (profileid INT, userid TEXT, password TEXT," + " gsbrcd TEXT, email TEXT, uniquenick TEXT," + " pid TEXT, lon TEXT, lat TEXT, loc TEXT," + " firstname TEXT, lastname TEXT, stat TEXT," + " partnerid TEXT, console INT, csnum TEXT," + " cfc TEXT, bssid TEXT, devname BLOB, birth TEXT," + " gameid TEXT, enabled INT, zipcode TEXT, aim TEXT)") + tx.nonquery("CREATE TABLE IF NOT EXISTS sessions" + " (session TEXT, profileid INT, loginticket TEXT)") + tx.nonquery("CREATE TABLE IF NOT EXISTS buddies" + " (userProfileId INT, buddyProfileId INT, time INT," + " status INT, notified INT, gameid TEXT," + " blocked INT)") + tx.nonquery("CREATE TABLE IF NOT EXISTS pending_messages" + " (sourceid INT, targetid INT, msg TEXT)") + tx.nonquery("CREATE TABLE IF NOT EXISTS gamestat_profile" + " (profileid INT, dindex TEXT, ptype TEXT," + " data TEXT)") + tx.nonquery("CREATE TABLE IF NOT EXISTS gameinfo" + " (profileid INT, dindex TEXT, ptype TEXT," + " data TEXT)") + tx.nonquery("CREATE TABLE IF NOT EXISTS nas_logins" + " (userid TEXT, authtoken TEXT, data TEXT)") + tx.nonquery("CREATE TABLE IF NOT EXISTS banned" + " (gameid TEXT, ipaddr TEXT)") tx.nonquery("CREATE TABLE IF NOT EXISTS pending (macadr TEXT)") tx.nonquery("CREATE TABLE IF NOT EXISTS registered (macadr TEXT)") + # Create some indexes for performance. - tx.nonquery("CREATE UNIQUE INDEX IF NOT EXISTS gamestatprofile_triple on gamestat_profile(profileid,dindex,ptype)") - tx.nonquery("CREATE UNIQUE INDEX IF NOT EXISTS users_profileid_idx ON users (profileid)") - tx.nonquery("CREATE INDEX IF NOT EXISTS users_userid_idx ON users (userid)") - tx.nonquery("CREATE INDEX IF NOT EXISTS pending_messages_targetid_idx ON pending_messages (targetid)") - tx.nonquery("CREATE UNIQUE INDEX IF NOT EXISTS sessions_session_idx ON sessions (session)") - tx.nonquery("CREATE INDEX IF NOT EXISTS sessions_loginticket_idx ON sessions (loginticket)") - tx.nonquery("CREATE INDEX IF NOT EXISTS sessions_profileid_idx ON sessions (profileid)") - tx.nonquery("CREATE UNIQUE INDEX IF NOT EXISTS nas_logins_authtoken_idx ON nas_logins (authtoken)") - tx.nonquery("CREATE INDEX IF NOT EXISTS nas_logins_userid_idx ON nas_logins (userid)") - tx.nonquery("CREATE INDEX IF NOT EXISTS buddies_userProfileId_idx ON buddies (userProfileId)") - tx.nonquery("CREATE INDEX IF NOT EXISTS buddies_buddyProfileId_idx ON buddies (buddyProfileId)") - tx.nonquery("CREATE INDEX IF NOT EXISTS gamestat_profile_profileid_idx ON gamestat_profile (profileid)") + tx.nonquery("CREATE UNIQUE INDEX IF NOT EXISTS" + " gamestatprofile_triple" + " ON gamestat_profile(profileid,dindex,ptype)") + tx.nonquery("CREATE UNIQUE INDEX IF NOT EXISTS" + " users_profileid_idx ON users (profileid)") + tx.nonquery("CREATE INDEX IF NOT EXISTS" + " users_userid_idx ON users (userid)") + tx.nonquery("CREATE INDEX IF NOT EXISTS" + " pending_messages_targetid_idx" + " ON pending_messages (targetid)") + tx.nonquery("CREATE UNIQUE INDEX IF NOT EXISTS" + " sessions_session_idx ON sessions (session)") + tx.nonquery("CREATE INDEX IF NOT EXISTS" + " sessions_loginticket_idx ON sessions (loginticket)") + tx.nonquery("CREATE INDEX IF NOT EXISTS" + " sessions_profileid_idx ON sessions (profileid)") + tx.nonquery("CREATE UNIQUE INDEX IF NOT EXISTS" + " nas_logins_authtoken_idx ON nas_logins (authtoken)") + tx.nonquery("CREATE INDEX IF NOT EXISTS" + " nas_logins_userid_idx ON nas_logins (userid)") + tx.nonquery("CREATE INDEX IF NOT EXISTS" + " buddies_userProfileId_idx" + " ON buddies (userProfileId)") + tx.nonquery("CREATE INDEX IF NOT EXISTS" + " buddies_buddyProfileId_idx" + " ON buddies (buddyProfileId)") + tx.nonquery("CREATE INDEX IF NOT EXISTS" + " gamestat_profile_profileid_idx" + " ON gamestat_profile (profileid)") def get_dict(self, row): if not row: @@ -145,15 +193,17 @@ class GamespyDatabase(object): # User functions def get_next_free_profileid(self): - # TODO: Make profile ids start at 1 for each game? + """TODO: Make profile ids start at 1 for each game? - # TODO: This leads to a race condition if two users try to create accounts at the same time. - # Instead, it's better to create a new row and return the sqlite ROWID instead. + TODO: This leads to a race condition if two users try to create + accounts at the same time. Instead, it's better to create a new row + and return the sqlite ROWID instead. + """ with Transaction(self.conn) as tx: row = tx.queryone("SELECT max(profileid) AS m FROM users") r = self.get_dict(row) - profileid = 1 # Cannot be 0 or else it freezes the game. + profileid = 1 # Cannot be 0 or else it freezes the game. if r is not None and r['m'] is not None: profileid = int(r['m']) + 1 @@ -161,7 +211,10 @@ class GamespyDatabase(object): def check_user_exists(self, userid, gsbrcd): with Transaction(self.conn) as tx: - row = tx.queryone("SELECT COUNT(*) FROM users WHERE userid = ? AND gsbrcd = ?", (userid, gsbrcd)) + row = tx.queryone( + "SELECT COUNT(*) FROM users WHERE userid = ? AND gsbrcd = ?", + (userid, gsbrcd) + ) count = int(row[0]) valid_user = False # Default, user doesn't exist @@ -172,13 +225,19 @@ class GamespyDatabase(object): def check_user_enabled(self, userid, gsbrcd): with Transaction(self.conn) as tx: - row = tx.queryone("SELECT enabled FROM users WHERE userid = ? AND gsbrcd = ?", (userid, gsbrcd)) + row = tx.queryone( + "SELECT enabled FROM users WHERE userid = ? AND gsbrcd = ?", + (userid, gsbrcd) + ) enabled = int(row[0]) return enabled > 0 def check_profile_exists(self, profileid): with Transaction(self.conn) as tx: - row = tx.queryone("SELECT COUNT(*) FROM users WHERE profileid = ?", (profileid,)) + row = tx.queryone( + "SELECT COUNT(*) FROM users WHERE profileid = ?", + (profileid,) + ) count = int(row[0]) valid_profile = False # Default, user doesn't exist @@ -191,36 +250,44 @@ class GamespyDatabase(object): profile = {} if profileid != 0: with Transaction(self.conn) as tx: - row = tx.queryone("SELECT * FROM users WHERE profileid = ?", (profileid,)) + row = tx.queryone( + "SELECT * FROM users WHERE profileid = ?", + (profileid,) + ) profile = self.get_dict(row) return profile def perform_login(self, userid, password, gsbrcd): with Transaction(self.conn) as tx: - row = tx.queryone("SELECT * FROM users WHERE userid = ? and gsbrcd = ?", (userid, gsbrcd)) + row = tx.queryone( + "SELECT * FROM users WHERE userid = ? and gsbrcd = ?", + (userid, gsbrcd) + ) r = self.get_dict(row) profileid = None # Default, user doesn't exist if r is not None: - #md5 = hashlib.md5() - #md5.update(password) + # md5 = hashlib.md5() + # md5.update(password) - #if r['password'] == md5.hexdigest(): - # profileid = r['profileid'] # Valid password + # if r['password'] == md5.hexdigest(): + # profileid = r['profileid'] # Valid password if r['enabled'] == 1 and r['gsbrcd'] == gsbrcd: profileid = r['profileid'] # Valid password return profileid - def create_user(self, userid, password, email, uniquenick, gsbrcd, console, csnum, cfc, bssid, devname, birth, gameid, macadr): - + def create_user(self, userid, password, email, uniquenick, gsbrcd, + console, csnum, cfc, bssid, devname, birth, gameid, + macadr): if self.check_user_exists(userid, gsbrcd) == 0: profileid = self.get_next_free_profileid() - pid = "11" # Always 11??? Is this important? Not to be confused with dwc_pid. - # The three games I found it in (Tetris DS, Advance Wars - Days of Ruin, and - # Animal Crossing: Wild World) all use \pid\11. + # Always 11??? Is this important? Not to be confused with dwc_pid. + # The three games I found it in (Tetris DS, Advance Wars - Days of + # Ruin, and Animal Crossing: Wild World) all use \pid\11. + pid = "11" lon = "0.000000" # Always 0.000000? lat = "0.000000" # Always 0.000000? loc = "" @@ -234,19 +301,26 @@ class GamespyDatabase(object): # Hash password before entering it into the database. # For now I'm using a very simple MD5 hash. - # TODO: Replace with something stronger later, although it's overkill for the NDS. + # TODO: Replace with something stronger later, although it's + # overkill for the NDS. md5 = hashlib.md5() md5.update(password) password = md5.hexdigest() with Transaction(self.conn) as tx: - q = "INSERT INTO users VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)" - tx.nonquery(q, (profileid, str(userid), password, gsbrcd, email, uniquenick, pid, lon, lat, loc, firstname, lastname, stat, partnerid, console, csnum, cfc, bssid, devname, birth, gameid, enabled, zipcode, aim)) + q = "INSERT INTO users VALUES" \ + " (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)" + tx.nonquery(q, (profileid, str(userid), password, gsbrcd, + email, uniquenick, pid, lon, lat, loc, + firstname, lastname, stat, partnerid, + console, csnum, cfc, bssid, devname, birth, + gameid, enabled, zipcode, aim)) return profileid return None - def import_user(self, profileid, uniquenick, firstname, lastname, email, gsbrcd, gameid, console): + def import_user(self, profileid, uniquenick, firstname, lastname, email, + gsbrcd, gameid, console): if self.check_profile_exists(profileid) == 0: pid = "11" lon = "0.000000" @@ -268,8 +342,13 @@ class GamespyDatabase(object): enabled = 1 with Transaction(self.conn) as tx: - q = "INSERT INTO users VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)" - tx.nonquery(q, (profileid, str(userid), password, gsbrcd, email, uniquenick, pid, lon, lat, loc, firstname, lastname, stat, partnerid, console, csnum, cfc, bssid, devname, birth, gameid, enabled, zipcode, aim)) + q = "INSERT INTO users VALUES" \ + " (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)" + tx.nonquery(q, (profileid, str(userid), password, gsbrcd, + email, uniquenick, pid, lon, lat, loc, + firstname, lastname, stat, partnerid, + console, csnum, cfc, bssid, devname, birth, + gameid, enabled, zipcode, aim)) return profileid @@ -277,40 +356,43 @@ class GamespyDatabase(object): with Transaction(self.conn) as tx: rows = tx.queryall("SELECT * FROM users") - users = [] - for row in rows: - users.append(self.get_dict(row)) - - return users + return [self.get_dict(row) for row in rows] def save_pending_message(self, sourceid, targetid, msg): with Transaction(self.conn) as tx: - tx.nonquery("INSERT INTO pending_messages VALUES (?,?,?)", (sourceid, targetid, msg)) + tx.nonquery("INSERT INTO pending_messages VALUES (?,?,?)", + (sourceid, targetid, msg)) def get_pending_messages(self, profileid): with Transaction(self.conn) as tx: - rows = tx.queryall("SELECT * FROM pending_messages WHERE targetid = ?", (profileid,)) + rows = tx.queryall( + "SELECT * FROM pending_messages WHERE targetid = ?", + (profileid,) + ) - messages = [] - for row in rows: - messages.append(self.get_dict(row)) - - return messages + return [self.get_dict(row) for row in rows] def update_profile(self, profileid, field): - # Found profile id associated with session key. - # Start replacing each field one by one. - # TODO: Optimize this so it's done all in one update. - # FIXME: Possible security issue due to embedding an unsanitized string directly into the statement. + """Found profile id associated with session key. + + Start replacing each field one by one. + TODO: Optimize this so it's done all in one update. + FIXME: Possible security issue due to embedding an unsanitized + string directly into the statement. + """ with Transaction(self.conn) as tx: q = "UPDATE users SET \"%s\" = ? WHERE profileid = ?" tx.nonquery(q % field[0], (field[1], profileid)) # Session functions - # TODO: Cache session keys so we don't have to query the database every time we get a profile id. + # TODO: Cache session keys so we don't have to query the database every + # time we get a profile id. def get_profileid_from_session_key(self, session_key): with Transaction(self.conn) as tx: - row = tx.queryone("SELECT profileid FROM sessions WHERE session = ?", (session_key,)) + row = tx.queryone( + "SELECT profileid FROM sessions WHERE session = ?", + (session_key,) + ) r = self.get_dict(row) profileid = -1 # Default, invalid session key @@ -321,7 +403,10 @@ class GamespyDatabase(object): def get_profileid_from_loginticket(self, loginticket): with Transaction(self.conn) as tx: - row = tx.queryone("SELECT profileid FROM sessions WHERE loginticket = ?", (loginticket,)) + row = tx.queryone( + "SELECT profileid FROM sessions WHERE loginticket = ?", + (loginticket,) + ) profileid = -1 if row: @@ -335,28 +420,40 @@ class GamespyDatabase(object): profile = {} if profileid != 0: with Transaction(self.conn) as tx: - row = tx.queryone("SELECT profileid FROM sessions WHERE session = ?", (session_key,)) + row = tx.queryone( + "SELECT profileid FROM sessions WHERE session = ?", + (session_key,) + ) profile = self.get_dict(row) return profile def generate_session_key(self, min_size): - # TODO: There's probably a better way to do this. - # The point is preventing duplicate session keys. + """Generate session key. + + TODO: There's probably a better way to do this. + The point is preventing duplicate session keys. + """ while True: with Transaction(self.conn) as tx: session_key = utils.generate_random_number_str(min_size) - row = tx.queryone("SELECT COUNT(*) FROM sessions WHERE session = ?", (session_key,)) + row = tx.queryone( + "SELECT COUNT(*) FROM sessions WHERE session = ?", + (session_key,) + ) count = int(row[0]) if count == 0: return session_key def delete_session(self, profileid): with Transaction(self.conn) as tx: - tx.nonquery("DELETE FROM sessions WHERE profileid = ?", (profileid,)) + tx.nonquery( + "DELETE FROM sessions WHERE profileid = ?", + (profileid,) + ) def create_session(self, profileid, loginticket): - if profileid is not None and self.check_profile_exists(profileid) is False: + if profileid is not None and not self.check_profile_exists(profileid): return None # Remove any old sessions associated with this user id @@ -365,27 +462,32 @@ class GamespyDatabase(object): # Create new session session_key = self.generate_session_key(8) with Transaction(self.conn) as tx: - tx.nonquery("INSERT INTO sessions VALUES (?, ?, ?)", (session_key, profileid, loginticket)) + tx.nonquery( + "INSERT INTO sessions VALUES (?, ?, ?)", + (session_key, profileid, loginticket) + ) return session_key def get_session_list(self, profileid=None): - sessions = [] with Transaction(self.conn) as tx: if profileid is not None: - r = tx.queryall("SELECT * FROM sessions WHERE profileid = ?", (profileid,)) + r = tx.queryall( + "SELECT * FROM sessions WHERE profileid = ?", + (profileid,) + ) else: r = tx.queryall("SELECT * FROM sessions") - for row in r: - sessions.append(self.get_dict(row)) - - return sessions + return [self.get_dict(row) for row in r] # nas server functions def get_nas_login(self, authtoken): with Transaction(self.conn) as tx: - row = tx.queryone("SELECT data FROM nas_logins WHERE authtoken = ?", (authtoken,)) + row = tx.queryone( + "SELECT data FROM nas_logins WHERE authtoken = ?", + (authtoken,) + ) r = self.get_dict(row) if r is None: @@ -395,7 +497,10 @@ class GamespyDatabase(object): def get_nas_login_from_userid(self, userid): with Transaction(self.conn) as tx: - row = tx.queryone("SELECT data FROM nas_logins WHERE userid = ?", (userid,)) + row = tx.queryone( + "SELECT data FROM nas_logins WHERE userid = ?", + (userid,) + ) r = self.get_dict(row) if r is None: @@ -403,17 +508,28 @@ class GamespyDatabase(object): else: return json.loads(r["data"]) - def is_banned(self,postdata): + def is_banned(self, postdata): with Transaction(self.conn) as tx: - row = tx.queryone("SELECT COUNT(*) FROM banned WHERE gameid = ? AND ipaddr = ?",(postdata['gamecd'][:-1],postdata['ipaddr'])) + row = tx.queryone( + "SELECT COUNT(*) FROM banned WHERE gameid = ? AND ipaddr = ?", + (postdata['gamecd'][:-1], postdata['ipaddr']) + ) + return int(row[0]) > 0 + + def pending(self, postdata): + with Transaction(self.conn) as tx: + row = tx.queryone( + "SELECT COUNT(*) FROM pending WHERE macadr = ?", + (postdata['macadr'],) + ) return int(row[0]) > 0 - def pending(self,postdata): + + def registered(self, postdata): with Transaction(self.conn) as tx: - row = tx.queryone("SELECT COUNT(*) FROM pending WHERE macadr = ?",(postdata['macadr'],)) - return int(row[0]) > 0 - def registered(self,postdata): - with Transaction(self.conn) as tx: - row = tx.queryone("SELECT COUNT(*) FROM registered WHERE macadr = ?",(postdata['macadr'],)) + row = tx.queryone( + "SELECT COUNT(*) FROM registered WHERE macadr = ?", + (postdata['macadr'],) + ) return int(row[0]) > 0 def get_next_available_userid(self): @@ -421,31 +537,44 @@ class GamespyDatabase(object): row = tx.queryone("SELECT max(userid) AS maxuser FROM users") r = self.get_dict(row) if r is None or r['maxuser'] is None: - return '0000000000002'#Because all zeroes means Dolphin. Don't wanna get confused during debugging later. + # Because all zeroes means Dolphin. Don't wanna get confused + # during debugging later. + return '0000000000002' else: userid = str(int(r['maxuser']) + 1) while len(userid) < 13: - userid = "0"+userid + userid = "0" + userid return userid def generate_authtoken(self, userid, data): - # Since the auth token passed back to the game will be random, we can make it small enough that there - # should never be a crash due to the size of the token. - # ^ real authtoken is 80 + 3 bytes though and I want to figure out what's causing the 52200 - # so I'm matching everything as closely as possible to the real thing + """Generate authentication token. + + Since the auth token passed back to the game will be random, we can + make it small enough that there should never be a crash due to the + size of the token. + ^ real authtoken is 80 + 3 bytes though and I want to figure out + what's causing the 52200 so I'm matching everything as closely as + possible to the real thing. + """ size = 80 # TODO: Another one of those questionable dupe-preventations while True: with Transaction(self.conn) as tx: authtoken = "NDS" + utils.generate_random_str(size) - row = tx.queryone("SELECT COUNT(*) FROM nas_logins WHERE authtoken = ?", (authtoken,)) + row = tx.queryone( + "SELECT COUNT(*) FROM nas_logins WHERE authtoken = ?", + (authtoken,) + ) count = int(row[0]) if count == 0: break with Transaction(self.conn) as tx: - row = tx.queryone("SELECT * FROM nas_logins WHERE userid = ?", (userid,)) + row = tx.queryone( + "SELECT * FROM nas_logins WHERE userid = ?", + (userid,) + ) r = self.get_dict(row) if "devname" in data: @@ -456,10 +585,17 @@ class GamespyDatabase(object): data = json.dumps(data) with Transaction(self.conn) as tx: - if r is None: # no row, add it - tx.nonquery("INSERT INTO nas_logins VALUES (?, ?, ?)", (userid, authtoken, data)) + if r is None: # no row, add it + tx.nonquery( + "INSERT INTO nas_logins VALUES (?, ?, ?)", + (userid, authtoken, data) + ) else: - tx.nonquery("UPDATE nas_logins SET authtoken = ?, data = ? WHERE userid = ?", (authtoken, data, userid)) + tx.nonquery( + "UPDATE nas_logins SET authtoken = ?, data = ?" + " WHERE userid = ?", + (authtoken, data, userid) + ) return authtoken @@ -469,88 +605,130 @@ class GamespyDatabase(object): # status == 0 -> not authorized with Transaction(self.conn) as tx: - tx.nonquery("INSERT INTO buddies VALUES (?, ?, ?, ?, ?, ?, ?)", (userProfileId, buddyProfileId, now, 0, 0, "", 0)) + tx.nonquery( + "INSERT INTO buddies VALUES (?, ?, ?, ?, ?, ?, ?)", + (userProfileId, buddyProfileId, now, 0, 0, "", 0) + ) def auth_buddy(self, userProfileId, buddyProfileId): # status == 1 -> authorized with Transaction(self.conn) as tx: - tx.nonquery("UPDATE buddies SET status = ? WHERE userProfileId = ? AND buddyProfileId = ?", (1, userProfileId, buddyProfileId)) + tx.nonquery( + "UPDATE buddies SET status = ?" + " WHERE userProfileId = ? AND buddyProfileId = ?", + (1, userProfileId, buddyProfileId) + ) def block_buddy(self, userProfileId, buddyProfileId): with Transaction(self.conn) as tx: - tx.nonquery("UPDATE buddies SET blocked = ? WHERE userProfileId = ? AND buddyProfileId = ?", (1, userProfileId, buddyProfileId)) + tx.nonquery( + "UPDATE buddies SET blocked = ?" + " WHERE userProfileId = ? AND buddyProfileId = ?", + (1, userProfileId, buddyProfileId) + ) def unblock_buddy(self, userProfileId, buddyProfileId): with Transaction(self.conn) as tx: - tx.nonquery("UPDATE buddies SET blocked = ? WHERE userProfileId = ? AND buddyProfileId = ?", (0, userProfileId, buddyProfileId)) + tx.nonquery( + "UPDATE buddies SET blocked = ?" + " WHERE userProfileId = ? AND buddyProfileId = ?", + (0, userProfileId, buddyProfileId) + ) def get_buddy(self, userProfileId, buddyProfileId): - profile = {} if userProfileId != 0 and buddyProfileId != 0: with Transaction(self.conn) as tx: - row = tx.queryone("SELECT * FROM buddies WHERE userProfileId = ? AND buddyProfileId = ?", (userProfileId, buddyProfileId)) - profile = self.get_dict(row) - return profile + row = tx.queryone( + "SELECT * FROM buddies" + " WHERE userProfileId = ? AND buddyProfileId = ?", + (userProfileId, buddyProfileId) + ) + return self.get_dict(row) + return {} def delete_buddy(self, userProfileId, buddyProfileId): with Transaction(self.conn) as tx: - tx.nonquery("DELETE FROM buddies WHERE userProfileId = ? AND buddyProfileId = ?", (userProfileId, buddyProfileId)) + tx.nonquery( + "DELETE FROM buddies" + " WHERE userProfileId = ? AND buddyProfileId = ?", + (userProfileId, buddyProfileId) + ) def get_buddy_list(self, userProfileId): with Transaction(self.conn) as tx: - rows = tx.queryall("SELECT * FROM buddies WHERE userProfileId = ? AND blocked = 0", (userProfileId,)) + rows = tx.queryall( + "SELECT * FROM buddies" + " WHERE userProfileId = ? AND blocked = 0", + (userProfileId,) + ) - users = [] - for row in rows: - users.append(self.get_dict(row)) - - return users + return [self.get_dict(row) for row in rows] def get_blocked_list(self, userProfileId): with Transaction(self.conn) as tx: - rows = tx.queryall("SELECT * FROM buddies WHERE userProfileId = ? AND blocked = 1", (userProfileId,)) + rows = tx.queryall( + "SELECT * FROM buddies" + " WHERE userProfileId = ? AND blocked = 1", + (userProfileId,) + ) - users = [] - for row in rows: - users.append(self.get_dict(row)) - - return users + return [self.get_dict(row) for row in rows] def get_pending_buddy_requests(self, userProfileId): with Transaction(self.conn) as tx: - rows = tx.queryall("SELECT * FROM buddies WHERE buddyProfileId = ? AND status = 0", (userProfileId,)) + rows = tx.queryall( + "SELECT * FROM buddies" + " WHERE buddyProfileId = ? AND status = 0", + (userProfileId,) + ) - users = [] - for row in rows: - users.append(self.get_dict(row)) - - return users + return [self.get_dict(row) for row in rows] def buddy_need_auth_message(self, userProfileId): with Transaction(self.conn) as tx: - rows = tx.queryall("SELECT * FROM buddies WHERE buddyProfileId = ? AND status = 1 AND notified = 0", (userProfileId,)) + rows = tx.queryall( + "SELECT * FROM buddies" + " WHERE buddyProfileId = ? AND status = 1 AND notified = 0", + (userProfileId,) + ) - users = [] - for row in rows: - users.append(self.get_dict(row)) - - return users + return [self.get_dict(row) for row in rows] def buddy_sent_auth_message(self, userProfileId, buddyProfileId): with Transaction(self.conn) as tx: - tx.nonquery("UPDATE buddies SET notified = ? WHERE userProfileId = ? AND buddyProfileId = ?", (1, userProfileId, buddyProfileId)) + tx.nonquery( + "UPDATE buddies SET notified = ?" + " WHERE userProfileId = ? AND buddyProfileId = ?", + (1, userProfileId, buddyProfileId) + ) # Gamestats-related functions def pd_insert(self, profileid, dindex, ptype, data): with Transaction(self.conn) as tx: - row = tx.queryone("SELECT COUNT(*) FROM gamestat_profile WHERE profileid = ? AND dindex = ? AND ptype = ?", (profileid, dindex, ptype)) + row = tx.queryone( + "SELECT COUNT(*) FROM gamestat_profile" + " WHERE profileid = ? AND dindex = ? AND ptype = ?", + (profileid, dindex, ptype) + ) count = int(row[0]) if count > 0: - tx.nonquery("UPDATE gamestat_profile SET data = ? WHERE profileid = ? AND dindex = ? AND ptype = ?", (data, profileid, dindex, ptype)) + tx.nonquery( + "UPDATE gamestat_profile SET data = ?" + " WHERE profileid = ? AND dindex = ? AND ptype = ?", + (data, profileid, dindex, ptype) + ) else: - tx.nonquery("INSERT INTO gamestat_profile (profileid, dindex, ptype, data) VALUES(?,?,?,?)", (profileid, dindex, ptype, data)) + tx.nonquery( + "INSERT INTO gamestat_profile" + " (profileid, dindex, ptype, data) VALUES(?,?,?,?)", + (profileid, dindex, ptype, data) + ) def pd_get(self, profileid, dindex, ptype): with Transaction(self.conn) as tx: - row = tx.queryone("SELECT * FROM gamestat_profile WHERE profileid = ? AND dindex = ? AND ptype = ?", (profileid, dindex, ptype)) + row = tx.queryone( + "SELECT * FROM gamestat_profile" + " WHERE profileid = ? AND dindex = ? AND ptype = ?", + (profileid, dindex, ptype) + ) return self.get_dict(row) From ffc16af7a7c2c7fb4247a8e004f7472db0869001 Mon Sep 17 00:00:00 2001 From: payet_s Date: Sun, 2 Aug 2015 19:04:10 +0200 Subject: [PATCH 08/21] Cleaned: gamestats_server_http.py --- gamestats_server_http.py | 66 ++++++++++++++++++++++++++++------------ internal_stats_server.py | 4 +-- 2 files changed, 49 insertions(+), 21 deletions(-) diff --git a/gamestats_server_http.py b/gamestats_server_http.py index ba94082..13a606a 100644 --- a/gamestats_server_http.py +++ b/gamestats_server_http.py @@ -1,6 +1,7 @@ """DWC Network Server Emulator Copyright (C) 2014 polaris- + Copyright (C) 2015 Sepalani This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as @@ -15,7 +16,8 @@ You should have received a copy of the GNU Affero General Public License along with this program. If not, see . - TODO: Seperate gamestats.gs.nintendowifi.net and gamestats2.gs.nintendowifi.net + TODO: Seperate gamestats.gs.nintendowifi.net + and gamestats2.gs.nintendowifi.net TODO: Move gamestats list to database? """ @@ -35,13 +37,15 @@ logger_output_to_console = True logger_output_to_file = True logger_name = "GameStatsServerHttp" logger_filename = "gamestats_server_http.log" -logger = utils.create_logger(logger_name, logger_filename, -1, logger_output_to_console, logger_output_to_file) +logger = utils.create_logger(logger_name, logger_filename, -1, + logger_output_to_console, logger_output_to_file) -#address = ("0.0.0.0", 80) +# address = ("0.0.0.0", 80) address = ("127.0.0.1", 9002) + class GameStatsBase(object): - def do_GET(self, conn, key, append_hash, append_text = ""): + def do_GET(self, conn, key, append_hash, append_text=""): try: conn.send_response(200) conn.send_header("Content-type", "text/html") @@ -54,21 +58,24 @@ class GameStatsBase(object): ret = "" if "hash" not in params: # The token is used in combination with the game's secret key. - # The format of the hash parameter sent from the client is sha1(secret_key + token). + # The format of the hash parameter sent from the client is + # sha1(secret_key + token). token = utils.generate_random_str(32) ret = token else: # Handle data (generic response for now) ret += append_text - if append_hash == True: + if append_hash: h = hashlib.sha1() h.update(key + base64.urlsafe_b64encode(ret) + key) ret += h.hexdigest() conn.wfile.write(ret) except: - logger.log(logging.ERROR, "Unknown exception: %s" % traceback.format_exc()) + logger.log(logging.ERROR, + "Unknown exception: %s", + traceback.format_exc()) def do_POST(self, conn, key): try: @@ -81,35 +88,52 @@ class GameStatsBase(object): conn.wfile.write("") except: - logger.log(logging.ERROR, "Unknown exception: %s" % traceback.format_exc()) + logger.log(logging.ERROR, + "Unknown exception: %s", + traceback.format_exc()) + class GameStatsVersion1(GameStatsBase): def do_GET(self, conn, key): super(self.__class__, self).do_GET(conn, key, False, "") + class GameStatsVersion2(GameStatsBase): def do_GET(self, conn, key): super(self.__class__, self).do_GET(conn, key, True, "") + class GameStatsVersion3(GameStatsBase): def do_GET(self, conn, key): super(self.__class__, self).do_GET(conn, key, True, "done") + class GameStatsServer(object): def start(self): - httpd = GameStatsHTTPServer((address[0], address[1]), GameStatsHTTPServerHandler) - logger.log(logging.INFO, "Now listening for connections on %s:%d...", address[0], address[1]) + httpd = GameStatsHTTPServer((address[0], address[1]), + GameStatsHTTPServerHandler) + logger.log(logging.INFO, + "Now listening for connections on %s:%d...", + address[0], address[1]) httpd.serve_forever() + class GameStatsHTTPServer(BaseHTTPServer.HTTPServer): + gamestats_list = [ + GameStatsBase, + GameStatsVersion1, + GameStatsVersion2, + GameStatsVersion3 + ] + def __init__(self, server_address, RequestHandlerClass): - #self.db = gs_database.GamespyDatabase() + # self.db = gs_database.GamespyDatabase() self.gamelist = self.parse_key_file() - BaseHTTPServer.HTTPServer.__init__(self, server_address, RequestHandlerClass) + BaseHTTPServer.HTTPServer.__init__(self, server_address, + RequestHandlerClass) - def parse_key_file(self, filename = "gamestats.cfg"): - gamestats_list = [GameStatsBase, GameStatsVersion1, GameStatsVersion2, GameStatsVersion3] + def parse_key_file(self, filename="gamestats.cfg"): gamelist = {} with open(filename) as config_file: @@ -122,14 +146,15 @@ class GameStatsHTTPServer(BaseHTTPServer.HTTPServer): if len(s) != 3: continue - gamestats = gamestats_list[0] - if int(s[1]) < len(gamestats_list): - gamestats = gamestats_list[int(s[1])] + gamestats = self.gamestats_list[0] + if int(s[1]) < len(self.gamestats_list): + gamestats = self.gamestats_list[int(s[1])] gamelist[s[0]] = {'key': s[2], 'class': gamestats} return gamelist + class GameStatsHTTPServerHandler(BaseHTTPServer.BaseHTTPRequestHandler): def version_string(self): return "Nintendo Wii (http)" @@ -139,12 +164,14 @@ class GameStatsHTTPServerHandler(BaseHTTPServer.BaseHTTPRequestHandler): if '/' in gameid: gameid = gameid[:gameid.index('/')] - #logger.log(logging.DEBUG, "Request for '%s': %s", gameid, self.path) + # logger.log(logging.DEBUG, "Request for '%s': %s", gameid, self.path) if gameid in self.server.gamelist: game = self.server.gamelist[gameid]['class']() game.do_GET(self, self.server.gamelist[gameid]['key']) else: - logger.log(logging.DEBUG, "WARNING: Could not find '%s' in gamestats list", gameid) + logger.log(logging.DEBUG, + "WARNING: Could not find '%s' in gamestats list", + gameid) default = GameStatsBase() default.do_GET(self, "", False, "") @@ -159,6 +186,7 @@ class GameStatsHTTPServerHandler(BaseHTTPServer.BaseHTTPRequestHandler): return ret + if __name__ == "__main__": gamestats = GameStatsServer() gamestats.start() diff --git a/internal_stats_server.py b/internal_stats_server.py index 88f48d8..75b7255 100644 --- a/internal_stats_server.py +++ b/internal_stats_server.py @@ -108,8 +108,8 @@ class InternalStatsServer(object): """Internal Statistics server. Running on port 9001 by default: http://127.0.0.1:9001/ - Can be displayed in json format: http://127.0.0.1:9001/json""" - + Can be displayed in json format: http://127.0.0.1:9001/json + """ def __init__(self): self.last_update = 0 self.next_update = 0 From 879fa8e33a8018d6d553a0a7e6d4694a7cf513f7 Mon Sep 17 00:00:00 2001 From: payet_s Date: Sun, 2 Aug 2015 21:38:32 +0200 Subject: [PATCH 09/21] Cleaned: gamespy_server_browser_server.py --- gamespy_server_browser_server.py | 366 ++++++++++++++++++++++--------- 1 file changed, 259 insertions(+), 107 deletions(-) diff --git a/gamespy_server_browser_server.py b/gamespy_server_browser_server.py index b62a0a3..e9bfc42 100644 --- a/gamespy_server_browser_server.py +++ b/gamespy_server_browser_server.py @@ -17,8 +17,9 @@ You should have received a copy of the GNU Affero General Public License along with this program. If not, see . - I found an open source implemention of this exact server I'm trying to emulate here: https://github.com/sfcspanky/Openspy-Core/blob/master/serverbrowsing/ - Use as reference later. +I found an open source implemention of this exact server I'm trying to +emulate here: (use as reference later) +https://github.com/sfcspanky/Openspy-Core/blob/master/serverbrowsing/ """ import logging @@ -36,6 +37,7 @@ import other.utils as utils from multiprocessing.managers import BaseManager + class ServerListFlags: UNSOLICITED_UDP_FLAG = 1 PRIVATE_IP_FLAG = 2 @@ -51,7 +53,9 @@ logger_output_to_console = True logger_output_to_file = True logger_name = "GameSpyServerBrowserServer" logger_filename = "gamespy_server_browser_server.log" -logger = utils.create_logger(logger_name, logger_filename, -1, logger_output_to_console, logger_output_to_file) +logger = utils.create_logger(logger_name, logger_filename, -1, + logger_output_to_console, logger_output_to_file) + class GameSpyServerDatabase(BaseManager): pass @@ -65,25 +69,30 @@ GameSpyServerDatabase.register("get_natneg_server") GameSpyServerDatabase.register("delete_natneg_server") address = ("0.0.0.0", 28910) + + class GameSpyServerBrowserServer(object): - def __init__(self, qr = None): + def __init__(self, qr=None): self.qr = qr def start(self): - endpoint = serverFromString(reactor, "tcp:%d:interface=%s" % (address[1], address[0])) + endpoint = serverFromString( + reactor, "tcp:%d:interface=%s" % (address[1], address[0]) + ) conn = endpoint.listen(SessionFactory(self.qr)) try: - if reactor.running is False: + if not reactor.running: reactor.run(installSignalHandlers=0) except ReactorAlreadyRunning: pass - class SessionFactory(Factory): def __init__(self, qr): - logger.log(logging.INFO, "Now listening for connections on %s:%d...", address[0], address[1]) + logger.log(logging.INFO, + "Now listening for connections on %s:%d...", + address[0], address[1]) self.secret_key_list = gs_utils.generate_secret_keys("gslist.cfg") # TODO: Prune server cache at some point @@ -91,14 +100,17 @@ class SessionFactory(Factory): self.qr = qr def buildProtocol(self, address): - return Session(address, self.secret_key_list, self.server_cache, self.qr) + return Session(address, self.secret_key_list, self.server_cache, + self.qr) class Session(LineReceiver): def __init__(self, address, secret_key_list, server_cache, qr): - self.setRawMode() # We're dealing with binary data so set to raw mode + self.setRawMode() # We're dealing with binary data so set to raw mode self.address = address - self.secret_key_list = secret_key_list # Don't waste time parsing every session, so just accept it from the parent + # Don't waste time parsing every session, so just accept it from + # the parent + self.secret_key_list = secret_key_list self.console = 0 self.server_cache = server_cache self.qr = qr @@ -107,11 +119,15 @@ class Session(LineReceiver): manager_address = ("127.0.0.1", 27500) manager_password = "" - self.server_manager = GameSpyServerDatabase(address = manager_address, authkey= manager_password) + self.server_manager = GameSpyServerDatabase(address=manager_address, + authkey=manager_password) self.server_manager.connect() - def log(self, level, message): - logger.log(level, "[%s:%d] %s", self.address.host, self.address.port,message) + def log(self, level, msg, *args, **kwargs): + """TODO: Use logger format""" + logger.log(level, "[%s:%d] " + msg, + self.address.host, self.address.port, + *arg, **kwarg) def rawDataReceived(self, data): try: @@ -126,7 +142,8 @@ class Session(LineReceiver): # 0x04 - Map loop request (?) # 0x05 - Player search request # - # For Tetris DS, at the very least 0x00 and 0x02 need to be implemented. + # For Tetris DS, at the very least 0x00 and 0x02 need to be + # implemented. self.buffer += data @@ -142,10 +159,13 @@ class Session(LineReceiver): # Don't have enough for the entire packet, break. break - if packet[2] == '\x00': # Server list request - self.log(logging.DEBUG, "Received server list request from %s:%s..." % (self.address.host, self.address.port)) + if packet[2] == '\x00': # Server list request + self.log(logging.DEBUG, + "Received server list request from %s:%s...", + self.address.host, self.address.port) - # This code is so... not python. The C programmer in me is coming out strong. + # This code is so... not python. The C programmer in me is + # coming out strong. # TODO: Rewrite this section later? idx = 3 list_version = ord(packet[idx]) @@ -187,65 +207,104 @@ class Session(LineReceiver): send_ip = True if '\\' in fields: - fields = [x for x in fields.split('\\') if x and not x.isspace()] + fields = [x for x in fields.split('\\') + if x and not x.isspace()] - #print "%02x %02x %08x" % (list_version, encoding_version, game_version) - #print "%s" % query_game - #print "%s" % game_name - #print "%s" % challenge - #print "%s" % filter - #print "%s" % fields + # print "%02x %02x %08x" % \ + # (list_version, encoding_version, game_version) + # print "%s" % query_game + # print "%s" % game_name + # print "%s" % challenge + # print "%s" % filter + # print "%s" % fields - #print "%08x" % options - #print "%d %08x" % (max_servers, source_ip) + # print "%08x" % options + # print "%d %08x" % (max_servers, source_ip) - self.log(logging.DEBUG, "list version: %02x / encoding version: %02x / game version: %08x / query game: %s / game name: %s / challenge: %s / filter: %s / fields: %s / options: %08x / max servers: %d / source ip: %08x" % (list_version, encoding_version, game_version, query_game, game_name, challenge, filter, fields, options, max_servers, source_ip)) + self.log(logging.DEBUG, + "list version: %02x / encoding version: %02x /" + " game version: %08x / query game: %s /" + " game name: %s / challenge: %s / filter: %s /" + " fields: %s / options: %08x / max servers: %d /" + " source ip: %08x" % ( + list_version, encoding_version, + game_version, query_game, + game_name, challenge, filter, + fields, options, max_servers, + source_ip + )) # Requesting ip and port of client, not server - if filter == "" and fields == "" or send_ip == True: - output = bytearray([int(x) for x in self.address.host.split('.')]) - output += utils.get_bytes_from_short(6500, True) # Does this ever change? + if not filter and not fields or send_ip: + output = bytearray( + [int(x) for x in self.address.host.split('.')] + ) + # Does this ever change? + output += utils.get_bytes_from_short(6500, True) enc = gs_utils.EncTypeX() - output_enc = enc.encrypt(self.secret_key_list[game_name], challenge, output) + output_enc = enc.encrypt( + self.secret_key_list[game_name], + challenge, + output + ) self.transport.write(bytes(output_enc)) - self.log(logging.DEBUG, "Responding with own IP and game port...") - self.log(logging.DEBUG, utils.pretty_print_hex(output)) + self.log(logging.DEBUG, + "Responding with own IP and game port...") + self.log(logging.DEBUG, + utils.pretty_print_hex(output)) else: - self.find_server(query_game, filter, fields, max_servers, game_name, challenge) + self.find_server(query_game, filter, fields, + max_servers, game_name, challenge) - elif packet[2] == '\x02': # Send message request + elif packet[2] == '\x02': # Send message request packet_len = utils.get_short(packet, 0, True) dest_addr = '.'.join(["%d" % ord(x) for x in packet[3:7]]) - dest_port = utils.get_short(packet, 7, True) # What's the pythonic way to do this? unpack? + # What's the pythonic way to do this? unpack? + dest_port = utils.get_short(packet, 7, True) dest = (dest_addr, dest_port) - self.log(logging.DEBUG, "Received send message request from %s:%s to %s:%d... expecting %d byte packet." % (self.address.host, self.address.port, dest_addr, dest_port, packet_len)) - self.log(logging.DEBUG, utils.pretty_print_hex(bytearray(packet))) + self.log(logging.DEBUG, + "Received send message request from %s:%s to" + " %s:%d... expecting %d byte packet.", + self.address.host, self.address.port, + dest_addr, dest_port, packet_len) + self.log(logging.DEBUG, + utils.pretty_print_hex(bytearray(packet))) if packet_len == len(packet): # Contains entire packet, send immediately. self.forward_data_to_client(packet[9:], dest) else: - self.log(logging.ERROR, "ERROR: Could not find entire packet.") + self.log(logging.ERROR, + "ERROR: Could not find entire packet.") - elif packet[2] == '\x03': # Keep alive reply - self.log(logging.DEBUG, "Received keep alive from %s:%s..." % (self.address.host, self.address.port)) + elif packet[2] == '\x03': # Keep alive reply + self.log(logging.DEBUG, + "Received keep alive from %s:%s...", + (self.address.host, self.address.port)) else: - self.log(logging.DEBUG, "Received unknown command (%02x) from %s:%s..." % (ord(packet[2]), self.address.host, self.address.port)) - self.log(logging.DEBUG, utils.pretty_print_hex(bytearray(packet))) + self.log(logging.DEBUG, + "Received unknown command (%02x) from %s:%s...", + ord(packet[2]), + self.address.host, self.address.port) + self.log(logging.DEBUG, + utils.pretty_print_hex(bytearray(packet))) except: - self.log(logging.ERROR, "Unknown exception: %s" % traceback.format_exc()) + self.log(logging.ERROR, + "Unknown exception: %s", + traceback.format_exc()) def get_game_id(self, data): game_id = data[5: -1] return game_id def get_server_list(self, game, filter, fields, max_count): - results = self.server_manager.find_servers(game, filter, fields, max_count) + results = self.server_manager.find_servers(game, filter, fields, + max_count) return results def generate_server_list_header_data(self, address, fields): @@ -263,7 +322,9 @@ class Session(LineReceiver): if key_count != len(fields): # For some reason we didn't get all of the expected data. - self.log(logging.WARNING, "key_count[%d] != len(fields)[%d]" % (key_count, len(fields))) + self.log(logging.WARNING, + "key_count[%d] != len(fields)[%d]", + key_count, len(fields)) self.log(logging.WARNING, fields) # Write the fields @@ -272,40 +333,55 @@ class Session(LineReceiver): return output - def generate_server_list_data(self, address, fields, server_info, finalize = False): + def generate_server_list_data(self, address, fields, server_info, + finalize=False): output = bytearray() flags_buffer = bytearray() if len(server_info) > 0: - # Start server loop here instead of including all of the fields and stuff again + # Start server loop here instead of including all of the fields + # and stuff again flags = 0 if len(server_info) != 0: + # This condition is always true? Isn't it? flags |= ServerListFlags.HAS_KEYS_FLAG if "natneg" in server_info: flags |= ServerListFlags.CONNECT_NEGOTIATE_FLAG - ip = utils.get_bytes_from_int_signed(int(server_info['publicip']), self.console) + ip = utils.get_bytes_from_int_signed( + int(server_info['publicip']), self.console + ) flags_buffer += ip flags |= ServerListFlags.NONSTANDARD_PORT_FLAG if server_info['publicport'] != "0": - flags_buffer += utils.get_bytes_from_short(int(server_info['publicport']), True) + flags_buffer += utils.get_bytes_from_short( + int(server_info['publicport']), True + ) else: - flags_buffer += utils.get_bytes_from_short(int(server_info['localport']), True) + flags_buffer += utils.get_bytes_from_short( + int(server_info['localport']), True + ) if "localip0" in server_info: # How to handle multiple localips? flags |= ServerListFlags.PRIVATE_IP_FLAG - flags_buffer += bytearray([int(x) for x in server_info['localip0'].split('.')]) #ip + flags_buffer += bytearray( + [int(x) for x in server_info['localip0'].split('.')] + ) # IP if "localport" in server_info: flags |= ServerListFlags.NONSTANDARD_PRIVATE_PORT_FLAG - flags_buffer += utils.get_bytes_from_short(int(server_info['localport']), True) + flags_buffer += utils.get_bytes_from_short( + int(server_info['localport']), True + ) flags |= ServerListFlags.ICMP_IP_FLAG - flags_buffer += bytearray([int(x) for x in "0.0.0.0".split('.')]) + flags_buffer += bytearray( + [int(x) for x in "0.0.0.0".split('.')] + ) output += bytearray([flags & 0xff]) output += flags_buffer @@ -314,134 +390,203 @@ class Session(LineReceiver): # Write data for associated fields if 'requested' in server_info: for field in fields: - output += '\xff' + bytearray(server_info['requested'][field]) + '\0' + output += '\xff' + \ + bytearray( + server_info['requested'][field] + ) + '\0' return output - def find_server(self, query_game, filter, fields, max_servers, game_name, challenge): + def find_server(self, query_game, filter, fields, max_servers, game_name, + challenge): def send_encrypted_data(self, challenge, data): - self.log(logging.DEBUG, "Sent server list message to %s:%s..." % (self.address.host, self.address.port)) + self.log(logging.DEBUG, + "Sent server list message to %s:%s...", + self.address.host, self.address.port) self.log(logging.DEBUG, utils.pretty_print_hex(data)) - + # Encrypt data enc = gs_utils.EncTypeX() - data = enc.encrypt(self.secret_key_list[game_name], challenge, data) + data = enc.encrypt(self.secret_key_list[game_name], + challenge, data) # Send to client self.transport.write(bytes(data)) - max_packet_length = 256 + 511 + 255 # OpenSpy's max packet length, just go with it for now + # OpenSpy's max packet length, just go with it for now + max_packet_length = 256 + 511 + 255 # Get dictionary from master server list server. - self.log(logging.DEBUG, "Searching for server matching '%s' with the fields '%s'" % (filter, fields)) + self.log(logging.DEBUG, + "Searching for server matching '%s' with the fields '%s'", + filter, fields) - self.server_list = self.server_manager.find_servers(query_game, filter, fields, max_servers)._getvalue() + self.server_list = self.server_manager.find_servers( + query_game, filter, fields, max_servers + )._getvalue() self.log(logging.DEBUG, "Found server(s):") self.log(logging.DEBUG, self.server_list) - if self.server_list == []: - self.server_list.append({}) + if not self.server_list: + self.server_list = [{}] data = self.generate_server_list_header_data(self.address, fields) for i in range(0, len(self.server_list)): server = self.server_list[i] - if server and fields and 'requested' in server and server['requested'] == {}: - # If the requested fields weren't found then don't return a server. - # This fixes a bug with Mario Kart DS. - #print "Requested was empty" + if server and fields and 'requested' in server and \ + not server['requested']: + # If the requested fields weren't found then don't return + # a server. This fixes a bug with Mario Kart DS. + # print "Requested was empty" server = {} if "__console__" in server: self.console = int(server['__console__']) # Generate binary server list data - data += self.generate_server_list_data(self.address, fields, server, i >= len(self.server_list)) + data += self.generate_server_list_data( + self.address, fields, server, i >= len(self.server_list) + ) if len(data) >= max_packet_length: send_encrypted_data(self, challenge, data) data = bytearray() # if "publicip" in server and "publicport" in server: - # self.server_cache[str(server['publicip']) + str(server['publicport'])] = server + # self.server_cache[str(server['publicip']) + \ + # str(server['publicport'])] = server data += '\0' data += utils.get_bytes_from_int(0xffffffff) send_encrypted_data(self, challenge, data) def find_server_in_cache(self, addr, port, console): - ip = str(utils.get_ip(bytearray([int(x) for x in addr.split('.')]), 0, console)) - server = self.server_manager.find_server_by_address(ip, port)._getvalue() - self.log(logging.DEBUG, "find_server_in_cache is returning: %s %s" % (server, ip)) + ip = str(utils.get_ip( + bytearray([int(x) for x in addr.split('.')]), + 0, + console + )) + server = self.server_manager.find_server_by_address(ip, + port)._getvalue() + self.log(logging.DEBUG, + "find_server_in_cache is returning: %s %s", + server, ip) return server, ip def forward_data_to_client(self, data, forward_client): # Find session id of server - # Iterate through the list of servers sent to the client and match by IP and port. - # Is there a better way to determine this information? + # Iterate through the list of servers sent to the client and match by + # IP and port. Is there a better way to determine this information? if forward_client is None or len(forward_client) != 2: return - server, ip = self.find_server_in_cache(forward_client[0], forward_client[1], self.console) + server, ip = self.find_server_in_cache(forward_client[0], + forward_client[1], self.console) if server is None: if self.console == 0: - server, ip = self.find_server_in_cache(forward_client[0], forward_client[1], 1) # Try Wii + server, ip = self.find_server_in_cache(forward_client[0], + forward_client[1], + 1) # Try Wii elif self.console == 1: - server, ip = self.find_server_in_cache(forward_client[0], forward_client[1], 0) # Try DS + server, ip = self.find_server_in_cache(forward_client[0], + forward_client[1], + 0) # Try DS - self.log(logging.DEBUG, "find_server_in_cache returned: %s" % server) - self.log(logging.DEBUG, "Trying to send message to %s:%d..." % (forward_client[0], forward_client[1])) + self.log(logging.DEBUG, + "find_server_in_cache returned: %s", + server) + self.log(logging.DEBUG, + "Trying to send message to %s:%d...", + forward_client[0], forward_client[1]) self.log(logging.DEBUG, utils.pretty_print_hex(bytearray(data))) if server is None: return self.log(logging.DEBUG, "%s %s" % (ip, server['publicip'])) - if server['publicip'] == ip and server['publicport'] == str(forward_client[1]): + if server['publicip'] == ip and \ + server['publicport'] == str(forward_client[1]): if forward_client[1] == 0 and 'localport' in server: - # No public port returned from client, try contacting on the local port. + # No public port returned from client, try contacting on + # the local port. forward_client = (forward_client[0], int(server['localport'])) # Send command to server to get it to connect to natneg - cookie = int(utils.generate_random_hex_str(8), 16) # Quick and lazy way to get a random 32bit integer. Replace with something else later + # Quick and lazy way to get a random 32bit integer. Replace with + # something else later + cookie = int(utils.generate_random_hex_str(8), 16) - #if (len(data) == 24 and bytearray(data)[0:10] == bytearray([0x53, 0x42, 0x43, 0x4d, 0x03, 0x00, 0x00, 0x00, 0x01, 0x04])) or (len(data) == 40 and bytearray(data)[0:10] == bytearray([0x53, 0x42, 0x43, 0x4d, 0x0b, 0x00, 0x00, 0x00, 0x01, 0x04])): - if self.own_server is None and len(data) >= 16 and bytearray(data)[0:4] in (bytearray([0xbb, 0x49, 0xcc, 0x4d]), bytearray([0x53, 0x42, 0x43, 0x4d])): - # Is the endianness the same between the DS and Wii here? It seems so but I'm not positive. - self_port = utils.get_short(bytearray(data[10:12]), 0, False) # Note to self: Port is little endian here. + # if (len(data) == 24 and bytearray(data)[0:10] == \ + # bytearray([0x53, 0x42, 0x43, 0x4d, 0x03, + # 0x00, 0x00, 0x00, 0x01, 0x04])) or \ + # (len(data) == 40 and bytearray(data)[0:10] == \ + # bytearray([0x53, 0x42, 0x43, 0x4d, + # 0x0b, 0x00, 0x00, 0x00, + # 0x01, 0x04])): + if self.own_server is None and len(data) >= 16 and \ + bytearray(data)[0:4] in (bytearray([0xbb, 0x49, 0xcc, 0x4d]), + bytearray([0x53, 0x42, 0x43, 0x4d])): + # Is the endianness the same between the DS and Wii here? + # It seems so but I'm not positive. + # Note to self: Port is little endian here. + self_port = utils.get_short(bytearray(data[10:12]), 0, False) self_ip = '.'.join(["%d" % x for x in bytearray(data[12:16])]) - self.own_server, _ = self.find_server_in_cache(self_ip, self_port, self.console) + self.own_server, _ = self.find_server_in_cache(self_ip, + self_port, + self.console) if self.own_server is None: if self.console == 0: - self.own_server, _ = self.find_server_in_cache(self_ip, self_port, 1) # Try Wii + # Try Wii + self.own_server, _ = self.find_server_in_cache( + self_ip, self_port, 1 + ) elif self.console == 1: - self.own_server, _ = self.find_server_in_cache(self_ip, self_port, 0) # Try DS + # Try DS + self.own_server, _ = self.find_server_in_cache( + self_ip, self_port, 0 + ) if self.own_server is None: - self.log(logging.DEBUG, "Could not find own server: %s:%d" % (self_ip, self_port)) + self.log(logging.DEBUG, + "Could not find own server: %s:%d", + self_ip, self_port) else: - self.log(logging.DEBUG, "Found own server: %s" % (self.own_server)) + self.log(logging.DEBUG, + "Found own server: %s", + self.own_server) - - elif len(data) == 10 and bytearray(data)[0:6] == bytearray([0xfd, 0xfc, 0x1e, 0x66, 0x6a, 0xb2]): - natneg_session = utils.get_int_signed(data,6) - self.log(logging.DEBUG, "Adding %d to natneg server list: %s" % (natneg_session, server)) - self.server_manager.add_natneg_server(natneg_session, server) # Store info in backend so we can get it later in natneg + elif len(data) == 10 and \ + bytearray(data)[0:6] == \ + bytearray([0xfd, 0xfc, 0x1e, 0x66, 0x6a, 0xb2]): + natneg_session = utils.get_int_signed(data, 6) + self.log(logging.DEBUG, + "Adding %d to natneg server list: %s", + natneg_session, server) + # Store info in backend so we can get it later in natneg + self.server_manager.add_natneg_server(natneg_session, server) if self.own_server is not None: - self.log(logging.DEBUG, "Adding %d to natneg server list: %s (self)" % (natneg_session, self.own_server)) - self.server_manager.add_natneg_server(natneg_session, self.own_server) # Store info in backend so we can get it later in natneg + self.log(logging.DEBUG, + "Adding %d to natneg server list: %s (self)", + natneg_session, self.own_server) + # Store info in backend so we can get it later in natneg + self.server_manager.add_natneg_server(natneg_session, + self.own_server) # if self.qr is not None: # own_server = self.qr.get_own_server() # - # self.log(logging.DEBUG, "Adding %d to natneg server list: %s" % (natneg_session, own_server)) - # self.server_manager.add_natneg_server(natneg_session, own_server) # Store info in backend so we can get it later in natneg + # self.log(logging.DEBUG, + # "Adding %d to natneg server list: %s", + # natneg_session, own_server) + # self.server_manager.add_natneg_server(natneg_session, + # own_server) output = bytearray([0xfe, 0xfd, 0x06]) output += utils.get_bytes_from_int(server['__session__']) @@ -449,15 +594,22 @@ class Session(LineReceiver): output += bytearray(data) if self.qr is not None: - self.log(logging.DEBUG, "Forwarded data to %s:%s through QR server..." % (forward_client[0], forward_client[1])) + self.log(logging.DEBUG, + "Forwarded data to %s:%s through QR server...", + forward_client[0], forward_client[1]) self.qr.socket.sendto(output, forward_client) else: - # In case we can't contact the QR server, just try sending the packet directly. - # This isn't standard behavior but it can work in some instances. - self.log(logging.DEBUG, "Forwarded data to %s:%s directly (potential error occurred)..." % (forward_client[0], forward_client[1])) + # In case we can't contact the QR server, just try sending + # the packet directly. This isn't standard behavior but it + # can work in some instances. + self.log(logging.DEBUG, + "Forwarded data to %s:%s directly" + " (potential error occurred)...", + forward_client[0], forward_client[1]) client_s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) client_s.sendto(output, forward_client) - + + if __name__ == "__main__": server_browser = GameSpyServerBrowserServer() server_browser.start() From 27a25ef8531ad38443c4cd68ee2fc7269761d0fc Mon Sep 17 00:00:00 2001 From: payet_s Date: Sun, 2 Aug 2015 23:09:00 +0200 Subject: [PATCH 10/21] Cleaned: gamespy_qr_server.py --- gamespy_qr_server.py | 508 ++++++++++++++++++++++++++++--------------- 1 file changed, 331 insertions(+), 177 deletions(-) diff --git a/gamespy_qr_server.py b/gamespy_qr_server.py index 1c55952..aeb748b 100644 --- a/gamespy_qr_server.py +++ b/gamespy_qr_server.py @@ -18,8 +18,10 @@ You should have received a copy of the GNU Affero General Public License along with this program. If not, see . - Server emulator for *.available.gs.nintendowifi.net and *.master.gs.nintendowifi.net - Query and Reporting: http://docs.poweredbygamespy.com/wiki/Query_and_Reporting_Overview +Server emulator for *.available.gs.nintendowifi.net and + *.master.gs.nintendowifi.net +Query and Reporting: +http://docs.poweredbygamespy.com/wiki/Query_and_Reporting_Overview """ import logging @@ -43,17 +45,20 @@ logger_output_to_console = True logger_output_to_file = True logger_name = "GameSpyQRServer" logger_filename = "gamespy_qr_server.log" -logger = utils.create_logger(logger_name, logger_filename, -1, logger_output_to_console, logger_output_to_file) +logger = utils.create_logger(logger_name, logger_filename, -1, + logger_output_to_console, logger_output_to_file) + class GameSpyServerDatabase(BaseManager): pass + class GameSpyQRServer(object): class Session(object): def __init__(self, address): self.session = "" self.challenge = "" - self.secretkey = "" # Parse gslist.cfg later + self.secretkey = "" # Parse gslist.cfg later self.sent_challenge = False self.heartbeat_data = None self.address = address @@ -64,47 +69,63 @@ class GameSpyQRServer(object): self.gamename = "" self.keepalive = -1 - def __init__(self): self.sessions = {} - # Generate a dictionary "secret_key_list" containing the secret game keys associated with their game IDs. - # The dictionary key will be the game's ID, and the value will be the secret key. + # Generate a dictionary "secret_key_list" containing the secret game + # keys associated with their game IDs. The dictionary key will be the + # game's ID, and the value will be the secret key. self.secret_key_list = gs_utils.generate_secret_keys("gslist.cfg") - #self.log(logging.DEBUG, address, session_id, "Generated list of secret game keys...") + # self.log(logging.DEBUG, address, session_id, + # "Generated list of secret game keys...") GameSpyServerDatabase.register("update_server_list") GameSpyServerDatabase.register("delete_server") - def log(self, level, address, session_id, message): + def log(self, level, address, session_id, msg, *args, **kwargs): + """TODO: Use logger format""" if address is None: - logger.log(level, "%s", message) + logger.log(level, msg, *args, **kwargs) else: if session_id is not None: - logger.log(level, "[%s:%d %08x] %s", address[0], address[1], session_id, message) + logger.log(level, "[%s:%d %08x] " + msg, + address[0], address[1], session_id, + *args, **kwargs) else: - logger.log(level, "[%s:%d] %s", address[0], address[1], message) + logger.log(level, "[%s:%d] " + msg, + address[0], address[1], + *args, **kwargs) def start(self): try: manager_address = ("127.0.0.1", 27500) manager_password = "" - self.server_manager = GameSpyServerDatabase(address = manager_address, authkey= manager_password) + self.server_manager = GameSpyServerDatabase( + address=manager_address, + authkey=manager_password + ) self.server_manager.connect() # Start QR server - address = ('0.0.0.0', 27900) # accessible to outside connections (use this if you don't know what you're doing) + # Accessible to outside connections (use this if you don't know + # what you're doing) + address = ('0.0.0.0', 27900) self.socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) self.socket.bind(address) self.socket.setblocking(0) - logger.log(logging.INFO, "Server is now listening on %s:%s..." % (address[0], address[1])) + logger.log(logging.INFO, + "Server is now listening on %s:%s...", + address[0], address[1]) - # Dependencies! I don't really like this solution but it's easier than trying to manage it another way. + # Dependencies! I don't really like this solution but it's easier + # than trying to manage it another way. server_browser_server = GameSpyServerBrowserServer(self) - server_browser_server_thread = threading.Thread(target=server_browser_server.start) + server_browser_server_thread = threading.Thread( + target=server_browser_server.start + ) server_browser_server_thread.start() self.write_queue = Queue.Queue() @@ -120,124 +141,153 @@ class GameSpyQRServer(object): self.keepalive_check() except: - logger.log(logging.ERROR, "Unknown exception: %s" % traceback.format_exc()) + logger.log(logging.ERROR, + "Unknown exception: %s", + traceback.format_exc()) def write_queue_send(self, data, address): time.sleep(0.05) self.socket.sendto(data, address) def write_queue_worker(self): - while 1: + while True: data, address = self.write_queue.get() - threading.Thread(target=self.write_queue_send, args=(data, address)).start() + threading.Thread(target=self.write_queue_send, + args=(data, address)).start() self.write_queue.task_done() def update_server_list(self, session_id, k): - if "statechanged" in k and k['statechanged'] == "2": # Close server - self.server_manager.delete_server(k['gamename'] , session_id) + if "statechanged" in k and k['statechanged'] == "2": # Close server + self.server_manager.delete_server(k['gamename'], session_id) - if session_id in self.sessions: - del self.sessions[session_id] + if session_id in self.sessions: + del self.sessions[session_id] else: # dwc_mtype controls what kind of server query we're looking for. # dwc_mtype = 0 is used when looking for a matchmaking game. # dwc_mtype = 1 is unknown. - # dwc_mtype = 2 is used when hosting a friends only game (possibly other uses too). - # dwc_mtype = 3 is used when looking for a friends only game (possibly other uses too). + # dwc_mtype = 2 is used when hosting a friends only game + # (possibly other uses too). + # dwc_mtype = 3 is used when looking for a friends only game + # (possibly other uses too). - # Some memory could be saved by clearing out any unwanted fields from k before sending. - self.server_manager.update_server_list(k['gamename'], session_id, k, self.sessions[session_id].console)._getvalue() + # Some memory could be saved by clearing out any unwanted fields + # from k before sending. + self.server_manager.update_server_list( + k['gamename'], session_id, k, + self.sessions[session_id].console + )._getvalue() if session_id in self.sessions: self.sessions[session_id].gamename = k['gamename'] def handle_packet(self, socket, recv_data, address): - # Tetris DS overlay 10 @ 02144184 - Handle responses back to server - # Tetris DS overlay 10 @ 02144184 - Handle responses back to server - # - # After some more packet inspection, it seems the format goes something like this: - # - All server messages seem to always start with \xfe\xfd. - # - The first byte from the client (or third byte from the server) is a command. - # - Bytes 2 - 5 from the client is some kind of ID. This will have to be inspected later. I believe it's a - # session-like ID because the number changes between connections. Copying the client's ID might be enough. - # - # The above was as guessed. - # The code in Tetris DS (overlay 10) @ 0216E974 handles the network command creation. - # R1 contains the command to be sent to the server. - # R2 contains a pointer to some unknown integer that gets written after the command. - # - # - Commands - # Commands range from 0x00 to 0x09 (for client only at least?) (Tetris DS overlay 10 @ 0216DDCC) - # - # CLIENT: - # 0x01 - Response (Tetris DS overlay 10 @ 216DCA4) - # Sends back base64 of RC4 encrypted string that was gotten from the server's 0x01. - # - # 0x03 - Send client state? (Tetris DS overlay 10 @ 216DA30) - # Data sent: - # 1) Loop for each localip available on the system, write as localip%d\x00(local ip) - # 2) localport\x00(local port) - # 3) natneg (either 0 or 1) - # 4) ONLY IF STATE CHANGED: statechanged\x00(state) (Possible values: 0, 1, 2, 3) - # 5) gamename\x00(game name) - # 6) ONLY IF PUBLIC IP AND PORT ARE AVAILABLE: publicip\x00(public ip) - # 7) ONLY IF PUBLIC IP AND PORT ARE AVAILABLE: publicport\x00(public port) - # - # if statechanged != 2: - # Write various other data described here: http://docs.poweredbygamespy.com/wiki/Query_and_Reporting_Implementation - # - # 0x07 - Unknown, related to server's 0x06 (returns value sent from server) - # - # 0x08 - Keep alive? Sent after 0x03 - # - # 0x09 - Availability check - # - # SERVER: - # 0x01 - Unknown - # Data sent: - # 8 random ASCII characters (?) followed by the public IP and port of the client as a hex string - # - # 0x06 - Unknown - # First 4 bytes is some kind of id? I believe it's a unique identifier for the data being sent, - # seeing how the server can send the same IP information many times in a row. If the IP information has - # already been parsed then it doesn't waste time handling it. - # - # After that is a "SBCM" section which is 0x14 bytes in total. - # SBCM information gets parsed at 2141A0C in Tetris DS overlay 10. - # Seems to contain IP address information. - # - # The SBCM seems to contain a little information that must be parsed before. - # After the SBCM: - # \x03\x00\x00\x00 - Always the same? - # \x01 - Found player? - # \x04 - Unknown - # (2 bytes) - Unknown. Port? - # (4 bytes) - Player's IP - # (4 bytes) - Unknown. Some other IP? Remote server IP? - # \x00\x00\x00\x00 - Unknown but seems to get checked - # - # Another SBCM, after a player has been found and attempting to start a game: - # \x03\x00\x00\x00 - Always the same? - # \x05 - Connecting to player? - # \x00 - Unknown - # (2 bytes) - Unknown. Port? Same as before. - # (4 bytes) - Player's IP - # (4 bytes) - Unknown. Some other IP? Remote server IP? - # - # 0x0a - Response to 0x01 - # Gets sent after receiving 0x01 from the client. So, server 0x01 -> client 0x01 -> server 0x0a. - # Has no other data besides the client ID. - # - # - \xfd\xfc commands get passed directly between the other player(s)? - # - # - # Open source version of GameSpy found here: https://github.com/sfcspanky/Openspy-Core/tree/master/qr - # Use as reference. + """Tetris DS overlay 10 @ 02144184 - Handle responses back to server. + After some more packet inspection, it seems the format goes something + like this: + - All server messages seem to always start with \xfe\xfd. + - The first byte from the client (or third byte from the server) is + a command. + - Bytes 2 - 5 from the client is some kind of ID. This will have to + be inspected later. I believe it's a session-like ID because the + number changes between connections. Copying the client's ID might + be enough. + + The above was as guessed. + The code in Tetris DS (overlay 10) @ 0216E974 handles the network + command creation. + R1 contains the command to be sent to the server. + R2 contains a pointer to some unknown integer that gets written + after the command. + + - Commands + Commands range from 0x00 to 0x09 (for client only at least?) + (Tetris DS overlay 10 @ 0216DDCC) + + CLIENT: + 0x01 - Response (Tetris DS overlay 10 @ 216DCA4) + Sends back base64 of RC4 encrypted string that was + gotten from the server's 0x01. + + 0x03 - Send client state? (Tetris DS overlay 10 @ 216DA30) + Data sent: + 1) Loop for each localip available on the system, write + as localip%d\x00(local ip) + 2) localport\x00(local port) + 3) natneg (either 0 or 1) + 4) ONLY IF STATE CHANGED: statechanged\x00(state) + (Possible values: 0, 1, 2, 3) + 5) gamename\x00(game name) + 6) ONLY IF PUBLIC IP AND PORT ARE AVAILABLE: + publicip\x00(public ip) + 7) ONLY IF PUBLIC IP AND PORT ARE AVAILABLE: + publicport\x00(public port) + + if statechanged != 2: + Write various other data described here: + http://docs.poweredbygamespy.com/wiki/Query_and_Reporting_Implementation + + 0x07 - Unknown, related to server's 0x06 + (returns value sent from server) + + 0x08 - Keep alive? Sent after 0x03 + + 0x09 - Availability check + + SERVER: + 0x01 - Unknown + Data sent: + 8 random ASCII characters (?) followed by the public IP and + port of the client as a hex string + + 0x06 - Unknown + First 4 bytes is some kind of id? I believe it's a unique + identifier for the data being sent, seeing how the server + can send the same IP information many times in a row. If + the IP information has already been parsed then it doesn't + waste time handling it. + + After that is a "SBCM" section which is 0x14 bytes in + total. SBCM information gets parsed at 2141A0C in Tetris DS + overlay 10. Seems to contain IP address information. + + The SBCM seems to contain a little information that must be + parsed before. + After the SBCM: + \x03\x00\x00\x00 - Always the same? + \x01 - Found player? + \x04 - Unknown + (2 bytes) - Unknown. Port? + (4 bytes) - Player's IP + (4 bytes) - Unknown. Some other IP? Remote server IP? + \x00\x00\x00\x00 - Unknown but seems to get checked + + Another SBCM, after a player has been found and attempting + to start a game: + \x03\x00\x00\x00 - Always the same? + \x05 - Connecting to player? + \x00 - Unknown + (2 bytes) - Unknown. Port? Same as before. + (4 bytes) - Player's IP + (4 bytes) - Unknown. Some other IP? Remote server IP? + + 0x0a - Response to 0x01 + Gets sent after receiving 0x01 from the client. + So, server 0x01 -> client 0x01 -> server 0x0a. + Has no other data besides the client ID. + + - \xfd\xfc commands get passed directly between the other player(s)? + + Open source version of GameSpy found here: + https://github.com/sfcspanky/Openspy-Core/tree/master/qr + Use as reference. + """ session_id = None if recv_data[0] != '\x09': - # Don't add a session if the client is trying to check if the game is available or not + # Don't add a session if the client is trying to check if the game + # is available or not session_id = struct.unpack("= timeout: pruned.append(session_id) - self.server_manager.delete_server(self.sessions[session_id].gamename, self.sessions[session_id].session) - self.log(logging.DEBUG, None, session_id, "Keep alive check removed %s:%s for game %s. Client hasn't responded in %d seconds." % (self.sessions[session_id].address[0], self.sessions[session_id].address[1], self.sessions[session_id].gamename, delta)) + self.server_manager.delete_server( + self.sessions[session_id].gamename, + self.sessions[session_id].session + ) + self.log(logging.DEBUG, None, session_id, + "Keep alive check removed %s:%s for game %s." + " Client hasn't responded in %d seconds.", + self.sessions[session_id].address[0], + self.sessions[session_id].address[1], + self.sessions[session_id].gamename, + delta) for session_id in pruned: del self.sessions[session_id] + if __name__ == "__main__": qr_server = GameSpyQRServer() qr_server.start() From 0cf2d6d6b0b0e41ee08769dbda1a248427562ccd Mon Sep 17 00:00:00 2001 From: payet_s Date: Sun, 2 Aug 2015 23:11:18 +0200 Subject: [PATCH 11/21] Fixed: Format string vulnerabilities --- gamespy_server_browser_server.py | 17 +++++++++++------ internal_stats_server.py | 2 +- nas_server.py | 16 ++++++++-------- 3 files changed, 20 insertions(+), 15 deletions(-) diff --git a/gamespy_server_browser_server.py b/gamespy_server_browser_server.py index e9bfc42..4c9d11b 100644 --- a/gamespy_server_browser_server.py +++ b/gamespy_server_browser_server.py @@ -252,8 +252,10 @@ class Session(LineReceiver): self.transport.write(bytes(output_enc)) self.log(logging.DEBUG, + "%s", "Responding with own IP and game port...") self.log(logging.DEBUG, + "%s", utils.pretty_print_hex(output)) else: self.find_server(query_game, filter, fields, @@ -272,6 +274,7 @@ class Session(LineReceiver): self.address.host, self.address.port, dest_addr, dest_port, packet_len) self.log(logging.DEBUG, + "%s", utils.pretty_print_hex(bytearray(packet))) if packet_len == len(packet): @@ -279,6 +282,7 @@ class Session(LineReceiver): self.forward_data_to_client(packet[9:], dest) else: self.log(logging.ERROR, + "%s", "ERROR: Could not find entire packet.") elif packet[2] == '\x03': # Keep alive reply @@ -292,6 +296,7 @@ class Session(LineReceiver): ord(packet[2]), self.address.host, self.address.port) self.log(logging.DEBUG, + "%s", utils.pretty_print_hex(bytearray(packet))) except: self.log(logging.ERROR, @@ -325,7 +330,7 @@ class Session(LineReceiver): self.log(logging.WARNING, "key_count[%d] != len(fields)[%d]", key_count, len(fields)) - self.log(logging.WARNING, fields) + self.log(logging.WARNING, "%s", fields) # Write the fields for field in fields: @@ -403,7 +408,7 @@ class Session(LineReceiver): self.log(logging.DEBUG, "Sent server list message to %s:%s...", self.address.host, self.address.port) - self.log(logging.DEBUG, utils.pretty_print_hex(data)) + self.log(logging.DEBUG, "%s", utils.pretty_print_hex(data)) # Encrypt data enc = gs_utils.EncTypeX() @@ -425,8 +430,8 @@ class Session(LineReceiver): query_game, filter, fields, max_servers )._getvalue() - self.log(logging.DEBUG, "Found server(s):") - self.log(logging.DEBUG, self.server_list) + self.log(logging.DEBUG, "%s", "Found server(s):") + self.log(logging.DEBUG, "%s", self.server_list) if not self.server_list: self.server_list = [{}] @@ -502,12 +507,12 @@ class Session(LineReceiver): self.log(logging.DEBUG, "Trying to send message to %s:%d...", forward_client[0], forward_client[1]) - self.log(logging.DEBUG, utils.pretty_print_hex(bytearray(data))) + self.log(logging.DEBUG, "%s", utils.pretty_print_hex(bytearray(data))) if server is None: return - self.log(logging.DEBUG, "%s %s" % (ip, server['publicip'])) + self.log(logging.DEBUG, "%s %s", ip, server['publicip']) if server['publicip'] == ip and \ server['publicport'] == str(forward_client[1]): if forward_client[1] == 0 and 'localport' in server: diff --git a/internal_stats_server.py b/internal_stats_server.py index 75b7255..4a0bba7 100644 --- a/internal_stats_server.py +++ b/internal_stats_server.py @@ -141,7 +141,7 @@ class InternalStatsServer(object): self.server_list = self.server_manager.get_server_list() \ ._getvalue() - logger.log(logging.DEBUG, self.server_list) + logger.log(logging.DEBUG, "%s", self.server_list) return self.server_list diff --git a/nas_server.py b/nas_server.py index 30d39f9..ec3d805 100644 --- a/nas_server.py +++ b/nas_server.py @@ -130,7 +130,7 @@ class NasHTTPServerHandler(BaseHTTPServer.BaseHTTPRequestHandler): if self.path == "/ac": logger.log(logging.DEBUG, "Request to %s from %s", self.path, client_address) - logger.log(logging.DEBUG, post) + logger.log(logging.DEBUG, "%s", post) ret = { "datetime": time.strftime("%Y%m%d%H%M%S"), "retry": "0" @@ -161,7 +161,7 @@ class NasHTTPServerHandler(BaseHTTPServer.BaseHTTPRequestHandler): logger.log(logging.DEBUG, "acctcreate response to %s", client_address) - logger.log(logging.DEBUG, ret) + logger.log(logging.DEBUG, "%s", ret) ret = self.dict_to_str(ret) @@ -211,7 +211,7 @@ class NasHTTPServerHandler(BaseHTTPServer.BaseHTTPRequestHandler): logger.log(logging.DEBUG, "login response to %s", client_address) - logger.log(logging.DEBUG, ret) + logger.log(logging.DEBUG, "%s", ret) ret = self.dict_to_str(ret) @@ -247,7 +247,7 @@ class NasHTTPServerHandler(BaseHTTPServer.BaseHTTPRequestHandler): logger.log(logging.DEBUG, "svcloc response to %s", client_address) - logger.log(logging.DEBUG, ret) + logger.log(logging.DEBUG, "%s", ret) ret = self.dict_to_str(ret) else: @@ -258,7 +258,7 @@ class NasHTTPServerHandler(BaseHTTPServer.BaseHTTPRequestHandler): elif self.path == "/pr": logger.log(logging.DEBUG, "Request to %s from %s", self.path, client_address) - logger.log(logging.DEBUG, post) + logger.log(logging.DEBUG, "%s", post) words = len(post["words"].split('\t')) wordsret = "0" * words ret = { @@ -275,14 +275,14 @@ class NasHTTPServerHandler(BaseHTTPServer.BaseHTTPRequestHandler): self.send_header("NODE", "wifiappe1") logger.log(logging.DEBUG, "pr response to %s", client_address) - logger.log(logging.DEBUG, ret) + logger.log(logging.DEBUG, "%s", ret) ret = self.dict_to_str(ret) elif self.path == "/download": logger.log(logging.DEBUG, "Request to %s from %s", self.path, client_address) - logger.log(logging.DEBUG, post) + logger.log(logging.DEBUG, "%s", post) action = post["action"] @@ -404,7 +404,7 @@ class NasHTTPServerHandler(BaseHTTPServer.BaseHTTPRequestHandler): client_address) # if dlc_contenttype is False: - # logger.log(logging.DEBUG, ret) + # logger.log(logging.DEBUG, "%s", ret) else: self.send_response(404) logger.log(logging.WARNING, From d903c0a62e74907802a294953152e27e4f7b12dc Mon Sep 17 00:00:00 2001 From: payet_s Date: Tue, 4 Aug 2015 22:24:54 +0200 Subject: [PATCH 12/21] Cleaned: gamespy_profile_server.py --- gamespy_profile_server.py | 393 ++++++++++++++++++++++++++------------ 1 file changed, 268 insertions(+), 125 deletions(-) diff --git a/gamespy_profile_server.py b/gamespy_profile_server.py index 903ceef..bd0931d 100644 --- a/gamespy_profile_server.py +++ b/gamespy_profile_server.py @@ -41,41 +41,57 @@ logger_output_to_console = True logger_output_to_file = True logger_name = "GameSpyProfileServer" logger_filename = "gamespy_profile_server.log" -logger = utils.create_logger(logger_name, logger_filename, -1, logger_output_to_console, logger_output_to_file) +logger = utils.create_logger(logger_name, logger_filename, -1, + logger_output_to_console, logger_output_to_file) address = ("0.0.0.0", 29900) + + class GameSpyProfileServer(object): def __init__(self): pass def start(self): - endpoint = serverFromString(reactor, "tcp:%d:interface=%s" % (address[1], address[0])) + endpoint = serverFromString( + reactor, + "tcp:%d:interface=%s" % (address[1], address[0]) + ) conn = endpoint.listen(PlayerFactory()) try: - if reactor.running is False: + if not reactor.running: reactor.run(installSignalHandlers=0) except ReactorAlreadyRunning: pass + class PlayerFactory(Factory): def __init__(self): - # Instead of storing the sessions in the database, it might make more sense to store them in the PlayerFactory. - logger.log(logging.INFO, "Now listening for connections on %s:%d...", address[0], address[1]) + """Player Factory. + + Instead of storing the sessions in the database, it might make more + sense to store them in the PlayerFactory. + """ + logger.log(logging.INFO, + "Now listening for connections on %s:%d...", + address[0], address[1]) self.sessions = {} def buildProtocol(self, address): return PlayerSession(self.sessions, address) + class PlayerSession(LineReceiver): def __init__(self, sessions, address): - self.setRawMode() # We're dealing with binary data so set to raw mode + self.setRawMode() # We're dealing with binary data so set to raw mode self.db = gs_database.GamespyDatabase() self.sessions = sessions self.address = address - self.remaining_message = "" # Stores any unparsable/incomplete commands until the next rawDataReceived + # Stores any unparsable/incomplete commands until the next + # rawDataReceived + self.remaining_message = "" self.profileid = 0 self.gameid = "" @@ -92,17 +108,25 @@ class PlayerSession(LineReceiver): self.sdkrevision = "0" - def log(self, level, message): - if self.profileid == 0: - if self.gameid == "": - logger.log(level, "[%s:%d] %s", self.address.host, self.address.port,message) + def log(self, level, msg, *args, **kwargs): + if not self.profileid: + if not self.gameid: + logger.log(level, "[%s:%d] " + msg, + self.address.host, self.address.port, + *args, **kwargs) else: - logger.log(level, "[%s:%d | %s] %s", self.address.host, self.address.port, self.gameid, message) + logger.log(level, "[%s:%d | %s] " + msg, + self.address.host, self.address.port, self.gameid, + *args, **kwargs) else: - if self.gameid == "": - logger.log(level, "[%s:%d | %d] %s", self.address.host, self.address.port, self.profileid, message) + if not self.gameid: + logger.log(level, "[%s:%d | %d] " + msg, + self.address.host, self.address.port, + self.profileid, *args, **kwargs) else: - logger.log(level, "[%s:%d | %d | %s] %s", self.address.host, self.address.port, self.profileid, self.gameid, message) + logger.log(level, "[%s:%d | %d | %s] " + msg, + self.address.host, self.address.port, + self.profileid, self.gameid, *args, **kwargs) def get_ip_as_int(self, address): ipaddress = 0 @@ -117,15 +141,20 @@ class PlayerSession(LineReceiver): try: self.transport.setTcpKeepAlive(1) - self.log(logging.INFO, "Received connection from %s:%d" % (self.address.host, self.address.port)) + self.log(logging.INFO, + "Received connection from %s:%d", + self.address.host, self.address.port) # Create new session id self.session = "" # Generate a random challenge string - self.challenge = utils.generate_random_str(10, "ABCDEFGHIJKLMNOPQRSTUVWXYZ") + self.challenge = utils.generate_random_str( + 10, "ABCDEFGHIJKLMNOPQRSTUVWXYZ" + ) - # The first command sent to the client is always a login challenge containing the server challenge key. + # The first command sent to the client is always a login challenge + # containing the server challenge key. msg = gs_query.create_gamespy_message([ ('__cmd__', "lc"), ('__cmd_val__', "1"), @@ -133,14 +162,16 @@ class PlayerSession(LineReceiver): ('id', "1"), ]) - self.log(logging.DEBUG, "SENDING: '%s'..." % msg) + self.log(logging.DEBUG, "SENDING: '%s'...", msg) self.transport.write(bytes(msg)) except: - self.log(logging.ERROR, "Unknown exception: %s" % traceback.format_exc()) + self.log(logging.ERROR, + "Unknown exception: %s", + traceback.format_exc()) def connectionLost(self, reason): try: - self.log(logging.INFO, "Client disconnected") + self.log(logging.INFO, "%s", "Client disconnected") self.status = "0" self.statstring = "Offline" @@ -151,28 +182,35 @@ class PlayerSession(LineReceiver): del self.sessions[self.profileid] self.db.delete_session(self.sesskey) - self.log(logging.INFO, "Deleted session " + self.session) + self.log(logging.INFO, "Deleted session %s", self.session) except: - self.log(logging.ERROR, "Unknown exception: %s" % traceback.format_exc()) + self.log(logging.ERROR, + "Unknown exception: %s", + traceback.format_exc()) def rawDataReceived(self, data): try: - self.log(logging.DEBUG, "RESPONSE: '%s'..." % data) + self.log(logging.DEBUG, "RESPONSE: '%s'...", data) - # In the case where command string is too big to fit into one read, any parts that could not be successfully - # parsed are stored in the variable remaining_message. On the next rawDataReceived command, the remaining - # message and the data are combined to create a full command. + # In the case where command string is too big to fit into one + # read, any parts that could not be successfully parsed are + # stored in the variable remaining_message. On the next + # rawDataReceived command, the remaining message and the data + # are combined to create a full command. data = self.remaining_message + data # Check to make sure the data buffer starts with a valid command. if len(data) > 0 and data[0] != '\\': - # There is data in the buffer but it doesn't start with a \ so there's no chance of it being valid. - # Look for the first instance of \final\ and remove everything before it. - # If \final\ is not in the command string then ignore it. + # There is data in the buffer but it doesn't start with a \ so + # there's no chance of it being valid. Look for the first + # instance of \final\ and remove everything before it. If + # \final\ is not in the command string then ignore it. final = "\\final\\" - data = data[data.index(final) + len(final):] if final in data else "" + data = data[data.index(final) + len(final):] \ + if final in data else "" - commands, self.remaining_message = gs_query.parse_gamespy_message(data) + commands, self.remaining_message = \ + gs_query.parse_gamespy_message(data) cmds = { "login": self.perform_login, @@ -186,28 +224,36 @@ class PlayerSession(LineReceiver): "delbuddy": self.perform_delbuddy, "authadd": self.perform_authadd, } + def cmd_err(data_parsed): - # Maybe write unknown commands to a separate file later so new data can be collected more easily? - self.log(logging.ERROR, "Found unknown command, don't know how to handle '%s'." % data_parsed['__cmd__']) + # Maybe write unknown commands to a separate file later so + # new data can be collected more easily? + self.log(logging.ERROR, + "Found unknown command, don't know how to handle" + " '%s'.", data_parsed['__cmd__']) for data_parsed in commands: - #self.log(-1, data_parsed) - self.log(logging.DEBUG, data_parsed) + # self.log(-1, data_parsed) + self.log(logging.DEBUG, "%s", data_parsed) cmds.get(data_parsed['__cmd__'], cmd_err)(data_parsed) except: - self.log(logging.ERROR, "Unknown exception: %s" % traceback.format_exc()) + self.log(logging.ERROR, + "Unknown exception: %s", + traceback.format_exc()) def perform_login(self, data_parsed): - authtoken_parsed = gs_utils.parse_authtoken(data_parsed['authtoken'], self.db) + authtoken_parsed = gs_utils.parse_authtoken(data_parsed['authtoken'], + self.db) if authtoken_parsed is None: - self.log(logging.WARNING, "Invalid Authtoken.") + self.log(logging.WARNING, "%s", "Invalid Authtoken.") msg = gs_query.create_gamespy_message([ ('__cmd__', "error"), ('__cmd_val__', ""), ('err', '266'), ('fatal', ''), - ('errmsg', 'There was an error validating the pre-authentication.'), + ('errmsg', 'There was an error validating the' + ' pre-authentication.'), ('id', data_parsed['id']), ]) self.transport.write(bytes(msg)) @@ -217,17 +263,35 @@ class PlayerSession(LineReceiver): self.sdkrevision = data_parsed['sdkrevision'] # Verify the client's response - valid_response = gs_utils.generate_response(self.challenge, authtoken_parsed['challenge'], data_parsed['challenge'], data_parsed['authtoken']) + valid_response = gs_utils.generate_response( + self.challenge, + authtoken_parsed['challenge'], + data_parsed['challenge'], + data_parsed['authtoken'] + ) if data_parsed['response'] != valid_response: - self.log(logging.ERROR, "ERROR: Got invalid response. Got %s, expected %s" % (data_parsed['response'], valid_response)) + self.log(logging.ERROR, + "ERROR: Got invalid response." + " Got %s, expected %s", + data_parsed['response'], valid_response) - proof = gs_utils.generate_proof(self.challenge, authtoken_parsed['challenge'], data_parsed['challenge'], data_parsed['authtoken']) + proof = gs_utils.generate_proof( + self.challenge, + authtoken_parsed['challenge'], + data_parsed['challenge'], + data_parsed['authtoken'] + ) - userid, profileid, gsbrcd, uniquenick = gs_utils.login_profile_via_parsed_authtoken(authtoken_parsed, self.db) + userid, profileid, gsbrcd, uniquenick = \ + gs_utils.login_profile_via_parsed_authtoken(authtoken_parsed, + self.db) if profileid is not None: - # Successfully logged in or created account, continue creating session. - loginticket = gs_utils.base64_encode(utils.generate_random_str(16)) + # Successfully logged in or created account, continue + # creating session. + loginticket = gs_utils.base64_encode( + utils.generate_random_str(16) + ) self.sesskey = self.db.create_session(profileid, loginticket) self.sessions[profileid] = self @@ -235,13 +299,11 @@ class PlayerSession(LineReceiver): self.buddies = self.db.get_buddy_list(self.profileid) self.blocked = self.db.get_blocked_list(self.profileid) - if self.sdkrevision == "11": # Used in Tatsunoko vs Capcom + if self.sdkrevision == "11": # Used in Tatsunoko vs Capcom def make_list(data): - list = [] - for d in data: - if d['status'] == 1: - list.append(str(d['buddyProfileId'])) - return list + return [str(d['buddyProfileId']) + for d in data + if d['status'] == 1] block_list = make_list(self.blocked) msg = gs_query.create_gamespy_message([ @@ -250,7 +312,7 @@ class PlayerSession(LineReceiver): ('list', ','.join(block_list)), ]) - self.log(logging.DEBUG, "SENDING: %s" % msg) + self.log(logging.DEBUG, "SENDING: %s", msg) self.transport.write(bytes(msg)) buddy_list = make_list(self.buddies) @@ -260,10 +322,9 @@ class PlayerSession(LineReceiver): ('list', ','.join(buddy_list)), ]) - self.log(logging.DEBUG, "SENDING: %s" % msg) + self.log(logging.DEBUG, "SENDING: %s", msg) self.transport.write(bytes(msg)) - msg = gs_query.create_gamespy_message([ ('__cmd__', "lc"), ('__cmd_val__', "2"), @@ -272,29 +333,35 @@ class PlayerSession(LineReceiver): ('userid', userid), ('profileid', profileid), ('uniquenick', uniquenick), - # Some kind of token... don't know it gets used or generated, but it doesn't seem to have any negative effects if it's not properly generated. + # Some kind of token... don't know it gets used or generated, + # but it doesn't seem to have any negative effects if it's + # not properly generated. ('lt', loginticket), ('id', data_parsed['id']), ]) - # Take the first 4 letters of gsbrcd instead of gamecd because they should be consistent across game - # regions. For example, the US version of Metroid Prime Hunters has the gamecd "AMHE" and the first 4 letters - # of gsbrcd are "AMHE". However, the Japanese version of Metroid Prime Hunters has the gamecd "AMHJ" with - # the first 4 letters of bsbrcd as "AMHE". Tetris DS is the other way, with the first 4 letters as the - # Japanese version (ATRJ) while the gamecd is region specific (ATRE for US and ATRJ for JP). - # gameid is used to send all people on the player's friends list a status updates, so don't make it region - # specific. + # Take the first 4 letters of gsbrcd instead of gamecd because + # they should be consistent across game regions. For example, the + # US version of Metroid Prime Hunters has the gamecd "AMHE" and + # the first 4 letters of gsbrcd are "AMHE". However, the Japanese + # version of Metroid Prime Hunters has the gamecd "AMHJ" with the + # first 4 letters of bsbrcd as "AMHE". Tetris DS is the other way, + # with the first 4 letters as the Japanese version (ATRJ) while + # the gamecd is region specific (ATRE for US and ATRJ for JP). + # gameid is used to send all people on the player's friends list a + # status updates, so don't make it region specific. self.gameid = gsbrcd[:4] self.profileid = int(profileid) - self.log(logging.DEBUG, "SENDING: %s" % msg) + self.log(logging.DEBUG, "SENDING: %s", msg) self.transport.write(bytes(msg)) # Get pending messages. self.get_pending_messages() # Send any friend statuses when the user logs in. - # This will allow the user to see if their friends are hosting a game as soon as they log in. + # This will allow the user to see if their friends are hosting a + # game as soon as they log in. self.get_status_from_friends() self.send_status_to_friends() @@ -303,7 +370,7 @@ class PlayerSession(LineReceiver): # self.statstring = profile['stat'] # self.locstring = profile['loc'] else: - self.log(logging.INFO, "Invalid password or banned user") + self.log(logging.INFO, "%s", "Invalid password or banned user") msg = gs_query.create_gamespy_message([ ('__cmd__', "error"), ('__cmd_val__', ""), @@ -312,11 +379,13 @@ class PlayerSession(LineReceiver): ('errmsg', 'Login failed.'), ('id', data_parsed['id']), ]) - self.log(logging.DEBUG, "SENDING: %s" % msg) + self.log(logging.DEBUG, "SENDING: %s", msg) self.transport.write(bytes(msg)) def perform_logout(self, data_parsed): - self.log(logging.INFO, "Session %s has logged off" % (data_parsed['sesskey'])) + self.log(logging.INFO, + "Session %s has logged off", + data_parsed['sesskey']) self.db.delete_session(data_parsed['sesskey']) if self.profileid in self.sessions: @@ -325,10 +394,16 @@ class PlayerSession(LineReceiver): self.transport.loseConnection() def perform_getprofile(self, data_parsed): - #profile = self.db.get_profile_from_session_key(data_parsed['sesskey']) + # profile = self.db.get_profile_from_session_key( + # data_parsed['sesskey'] + # ) profile = self.db.get_profile_from_profileid(data_parsed['profileid']) - # Wii example: \pi\\profileid\474888031\nick\5pde5vhn1WR9E2g1t533\userid\442778352\email\5pde5vhn1WR9E2g1t533@nds\sig\b126556e5ee62d4da9629dfad0f6b2a8\uniquenick\5pde5vhn1WR9E2g1t533\pid\11\lon\0.000000\lat\0.000000\loc\\id\2\final\ + # Wii example: + # \pi\\profileid\474888031\nick\5pde5vhn1WR9E2g1t533\userid\442778352 + # \email\5pde5vhn1WR9E2g1t533@nds\sig\b126556e5ee62d4da9629dfad0f6b2a8 + # \uniquenick\5pde5vhn1WR9E2g1t533\pid\11\lon\0.000000\lat\0.000000 + # \loc\\id\2\final\ sig = utils.generate_random_hex_str(32) msg_d = [ @@ -343,10 +418,11 @@ class PlayerSession(LineReceiver): ('pid', profile['pid']), ] - if profile['firstname'] != "": - msg_d.append(('firstname', profile['firstname'])) # Wii gets a firstname + if profile['firstname']: + # Wii gets a firstname + msg_d.append(('firstname', profile['firstname'])) - if profile['lastname'] != "": + if profile['lastname']: msg_d.append(('lastname', profile['lastname'])) msg_d.extend([ @@ -357,14 +433,18 @@ class PlayerSession(LineReceiver): ]) msg = gs_query.create_gamespy_message(msg_d) - self.log(logging.DEBUG, "SENDING: %s" % msg) + self.log(logging.DEBUG, "SENDING: %s", msg) self.transport.write(bytes(msg)) def perform_updatepro(self, data_parsed): - # Wii example: \updatepro\\sesskey\199714190\firstname\Wii:2555151656076614@WR9E\partnerid\11\final\ + """Wii example: + \updatepro\\sesskey\199714190\firstname\Wii:2555151656076614@WR9E + \partnerid\11\final\ - # Remove any fields not related to what we should be updating. - # To avoid any crashes, make sure the key is actually in the dictionary before removing it. + Remove any fields not related to what we should be updating. + To avoid any crashes, make sure the key is actually in the dictionary + before removing it. + """ if "__cmd__" in data_parsed: data_parsed.pop('__cmd__') if "__cmd_val__" in data_parsed: @@ -380,7 +460,6 @@ class PlayerSession(LineReceiver): for f in data_parsed: self.db.update_profile(self.profileid, (f, data_parsed[f])) - def perform_ka(self, data_parsed): self.keepalive = int(time.time()) @@ -390,7 +469,6 @@ class PlayerSession(LineReceiver): ]) self.transport.write(msg) - def perform_status(self, data_parsed): self.sesskey = data_parsed['sesskey'] self.status = data_parsed['__cmd_val__'] @@ -405,9 +483,9 @@ class PlayerSession(LineReceiver): self.send_status_to_friends() - def perform_bm(self, data_parsed): - if data_parsed['__cmd_val__'] in ("1", "5", "102", "103"): # Message to/from clients? + # Message to/from clients? + if data_parsed['__cmd_val__'] in ("1", "5", "102", "103"): if "t" in data_parsed: # Send message to the profile id in "t" dest_profileid = int(data_parsed['t']) @@ -416,7 +494,8 @@ class PlayerSession(LineReceiver): not_buddies = False - # Check if the user is buddies with the target user before sending message. + # Check if the user is buddies with the target user before + # sending message. if not_buddies: for buddy in self.buddies: if buddy['userProfileId'] == dest_profileid: @@ -429,16 +508,21 @@ class PlayerSession(LineReceiver): not_buddies = True break - # Send error to user if they tried to send a message to someone who isn't a buddy. + # Send error to user if they tried to send a message to + # someone who isn't a buddy. if not_buddies: msg = gs_query.create_gamespy_message([ ('__cmd__', "error"), ('__cmd_val__', ""), ('err', 2305), - ('errmsg', "The profile the message was to be sent to is not a buddy."), + ('errmsg', + "The profile the message was to be sent to is not" + " a buddy."), ('id', 1), ]) - logger.log(logging.DEBUG, "Trying to send message to someone who isn't a buddy: %s" % msg) + logger.log(logging.DEBUG, + "Trying to send message to someone who isn't" + " a buddy: %s", msg) self.transport.write(msg) return @@ -450,33 +534,44 @@ class PlayerSession(LineReceiver): ]) if dest_profileid in self.sessions: - self.log(logging.DEBUG, "SENDING TO %s:%s: %s" % (self.sessions[dest_profileid].address.host, self.sessions[dest_profileid].address.port, msg)) + self.log(logging.DEBUG, + "SENDING TO %s:%s: %s", + self.sessions[dest_profileid].address.host, + self.sessions[dest_profileid].address.port, msg) self.sessions[dest_profileid].transport.write(bytes(msg)) self.send_status_to_friends(dest_profileid) self.get_status_from_friends(dest_profileid) else: if data_parsed['__cmd_val__'] == "1": - self.log(logging.DEBUG, "Saving message to %d: %s" % (dest_profileid, msg)) - self.db.save_pending_message(self.profileid, dest_profileid, msg) + self.log(logging.DEBUG, + "Saving message to %d: %s", + dest_profileid, msg) + self.db.save_pending_message(self.profileid, + dest_profileid, msg) else: msg = gs_query.create_gamespy_message([ ('__cmd__', "error"), ('__cmd_val__', ""), ('err', 2307), - ('errmsg', "The buddy to send a message to is offline."), + ('errmsg', "The buddy to send a message to" + " is offline."), ('id', 1), ]) - logger.log(logging.DEBUG, "Trying to send message to someone who isn't online: %s" % msg) + logger.log(logging.DEBUG, + "Trying to send message to someone who" + " isn't online: %s", msg) self.transport.write(msg) - def perform_addbuddy(self, data_parsed): newprofileid = int(data_parsed['newprofileid']) if newprofileid == self.profileid: - logger.log(logging.DEBUG, "Can't add self as friend: %d == %d", newprofileid, self.profileid) + logger.log(logging.DEBUG, + "Can't add self as friend: %d == %d", + newprofileid, self.profileid) return - # Sample: \addbuddy\\sesskey\231601763\newprofileid\476756820\reason\\final\ + # Sample: + # \addbuddy\\sesskey\231601763\newprofileid\476756820\reason\\final\ self.buddies = self.db.get_buddy_list(self.profileid) buddy_exists = False @@ -485,27 +580,37 @@ class PlayerSession(LineReceiver): buddy_exists = True break - if buddy_exists is False: + if not buddy_exists: self.db.add_buddy(self.profileid, newprofileid) if newprofileid in self.sessions: - logger.log(logging.DEBUG, "User is online, sending direct request from profile id %d to profile id %d..." % (self.profileid, newprofileid)) + logger.log(logging.DEBUG, + "User is online, sending direct request from" + " profile id %d to profile id %d...", + self.profileid, newprofileid) - # TODO: Add a way to check if a profile id is already a buddy using SQL + # TODO: Add a way to check if a profile id is already a buddy + # using SQL other_player_authorized = False target_buddy_list = self.db.get_buddy_list(newprofileid) - logger.log(logging.DEBUG, target_buddy_list) + logger.log(logging.DEBUG, "%s", target_buddy_list) for buddy in target_buddy_list: - if buddy['buddyProfileId'] == self.profileid and buddy['blocked'] == 0: + if buddy['buddyProfileId'] == self.profileid and \ + not buddy['blocked']: other_player_authorized = True break - if other_player_authorized == True: - logger.log(logging.DEBUG, "Automatic authorization: %d (target) already has %d (source) as a friend." % (newprofileid, self.profileid)) + if other_player_authorized: + logger.log(logging.DEBUG, + "Automatic authorization: %d (target) already" + " has %d (source) as a friend.", + newprofileid, self.profileid) # Force them both to add each other - self.send_buddy_request(self.sessions[newprofileid], self.profileid) - self.send_buddy_request(self.sessions[self.profileid], newprofileid) + self.send_buddy_request(self.sessions[newprofileid], + self.profileid) + self.send_buddy_request(self.sessions[self.profileid], + newprofileid) self.send_bm4(newprofileid) @@ -516,9 +621,11 @@ class PlayerSession(LineReceiver): self.get_status_from_friends(newprofileid) else: - self.send_buddy_request(self.sessions[newprofileid], self.profileid) + self.send_buddy_request(self.sessions[newprofileid], + self.profileid) else: - # Trying to add someone who is already a friend. Just send status updates + # Trying to add someone who is already a friend. + # Just send status updates. self.send_status_to_friends(newprofileid) self.get_status_from_friends(newprofileid) @@ -537,32 +644,42 @@ class PlayerSession(LineReceiver): self.transport.write(bytes(msg)) def perform_delbuddy(self, data_parsed): - # Sample: \delbuddy\\sesskey\61913621\delprofileid\1\final\ + """Sample: + \delbuddy\\sesskey\61913621\delprofileid\1\final\ + """ self.db.delete_buddy(self.profileid, int(data_parsed['delprofileid'])) self.buddies = self.db.get_buddy_list(self.profileid) def perform_authadd(self, data_parsed): - # Sample: \authadd\\sesskey\231587549\fromprofileid\217936895\sig\f259f26d3273f8bda23c7c5e4bd8c5aa\final\ - # Authorize the other person's friend request. + """Authorize the other person's friend request. + + Sample: + \authadd\\sesskey\231587549\fromprofileid\217936895 + \sig\f259f26d3273f8bda23c7c5e4bd8c5aa\final\ + """ target_profile = int(data_parsed['fromprofileid']) self.db.auth_buddy(target_profile, self.profileid) self.get_buddy_authorized() self.buddies = self.db.get_buddy_list(self.profileid) - + self.send_bm4(target_profile) self.send_status_to_friends(target_profile) self.get_status_from_friends(target_profile) - def send_status_to_friends(self, buddy_profileid = None): - # TODO: Cache buddy list so we don't have to query the database every time + def send_status_to_friends(self, buddy_profileid=None): + """TODO: Cache buddy list so we don't have to query the + database every time.""" self.buddies = self.db.get_buddy_list(self.profileid) if self.status == "0" and self.statstring == "Offline": # Going offline, don't need to send the other information. status_msg = "|s|%s|ss|%s" % (self.status, self.statstring) else: - status_msg = "|s|%s|ss|%s|ls|%s|ip|%d|p|0|qm|0" % (self.status, self.statstring, self.locstring, self.get_ip_as_int(self.address.host)) + status_msg = "|s|%s|ss|%s|ls|%s|ip|%d|p|0|qm|0" % ( + self.status, self.statstring, self.locstring, + self.get_ip_as_int(self.address.host) + ) msg = gs_query.create_gamespy_message([ ('__cmd__', "bm"), @@ -573,28 +690,48 @@ class PlayerSession(LineReceiver): buddy_list = self.buddies if buddy_profileid is not None: - buddy_list = [{"buddyProfileId":buddy_profileid}] + buddy_list = [{"buddyProfileId": buddy_profileid}] for buddy in buddy_list: if buddy['buddyProfileId'] in self.sessions: - #self.log(logging.DEBUG, "Sending status to buddy id %s (%s:%d): %s" % (str(buddy['buddyProfileId']), self.sessions[buddy['buddyProfileId']].address.host, self.sessions[buddy['buddyProfileId']].address.port, msg)) - self.sessions[buddy['buddyProfileId']].transport.write(bytes(msg)) + # self.log(logging.DEBUG, + # "Sending status to buddy id %s (%s:%d): %s", + # str(buddy['buddyProfileId']), + # self.sessions[ + # buddy['buddyProfileId'] + # ].address.host, + # self.sessions[ + # buddy['buddyProfileId'] + # ].address.port, msg) + self.sessions[buddy['buddyProfileId']].transport \ + .write(bytes(msg)) - def get_status_from_friends(self, buddy_profileid = None): - # This will be called when the player logs in. Grab the player's buddy list and check the current sessions to - # see if anyone is online. If they are online, make them send an update to the calling client. + def get_status_from_friends(self, buddy_profileid=None): + """This will be called when the player logs in. + + Grab the player's buddy list and check the current sessions to + see if anyone is online. If they are online, make them send an update + to the calling client. + """ self.buddies = self.db.get_buddy_list(self.profileid) buddy_list = self.buddies if buddy_profileid is not None: - buddy_list = [{"buddyProfileId":buddy_profileid}] + buddy_list = [{"buddyProfileId": buddy_profileid}] for buddy in self.buddies: if buddy['status'] != 1: continue - if buddy['buddyProfileId'] in self.sessions and self.sessions[buddy['buddyProfileId']].gameid == self.gameid: - status_msg = "|s|%s|ss|%s|ls|%s|ip|%d|p|0|qm|0" % (self.sessions[buddy['buddyProfileId']].status, self.sessions[buddy['buddyProfileId']].statstring, self.sessions[buddy['buddyProfileId']].locstring, self.get_ip_as_int(self.sessions[buddy['buddyProfileId']].address.host)) + if buddy['buddyProfileId'] in self.sessions and \ + self.sessions[buddy['buddyProfileId']].gameid == self.gameid: + status_msg = "|s|%s|ss|%s|ls|%s|ip|%d|p|0|qm|0" % ( + self.sessions[buddy['buddyProfileId']].status, + self.sessions[buddy['buddyProfileId']].statstring, + self.sessions[buddy['buddyProfileId']].locstring, + self.get_ip_as_int(self.sessions[ + buddy['buddyProfileId'] + ].address.host)) else: status_msg = "|s|0|ss|Offline" @@ -615,20 +752,25 @@ class PlayerSession(LineReceiver): ('__cmd__', "bm"), ('__cmd_val__', "1"), ('f', buddy['userProfileId']), - ('msg', "I have authorized your request to add me to your list"), + ('msg', "I have authorized your request to add me to" + " your list"), ]) self.transport.write(bytes(msg)) - self.db.buddy_sent_auth_message(buddy['userProfileId'], buddy['buddyProfileId']) + self.db.buddy_sent_auth_message(buddy['userProfileId'], + buddy['buddyProfileId']) def get_buddy_requests(self): - # Get list people who have added the user but haven't been accepted yet. + """Get list people who have added the user but haven't been accepted + yet.""" buddies = self.db.get_pending_buddy_requests(self.profileid) for buddy in buddies: - self.send_buddy_request(self, buddy['userProfileId'], buddy['time']) + self.send_buddy_request(self, + buddy['userProfileId'], + buddy['time']) - def send_buddy_request(self, session, profileid, senttime = None): + def send_buddy_request(self, session, profileid, senttime=None): sig = utils.generate_random_hex_str(32) msg = "\r\n\r\n" msg += "|signed|" + sig @@ -656,6 +798,7 @@ class PlayerSession(LineReceiver): except: self.transport.write(bytearray(message['msg'], "utf-8")) + if __name__ == "__main__": gsps = GameSpyProfileServer() gsps.start() From 94e391a4a9f9f3847e3019e9f8c1a657d27adfa0 Mon Sep 17 00:00:00 2001 From: payet_s Date: Tue, 4 Aug 2015 23:29:55 +0200 Subject: [PATCH 13/21] Cleaned: gamespy_player_search_server.py --- gamespy_player_search_server.py | 68 ++++++++++++++++++++++++++------- 1 file changed, 54 insertions(+), 14 deletions(-) diff --git a/gamespy_player_search_server.py b/gamespy_player_search_server.py index f51b012..3322060 100644 --- a/gamespy_player_search_server.py +++ b/gamespy_player_search_server.py @@ -36,20 +36,25 @@ logger_output_to_console = True logger_output_to_file = True logger_name = "GameSpyPlayerSearchServer" logger_filename = "gamespy_player_search_server.log" -logger = utils.create_logger(logger_name, logger_filename, -1, logger_output_to_console, logger_output_to_file) +logger = utils.create_logger(logger_name, logger_filename, -1, + logger_output_to_console, logger_output_to_file) address = ("0.0.0.0", 29901) + class GameSpyPlayerSearchServer(object): def __init__(self): pass def start(self): - endpoint_search = serverFromString(reactor, "tcp:%d:interface=%s" % (address[1], address[0])) + endpoint_search = serverFromString( + reactor, + "tcp:%d:interface=%s" % (address[1], address[0]) + ) conn_search = endpoint_search.listen(PlayerSearchFactory()) try: - if reactor.running is False: + if not reactor.running: reactor.run(installSignalHandlers=0) except ReactorAlreadyRunning: pass @@ -57,7 +62,9 @@ class GameSpyPlayerSearchServer(object): class PlayerSearchFactory(Factory): def __init__(self): - logger.log(logging.INFO, "Now listening for player search connections on %s:%d...", address[0], address[1]) + logger.log(logging.INFO, + "Now listening for player search connections on %s:%d...", + address[0], address[1]) def buildProtocol(self, address): return PlayerSearch(address) @@ -79,7 +86,7 @@ class PlayerSearch(LineReceiver): def rawDataReceived(self, data): try: - logger.log(logging.DEBUG, "SEARCH RESPONSE: %s" % data) + logger.log(logging.DEBUG, "SEARCH RESPONSE: %s", data) data = self.leftover + data commands, self.leftover = gs_query.parse_gamespy_message(data) @@ -90,15 +97,45 @@ class PlayerSearch(LineReceiver): if data_parsed['__cmd__'] == "otherslist": self.perform_otherslist(data_parsed) else: - logger.log(logging.DEBUG, "Found unknown search command, don't know how to handle '%s'." % data_parsed['__cmd__']) + logger.log(logging.DEBUG, + "Found unknown search command, don't know" + " how to handle '%s'.", + data_parsed['__cmd__']) except: - logger.log(logging.ERROR, "Unknown exception: %s" % traceback.format_exc()) + logger.log(logging.ERROR, + "Unknown exception: %s", + traceback.format_exc()) def perform_otherslist(self, data_parsed): - # Reference: http://wiki.tockdom.com/wiki/MKWii_Network_Protocol/Server/gpsp.gs.nintendowifi.net - # Example from: filtered-mkw-log-2014-01-01-ct1310.eth - # \otherslist\\o\146376154\uniquenick\2m0isbjmvRMCJ2i5321j\o\192817284\uniquenick\1jhggtmghRMCJ2jrsh23\o\302594991\uniquenick\7dkjp51v5RMCJ2nr3vs9\o\368031897\uniquenick\1v7p3qmkpRMCJ1o8f56p\o\447214276\uniquenick\7dkt0p6gtRMCJ2ljh72h\o\449615791\uniquenick\4puvrm1g4RMCJ00ho3v1\o\460250854\uniquenick\4rik5l1u1RMCJ0tc3fii\o\456284963\uniquenick\1unitvi86RMCJ1b10u02\o\453830866\uniquenick\7de3q52dbRMCJ2877ss2\o\450197498\uniquenick\3qtutr1ikRMCJ38gem1n\o\444241868\uniquenick\67tp53bs9RMCJ1abs7ej\o\420030955\uniquenick\5blesqia3RMCJ322bbd6\o\394609454\uniquenick\0hddp7mq2RMCJ30uv7r7\o\369478991\uniquenick\59de9c2bhRMCJ0re0fii\o\362755626\uniquenick\5tte2lif7RMCJ0cscgtg\o\350951571\uniquenick\7aeummjlaRMCJ3li4ls2\o\350740680\uniquenick\484uiqhr4RMCJ18opoj0\o\349855648\uniquenick\5blesqia3RMCJ1c245dn\o\324078642\uniquenick\62go5gpt0RMCJ0v0uhc9\o\304111337\uniquenick\4lcg6ampvRMCJ1gjre51\o\301273266\uniquenick\1dhdpjhn8RMCJ2da6f9h\o\193178453\uniquenick\3pcgu0299RMCJ3nhu50f\o\187210028\uniquenick\3tau15a9lRMCJ2ar247h\o\461622261\uniquenick\59epddrnkRMCJ1t2ge7l\oldone\\final\ + """Reference: http://wiki.tockdom.com/wiki/MKWii_Network_Protocol/Server/gpsp.gs.nintendowifi.net + Example from: filtered-mkw-log-2014-01-01-ct1310.eth + \otherslist\\o\146376154\uniquenick\2m0isbjmvRMCJ2i5321j + \o\192817284\uniquenick\1jhggtmghRMCJ2jrsh23 + \o\302594991\uniquenick\7dkjp51v5RMCJ2nr3vs9 + \o\368031897\uniquenick\1v7p3qmkpRMCJ1o8f56p + \o\447214276\uniquenick\7dkt0p6gtRMCJ2ljh72h + \o\449615791\uniquenick\4puvrm1g4RMCJ00ho3v1 + \o\460250854\uniquenick\4rik5l1u1RMCJ0tc3fii + \o\456284963\uniquenick\1unitvi86RMCJ1b10u02 + \o\453830866\uniquenick\7de3q52dbRMCJ2877ss2 + \o\450197498\uniquenick\3qtutr1ikRMCJ38gem1n + \o\444241868\uniquenick\67tp53bs9RMCJ1abs7ej + \o\420030955\uniquenick\5blesqia3RMCJ322bbd6 + \o\394609454\uniquenick\0hddp7mq2RMCJ30uv7r7 + \o\369478991\uniquenick\59de9c2bhRMCJ0re0fii + \o\362755626\uniquenick\5tte2lif7RMCJ0cscgtg + \o\350951571\uniquenick\7aeummjlaRMCJ3li4ls2 + \o\350740680\uniquenick\484uiqhr4RMCJ18opoj0 + \o\349855648\uniquenick\5blesqia3RMCJ1c245dn + \o\324078642\uniquenick\62go5gpt0RMCJ0v0uhc9 + \o\304111337\uniquenick\4lcg6ampvRMCJ1gjre51 + \o\301273266\uniquenick\1dhdpjhn8RMCJ2da6f9h + \o\193178453\uniquenick\3pcgu0299RMCJ3nhu50f + \o\187210028\uniquenick\3tau15a9lRMCJ2ar247h + \o\461622261\uniquenick\59epddrnkRMCJ1t2ge7l + \oldone\\final\ + """ msg_d = [ ('__cmd__', "otherslist"), ('__cmd_val__', ""), @@ -107,10 +144,13 @@ class PlayerSearch(LineReceiver): if "numopids" in data_parsed and "opids" in data_parsed: numopids = int(data_parsed['numopids']) opids = data_parsed['opids'].split('|') - if (len(opids) != numopids) and (not int(opids[0]) == 0): - logger.log(logging.ERROR, "Unexpected number of opids, got %d, expected %d." % (len(opids), numopids)) + if len(opids) != numopids and int(opids[0]): + logger.log(logging.ERROR, + "Unexpected number of opids, got %d, expected %d.", + len(opids), numopids) # Return all uniquenicks despite any unexpected/missing opids + # We can do better than that, I think... for opid in opids: profile = self.db.get_profile_from_profileid(opid) @@ -123,10 +163,10 @@ class PlayerSearch(LineReceiver): msg_d.append(('oldone', "")) msg = gs_query.create_gamespy_message(msg_d) - logger.log(logging.DEBUG, "SENDING: %s" % msg) + logger.log(logging.DEBUG, "SENDING: %s", msg) self.transport.write(bytes(msg)) + if __name__ == "__main__": gsps = GameSpyPlayerSearchServer() gsps.start() - From fea1cd37acbd371d4abb85d4123a1da353b3bb2b Mon Sep 17 00:00:00 2001 From: payet_s Date: Wed, 5 Aug 2015 15:19:01 +0200 Subject: [PATCH 14/21] Cleaned: gamespy_natneg_server.py --- gamespy_natneg_server.py | 318 ++++++++++++++++++++++++++------------- 1 file changed, 214 insertions(+), 104 deletions(-) diff --git a/gamespy_natneg_server.py b/gamespy_natneg_server.py index 451bc74..5601f88 100644 --- a/gamespy_natneg_server.py +++ b/gamespy_natneg_server.py @@ -17,8 +17,10 @@ You should have received a copy of the GNU Affero General Public License along with this program. If not, see . - Server emulator for *.available.gs.nintendowifi.net and *.master.gs.nintendowifi.net - Query and Reporting: http://docs.poweredbygamespy.com/wiki/Query_and_Reporting_Overview + Server emulator for *.available.gs.nintendowifi.net + and *.master.gs.nintendowifi.net + Query and Reporting: + http://docs.poweredbygamespy.com/wiki/Query_and_Reporting_Overview """ import logging @@ -38,7 +40,9 @@ logger_output_to_console = True logger_output_to_file = True logger_name = "GameSpyNatNegServer" logger_filename = "gamespy_natneg_server.log" -logger = utils.create_logger(logger_name, logger_filename, -1, logger_output_to_console, logger_output_to_file) +logger = utils.create_logger(logger_name, logger_filename, -1, + logger_output_to_console, logger_output_to_file) + class GameSpyServerDatabase(BaseManager): pass @@ -52,26 +56,34 @@ GameSpyServerDatabase.register("add_natneg_server") GameSpyServerDatabase.register("get_natneg_server") GameSpyServerDatabase.register("delete_natneg_server") + class GameSpyNatNegServer(object): def __init__(self): self.session_list = {} self.natneg_preinit_session = {} self.secret_key_list = gs_utils.generate_secret_keys("gslist.cfg") - self.server_manager = GameSpyServerDatabase(address=("127.0.0.1", 27500), authkey="") + self.server_manager = GameSpyServerDatabase( + address=("127.0.0.1", 27500), + authkey="" + ) self.server_manager.connect() def start(self): try: # Start natneg server - address = ('0.0.0.0', 27901) # accessible to outside connections (use this if you don't know what you're doing) + # Accessible to outside connections (use this if you don't know + # what you're doing) + address = ('0.0.0.0', 27901) self.socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) self.socket.bind(address) self.write_queue = Queue.Queue() - logger.log(logging.INFO, "Server is now listening on %s:%s..." % (address[0], address[1])) + logger.log(logging.INFO, + "Server is now listening on %s:%s...", + address[0], address[1]) threading.Thread(target=self.write_queue_worker).start() while True: @@ -79,7 +91,9 @@ class GameSpyNatNegServer(object): self.handle_packet(recv_data, addr) except: - logger.log(logging.ERROR, "Unknown exception: %s" % traceback.format_exc()) + logger.log(logging.ERROR, + "Unknown exception: %s", + traceback.format_exc()) def write_queue_send(self, data, address): time.sleep(0.05) @@ -88,11 +102,17 @@ class GameSpyNatNegServer(object): def write_queue_worker(self): while True: data, address = self.write_queue.get() - threading.Thread(target=self.write_queue_send, args=(data, address)).start() + threading.Thread(target=self.write_queue_send, + args=(data, address)).start() self.write_queue.task_done() def handle_packet(self, recv_data, addr): - logger.log(logging.DEBUG, "Connection from %s:%d..." % (addr[0], addr[1])) + """Handle NATNEG. + + TODO: Pointer to methods for recv_data[7].""" + logger.log(logging.DEBUG, + "Connection from %s:%d...", + addr[0], addr[1]) logger.log(logging.DEBUG, utils.pretty_print_hex(recv_data)) # Make sure it's a legal packet @@ -104,11 +124,15 @@ class GameSpyNatNegServer(object): # Handle commands if recv_data[7] == '\x00': - logger.log(logging.DEBUG, "Received initialization from %s:%s..." % (addr[0], addr[1])) + logger.log(logging.DEBUG, + "Received initialization from %s:%s...", + addr[0], addr[1]) output = bytearray(recv_data[0:14]) - output += bytearray([0xff, 0xff, 0x6d, 0x16, 0xb5, 0x7d, 0xea ]) # Checked with Tetris DS, Mario Kart DS, and Metroid Prime Hunters, and this seems to be the standard response to 0x00 - output[7] = 0x01 # Initialization response + # Checked with Tetris DS, Mario Kart DS, and Metroid Prime + # Hunters, and this seems to be the standard response to 0x00 + output += bytearray([0xff, 0xff, 0x6d, 0x16, 0xb5, 0x7d, 0xea]) + output[7] = 0x01 # Initialization response self.write_queue.put((output, addr)) # Try to connect to the server @@ -123,90 +147,138 @@ class GameSpyNatNegServer(object): localport = utils.get_short(localport_raw, 0, True) localaddr = (localip, localport, localip_int_le, localip_int_be) - self.session_list.setdefault(session_id, {}).setdefault(client_id, { - 'connected': False, - 'addr': '', - 'localaddr': None, - 'serveraddr': None, - 'gameid': None - }) + self.session_list \ + .setdefault(session_id, {}) \ + .setdefault(client_id, + { + 'connected': False, + 'addr': '', + 'localaddr': None, + 'serveraddr': None, + 'gameid': None + }) + # In fact, it's a pointer (cf. shallow copy) + client_id_session = self.session_list[session_id][client_id] - self.session_list[session_id][client_id]['gameid'] = gameid - self.session_list[session_id][client_id]['addr'] = addr - self.session_list[session_id][client_id]['localaddr'] = localaddr - clients = len(self.session_list[session_id]) + client_id_session['gameid'] = gameid + client_id_session['addr'] = addr + client_id_session['localaddr'] = localaddr + clients = len(self.session_list[session_id]) # Unused? for client in self.session_list[session_id]: - if self.session_list[session_id][client]['connected'] is False: # and self.session_list[session_id][client]['localaddr'][1] != 0: - if client == client_id: - continue + # Another shallow copy + client_session = self.session_list[session_id][client] + if client_session['connected'] or client == client_id: + continue - #if self.session_list[session_id][client]['serveraddr'] is None: - serveraddr = self.get_server_info(gameid, session_id, client) - if serveraddr is None: - serveraddr = self.get_server_info_alt(gameid, session_id, client) + # if client_session['serveraddr'] \ + # is None: + serveraddr = self.get_server_info(gameid, session_id, client) + if serveraddr is None: + serveraddr = self.get_server_info_alt( + gameid, session_id, client + ) - self.session_list[session_id][client]['serveraddr'] = serveraddr - logger.log(logging.DEBUG, "Found server from local ip/port: %s from %d" % (serveraddr, session_id)) + client_session['serveraddr'] = serveraddr + logger.log(logging.DEBUG, + "Found server from local ip/port: %s from %d", + serveraddr, session_id) - publicport = self.session_list[session_id][client]['addr'][1] - if self.session_list[session_id][client]['localaddr'][1] != 0: - publicport = self.session_list[session_id][client]['localaddr'][1] + publicport = client_session['addr'][1] + if not client_session['localaddr'][1]: + publicport = client_session['localaddr'][1] - if self.session_list[session_id][client]['serveraddr'] is not None: - publicport = int(self.session_list[session_id][client]['serveraddr']['publicport']) + if client_session['serveraddr'] is not None: + publicport = int( + client_session['serveraddr']['publicport'] + ) - # Send to requesting client - output = bytearray(recv_data[0:12]) - output += bytearray([int(x) for x in self.session_list[session_id][client]['addr'][0].split('.')]) - output += utils.get_bytes_from_short(publicport, True) + # Send to requesting client + output = bytearray(recv_data[0:12]) + output += bytearray([ + int(x) for x in client_session['addr'][0].split('.') + ]) + output += utils.get_bytes_from_short(publicport, True) - output += bytearray([0x42, 0x00]) # Unknown, always seems to be \x42\x00 - output[7] = 0x05 - #self.write_queue.put((output, (self.session_list[session_id][client_id]['addr']))) - self.write_queue.put((output, (self.session_list[session_id][client_id]['addr'][0], self.session_list[session_id][client_id]['addr'][1]))) + # Unknown, always seems to be \x42\x00 + output += bytearray([0x42, 0x00]) + output[7] = 0x05 + # self.write_queue.put(( + # output, + # (client_id_session['addr']) + # )) + self.write_queue.put(( + output, + (client_id_session['addr'][0], + client_id_session['addr'][1]) + )) - logger.log(logging.DEBUG, "Sent connection request to %s:%d..." % (self.session_list[session_id][client_id]['addr'][0], self.session_list[session_id][client_id]['addr'][1])) - logger.log(logging.DEBUG, utils.pretty_print_hex(output)) + logger.log(logging.DEBUG, + "Sent connection request to %s:%d...", + client_id_session['addr'][0], + client_id_session['addr'][1]) + logger.log(logging.DEBUG, '%s', utils.pretty_print_hex(output)) - # Send to other client - #if self.session_list[session_id][client_id]['serveraddr'] is None: - serveraddr = self.get_server_info(gameid, session_id, client_id) - if serveraddr is None: - serveraddr = self.get_server_info_alt(gameid, session_id, client_id) + # Send to other client + # if client_id_session['serveraddr'] is None: + serveraddr = self.get_server_info( + gameid, session_id, client_id + ) + if serveraddr is None: + serveraddr = self.get_server_info_alt( + gameid, session_id, client_id + ) - self.session_list[session_id][client_id]['serveraddr'] = serveraddr - logger.log(logging.DEBUG, "Found server 2 from local ip/port: %s from %d" % (serveraddr, session_id)) + client_id_session['serveraddr'] = serveraddr + logger.log(logging.DEBUG, + "Found server 2 from local ip/port: %s from %d", + serveraddr, session_id) - publicport = self.session_list[session_id][client_id]['addr'][1] - if self.session_list[session_id][client_id]['localaddr'][1] != 0: - publicport = self.session_list[session_id][client_id]['localaddr'][1] + publicport = client_id_session['addr'][1] + if client_id_session['localaddr'][1]: + publicport = client_id_session['localaddr'][1] - if self.session_list[session_id][client_id]['serveraddr'] is not None: - publicport = int(self.session_list[session_id][client_id]['serveraddr']['publicport']) + if client_id_session['serveraddr'] is not None: + publicport = int( + client_id_session['serveraddr']['publicport'] + ) - output = bytearray(recv_data[0:12]) - output += bytearray([int(x) for x in self.session_list[session_id][client_id]['addr'][0].split('.')]) - output += utils.get_bytes_from_short(publicport, True) + output = bytearray(recv_data[0:12]) + output += bytearray( + [int(x) for x in client_id_session['addr'][0].split('.')] + ) + output += utils.get_bytes_from_short(publicport, True) - output += bytearray([0x42, 0x00]) # Unknown, always seems to be \x42\x00 - output[7] = 0x05 - #self.write_queue.put((output, (self.session_list[session_id][client]['addr']))) - self.write_queue.put((output, (self.session_list[session_id][client]['addr'][0], self.session_list[session_id][client]['addr'][1]))) + # Unknown, always seems to be \x42\x00 + output += bytearray([0x42, 0x00]) + output[7] = 0x05 + # self.write_queue.put((output, (client_session['addr']))) + self.write_queue.put((output, (client_session['addr'][0], + client_session['addr'][1]))) - logger.log(logging.DEBUG, "Sent connection request to %s:%d..." % (self.session_list[session_id][client]['addr'][0], self.session_list[session_id][client]['addr'][1])) - logger.log(logging.DEBUG, utils.pretty_print_hex(output)) + logger.log(logging.DEBUG, + "Sent connection request to %s:%d...", + client_session['addr'][0], + client_session['addr'][1]) + logger.log(logging.DEBUG, + '%s', + utils.pretty_print_hex(output)) - elif recv_data[7] == '\x06': # Was able to connect + elif recv_data[7] == '\x06': # Was able to connect client_id = "%02x" % ord(recv_data[13]) - logger.log(logging.DEBUG, "Received connected command from %s:%s..." % (addr[0], addr[1])) + logger.log(logging.DEBUG, + "Received connected command from %s:%s...", + addr[0], addr[1]) - if session_id in self.session_list and client_id in self.session_list[session_id]: + if session_id in self.session_list and \ + client_id in self.session_list[session_id]: self.session_list[session_id][client_id]['connected'] = True - elif recv_data[7] == '\x0a': # Address check. Note: UNTESTED! + elif recv_data[7] == '\x0a': # Address check. Note: UNTESTED! client_id = "%02x" % ord(recv_data[13]) - logger.log(logging.DEBUG, "Received address check command from %s:%s..." % (addr[0], addr[1])) + logger.log(logging.DEBUG, + "Received address check command from %s:%s...", + addr[0], addr[1]) output = bytearray(recv_data[0:15]) output += bytearray([int(x) for x in addr[0].split('.')]) @@ -216,53 +288,66 @@ class GameSpyNatNegServer(object): output[7] = 0x0b self.write_queue.put((output, addr)) - logger.log(logging.DEBUG, "Sent address check response to %s:%d..." % (addr[0], addr[1])) - logger.log(logging.DEBUG, utils.pretty_print_hex(output)) + logger.log(logging.DEBUG, + "Sent address check response to %s:%d...", + addr[0], addr[1]) + logger.log(logging.DEBUG, "%s", utils.pretty_print_hex(output)) - elif recv_data[7] == '\x0c': # Natify + elif recv_data[7] == '\x0c': # Natify port_type = "%02x" % ord(recv_data[12]) - logger.log(logging.DEBUG, "Received natify command from %s:%s..." % (addr[0], addr[1])) + logger.log(logging.DEBUG, + "Received natify command from %s:%s...", + addr[0], addr[1]) output = bytearray(recv_data) - output[7] = 0x02 # ERT Test + output[7] = 0x02 # ERT Test self.write_queue.put((output, addr)) - logger.log(logging.DEBUG, "Sent natify response to %s:%d..." % (addr[0], addr[1])) - logger.log(logging.DEBUG, utils.pretty_print_hex(output)) + logger.log(logging.DEBUG, + "Sent natify response to %s:%d...", + addr[0], addr[1]) + logger.log(logging.DEBUG, "%s", utils.pretty_print_hex(output)) - elif recv_data[7] == '\x0d': # Report - logger.log(logging.DEBUG, "Received report command from %s:%s..." % (addr[0], addr[1])) - logger.log(logging.DEBUG, utils.pretty_print_hex(recv_data)) + elif recv_data[7] == '\x0d': # Report + logger.log(logging.DEBUG, + "Received report command from %s:%s...", + addr[0], addr[1]) + logger.log(logging.DEBUG, "%s", utils.pretty_print_hex(recv_data)) # Report response output = bytearray(recv_data[:21]) - output[7] = 0x0e # Report response - output[14] = 0 # Clear byte to match real server's response + output[7] = 0x0e # Report response + output[14] = 0 # Clear byte to match real server's response self.write_queue.put((output, addr)) elif recv_data[7] == '\x0f': # Natneg v4 command thanks to Pipian. - # Only seems to be used in very few DS games (namely, Pokemon Black/White/Black 2/White 2). - logger.log(logging.DEBUG, "Received pre-init command from %s:%s..." % (addr[0], addr[1])) - logger.log(logging.DEBUG, utils.pretty_print_hex(recv_data)) + # Only seems to be used in very few DS games (namely, + # Pokemon Black/White/Black 2/White 2). + logger.log(logging.DEBUG, + "Received pre-init command from %s:%s...", + addr[0], addr[1]) + logger.log(logging.DEBUG, "%s", utils.pretty_print_hex(recv_data)) session = utils.get_int(recv_data[-4:], 0) # Report response output = bytearray(recv_data[:-4]) + bytearray([0, 0, 0, 0]) - output[7] = 0x10 # Pre-init response + output[7] = 0x10 # Pre-init response - if session == 0: + if not session: # What's the correct behavior when session == 0? output[13] = 2 elif session in self.natneg_preinit_session: - # Should this be sent to both clients or just the one that connected most recently? + # Should this be sent to both clients or just the one that + # connected most recently? # I can't tell from a one sided packet capture of Pokemon. # For the time being, send to both clients just in case. output[13] = 2 - self.write_queue.put((output, self.natneg_preinit_session[session])) + self.write_queue.put((output, + self.natneg_preinit_session[session])) - output[12] = (1, 0)[output[12]] # Swap the index + output[12] = (1, 0)[output[12]] # Swap the index del self.natneg_preinit_session[session] else: output[13] = 0 @@ -270,12 +355,15 @@ class GameSpyNatNegServer(object): self.write_queue.put((output, addr)) - else: # Was able to connect - logger.log(logging.DEBUG, "Received unknown command %02x from %s:%s..." % (ord(recv_data[7]), addr[0], addr[1])) + else: # Was able to connect + logger.log(logging.DEBUG, + "Received unknown command %02x from %s:%s...", + ord(recv_data[7]), addr[0], addr[1]) def get_server_info(self, gameid, session_id, client_id): server_info = None - servers = self.server_manager.get_natneg_server(session_id)._getvalue() + servers = self.server_manager.get_natneg_server(session_id) \ + ._getvalue() if servers is None: return None @@ -283,15 +371,23 @@ class GameSpyNatNegServer(object): console = False ipstr = self.session_list[session_id][client_id]['addr'][0] - ip = str(utils.get_ip(bytearray([int(x) for x in ipstr.split('.')]), 0, console)) + ip = str(utils.get_ip(bytearray( + [int(x) for x in ipstr.split('.')] + ), 0, console)) console = not console server_info = next((s for s in servers if s['publicip'] == ip), None) if server_info is None: - ip = str(utils.get_ip(bytearray([int(x) for x in ipstr.split('.')]), 0, console)) + ip = str(utils.get_ip( + bytearray([int(x) for x in ipstr.split('.')]), + 0, console + )) - server_info = next((s for s in servers if s['publicip'] == ip), None) + server_info = next( + (s for s in servers if s['publicip'] == ip), + None + ) return server_info @@ -299,19 +395,33 @@ class GameSpyNatNegServer(object): console = False ipstr = self.session_list[session_id][client_id]['addr'][0] - ip = str(utils.get_ip(bytearray([int(x) for x in ipstr.split('.')]), 0, console)) + ip = str(utils.get_ip( + bytearray([int(x) for x in ipstr.split('.')]), + 0, console + )) console = not console - serveraddr = self.server_manager.find_server_by_local_address(ip, self.session_list[session_id][client_id]['localaddr'], self.session_list[session_id][client_id]['gameid'])._getvalue() + serveraddr = self.server_manager.find_server_by_local_address( + ip, + self.session_list[session_id][client_id]['localaddr'], + self.session_list[session_id][client_id]['gameid'] + )._getvalue() if serveraddr is None: - ip = str(utils.get_ip(bytearray([int(x) for x in ipstr.split('.')]), 0, console)) + ip = str(utils.get_ip( + bytearray([int(x) for x in ipstr.split('.')]), + 0, console + )) - serveraddr = self.server_manager.find_server_by_local_address(ip, self.session_list[session_id][client_id]['localaddr'], - self.session_list[session_id][client_id]['gameid'])._getvalue() + serveraddr = self.server_manager.find_server_by_local_address( + ip, + self.session_list[session_id][client_id]['localaddr'], + self.session_list[session_id][client_id]['gameid'] + )._getvalue() return serveraddr + if __name__ == "__main__": natneg_server = GameSpyNatNegServer() natneg_server.start() From 35b478e5cf4a73c3e653cab85524c79f079e88cb Mon Sep 17 00:00:00 2001 From: veso266 Date: Wed, 5 Aug 2015 17:30:35 +0200 Subject: [PATCH 15/21] Update README.md --- README.md | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/README.md b/README.md index 20e94ea..aa1ffae 100644 --- a/README.md +++ b/README.md @@ -11,3 +11,12 @@ Whether it's to add new features or clean up existing code, we could always use Open source projects referenced during the creation of this project: [OpenSpy Core](https://github.com/sfcspanky/Openspy-Core/) | [Luigi Auriemma's Gslist and enctypex_decoder](http://aluigi.altervista.org/papers.htm) Instructions for setting up your own server can be found [here](https://github.com/polaris-/dwc_network_server_emulator/wiki/Setting-up-a-server-from-a-fresh-installation-of-Linux). + +##Requirements: + + Python 2.7 + Twisted + zope.interface (Twisted dependency) + pywin32 (Twisted dependency) + Apache (or your favorite httpd) + PHP5 From b2c3694ed6a994a29a0c6d6ed0940082ea89fb84 Mon Sep 17 00:00:00 2001 From: payet_s Date: Thu, 6 Aug 2015 14:55:40 +0200 Subject: [PATCH 16/21] Cleaned: gamespy_gamestats_server.py --- gamespy_gamestats_server.py | 207 +++++++++++++++++++++++------------- 1 file changed, 134 insertions(+), 73 deletions(-) diff --git a/gamespy_gamestats_server.py b/gamespy_gamestats_server.py index 0b548b7..3fa6c5b 100644 --- a/gamespy_gamestats_server.py +++ b/gamespy_gamestats_server.py @@ -41,20 +41,25 @@ logger_output_to_console = True logger_output_to_file = True logger_name = "GameSpyGamestatsServer" logger_filename = "gamespy_gamestats_server.log" -logger = utils.create_logger(logger_name, logger_filename, -1, logger_output_to_console, logger_output_to_file) +logger = utils.create_logger(logger_name, logger_filename, -1, + logger_output_to_console, logger_output_to_file) address = ("0.0.0.0", 29920) + class GameSpyGamestatsServer(object): def __init__(self): pass def start(self): - endpoint_search = serverFromString(reactor, "tcp:%d:interface=%s" % (address[1], address[0])) + endpoint_search = serverFromString( + reactor, + "tcp:%d:interface=%s" % (address[1], address[0]) + ) conn_search = endpoint_search.listen(GamestatsFactory()) try: - if reactor.running is False: + if not reactor.running: reactor.run(installSignalHandlers=0) except ReactorAlreadyRunning: pass @@ -62,7 +67,9 @@ class GameSpyGamestatsServer(object): class GamestatsFactory(Factory): def __init__(self): - logger.log(logging.INFO, "Now listening for connections on %s:%d...", address[0], address[1]) + logger.log(logging.INFO, + "Now listening for connections on %s:%d...", + address[0], address[1]) self.sessions = {} def buildProtocol(self, address): @@ -71,13 +78,15 @@ class GamestatsFactory(Factory): class Gamestats(LineReceiver): def __init__(self, sessions, address): - self.setRawMode() # We're dealing with binary data so set to raw mode + self.setRawMode() # We're dealing with binary data so set to raw mode self.db = gs_database.GamespyDatabase() self.sessions = sessions self.address = address - self.remaining_message = "" # Stores any unparsable/incomplete commands until the next rawDataReceived + # Stores any unparsable/incomplete commands until the next + # rawDataReceived + self.remaining_message = "" self.session = "" self.gameid = "" @@ -86,26 +95,39 @@ class Gamestats(LineReceiver): self.data = "" - def log(self, level, message): - if self.session == "": - if self.gameid == "": - logger.log(level, "[%s:%d] %s", self.address.host, self.address.port,message) + def log(self, level, msg, *args, **kwargs): + if not self.session: + if not self.gameid: + logger.log(level, "[%s:%d] " + msg, + self.address.host, self.address.port, + *args, **kwargs) else: - logger.log(level, "[%s:%d | %s] %s", self.address.host, self.address.port, self.gameid, message) + logger.log(level, "[%s:%d | %s] " + msg, + self.address.host, self.address.port, self.gameid, + *args, **kwargs) else: - if self.gameid == "": - logger.log(level, "[%s:%d | %s] %s", self.address.host, self.address.port, self.session, message) + if not self.gameid: + logger.log(level, "[%s:%d | %s] " + msg, + self.address.host, self.address.port, self.session, + *args, **kwargs) else: - logger.log(level, "[%s:%d | %s | %s] %s", self.address.host, self.address.port, self.session, self.gameid, message) + logger.log(level, "[%s:%d | %s | %s] " + msg, + self.address.host, self.address.port, self.session, + self.gameid, *args, **kwargs) def connectionMade(self): try: - self.log(logging.INFO, "Received connection from %s:%d" % (self.address.host, self.address.port)) + self.log(logging.INFO, + "Received connection from %s:%d", + self.address.host, self.address.port) # Generate a random challenge string - self.challenge = utils.generate_random_str(10, "ABCDEFGHIJKLMNOPQRSTUVWXYZ") + self.challenge = utils.generate_random_str( + 10, "ABCDEFGHIJKLMNOPQRSTUVWXYZ" + ) - # The first command sent to the client is always a login challenge containing the server challenge key. + # The first command sent to the client is always a login challenge + # containing the server challenge key. msg = gs_query.create_gamespy_message([ ('__cmd__', "lc"), ('__cmd_val__', "1"), @@ -113,12 +135,14 @@ class Gamestats(LineReceiver): ('id', "1"), ]) - self.log(logging.DEBUG, "SENDING: '%s'..." % msg) + self.log(logging.DEBUG, "SENDING: '%s'...", msg) msg = self.crypt(msg) self.transport.write(bytes(msg)) except: - self.log(logging.ERROR, "Unknown exception: %s" % traceback.format_exc()) + self.log(logging.ERROR, + "Unknown exception: %s", + traceback.format_exc()) def connectionLost(self, reason): return @@ -135,31 +159,36 @@ class Gamestats(LineReceiver): self.data = msg self.remaining_message = "" - commands, self.remaining_message = gs_query.parse_gamespy_message(msg) - logger.log(logging.DEBUG, "STATS RESPONSE: %s" % msg) + commands, self.remaining_message = \ + gs_query.parse_gamespy_message(msg) + logger.log(logging.DEBUG, "STATS RESPONSE: %s", msg) cmds = { - "auth": self.perform_auth, - "authp": self.perform_authp, - "ka": self.perform_ka, - "setpd": self.perform_setpd, - "getpd": self.perform_getpd, - "newgame": self.perform_newgame, - "updgame": self.perform_updgame, + "auth": self.perform_auth, + "authp": self.perform_authp, + "ka": self.perform_ka, + "setpd": self.perform_setpd, + "getpd": self.perform_getpd, + "newgame": self.perform_newgame, + "updgame": self.perform_updgame, } def cmd_err(data_parsed): - logger.log(logging.DEBUG, "Found unknown command, don't know how to handle '%s'.", data_parsed['__cmd__']) + logger.log(logging.DEBUG, + "Found unknown command, don't know how to" + " handle '%s'.", data_parsed['__cmd__']) for data_parsed in commands: print(data_parsed) cmds.get(data_parsed['__cmd__'], cmd_err)(data_parsed) except: - self.log(logging.ERROR, "Unknown exception: %s" % traceback.format_exc()) + self.log(logging.ERROR, + "Unknown exception: %s", + traceback.format_exc()) def perform_auth(self, data_parsed): - self.log(logging.DEBUG, "Parsing 'auth'...") + self.log(logging.DEBUG, "%s", "Parsing 'auth'...") if "gamename" in data_parsed: self.gameid = data_parsed['gamename'] @@ -174,22 +203,26 @@ class Gamestats(LineReceiver): ('id', "1"), ]) - self.log(logging.DEBUG, "SENDING: '%s'..." % msg) + self.log(logging.DEBUG, "SENDING: '%s'...", msg) msg = self.crypt(msg) self.transport.write(bytes(msg)) def perform_authp(self, data_parsed): - authtoken_parsed = gs_utils.parse_authtoken(data_parsed['authtoken'], self.db) - #print authtoken_parsed + authtoken_parsed = gs_utils.parse_authtoken(data_parsed['authtoken'], + self.db) + # print authtoken_parsed if "lid" in data_parsed: self.lid = data_parsed['lid'] - userid, profileid, gsbrcd, uniquenick = gs_utils.login_profile_via_parsed_authtoken(authtoken_parsed, self.db) + userid, profileid, gsbrcd, uniquenick = \ + gs_utils.login_profile_via_parsed_authtoken(authtoken_parsed, + self.db) if profileid is not None: - # Successfully logged in or created account, continue creating session. + # Successfully logged in or created account, continue + # creating session. sesskey = self.db.create_session(profileid, '') self.sessions[profileid] = self self.profileid = int(profileid) @@ -209,7 +242,7 @@ class Gamestats(LineReceiver): ('errmsg', 'Invalid Validation'), ]) - self.log(logging.DEBUG, "SENDING: '%s'..." % msg) + self.log(logging.DEBUG, "SENDING: '%s'...", msg) msg = self.crypt(msg) self.transport.write(bytes(msg)) @@ -220,7 +253,7 @@ class Gamestats(LineReceiver): ('__cmd_val__', ""), ]) - self.log(logging.DEBUG, "SENDING: '%s'..." % msg) + self.log(logging.DEBUG, "SENDING: '%s'...", msg) msg = self.crypt(msg) self.transport.write(bytes(msg)) @@ -237,80 +270,106 @@ class Gamestats(LineReceiver): ('mod', int(time.time())), ]) - self.log(logging.DEBUG, "SENDING: '%s'..." % msg) + self.log(logging.DEBUG, "SENDING: '%s'...", msg) msg = self.crypt(msg) self.transport.write(bytes(msg)) # TODO: Return error message. if int(data_parsed['pid']) != self.profileid: - logger.log(logging.WARNING, "ERROR: %d tried to update %d's profile" % (int(data_parsed['pid']), self.profileid)) + logger.log(logging.WARNING, + "ERROR: %d tried to update %d's profile", + int(data_parsed['pid']), self.profileid) return data_str = "\\data\\" length = int(data_parsed['length']) if len(data) < length: - # The packet isn't complete yet, keep loop until we get the entire packet. - # The length entire packet SHOULD always be greater than the data field, so this check should be fine. + # The packet isn't complete yet, keep loop until we get the + # entire packet. The length entire packet SHOULD always be + # greater than the data field, so this check should be fine. return if data_str in data: idx = data.index(data_str) + len(data_str) - data = data[idx:idx+length].rstrip("\\") + data = data[idx:idx + length].rstrip("\\") else: - logger.log(logging.ERROR, "ERROR: Could not find \data\ in setpd command: %s", data) + logger.log(logging.ERROR, + "ERROR: Could not find \data\ in setpd command: %s", + data) data = "" - current_data = self.db.pd_get(self.profileid, data_parsed['dindex'], data_parsed['ptype']) + current_data = self.db.pd_get(self.profileid, + data_parsed['dindex'], + data_parsed['ptype']) if current_data and data and 'data' in current_data: current_data = current_data['data'].lstrip('\\').split('\\') new_data = data.lstrip('\\').split('\\') - current_data = dict(zip(current_data[0::2],current_data[1::2])) - new_data = dict(zip(new_data[0::2],new_data[1::2])) + current_data = dict(zip(current_data[0::2], + current_data[1::2])) + new_data = dict(zip(new_data[0::2], new_data[1::2])) for k in new_data.keys(): current_data[k] = new_data[k] + # TODO: use str.join() data = "\\" for k in current_data.keys(): - data += k+"\\"+current_data[k]+"\\" - data = data.rstrip("\\") # Don't put trailing \ into db + data += k + "\\" + current_data[k] + "\\" + data = data.rstrip("\\") # Don't put trailing \ into db - self.db.pd_insert(self.profileid, data_parsed['dindex'], data_parsed['ptype'], data) + self.db.pd_insert(self.profileid, + data_parsed['dindex'], + data_parsed['ptype'], + data) def perform_getpd(self, data_parsed): pid = int(data_parsed['pid']) - profile = self.db.pd_get(pid, data_parsed['dindex'], data_parsed['ptype']) + profile = self.db.pd_get(pid, + data_parsed['dindex'], + data_parsed['ptype']) if profile is None: - self.log(logging.WARNING, "Could not find profile for %d %s %s" % (pid, data_parsed['dindex'], data_parsed['ptype'])) + self.log(logging.WARNING, + "Could not find profile for %d %s %s", + pid, data_parsed['dindex'], data_parsed['ptype']) keys = data_parsed['keys'].split('\x01') profile_data = None data = "" - # Someone figure out if this is actually a good way to handle this when no profile is found + # Someone figure out if this is actually a good way to handle this + # when no profile is found if profile is not None and 'data' in profile: profile_data = profile['data'] if profile_data.endswith("\\"): profile_data = profile_data[:-1] - profile_data = gs_query.parse_gamespy_message("\\prof\\" + profile_data + "\\final\\") + profile_data = \ + gs_query.parse_gamespy_message("\\prof\\" + profile_data + + "\\final\\") if profile_data is not None: profile_data = profile_data[0][0] else: - self.log(logging.WARNING, "Could not get data section from profile for %d" % pid) + self.log(logging.WARNING, + "Could not get data section from profile for %d", + pid) if len(keys): - for key in (key for key in keys if key not in ("__cmd__", "__cmd_val__", "")): + # TODO: more clean/pythonic way to do (join?) + for key in keys: + if key in ("__cmd__", "__cmd_val__", ""): + continue data += "\\" + key + "\\" if profile_data is not None and key in profile_data: data += profile_data[key] else: - self.log(logging.WARNING, "No keys requested, defaulting to all keys: %s" % (profile['data'])) + self.log(logging.WARNING, + "No keys requested, defaulting to all keys: %s", + profile['data']) data = profile['data'] modified = int(time.time()) @@ -325,30 +384,31 @@ class Gamestats(LineReceiver): ('data', data), ]) - msg = msg.replace("\\data\\","\\data\\\\") # data needs to be preceded by an extra slash + # data needs to be preceded by an extra slash + msg = msg.replace("\\data\\", "\\data\\\\") datastring = "" try: - datastring = re.findall('.*data\\\\(.*)',msg)[0].replace("\\final\\","") + datastring = re.findall('.*data\\\\(.*)', msg)[0] \ + .replace("\\final\\", "") except: pass - # This works because the data string is a key-value pair, splitting the - # string by \ should yield a list with an even number of elements. But, - # because of the extra \ prepended to the datastring, it'll be odd. - # So ultimately I expect the list to have an odd number of elements. - # If it's even, len(list)%2 will be zero... and that means the last - # field in the datastring is empty and doesn't have a closing \. + # This works because the data string is a key-value pair, splitting + # the string by \ should yield a list with an even number of elements. + # But, because of the extra \ prepended to the datastring, it'll be + # odd. So ultimately I expect the list to have an odd number of + # elements. If it's even, len(list)%2 will be zero... and that means + # the last field in the datastring is empty and doesn't have a + # closing \. if datastring and not len(datastring.split('\\')) % 2: - msg = msg.replace("\\final\\","\\\\final\\") # An empty field must be terminated by \ before \final\ + # An empty field must be terminated by \ before \final\ + msg = msg.replace("\\final\\", "\\\\final\\") - self.log(logging.DEBUG, "SENDING: '%s'..." % msg) + self.log(logging.DEBUG, "SENDING: '%s'...", msg) msg = self.crypt(msg) self.transport.write(bytes(msg)) - - - def perform_newgame(self, data_parsed): # No op return @@ -358,7 +418,8 @@ class Gamestats(LineReceiver): return def crypt(self, data): - key = "GameSpy3D" + key = bytearray(b"GameSpy3D") + key_len = len(key) output = bytearray(data.encode("ascii")) if "\\final\\" in output: @@ -367,11 +428,11 @@ class Gamestats(LineReceiver): end = len(output) for i in range(end): - output[i] ^= ord(key[i % len(key)]) + output[i] ^= key[i % key_len] return output + if __name__ == "__main__": gsss = GameSpyGamestatsServer() gsss.start() - From f7877ced40a2b963ba93bab1adea4ed9d8834882 Mon Sep 17 00:00:00 2001 From: payet_s Date: Thu, 6 Aug 2015 14:56:36 +0200 Subject: [PATCH 17/21] Cleaned: gamespy_backend_server.py --- gamespy_backend_server.py | 331 +++++++++++++++++++++++++------------- 1 file changed, 216 insertions(+), 115 deletions(-) diff --git a/gamespy_backend_server.py b/gamespy_backend_server.py index b1073ec..f74445d 100644 --- a/gamespy_backend_server.py +++ b/gamespy_backend_server.py @@ -16,25 +16,35 @@ You should have received a copy of the GNU Affero General Public License along with this program. If not, see . - Master server list server +Master server list server - Basic idea: - The server listing does not need to be persistent, and it must be easily searchable for any unknown parameters. - So instead of using a SQL database, I've opted to create a server list database server which communicates between the - server browser and the qr server. The server list database will be stored in dictionaries as to allow dynamic columns - that can be easily searched. The main reason for this configuration is because it cannot be guaranteed what data - a game's server will required. For example, in addition to the common fields such as publicip, numplayers, dwc_pid, etc, - Lost Magic also uses fields such as LMname, LMsecN, LMrating, LMbtmode, and LMversion. +Basic idea: + The server listing does not need to be persistent, and it must be easily + searchable for any unknown parameters. So instead of using a SQL database, + I've opted to create a server list database server which communicates + between the server browser and the qr server. The server list database + will be stored in dictionaries as to allow dynamic columns that can be + easily searched. The main reason for this configuration is because it + cannot be guaranteed what data a game's server will required. For example, + in addition to the common fields such as publicip, numplayers, dwc_pid, + etc, Lost Magic also uses fields such as LMname, LMsecN, LMrating, + LMbtmode, and LMversion. - It would be possible to create game-specific databases but this would be more of a hassle and less universal. It would - also be possible pickle a dictionary containing all of the fields and store it in a SQL database instead, but that - would require unpickling every server each time you want to match search queries which would cause overhead if there - are a lot of running servers. One trade off here is that we'll be using more memory by storing each server as a - dictionary in the memory instead of storing it in a SQL database. + It would be possible to create game-specific databases but this would be + more of a hassle and less universal. It would also be possible pickle a + dictionary containing all of the fields and store it in a SQL database + instead, but that would require unpickling every server each time you want + to match search queries which would cause overhead if there are a lot of + running servers. One trade off here is that we'll be using more memory by + storing each server as a dictionary in the memory instead of storing it in + a SQL database. - qr_server and server_browser both will act as clients to gs_server_database. - qr_server will send an add and/or delete to add or remove servers from the server list. - server_browser will send a request with the game name followed by optional search parameters to get a list of servers. + - qr_server and server_browser both will act as clients to + gs_server_database. + - qr_server will send an add and/or delete to add or remove servers from the + server list. + - server_browser will send a request with the game name followed by optional + search parameters to get a list of servers. """ import logging @@ -45,6 +55,7 @@ from multiprocessing.managers import BaseManager from multiprocessing import freeze_support import other.utils as utils + class TokenType: UNKNOWN = 0 FIELD = 1 @@ -57,45 +68,86 @@ logger_output_to_console = True logger_output_to_file = True logger_name = "GamespyBackendServer" logger_filename = "gamespy_backend_server.log" -logger = utils.create_logger(logger_name, logger_filename, -1, logger_output_to_console, logger_output_to_file) +logger = utils.create_logger(logger_name, logger_filename, -1, + logger_output_to_console, logger_output_to_file) + class GameSpyServerDatabase(BaseManager): pass + class GameSpyBackendServer(object): def __init__(self): self.server_list = {} self.natneg_list = {} - GameSpyServerDatabase.register("get_server_list", callable=lambda:self.server_list) - GameSpyServerDatabase.register("find_servers", callable=self.find_servers) - GameSpyServerDatabase.register("find_server_by_address", callable=self.find_server_by_address) - GameSpyServerDatabase.register("find_server_by_local_address", callable=self.find_server_by_local_address) - GameSpyServerDatabase.register("update_server_list", callable=self.update_server_list) - GameSpyServerDatabase.register("delete_server", callable=self.delete_server) - GameSpyServerDatabase.register("add_natneg_server", callable=self.add_natneg_server) - GameSpyServerDatabase.register("get_natneg_server", callable=self.get_natneg_server) - GameSpyServerDatabase.register("delete_natneg_server", callable=self.delete_natneg_server) + GameSpyServerDatabase.register( + "get_server_list", + callable=lambda: self.server_list + ) + GameSpyServerDatabase.register( + "find_servers", + callable=self.find_servers + ) + GameSpyServerDatabase.register( + "find_server_by_address", + callable=self.find_server_by_address + ) + GameSpyServerDatabase.register( + "find_server_by_local_address", + callable=self.find_server_by_local_address + ) + GameSpyServerDatabase.register( + "update_server_list", + callable=self.update_server_list + ) + GameSpyServerDatabase.register( + "delete_server", + callable=self.delete_server + ) + GameSpyServerDatabase.register( + "add_natneg_server", + callable=self.add_natneg_server + ) + GameSpyServerDatabase.register( + "get_natneg_server", + callable=self.get_natneg_server + ) + GameSpyServerDatabase.register( + "delete_natneg_server", + callable=self.delete_natneg_server + ) def start(self): address = ("127.0.0.1", 27500) password = "" - logger.log(logging.INFO, "Started server on %s:%d..." % (address[0], address[1])) + logger.log(logging.INFO, + "Started server on %s:%d...", + address[0], address[1]) - manager = GameSpyServerDatabase(address = address, authkey = password) + manager = GameSpyServerDatabase(address=address, + authkey=password) server = manager.get_server() server.serve_forever() def get_token(self, filters): - # Complex example from Dungeon Explorer: Warriors of Ancient Arts - # dwc_mver = 3 and dwc_pid != 474890913 and maxplayers = 2 and numplayers < 2 and dwc_mtype = 0 and dwc_mresv != dwc_pid and (MatchType='english') - # - # Even more complex example from Phantasy Star Zero: - # dwc_mver = 3 and dwc_pid != 4 and maxplayers = 3 and numplayers < 3 and dwc_mtype = 0 and dwc_mresv != dwc_pid and (((20=auth)AND((1&mskdif)=mskdif)AND((14&mskstg)=mskstg))) - # - # Example with OR from Mario Kart Wii: - # dwc_mver = 90 and dwc_pid != 1 and maxplayers = 11 and numplayers < 11 and dwc_mtype = 0 and dwc_hoststate = 2 and dwc_suspend = 0 and (rk = 'vs_123' and (ev > 4263 or ev <= 5763) and p = 0) + """Complex example from Dungeon Explorer: Warriors of Ancient Arts + dwc_mver = 3 and dwc_pid != 474890913 and maxplayers = 2 and + numplayers < 2 and dwc_mtype = 0 and dwc_mresv != dwc_pid and + (MatchType='english') + + Even more complex example from Phantasy Star Zero: + dwc_mver = 3 and dwc_pid != 4 and maxplayers = 3 and + numplayers < 3 and dwc_mtype = 0 and dwc_mresv != dwc_pid and + (((20=auth)AND((1&mskdif)=mskdif)AND((14&mskstg)=mskstg))) + + Example with OR from Mario Kart Wii: + dwc_mver = 90 and dwc_pid != 1 and maxplayers = 11 and + numplayers < 11 and dwc_mtype = 0 and dwc_hoststate = 2 and + dwc_suspend = 0 and (rk = 'vs_123' and (ev > 4263 or ev <= 5763) + and p = 0) + """ i = 0 start = i special_chars = "_" @@ -132,7 +184,8 @@ class GameSpyBackendServer(object): # >= or <= i += 1 - elif i + 1 < len(filters) and filters[i] == "!" and filters[i + 1] == "=": + elif i + 1 < len(filters) and filters[i] == "!" and \ + filters[i + 1] == "=": i += 2 token_type = TokenType.TOKEN @@ -140,25 +193,27 @@ class GameSpyBackendServer(object): # String literal token_type = TokenType.STRING - i += 1 # Skip quotation mark + i += 1 # Skip quotation mark while i < len(filters) and filters[i] != "'": i += 1 if i < len(filters) and filters[i] == "'": - i += 1 # Skip quotation mark + i += 1 # Skip quotation mark elif filters[i] == "\"": - # I don't know if it's in the spec or not, but I added "" string literals as well just in case. + # I don't know if it's in the spec or not, but I added "" + # string literals as well just in case. token_type = TokenType.STRING - i += 1 # Skip quotation mark + i += 1 # Skip quotation mark while i < len(filters) and filters[i] != "\"": i += 1 if i < len(filters) and filters[i] == "\"": - i += 1 # Skip quotation mark + i += 1 # Skip quotation mark - elif i + 1 < len(filters) and filters[i] == '-' and filters[i + 1].isdigit(): + elif i + 1 < len(filters) and filters[i] == '-' and \ + filters[i + 1].isdigit(): # Negative number token_type = TokenType.NUMBER i += 1 @@ -172,11 +227,14 @@ class GameSpyBackendServer(object): elif filters[i].isalpha(): token_type = TokenType.FIELD - while i < len(filters) and (filters[i].isalnum() or filters[i] in special_chars) and filters[i] not in "!=>< ": + while i < len(filters) and (filters[i].isalnum() or + filters[i] in special_chars) and \ + filters[i] not in "!=>< ": i += 1 token = filters[start:i] - if token_type == TokenType.FIELD and (token.lower() == "and" or token.lower() == "or"): + if token_type == TokenType.FIELD and \ + (token.lower() == "and" or token.lower() == "or"): token = token.lower() return token, i, token_type @@ -190,7 +248,8 @@ class GameSpyBackendServer(object): filters = filters[i:] if token_type == TokenType.TOKEN: - # Python uses == instead of = for comparisons, so replace it with the proper token for compilation. + # Python uses == instead of = for comparisons, so replace + # it with the proper token for compilation. if token == "=": token = "==" @@ -203,16 +262,20 @@ class GameSpyBackendServer(object): return output, variables def validate_ast(self, node, num_literal_only): - # This function tries to verify that the expression is a valid expression before it gets evaluated. - # Anything besides the whitelisted things below are strictly forbidden: + # This function tries to verify that the expression is a valid + # expression before it gets evaluated. + # Anything besides the whitelisted things below are strictly + # forbidden: # - String literals # - Number literals # - Binary operators (CAN ONLY BE PERFORMED ON TWO NUMBER LITERALS) # - Comparisons (cannot use 'in', 'not in', 'is', 'is not' operators) # - # Anything such as variables or arrays or function calls are NOT VALID. - # Never run the expression received from the client before running this function on the expression first. - #print type(node) + # Anything such as variables or arrays or function calls are NOT + # VALID. + # Never run the expression received from the client before running + # this function on the expression first. + # print type(node) # Only allow literals, comparisons, and math operations valid_node = False @@ -220,20 +283,20 @@ class GameSpyBackendServer(object): valid_node = True elif isinstance(node, ast.Str): - if num_literal_only is False: + if not num_literal_only: valid_node = True elif isinstance(node, ast.BoolOp): for value in node.values: valid_node = self.validate_ast(value, num_literal_only) - if valid_node is False: + if not valid_node: break elif isinstance(node, ast.BinOp): valid_node = self.validate_ast(node.left, True) - if valid_node == True: + if valid_node: valid_node = self.validate_ast(node.right, True) elif isinstance(node, ast.UnaryOp): @@ -246,16 +309,18 @@ class GameSpyBackendServer(object): valid_node = self.validate_ast(node.left, num_literal_only) for op in node.ops: - #print type(op) + # print type(op) - # Restrict "is", "is not", "in", and "not in" python comparison operators. - # These are python-specific and the games have no way of knowing what they are, so there's no reason - # to keep them around. - if isinstance(op, ast.Is) or isinstance(op, ast.IsNot) or isinstance(op, ast.In) or isinstance(op, ast.NotIn): + # Restrict "is", "is not", "in", and "not in" python + # comparison operators. These are python-specific and the + # games have no way of knowing what they are, so there's no + # reason to keep them around. + if isinstance(op, ast.Is) or isinstance(op, ast.IsNot) or \ + isinstance(op, ast.In) or isinstance(op, ast.NotIn): valid_node = False break - if valid_node == True: + if valid_node: for expr in node.comparators: valid_node = self.validate_ast(expr, num_literal_only) @@ -264,7 +329,6 @@ class GameSpyBackendServer(object): return valid_node - def find_servers(self, gameid, filters, fields, max_count): matched_servers = [] @@ -276,7 +340,7 @@ class GameSpyBackendServer(object): for server in self.server_list[gameid]: stop_search = False - if filters != "": + if filters: translated, variables = self.translate_expression(filters) for idx in variables: @@ -289,69 +353,83 @@ class GameSpyBackendServer(object): if token_type == TokenType.FIELD: # At this point, any field should be a string. # This does not support stuff like: - # dwc_test = 'test', dwc_test2 = dwc_test, dwc_test3 = dwc_test2 + # dwc_test = 'test', dwc_test2 = dwc_test, + # dwc_test3 = dwc_test2 token = '"' + token + '"' elif token_type == TokenType.NUMBER: for idx2 in range(idx + 1, len(translated)): - _, _, token_type = self.get_token(translated[idx2]) + _, _, token_type = \ + self.get_token(translated[idx2]) - if token_type == TokenType.TOKEN and translated[idx2] not in ('(', ')'): + if token_type == TokenType.TOKEN and \ + translated[idx2] not in ('(', ')'): if idx2 == idx + 1: - # Skip boolean operator if it's the first token on the right + # Skip boolean operator if it's the + # first token on the right continue # Boolean operator, leave left as integer token = str(int(token)) break - elif token_type == TokenType.STRING or token_type == TokenType.NUMBER: + elif token_type == TokenType.STRING or \ + token_type == TokenType.NUMBER: if token_type == TokenType.STRING: - # Found string on far right, turn left into string as well + # Found string on far right, turn left + # into string as well token = "'" + token + "'" elif token_type == TokenType.NUMBER: token = str(int(token)) break - translated[idx] = token q = ' '.join(translated) - # Always run validate_ast over the entire AST before evaluating anything. eval() is dangerous to use on - # unsanitized inputs. The validate_ast function has a fairly strict whitelist so it should be safe in what - # it accepts as valid. + # Always run validate_ast over the entire AST before + # evaluating anything. eval() is dangerous to use on + # unsanitized inputs. The validate_ast function has a fairly + # strict whitelist so it should be safe in what it accepts as + # valid. m = ast.parse(q, "", "exec") valid_filter = True for node in m.body: valid_filter = self.validate_ast(node, False) - if valid_filter is False: + if not valid_filter: # Return only anything matched up until this point. - logger.log(logging.WARNING, "Invalid filter(s): %s" % (filters)) - #stop_search = True + logger.log(logging.WARNING, + "Invalid filter(s): %s", + filters) + # stop_search = True continue else: - # Use Python to evaluate the query. This method may take a little time but it shouldn't be all that - # big of a difference, I think. It takes about 0.0004 seconds per server to determine whether or not it's a - # match on my computer. Usually there's a low max_servers set when the game searches for servers, so assuming - # something like the game is asking for 6 servers, it would take about 0.0024 seconds total. These times - # will obviously be different per computer. It's not ideal, but it shouldn't be a huge bottleneck. - # A possible way to speed it up is to make validate_ast also evaluate the expressions at the same time as it - # validates it. + # Use Python to evaluate the query. This method may take a + # little time but it shouldn't be all that big of a + # difference, I think. It takes about 0.0004 seconds per + # server to determine whether or not it's a match on my + # computer. Usually there's a low max_servers set when the + # game searches for servers, so assuming something like + # the game is asking for 6 servers, it would take about + # 0.0024 seconds total. These times will obviously be + # different per computer. It's not ideal, but it shouldn't + # be a huge bottleneck. A possible way to speed it up is + # to make validate_ast also evaluate the expressions at + # the same time as it validates it. result = eval(q) else: # There are no filters, so just return the server. result = True valid_filter = True - if stop_search == True: + if stop_search: break - if valid_filter == True and result == True: + if valid_filter and result: matched_servers.append(server) - if max_count != 0 and len(matched_servers) >= max_count: + if max_count and len(matched_servers) >= max_count: break servers = [] @@ -370,70 +448,83 @@ class GameSpyBackendServer(object): "publicip", "publicport", "__session__", "__console__" ] - result.update({name:server[name] for name in attrs if name in server}) + result.update({name: server[name] + for name in attrs if name in server}) requested = {} for field in fields: - #if not field in result: + # if not field in result: if field in server: requested[field] = server[field] else: - # Return a dummy value. What's the normal behavior of the real server in this case? + # Return a dummy value. What's the normal behavior of + # the real server in this case? requested[field] = "" - result['requested'] = requested servers.append(result) - logger.log(logging.DEBUG, "Matched %d servers in %s seconds" % (len(servers), (time.time() - start))) + logger.log(logging.DEBUG, + "Matched %d servers in %s seconds", + len(servers), (time.time() - start)) return servers def update_server_list(self, gameid, session, value, console): - # Make sure the user isn't hosting multiple servers or there isn't some left over server information that - # never got handled properly (game crashed, etc). + """Make sure the user isn't hosting multiple servers or there isn't + some left over server information that never got handled properly + (game crashed, etc).""" self.delete_server(gameid, session) # If the game doesn't exist already, create a new list. - if not gameid in self.server_list: + if gameid not in self.server_list: self.server_list[gameid] = [] # Add new server value['__session__'] = session value['__console__'] = console - logger.log(logging.DEBUG, "Added %s to the server list for %s" % (value, gameid)) + logger.log(logging.DEBUG, + "Added %s to the server list for %s", + value, gameid) self.server_list[gameid].append(value) - logger.log(logging.DEBUG, "%s servers: %d" % (gameid, len(self.server_list[gameid]))) + logger.log(logging.DEBUG, + "%s servers: %d", + gameid, len(self.server_list[gameid])) return value def delete_server(self, gameid, session): - if not gameid in self.server_list: + if gameid not in self.server_list: # Nothing to do if no servers for that game even exist. return # Remove all servers hosted by the given session id. count = len(self.server_list[gameid]) - self.server_list[gameid] = [x for x in self.server_list[gameid] if x['__session__'] != session] + self.server_list[gameid] = [x for x in self.server_list[gameid] + if x['__session__'] != session] count -= len(self.server_list[gameid]) - logger.log(logging.DEBUG, "Deleted %d %s servers where session = %d" % (count, gameid, session)) + logger.log(logging.DEBUG, + "Deleted %d %s servers where session = %d", + count, gameid, session) - def find_server_by_address(self, ip, port, gameid = None): + def find_server_by_address(self, ip, port, gameid=None): if gameid is None: # Search all servers for gameid in self.server_list: for server in self.server_list[gameid]: - if server['publicip'] == ip and (port == 0 or server['publicport'] == str(port)): + if server['publicip'] == ip and \ + (not port or server['publicport'] == str(port)): return server else: for server in self.server_list[gameid]: - if server['publicip'] == ip and (port == 0 or server['publicport'] == str(port)): + if server['publicip'] == ip and \ + (not port or server['publicport'] == str(port)): return server return None - def find_server_by_local_address(self, publicip, localaddr, gameid = None): + def find_server_by_local_address(self, publicip, localaddr, gameid=None): localip = localaddr[0] localport = localaddr[1] localip_int_le = localaddr[2] @@ -443,27 +534,37 @@ class GameSpyBackendServer(object): best_match = None for server in self.server_list[gameid]: - logger.log(logging.DEBUG, "publicip: %s == %s ? %d localport: %s == %s ? %d" % (server['publicip'], publicip, server['publicip'] == publicip, server['localport'], str(localport), server['localport'] == str(localport))) + logger.log(logging.DEBUG, + "publicip: %s == %s ? %d localport: %s == %s ? %d", + server['publicip'], publicip, + server['publicip'] == publicip, + server['localport'], + str(localport), + server['localport'] == str(localport)) if server['publicip'] == publicip: if server['localport'] == str(localport): best_match = server break for x in range(0, 10): - s = 'localip%d' % x - if s in server: - if server[s] == localip: - best_match = server + s = 'localip%d' % x + if s in server: + if server[s] == localip: + best_match = server - if localport == 0 and best_match is None: + if not localport and best_match is None: # Kinda hackish. This sometimes happens. - # Assuming two clients aren't trying to connect from the same IP, this might be safe. - # The server wasn't verified to be the *correct* server, but it's on the same IP so it - # has a chance of being correct. At least make an attempt to establish the connection. + # Assuming two clients aren't trying to connect from + # the same IP, this might be safe. The server wasn't + # verified to be the *correct* server, but it's on the + # same IP so it has a chance of being correct. At + # least make an attempt to establish the connection. best_match = server if best_match is None: - logger.log(logging.DEBUG, "Couldn't find a match for %s" % (publicip)) + logger.log(logging.DEBUG, + "Couldn't find a match for %s", + publicip) return best_match @@ -480,7 +581,7 @@ class GameSpyBackendServer(object): if cookie not in self.natneg_list: self.natneg_list[cookie] = [] - logger.log(logging.DEBUG, "Added natneg server %d" % (cookie)) + logger.log(logging.DEBUG, "Added natneg server %d", cookie) self.natneg_list[cookie].append(server) def get_natneg_server(self, cookie): @@ -490,10 +591,10 @@ class GameSpyBackendServer(object): return None def delete_natneg_server(self, cookie): - # TODO: Find a good time to prune the natneg server listing. + """TODO: Find a good time to prune the natneg server listing.""" if cookie in self.natneg_list: del self.natneg_list[cookie] - logger.log(logging.DEBUG, "Deleted natneg server %d" % (cookie)) + logger.log(logging.DEBUG, "Deleted natneg server %d", cookie) if __name__ == '__main__': From dfa269c71dfc7895bb49d57e35388668862abece Mon Sep 17 00:00:00 2001 From: payet_s Date: Wed, 12 Aug 2015 16:56:04 +0200 Subject: [PATCH 18/21] Added: Configuration Module/File --- admin_page_server.py | 4 ++- altwfc.cfg | 51 ++++++++++++++++++++++++++++++ dwc_config.py | 50 +++++++++++++++++++++++++++++ dwc_network_server_emulator.pyproj | 1 + gamespy_backend_server.py | 3 +- gamespy_gamestats_server.py | 3 +- gamespy_natneg_server.py | 5 +-- gamespy_player_search_server.py | 3 +- gamespy_profile_server.py | 4 +-- gamespy_qr_server.py | 5 +-- gamespy_server_browser_server.py | 5 +-- gamestats_server_http.py | 4 +-- internal_stats_server.py | 6 ++-- nas_server.py | 6 ++-- storage_server.py | 3 +- 15 files changed, 134 insertions(+), 19 deletions(-) create mode 100755 altwfc.cfg create mode 100755 dwc_config.py diff --git a/admin_page_server.py b/admin_page_server.py index 6338531..26f2835 100644 --- a/admin_page_server.py +++ b/admin_page_server.py @@ -29,8 +29,10 @@ import collections import json import os.path import logging + import other.utils as utils import gamespy.gs_utility as gs_utils +import dwc_config logger_output_to_console = True logger_output_to_file = True @@ -478,7 +480,7 @@ class AdminPage(resource.Resource): else: return self.get_header() + self.get_footer() -port = 9009 +_, port = dwc_config.get_ip_port('AdminPage') class AdminPageServer(object): diff --git a/altwfc.cfg b/altwfc.cfg new file mode 100755 index 0000000..831fb6f --- /dev/null +++ b/altwfc.cfg @@ -0,0 +1,51 @@ +# ALTWFC - Configuration File + +[StorageServer] +IP = 127.0.0.1 +Port = 8000 + +[NasServer] +IP = 127.0.0.1 +Port = 9000 +# Port = 80 + +[InternalStatsServer] +IP = 127.0.0.1 +Port = 9001 + +[GameStatsServerHttp] +IP = 127.0.0.1 +Port = 9002 + +[AdminPage] +IP = 127.0.0.1 +Port = 9009 + +[GameSpyManager] +# GamespyBackendServer +IP = 127.0.0.1 +Port = 27500 + +[GameSpyQRServer] +IP = 0.0.0.0 +Port = 27900 + +[GameSpyNatNegServer] +IP = 0.0.0.0 +Port = 27901 + +[GameSpyServerBrowserServer] +IP = 0.0.0.0 +Port = 28910 + +[GameSpyProfileServer] +IP = 0.0.0.0 +Port = 29900 + +[GameSpyPlayerSearchServer] +IP = 0.0.0.0 +Port = 29901 + +[GameSpyGamestatsServer] +IP = 0.0.0.0 +Port = 29920 diff --git a/dwc_config.py b/dwc_config.py new file mode 100755 index 0000000..a034de2 --- /dev/null +++ b/dwc_config.py @@ -0,0 +1,50 @@ +"""DWC Network Server Emulator + + Copyright (C) 2014 SMTDDR + Copyright (C) 2014 kyle95wm + Copyright (C) 2014 AdmiralCurtiss + Copyright (C) 2015 Sepalani + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU Affero General Public License as + published by the Free Software Foundation, either version 3 of the + License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License + along with this program. If not, see . + +Configuration module. +""" + +try: + # Python 2 + import ConfigParser +except ImportError: + # Python 3 + import configparser as ConfigParser + + +def get_ip_port(section, filename='altwfc.cfg'): + """Return a tuple (IP, Port) of the corresponding section.""" + config = ConfigParser.RawConfigParser(allow_no_value=True) + config.read(filename) + return (config.get(section, 'IP'), config.getint(section, 'Port')) + + +def get_ip(section, filename='altwfc.cfg'): + """Return the IP of the corresponding section.""" + config = ConfigParser.RawConfigParser(allow_no_value=True) + config.read(filename) + return config.get(section, 'IP') + + +def get_port(section, filename='altwfc.cfg'): + """Return the port of the corresponding section.""" + config = ConfigParser.RawConfigParser(allow_no_value=True) + config.read(filename) + return config.getint(section, 'Port') diff --git a/dwc_network_server_emulator.pyproj b/dwc_network_server_emulator.pyproj index 12d0a08..06d0cb0 100644 --- a/dwc_network_server_emulator.pyproj +++ b/dwc_network_server_emulator.pyproj @@ -30,6 +30,7 @@ + diff --git a/gamespy_backend_server.py b/gamespy_backend_server.py index f74445d..f1ffd11 100644 --- a/gamespy_backend_server.py +++ b/gamespy_backend_server.py @@ -54,6 +54,7 @@ import ast from multiprocessing.managers import BaseManager from multiprocessing import freeze_support import other.utils as utils +import dwc_config class TokenType: @@ -119,7 +120,7 @@ class GameSpyBackendServer(object): ) def start(self): - address = ("127.0.0.1", 27500) + address = dwc_config.get_ip_port('GameSpyManager') password = "" logger.log(logging.INFO, diff --git a/gamespy_gamestats_server.py b/gamespy_gamestats_server.py index 3fa6c5b..dae6a38 100644 --- a/gamespy_gamestats_server.py +++ b/gamespy_gamestats_server.py @@ -35,6 +35,7 @@ import gamespy.gs_database as gs_database import gamespy.gs_query as gs_query import gamespy.gs_utility as gs_utils import other.utils as utils +import dwc_config # Logger settings logger_output_to_console = True @@ -44,7 +45,7 @@ logger_filename = "gamespy_gamestats_server.log" logger = utils.create_logger(logger_name, logger_filename, -1, logger_output_to_console, logger_output_to_file) -address = ("0.0.0.0", 29920) +address = dwc_config.get_ip_port('GameSpyGamestatsServer') class GameSpyGamestatsServer(object): diff --git a/gamespy_natneg_server.py b/gamespy_natneg_server.py index 5601f88..6a3051f 100644 --- a/gamespy_natneg_server.py +++ b/gamespy_natneg_server.py @@ -34,6 +34,7 @@ import other.utils as utils import traceback from multiprocessing.managers import BaseManager +import dwc_config # Logger settings logger_output_to_console = True @@ -64,7 +65,7 @@ class GameSpyNatNegServer(object): self.secret_key_list = gs_utils.generate_secret_keys("gslist.cfg") self.server_manager = GameSpyServerDatabase( - address=("127.0.0.1", 27500), + address=dwc_config.get_ip_port('GameSpyManager'), authkey="" ) self.server_manager.connect() @@ -74,7 +75,7 @@ class GameSpyNatNegServer(object): # Start natneg server # Accessible to outside connections (use this if you don't know # what you're doing) - address = ('0.0.0.0', 27901) + address = dwc_config.get_ip_port('GameSpyNatNegServer') self.socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) self.socket.bind(address) diff --git a/gamespy_player_search_server.py b/gamespy_player_search_server.py index 3322060..c473f75 100644 --- a/gamespy_player_search_server.py +++ b/gamespy_player_search_server.py @@ -30,6 +30,7 @@ from twisted.internet.error import ReactorAlreadyRunning import gamespy.gs_database as gs_database import gamespy.gs_query as gs_query import other.utils as utils +import dwc_config # Logger settings logger_output_to_console = True @@ -39,7 +40,7 @@ logger_filename = "gamespy_player_search_server.log" logger = utils.create_logger(logger_name, logger_filename, -1, logger_output_to_console, logger_output_to_file) -address = ("0.0.0.0", 29901) +address = dwc_config.get_ip_port('GameSpyPlayerSearchServer') class GameSpyPlayerSearchServer(object): diff --git a/gamespy_profile_server.py b/gamespy_profile_server.py index bd0931d..8192fdd 100644 --- a/gamespy_profile_server.py +++ b/gamespy_profile_server.py @@ -34,7 +34,7 @@ import gamespy.gs_database as gs_database import gamespy.gs_query as gs_query import gamespy.gs_utility as gs_utils import other.utils as utils - +import dwc_config # Logger settings logger_output_to_console = True @@ -44,7 +44,7 @@ logger_filename = "gamespy_profile_server.log" logger = utils.create_logger(logger_name, logger_filename, -1, logger_output_to_console, logger_output_to_file) -address = ("0.0.0.0", 29900) +address = dwc_config.get_ip_port('GameSpyProfileServer') class GameSpyProfileServer(object): diff --git a/gamespy_qr_server.py b/gamespy_qr_server.py index aeb748b..f1f8102 100644 --- a/gamespy_qr_server.py +++ b/gamespy_qr_server.py @@ -38,6 +38,7 @@ from multiprocessing.managers import BaseManager import gamespy.gs_utility as gs_utils import gamespy.gs_database as gs_database import other.utils as utils +import dwc_config from gamespy_server_browser_server import GameSpyServerBrowserServer # Logger settings @@ -98,7 +99,7 @@ class GameSpyQRServer(object): def start(self): try: - manager_address = ("127.0.0.1", 27500) + manager_address = dwc_config.get_ip_port('GameSpyManager') manager_password = "" self.server_manager = GameSpyServerDatabase( @@ -110,7 +111,7 @@ class GameSpyQRServer(object): # Start QR server # Accessible to outside connections (use this if you don't know # what you're doing) - address = ('0.0.0.0', 27900) + address = dwc_config.get_ip_port('GameSpyQRServer') self.socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) self.socket.bind(address) diff --git a/gamespy_server_browser_server.py b/gamespy_server_browser_server.py index 4c9d11b..ddb1f3e 100644 --- a/gamespy_server_browser_server.py +++ b/gamespy_server_browser_server.py @@ -34,6 +34,7 @@ from twisted.internet.error import ReactorAlreadyRunning import gamespy.gs_utility as gs_utils import other.utils as utils +import dwc_config from multiprocessing.managers import BaseManager @@ -68,7 +69,7 @@ GameSpyServerDatabase.register("add_natneg_server") GameSpyServerDatabase.register("get_natneg_server") GameSpyServerDatabase.register("delete_natneg_server") -address = ("0.0.0.0", 28910) +address = dwc_config.get_ip_port('GameSpyServerBrowserServer') class GameSpyServerBrowserServer(object): @@ -117,7 +118,7 @@ class Session(LineReceiver): self.own_server = None self.buffer = [] - manager_address = ("127.0.0.1", 27500) + manager_address = dwc_config.get_ip_port('GameSpyManager') manager_password = "" self.server_manager = GameSpyServerDatabase(address=manager_address, authkey=manager_password) diff --git a/gamestats_server_http.py b/gamestats_server_http.py index 13a606a..057424a 100644 --- a/gamestats_server_http.py +++ b/gamestats_server_http.py @@ -32,6 +32,7 @@ import base64 import gamespy.gs_database as gs_database import gamespy.gs_utility as gs_utils import other.utils as utils +import dwc_config logger_output_to_console = True logger_output_to_file = True @@ -40,8 +41,7 @@ logger_filename = "gamestats_server_http.log" logger = utils.create_logger(logger_name, logger_filename, -1, logger_output_to_console, logger_output_to_file) -# address = ("0.0.0.0", 80) -address = ("127.0.0.1", 9002) +address = dwc_config.get_ip_port('GameStatsServerHttp') class GameStatsBase(object): diff --git a/internal_stats_server.py b/internal_stats_server.py index 4a0bba7..de51fd9 100644 --- a/internal_stats_server.py +++ b/internal_stats_server.py @@ -26,7 +26,9 @@ import time import datetime import json import logging + import other.utils as utils +import dwc_config logger_output_to_console = True logger_output_to_file = True @@ -118,14 +120,14 @@ class InternalStatsServer(object): self.seconds_per_update = 60 def start(self): - manager_address = ("127.0.0.1", 27500) + manager_address = dwc_config.get_ip_port('GameSpyManager') manager_password = "" self.server_manager = GameSpyServerDatabase(address=manager_address, authkey=manager_password) self.server_manager.connect() site = server.Site(StatsPage(self)) - reactor.listenTCP(9001, site) + reactor.listenTCP(dwc_config.get_port('InternalStatsServer'), site) try: if not reactor.running: diff --git a/nas_server.py b/nas_server.py index ec3d805..c03cbf2 100644 --- a/nas_server.py +++ b/nas_server.py @@ -33,6 +33,7 @@ import traceback import gamespy.gs_database as gs_database import other.utils as utils +import dwc_config logger_output_to_console = True logger_output_to_file = True @@ -68,7 +69,7 @@ gamecodes_return_random_file = [ 'IPGS' ] -address = ("127.0.0.1", 9000) +address = dwc_config.get_ip_port('NasServer') class NasServer(object): @@ -363,7 +364,8 @@ class NasHTTPServerHandler(BaseHTTPServer.BaseHTTPRequestHandler): ret, post["token"] ) - elif post["gamecd"] in gamecodes_return_random_file: + elif post["gamecd"] in \ + gamecodes_return_random_file: # Pokemon Gen 4 Mystery Gifts, same here ret = self.filter_list( safeloadfi("_list.txt"), diff --git a/storage_server.py b/storage_server.py index 1651c06..c525be9 100644 --- a/storage_server.py +++ b/storage_server.py @@ -29,6 +29,7 @@ import xml.dom.minidom as minidom import other.utils as utils import gamespy.gs_database as gs_database +import dwc_config logger_output_to_console = True logger_output_to_file = True @@ -37,7 +38,7 @@ logger_filename = "storage_server.log" logger = utils.create_logger(logger_name, logger_filename, -1, logger_output_to_console, logger_output_to_file) # Paths to ProxyPass: /SakeStorageServer, /SakeFileServer -address = ("127.0.0.1", 8000) +address = dwc_config.get_ip_port('StorageServer') def escape_xml(s): s = s.replace( "&", "&" ) From e088954ef51331c2fbb5437b33b1a11a48c9c08e Mon Sep 17 00:00:00 2001 From: Sepalani Date: Fri, 14 Aug 2015 18:13:03 +0200 Subject: [PATCH 19/21] Fixed: Error Code 24101 Fixed: Request parsing ignoring blank values Tested with Boom Street and Monster Hunter Tri --- nas_server.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/nas_server.py b/nas_server.py index c03cbf2..e47c8c5 100644 --- a/nas_server.py +++ b/nas_server.py @@ -245,6 +245,10 @@ class NasHTTPServerHandler(BaseHTTPServer.BaseHTTPRequestHandler): # Pokemon requests this for some things ret["servicetoken"] = authtoken ret["svchost"] = "n/a" + else: + # Empty svc - Fix Error Code 24101 (Boom Street) + ret["svchost"] = "n/a" + ret["servicetoken"] = authtoken logger.log(logging.DEBUG, "svcloc response to %s", client_address) @@ -422,7 +426,9 @@ class NasHTTPServerHandler(BaseHTTPServer.BaseHTTPRequestHandler): traceback.format_exc()) def str_to_dict(self, s): - ret = urlparse.parse_qs(s) + # Enable keep_blank_values, skipped otherwise. + # TODO: Move this in utils module? + ret = urlparse.parse_qs(s, True) for k, v in ret.iteritems(): try: From 35c253dc097ef623706636edda9c0916e5c03216 Mon Sep 17 00:00:00 2001 From: Sepalani Date: Wed, 19 Aug 2015 17:41:42 +0200 Subject: [PATCH 20/21] Fixed: Not found or invalid authtoken --- gamespy/gs_utility.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/gamespy/gs_utility.py b/gamespy/gs_utility.py index 72ddfb1..63bc126 100644 --- a/gamespy/gs_utility.py +++ b/gamespy/gs_utility.py @@ -126,6 +126,12 @@ def parse_authtoken(authtoken, db): def login_profile_via_parsed_authtoken(authtoken_parsed, db): + """Return login profile via parsed authtoken. + + authtoken_parsed MUST HAVE userid field and can't be None! + """ + if authtoken_parsed is None or 'userid' not in authtoken_parsed: + return None, None, None, None console = 0 userid = authtoken_parsed['userid'] From 2102a73b0509fe9c6fd2f6f0c855afe3d9eac7a3 Mon Sep 17 00:00:00 2001 From: Sepalani Date: Sat, 5 Sep 2015 22:56:19 +0200 Subject: [PATCH 21/21] Improved configuration file Added: register_page.py into cfg and pyproj Added: Alternative configuration file Added: Logger into configuration file --- admin_page_server.py | 11 +-- altwfc.cfg | 74 ++++++++++++++++- altwfc_nas.cfg | 119 ++++++++++++++++++++++++++++ dwc_config.py | 35 ++++++++- dwc_network_server_emulator.pyproj | 1 + gamespy_backend_server.py | 10 +-- gamespy_gamestats_server.py | 9 +-- gamespy_natneg_server.py | 8 +- gamespy_player_search_server.py | 9 +-- gamespy_profile_server.py | 9 +-- gamespy_qr_server.py | 8 +- gamespy_server_browser_server.py | 10 +-- gamestats_server_http.py | 8 +- internal_stats_server.py | 7 +- master_server.py | 2 + nas_server.py | 46 +++++------ register_page.py | 122 +++++++++++++++++------------ storage_server.py | 14 ++-- 18 files changed, 342 insertions(+), 160 deletions(-) create mode 100644 altwfc_nas.cfg diff --git a/admin_page_server.py b/admin_page_server.py index 26f2835..9d0d854 100644 --- a/admin_page_server.py +++ b/admin_page_server.py @@ -34,12 +34,8 @@ import other.utils as utils import gamespy.gs_utility as gs_utils import dwc_config -logger_output_to_console = True -logger_output_to_file = True -logger_name = "AdminPage" -logger_filename = "admin_page.log" -logger = utils.create_logger(logger_name, logger_filename, -1, - logger_output_to_console, logger_output_to_file) +logger = dwc_config.get_logger('AdminPage') +_, port = dwc_config.get_ip_port('AdminPage') # Example of adminpageconf.json @@ -480,8 +476,6 @@ class AdminPage(resource.Resource): else: return self.get_header() + self.get_footer() -_, port = dwc_config.get_ip_port('AdminPage') - class AdminPageServer(object): def start(self): @@ -496,5 +490,6 @@ class AdminPageServer(object): except ReactorAlreadyRunning: pass + if __name__ == "__main__": AdminPageServer().start() diff --git a/altwfc.cfg b/altwfc.cfg index 831fb6f..326948f 100755 --- a/altwfc.cfg +++ b/altwfc.cfg @@ -1,51 +1,123 @@ # ALTWFC - Configuration File +[Config] +AlternativeConfig = OFF +AlternativeConfigFile = altwfc_nas.cfg + [StorageServer] IP = 127.0.0.1 Port = 8000 +LoggerName = StorageServer +LoggerFilename = storage_server.log +LoggerLevel = -1 +LoggerOutputConsole = ON +LoggerOutputFile = ON [NasServer] IP = 127.0.0.1 Port = 9000 -# Port = 80 +LoggerName = NasServer +LoggerFilename = nas_server.log +LoggerLevel = -1 +LoggerOutputConsole = ON +LoggerOutputFile = ON [InternalStatsServer] IP = 127.0.0.1 Port = 9001 +LoggerName = InternalStatsServer +LoggerFilename = internal_stats_server.log +LoggerLevel = -1 +LoggerOutputConsole = ON +LoggerOutputFile = ON [GameStatsServerHttp] IP = 127.0.0.1 Port = 9002 +LoggerName = GameStatsServerHttp +LoggerFilename = gamestats_server_http.log +LoggerLevel = -1 +LoggerOutputConsole = ON +LoggerOutputFile = ON [AdminPage] IP = 127.0.0.1 Port = 9009 +LoggerName = AdminPage +LoggerFilename = admin_page.log +LoggerLevel = -1 +LoggerOutputConsole = ON +LoggerOutputFile = ON + +[RegisterPage] +IP = 127.0.0.1 +Port = 9998 +LoggerName = RegisterPage +LoggerFilename = register_page.log +LoggerLevel = -1 +LoggerOutputConsole = ON +LoggerOutputFile = ON [GameSpyManager] # GamespyBackendServer IP = 127.0.0.1 Port = 27500 +LoggerName = GamespyBackendServer +LoggerFilename = gamespy_backend_server.log +LoggerLevel = -1 +LoggerOutputConsole = ON +LoggerOutputFile = ON [GameSpyQRServer] IP = 0.0.0.0 Port = 27900 +LoggerName = GameSpyQRServer +LoggerFilename = gamespy_qr_server.log +LoggerLevel = -1 +LoggerOutputConsole = ON +LoggerOutputFile = ON [GameSpyNatNegServer] IP = 0.0.0.0 Port = 27901 +LoggerName = GameSpyNatNegServer +LoggerFilename = gamespy_natneg_server.log +LoggerLevel = -1 +LoggerOutputConsole = ON +LoggerOutputFile = ON [GameSpyServerBrowserServer] IP = 0.0.0.0 Port = 28910 +LoggerName = GameSpyServerBrowserServer +LoggerFilename = gamespy_server_browser_server.log +LoggerLevel = -1 +LoggerOutputConsole = ON +LoggerOutputFile = ON [GameSpyProfileServer] IP = 0.0.0.0 Port = 29900 +LoggerName = GameSpyProfileServer +LoggerFilename = gamespy_profile_server.log +LoggerLevel = -1 +LoggerOutputConsole = ON +LoggerOutputFile = ON [GameSpyPlayerSearchServer] IP = 0.0.0.0 Port = 29901 +LoggerName = GameSpyPlayerSearchServer +LoggerFilename = gamespy_player_search_server.log +LoggerLevel = -1 +LoggerOutputConsole = ON +LoggerOutputFile = ON [GameSpyGamestatsServer] IP = 0.0.0.0 Port = 29920 +LoggerName = GameSpyGamestatsServer +LoggerFilename = gamespy_gamestats_server.log +LoggerLevel = -1 +LoggerOutputConsole = ON +LoggerOutputFile = ON diff --git a/altwfc_nas.cfg b/altwfc_nas.cfg new file mode 100644 index 0000000..5995a09 --- /dev/null +++ b/altwfc_nas.cfg @@ -0,0 +1,119 @@ +# ALTWFC - Alternative Configuration File + +[StorageServer] +IP = 127.0.0.1 +Port = 8000 +LoggerName = StorageServer +LoggerFilename = storage_server.log +LoggerLevel = -1 +LoggerOutputConsole = ON +LoggerOutputFile = ON + +[NasServer] +IP = 127.0.0.1 +Port = 80 +LoggerName = NasServer +LoggerFilename = nas_server.log +LoggerLevel = -1 +LoggerOutputConsole = ON +LoggerOutputFile = ON + +[InternalStatsServer] +IP = 127.0.0.1 +Port = 9001 +LoggerName = InternalStatsServer +LoggerFilename = internal_stats_server.log +LoggerLevel = -1 +LoggerOutputConsole = ON +LoggerOutputFile = ON + +[GameStatsServerHttp] +IP = 127.0.0.1 +Port = 9002 +LoggerName = GameStatsServerHttp +LoggerFilename = gamestats_server_http.log +LoggerLevel = -1 +LoggerOutputConsole = ON +LoggerOutputFile = ON + +[AdminPage] +IP = 127.0.0.1 +Port = 9009 +LoggerName = AdminPage +LoggerFilename = admin_page.log +LoggerLevel = -1 +LoggerOutputConsole = ON +LoggerOutputFile = ON + +[RegisterPage] +IP = 127.0.0.1 +Port = 9998 +LoggerName = RegisterPage +LoggerFilename = register_page.log +LoggerLevel = -1 +LoggerOutputConsole = ON +LoggerOutputFile = ON + +[GameSpyManager] +# GamespyBackendServer +IP = 127.0.0.1 +Port = 27500 +LoggerName = GamespyBackendServer +LoggerFilename = gamespy_backend_server.log +LoggerLevel = -1 +LoggerOutputConsole = ON +LoggerOutputFile = ON + +[GameSpyQRServer] +IP = 0.0.0.0 +Port = 27900 +LoggerName = GameSpyQRServer +LoggerFilename = gamespy_qr_server.log +LoggerLevel = -1 +LoggerOutputConsole = ON +LoggerOutputFile = ON + +[GameSpyNatNegServer] +IP = 0.0.0.0 +Port = 27901 +LoggerName = GameSpyNatNegServer +LoggerFilename = gamespy_natneg_server.log +LoggerLevel = -1 +LoggerOutputConsole = ON +LoggerOutputFile = ON + +[GameSpyServerBrowserServer] +IP = 0.0.0.0 +Port = 28910 +LoggerName = GameSpyServerBrowserServer +LoggerFilename = gamespy_server_browser_server.log +LoggerLevel = -1 +LoggerOutputConsole = ON +LoggerOutputFile = ON + +[GameSpyProfileServer] +IP = 0.0.0.0 +Port = 29900 +LoggerName = GameSpyProfileServer +LoggerFilename = gamespy_profile_server.log +LoggerLevel = -1 +LoggerOutputConsole = ON +LoggerOutputFile = ON + +[GameSpyPlayerSearchServer] +IP = 0.0.0.0 +Port = 29901 +LoggerName = GameSpyPlayerSearchServer +LoggerFilename = gamespy_player_search_server.log +LoggerLevel = -1 +LoggerOutputConsole = ON +LoggerOutputFile = ON + +[GameSpyGamestatsServer] +IP = 0.0.0.0 +Port = 29920 +LoggerName = GameSpyGamestatsServer +LoggerFilename = gamespy_gamestats_server.log +LoggerLevel = -1 +LoggerOutputConsole = ON +LoggerOutputFile = ON diff --git a/dwc_config.py b/dwc_config.py index a034de2..dddc956 100755 --- a/dwc_config.py +++ b/dwc_config.py @@ -1,4 +1,4 @@ -"""DWC Network Server Emulator +"""DWC Network Server Emulator Copyright (C) 2014 SMTDDR Copyright (C) 2014 kyle95wm @@ -28,23 +28,50 @@ except ImportError: # Python 3 import configparser as ConfigParser +import other.utils as utils + + +def get_config_filename(filename='altwfc.cfg'): + """Return the config filename that will be used.""" + try: + config = ConfigParser.RawConfigParser(allow_no_value=True) + config.read(filename) + if config.getboolean('Config', 'AlternativeConfig'): + return config.get('Config', 'AlternativeConfigFile') + except Exception as e: + pass + return filename + def get_ip_port(section, filename='altwfc.cfg'): """Return a tuple (IP, Port) of the corresponding section.""" config = ConfigParser.RawConfigParser(allow_no_value=True) - config.read(filename) + config.read(get_config_filename(filename)) return (config.get(section, 'IP'), config.getint(section, 'Port')) def get_ip(section, filename='altwfc.cfg'): """Return the IP of the corresponding section.""" config = ConfigParser.RawConfigParser(allow_no_value=True) - config.read(filename) + config.read(get_config_filename(filename)) return config.get(section, 'IP') def get_port(section, filename='altwfc.cfg'): """Return the port of the corresponding section.""" config = ConfigParser.RawConfigParser(allow_no_value=True) - config.read(filename) + config.read(get_config_filename(filename)) return config.getint(section, 'Port') + + +def get_logger(section, filename='altwfc.cfg'): + """Return the logger of the corresponding section.""" + config = ConfigParser.RawConfigParser(allow_no_value=True) + config.read(get_config_filename(filename)) + return utils.create_logger( + config.get(section, 'LoggerName'), + config.get(section, 'LoggerFilename'), + config.getint(section, 'LoggerLevel'), + config.getboolean(section, 'LoggerOutputConsole'), + config.getboolean(section, 'LoggerOutputFile') + ) diff --git a/dwc_network_server_emulator.pyproj b/dwc_network_server_emulator.pyproj index 06d0cb0..82c6ba4 100644 --- a/dwc_network_server_emulator.pyproj +++ b/dwc_network_server_emulator.pyproj @@ -42,6 +42,7 @@ + diff --git a/gamespy_backend_server.py b/gamespy_backend_server.py index f1ffd11..4adda33 100644 --- a/gamespy_backend_server.py +++ b/gamespy_backend_server.py @@ -56,6 +56,8 @@ from multiprocessing import freeze_support import other.utils as utils import dwc_config +logger = dwc_config.get_logger('GameSpyManager') + class TokenType: UNKNOWN = 0 @@ -64,14 +66,6 @@ class TokenType: NUMBER = 3 TOKEN = 4 -# Logger settings -logger_output_to_console = True -logger_output_to_file = True -logger_name = "GamespyBackendServer" -logger_filename = "gamespy_backend_server.log" -logger = utils.create_logger(logger_name, logger_filename, -1, - logger_output_to_console, logger_output_to_file) - class GameSpyServerDatabase(BaseManager): pass diff --git a/gamespy_gamestats_server.py b/gamespy_gamestats_server.py index dae6a38..f72b6c8 100644 --- a/gamespy_gamestats_server.py +++ b/gamespy_gamestats_server.py @@ -37,14 +37,7 @@ import gamespy.gs_utility as gs_utils import other.utils as utils import dwc_config -# Logger settings -logger_output_to_console = True -logger_output_to_file = True -logger_name = "GameSpyGamestatsServer" -logger_filename = "gamespy_gamestats_server.log" -logger = utils.create_logger(logger_name, logger_filename, -1, - logger_output_to_console, logger_output_to_file) - +logger = dwc_config.get_logger('GameSpyGamestatsServer') address = dwc_config.get_ip_port('GameSpyGamestatsServer') diff --git a/gamespy_natneg_server.py b/gamespy_natneg_server.py index 6a3051f..3e7ce41 100644 --- a/gamespy_natneg_server.py +++ b/gamespy_natneg_server.py @@ -36,13 +36,7 @@ import traceback from multiprocessing.managers import BaseManager import dwc_config -# Logger settings -logger_output_to_console = True -logger_output_to_file = True -logger_name = "GameSpyNatNegServer" -logger_filename = "gamespy_natneg_server.log" -logger = utils.create_logger(logger_name, logger_filename, -1, - logger_output_to_console, logger_output_to_file) +logger = dwc_config.get_logger('GameSpyNatNegServer') class GameSpyServerDatabase(BaseManager): diff --git a/gamespy_player_search_server.py b/gamespy_player_search_server.py index c473f75..65d7c77 100644 --- a/gamespy_player_search_server.py +++ b/gamespy_player_search_server.py @@ -32,14 +32,7 @@ import gamespy.gs_query as gs_query import other.utils as utils import dwc_config -# Logger settings -logger_output_to_console = True -logger_output_to_file = True -logger_name = "GameSpyPlayerSearchServer" -logger_filename = "gamespy_player_search_server.log" -logger = utils.create_logger(logger_name, logger_filename, -1, - logger_output_to_console, logger_output_to_file) - +logger = dwc_config.get_logger('GameSpyPlayerSearchServer') address = dwc_config.get_ip_port('GameSpyPlayerSearchServer') diff --git a/gamespy_profile_server.py b/gamespy_profile_server.py index 8192fdd..92c66ac 100644 --- a/gamespy_profile_server.py +++ b/gamespy_profile_server.py @@ -36,14 +36,7 @@ import gamespy.gs_utility as gs_utils import other.utils as utils import dwc_config -# Logger settings -logger_output_to_console = True -logger_output_to_file = True -logger_name = "GameSpyProfileServer" -logger_filename = "gamespy_profile_server.log" -logger = utils.create_logger(logger_name, logger_filename, -1, - logger_output_to_console, logger_output_to_file) - +logger = dwc_config.get_logger('GameSpyProfileServer') address = dwc_config.get_ip_port('GameSpyProfileServer') diff --git a/gamespy_qr_server.py b/gamespy_qr_server.py index f1f8102..19d40b8 100644 --- a/gamespy_qr_server.py +++ b/gamespy_qr_server.py @@ -41,13 +41,7 @@ import other.utils as utils import dwc_config from gamespy_server_browser_server import GameSpyServerBrowserServer -# Logger settings -logger_output_to_console = True -logger_output_to_file = True -logger_name = "GameSpyQRServer" -logger_filename = "gamespy_qr_server.log" -logger = utils.create_logger(logger_name, logger_filename, -1, - logger_output_to_console, logger_output_to_file) +logger = dwc_config.get_logger('GameSpyQRServer') class GameSpyServerDatabase(BaseManager): diff --git a/gamespy_server_browser_server.py b/gamespy_server_browser_server.py index ddb1f3e..400decc 100644 --- a/gamespy_server_browser_server.py +++ b/gamespy_server_browser_server.py @@ -38,6 +38,8 @@ import dwc_config from multiprocessing.managers import BaseManager +logger = dwc_config.get_logger('GameSpyServerBrowserServer') + class ServerListFlags: UNSOLICITED_UDP_FLAG = 1 @@ -49,14 +51,6 @@ class ServerListFlags: HAS_KEYS_FLAG = 64 HAS_FULL_RULES_FLAG = 128 -# Logger settings -logger_output_to_console = True -logger_output_to_file = True -logger_name = "GameSpyServerBrowserServer" -logger_filename = "gamespy_server_browser_server.log" -logger = utils.create_logger(logger_name, logger_filename, -1, - logger_output_to_console, logger_output_to_file) - class GameSpyServerDatabase(BaseManager): pass diff --git a/gamestats_server_http.py b/gamestats_server_http.py index 057424a..fb6bc3a 100644 --- a/gamestats_server_http.py +++ b/gamestats_server_http.py @@ -34,13 +34,7 @@ import gamespy.gs_utility as gs_utils import other.utils as utils import dwc_config -logger_output_to_console = True -logger_output_to_file = True -logger_name = "GameStatsServerHttp" -logger_filename = "gamestats_server_http.log" -logger = utils.create_logger(logger_name, logger_filename, -1, - logger_output_to_console, logger_output_to_file) - +logger = dwc_config.get_logger('GameStatsServerHttp') address = dwc_config.get_ip_port('GameStatsServerHttp') diff --git a/internal_stats_server.py b/internal_stats_server.py index de51fd9..5c34afa 100644 --- a/internal_stats_server.py +++ b/internal_stats_server.py @@ -30,12 +30,7 @@ import logging import other.utils as utils import dwc_config -logger_output_to_console = True -logger_output_to_file = True -logger_name = "InternalStatsServer" -logger_filename = "internal_stats_server.log" -logger = utils.create_logger(logger_name, logger_filename, -1, - logger_output_to_console, logger_output_to_file) +logger = dwc_config.get_logger('InternalStatsServer') class GameSpyServerDatabase(BaseManager): diff --git a/master_server.py b/master_server.py index 8e8c2eb..954c123 100644 --- a/master_server.py +++ b/master_server.py @@ -32,6 +32,7 @@ from internal_stats_server import InternalStatsServer from admin_page_server import AdminPageServer from storage_server import StorageServer from gamestats_server_http import GameStatsServer +from register_page import RegPageServer import gamespy.gs_database as gs_database @@ -60,6 +61,7 @@ if __name__ == "__main__": NasServer, InternalStatsServer, AdminPageServer, + RegPageServer, StorageServer, GameStatsServer, ] diff --git a/nas_server.py b/nas_server.py index e47c8c5..1e1264b 100644 --- a/nas_server.py +++ b/nas_server.py @@ -35,12 +35,7 @@ import gamespy.gs_database as gs_database import other.utils as utils import dwc_config -logger_output_to_console = True -logger_output_to_file = True -logger_name = "NasServer" -logger_filename = "nas_server.log" -logger = utils.create_logger(logger_name, logger_filename, -1, - logger_output_to_console, logger_output_to_file) +logger = dwc_config.get_logger('NasServer') # If a game from this list requests a file listing, the server will return # that only one exists and return a random one. @@ -178,23 +173,28 @@ class NasHTTPServerHandler(BaseHTTPServer.BaseHTTPRequestHandler): "retry": "1", "reason": "User banned." } - #Un-comment these lines to enable console registration feature - #elif not self.server.db.pending(post): - #logger.log(logging.DEBUG, "Login denied - Unknown console"+str(post)) - #ret = { - #"datetime": time.strftime("%Y%m%d%H%M%S"), - #"returncd": "3921", - #"locator": "gamespy.com", - #"retry": "1", - #} - #elif not self.server.db.registered(post): - #logger.log(logging.DEBUG, "Login denied - console pending"+str(post)) - #ret = { - #"datetime": time.strftime("%Y%m%d%H%M%S"), - #"returncd": "3888", - #"locator": "gamespy.com", - #"retry": "1", - #} + # Un-comment these lines to enable console registration + # feature + # elif not self.server.db.pending(post): + # logger.log(logging.DEBUG, + # "Login denied - Unknown console %s", + # post) + # ret = { + # "datetime": time.strftime("%Y%m%d%H%M%S"), + # "returncd": "3921", + # "locator": "gamespy.com", + # "retry": "1", + # } + # elif not self.server.db.registered(post): + # logger.log(logging.DEBUG, + # "Login denied - console pending %s", + # post) + # ret = { + # "datetime": time.strftime("%Y%m%d%H%M%S"), + # "returncd": "3888", + # "locator": "gamespy.com", + # "retry": "1", + # } else: challenge = utils.generate_random_str(8) post["challenge"] = challenge diff --git a/register_page.py b/register_page.py index 3af20d6..7b60893 100644 --- a/register_page.py +++ b/register_page.py @@ -1,62 +1,69 @@ -# DWC Network Server Emulator -# Copyright (C) 2014 SMTDDR -# Copyright (C) 2014 kyle95wm -# Copyright (C) 2014 AdmiralCurtiss -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU Affero General Public License as -# published by the Free Software Foundation, either version 3 of the -# License, or (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU Affero General Public License for more details. -# -# You should have received a copy of the GNU Affero General Public License -# along with this program. If not, see . +"""DWC Network Server Emulator + + Copyright (C) 2014 SMTDDR + Copyright (C) 2014 kyle95wm + Copyright (C) 2014 AdmiralCurtiss + Copyright (C) 2015 Sepalani + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU Affero General Public License as + published by the Free Software Foundation, either version 3 of the + License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License + along with this program. If not, see . +""" from twisted.web import server, resource from twisted.internet import reactor from twisted.internet.error import ReactorAlreadyRunning import re import base64 -import codecs +import codecs import sqlite3 import collections import json import time import datetime +import logging + import other.utils as utils -import gamespy import gamespy.gs_utility as gs_utils +import dwc_config + +logger = dwc_config.get_logger('RegisterPage') +_, port = dwc_config.get_ip_port('RegisterPage') + class RegPage(resource.Resource): isLeaf = True - def __init__(self,regpage): + def __init__(self, regpage): self.regpage = regpage - def get_header(self, title = None): + def get_header(self, title=None): if not title: title = 'Register a Console' - s = ( - '' - '' - '' + title + '' - '' - '' - '

' - 'Register a console' - '

' - ) + s = """ + + + %s + + +

+ Register a console +

""" % title return s - + def get_footer(self): - s = ( - '' - '' - ) + s = """ + + """ return s def update_maclist(self, request): @@ -65,13 +72,21 @@ class RegPage(resource.Resource): macadr = request.args['macadr'][0].strip() actiontype = request.args['action'][0] macadr = macadr.lower() - if not re.match("[0-9a-f]{2}([-:])[0-9a-f]{2}(\\1[0-9a-f]{2}){4}$", macadr): + if not re.match("[0-9a-f]{2}([-:])[0-9a-f]{2}(\\1[0-9a-f]{2}){4}$", + macadr): request.setResponseCode(500) - return "The MAC you entered was invalid. Please click the back button and try again!" - macadr = macadr.replace(":","").replace("-","") + return "The MAC you entered was invalid." \ + "Please click the back button and try again!" + macadr = macadr.replace(":", "").replace("-", "") if actiontype == 'add': - dbconn.cursor().execute('insert into pending values(?)',(macadr,)) - responsedata = "Added %s to pending list. Please close this window now. It's also not a bad idea to check back on the status of your activation by attempting to connect your console to the server." % (macadr) + dbconn.cursor().execute( + 'INSERT INTO pending VALUES(?)', + (macadr,) + ) + responsedata = "Added %s to pending list." % (macadr) + responsedata += "Please close this window now." + " It's also not a bad idea to check back on the status of your" + " activation by attempting to connect your console to the server." dbconn.commit() dbconn.close() request.setHeader("Content-Type", "text/html; charset=utf-8") @@ -87,13 +102,14 @@ class RegPage(resource.Resource): def render_maclist(self, request): address = request.getClientIP() dbconn = sqlite3.connect('gpcm.db') - responsedata = ("" - "
" - "macadr (must be in the format of aa:bb:cc:dd:ee:ff or aa-bb-cc-dd-ee-ff):\r\n" - "\r\n" - "
\r\n" - "" - "") + responsedata = """ + + macadr (must be in the format of %s or %s): + + + + +
""" % ('aa:bb:cc:dd:ee:ff', 'aa-bb-cc-dd-ee-ff') dbconn.close() request.setHeader("Content-Type", "text/html; charset=utf-8") return responsedata @@ -104,7 +120,7 @@ class RegPage(resource.Resource): if request.path == "/register": title = 'Register a Console' response = self.render_maclist(request) - + return self.get_header(title) + response + self.get_footer() def render_POST(self, request): @@ -113,16 +129,20 @@ class RegPage(resource.Resource): else: return self.get_header() + self.get_footer() -port = 9998 + class RegPageServer(object): def start(self): site = server.Site(RegPage(self)) reactor.listenTCP(port, site) + logger.log(logging.INFO, + "Now listening for connections on port %d...", + port) try: - if reactor.running == False: + if not reactor.running: reactor.run(installSignalHandlers=0) except ReactorAlreadyRunning: pass + if __name__ == "__main__": RegPageServer().start() diff --git a/storage_server.py b/storage_server.py index c525be9..5031a78 100644 --- a/storage_server.py +++ b/storage_server.py @@ -31,15 +31,11 @@ import other.utils as utils import gamespy.gs_database as gs_database import dwc_config -logger_output_to_console = True -logger_output_to_file = True -logger_name = "StorageServer" -logger_filename = "storage_server.log" -logger = utils.create_logger(logger_name, logger_filename, -1, logger_output_to_console, logger_output_to_file) - # Paths to ProxyPass: /SakeStorageServer, /SakeFileServer +logger = dwc_config.get_logger('StorageServer') address = dwc_config.get_ip_port('StorageServer') + def escape_xml(s): s = s.replace( "&", "&" ) s = s.replace( '"', """ ) @@ -48,12 +44,14 @@ def escape_xml(s): s = s.replace( ">", ">" ) return s + class StorageServer(object): def start(self): httpd = StorageHTTPServer((address[0], address[1]), StorageHTTPServerHandler) logger.log(logging.INFO, "Now listening for connections on %s:%d...", address[0], address[1]) httpd.serve_forever() + class StorageHTTPServer(BaseHTTPServer.HTTPServer): def __init__(self, server_address, RequestHandlerClass): BaseHTTPServer.HTTPServer.__init__(self, server_address, RequestHandlerClass) @@ -210,10 +208,14 @@ class StorageHTTPServer(BaseHTTPServer.HTTPServer): except TypeError: return 'UNKNOWN' + class IllegalColumnAccessException(Exception): pass + + class FilterSyntaxException(Exception): pass + class StorageHTTPServerHandler(BaseHTTPServer.BaseHTTPRequestHandler): def confirm_columns(self, columndata, table):