Skip to content
Permalink
master
Switch branches/tags
Go to file
 
 
Cannot retrieve contributors at this time
executable file 2771 lines (2392 sloc) 101 KB
#!/usr/bin/python
# -*- coding: utf-8 -*-
#
# open source ggpo server (re)implementation
#
# (c) 2014-2015 Pau Oliva Fora (@pof)
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# ggposrv.py includes portions of code borrowed from hircd.
# hircd is Copyright by Ferry Boender, 2009-2013
#
# Permission is hereby granted, free of charge, to any person
# obtaining a copy of this software and associated documentation
# files (the "Software"), to deal in the Software without
# restriction, including without limitation the rights to use,
# copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the
# Software is furnished to do so, subject to the following
# conditions:
#
# The above copyright notice and this permission notice shall be
# included in all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
# OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
# HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
# WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
# OTHER DEALINGS IN THE SOFTWARE.
#
import sys
import optparse
import logging
import ConfigParser
import os
import SocketServer
import socket
import select
import re
import struct
import time
import datetime
import random
import hmac
import hashlib
import json
import gzip
import traceback
import threading
import tarfile
import boto
from BaseHTTPServer import BaseHTTPRequestHandler,HTTPServer
import urlparse
try:
import requests
except:
pass
try:
# http://dev.maxmind.com/geoip/geoip2/geolite2/
import geoip2.database
reader = geoip2.database.Reader('GeoLite2-City.mmdb')
except:
pass
VERSION=24
MIN_CLIENT_VERSION=42
DB_ENGINE="mysql"
if DB_ENGINE=="sqlite3":
import sqlite3
PARAM="?"
elif DB_ENGINE=="mysql":
import MySQLdb
PARAM="%s"
class GGPOHttpHandler(BaseHTTPRequestHandler):
def print_dump(self):
path = self.path
if '?' in path:
path, tmp = path.split('?', 1)
o = urlparse.urlparse(self.path)
qs = urlparse.parse_qs(o.query)
out={}
if path == "/channels":
for channel in ggposerver.channels.values():
out[channel.name]=[]
for client in channel.clients:
out[channel.name].append(client.nick)
if path == "/clients":
timestamp = time.time()
for client in ggposerver.clients.values():
cli={}
cli["status"]=client.status
cli["channel"]=client.channel.name
cli["quark"]=client.quark
#cli["city"]=client.city
cli["idle"]=int(timestamp-client.lastmsgtime)
cli["country"]=client.country
cli["cc"]=client.cc
cli["version"]=client.version
out[client.nick]=cli
if path == "/games":
for quark in ggposerver.quarks.values():
if quark.p1!=None and quark.p2!=None and quark.p1.nick!=None and quark.p2.nick!=None and quark.channel!=None:
game={}
game["channel"]=quark.channel.name
game["p1"]=quark.p1.nick
game["p2"]=quark.p2.nick
game["spectators"]=len(quark.spectators)
game["useports"]=quark.useports
out[quark.quark]=game
if path == "/stats":
out["version"]='{0:.2f}'.format(VERSION/100.0)
clients = len(ggposerver.clients)
out["clients"]=clients
quarks=0
for quark in ggposerver.quarks.values():
if quark.p1!=None and quark.p2!=None and quark.p1.nick!=None and quark.p2.nick!=None:
quarks=quarks+1
out["games"]=quarks
spectators=0
connections = dict(ggposerver.connections)
for host in connections:
try:
client = ggposerver.connections[host]
if client.clienttype=="spectator":
spectators+=1
except:
pass
out["spectators"]=spectators
out["connections"]=len(ggposerver.connections)+len(ggposerver.clients)
if path == "/mute":
try:
nick=str(qs['nick'][0])
timestamp = time.time()
for client in ggposerver.clients.values():
if client.nick==nick:
cli={}
cli["status"]=client.status
cli["channel"]=client.channel.name
cli["cc"]=client.cc
cli["idle"]=int(timestamp-client.lastmsgtime)
cli["version"]=client.version
out[client.nick]=cli
client.spamhit+=10
except:
pass
if path == "/kill":
try:
nick=str(qs['nick'][0])
timestamp = time.time()
for client in ggposerver.clients.values():
if client.nick==nick:
cli={}
cli["status"]=client.status
cli["channel"]=client.channel.name
cli["cc"]=client.cc
cli["idle"]=int(timestamp-client.lastmsgtime)
cli["version"]=client.version
out[client.nick]=cli
client.handle_part(client.channel.name)
client.request.close()
ggposerver.clients.pop(client.nick)
except:
pass
if path == "/clean":
try:
limit=int(qs['limit'][0])
except:
limit=1000
try:
idle=int(qs['idle'][0])
except:
idle=0
try:
status=int(qs['status'][0])
except:
status=1
try:
clienttype=str(qs['clienttype'][0])
except:
clienttype="client"
num=0
timestamp = time.time()
if clienttype=="client":
for client in ggposerver.clients.values():
if num >= limit:
break
if client.status==status and timestamp-client.lastmsgtime > idle and client.nick!='pof':
cli={}
cli["status"]=client.status
cli["channel"]=client.channel.name
cli["cc"]=client.cc
cli["idle"]=int(timestamp-client.lastmsgtime)
cli["version"]=client.version
out[client.nick]=cli
client.handle_part(client.channel.name)
client.request.close()
ggposerver.clients.pop(client.nick)
num+=1
if clienttype=="spectator":
for host in dict(ggposerver.connections):
if num >= limit:
break
try:
client = ggposerver.connections[host]
if client.clienttype=="spectator":
cli={}
cli["quark"]=client.quark
out[str(host)]=cli
client.request.close()
num+=1
except KeyError:
pass
res = json.dumps(out, indent=4, sort_keys=True);
self.wfile.write(res)
#Handler for the GET requests
def do_GET(self):
self.send_response(200)
self.send_header('Content-type','text/html')
self.end_headers()
# Send the message
self.print_dump()
return
class GGPOError(Exception):
"""
Exception thrown by GGPO command handlers to notify client of a server/client error.
"""
def __init__(self, code, value):
self.code = code
self.value = value
def __str__(self):
return repr(self.value)
class GGPOChannel(object):
"""
Object representing an GGPO channel.
"""
def __init__(self, name, rom, topic, motd='', chunksize=1096, port=7000):
self.name = name
self.rom = rom
self.topic = topic
self.motd = motd
self.chunksize = chunksize
self.port = port
self.clients = set()
class GGPOQuark(object):
"""
Object representing a GGPO quark: an ongoing match that can be spectated.
"""
def __init__(self, quark):
self.quark = quark
self.p1 = None
self.p1client = None
self.p2 = None
self.p2client = None
self.spectators = set()
self.recorded = False
self.useports = False
self.channel = None
self.proxyport = {}
# http://stackoverflow.com/q/12248132
def set_keepalive_linux(sock, after_idle_sec=3600, interval_sec=3, max_fails=5):
"""Set TCP keepalive on an open socket.
It activates after 3600 seconds (after_idle_sec) of idleness,
then sends a keepalive ping once every 3 seconds (interval_sec),
and closes the connection after 5 failed ping (max_fails), or 15 seconds
"""
sock.setsockopt(socket.SOL_SOCKET, socket.SO_KEEPALIVE, 1)
sock.setsockopt(socket.IPPROTO_TCP, socket.TCP_KEEPIDLE, after_idle_sec)
sock.setsockopt(socket.IPPROTO_TCP, socket.TCP_KEEPINTVL, interval_sec)
sock.setsockopt(socket.IPPROTO_TCP, socket.TCP_KEEPCNT, max_fails)
def dbconnect():
if DB_ENGINE=="sqlite3":
createdb=False
dbfile = os.path.join(os.path.realpath(os.path.dirname(sys.argv[0])),'db', 'ggposrv.sqlite3')
if not os.path.exists(dbfile):
createdb=True
os.mkdir(os.path.dirname(dbfile))
conn = sqlite3.connect(dbfile)
if createdb==True:
cursor = conn.cursor()
cursor.execute("""CREATE TABLE IF NOT EXISTS users (
id INTEGER PRIMARY KEY,
username TEXT COLLATE NOCASE,
password TEXT,
salt TEXT,
email TEXT,
ip TEXT,
date TEXT);""")
cursor.execute("""CREATE UNIQUE INDEX users_username_idx on users (username COLLATE NOCASE);""")
logging.info("created empty user database")
cursor.execute("""CREATE TABLE IF NOT EXISTS quarks (
id INTEGER PRIMARY KEY,
quark TEXT,
player1 TEXT,
player2 TEXT,
channel TEXT,
date TEXT,
realtime_views INTEGER,
saved_views INTEGER,
p1_country CHAR(50),
p2_country CHAR(50),
duration INTEGER);""")
cursor.execute("""CREATE UNIQUE INDEX quarks_quark_idx on quarks (quark);""")
logging.info("created empty quark database")
conn.commit()
return conn
elif DB_ENGINE=="mysql":
conn = MySQLdb.connect(host="localhost", user="ggpo", passwd="ggpo", db="ggposrv")
return conn
class GGPOClient(SocketServer.BaseRequestHandler):
"""
GGPO client connect and command handling. Client connection is handled by
the `handle` method which sets up a two-way communication with the client.
It then handles commands sent by the client by dispatching them to the
handle_ methods.
"""
def __init__(self, request, client_address, server):
self.nick = None # Client's currently registered nickname
self.host = client_address # Client's hostname / ip.
self.status = 0 # Client's status (0=available, 1=away, 2=playing)
self.clienttype = None # can be: player(fba), spectator(fba) or client
self.previous_status = None # Client's previous status (0=available, 1=away, 2=playing)
self.opponent = None # Client's opponent
self.quark = None # Client's quark (in-game uri)
self.fbaport = 0 # Emulator's fbaport
self.side = 0 # Client's side: 1=P1, 2=P2 (0=spectator before savestate, 3=spectator after savestate)
self.port = 6009 # Client's port
self.city = "null" # Client's city
self.country = "null" # Client's country
self.cc = "null" # Client's country code
self.lastmsgtime = 0 # timestamp of the last chat message
self.challengetime = 0 # timestamp of the last challenge
self.lastmsg = '' # last chat message
self.spamhit = 0 # how many times has been warned for spam
self.useports = False # set to true when we have potential problems with NAT traversal
self.version = 0 # client version
self.warnmsg = '' # Warning message (shown after match)
self.turboflag = 0 # turbo flag helper
self.send_queue = [] # Messages to send to client (strings)
self.channel = GGPOChannel("lobby",'', "The Lobby") # Channel the client is in
self.challenging = {} # users (GGPOClient instances) that this client is challenging by host
try:
set_keepalive_linux(request)
except:
pass
SocketServer.BaseRequestHandler.__init__(self, request, client_address, server)
def pad2hex(self,l):
return "".join(reversed(struct.pack('I',l)))
def sizepad(self,value):
if value==None:
return('')
l=len(value)
pdu = self.pad2hex(l)
pdu += value
return pdu
def reply(self,sequence,pdu):
length=4+len(pdu)
return self.pad2hex(length) + self.pad2hex(sequence) + pdu
def send_ack(self, sequence):
ACK='\x00\x00\x00\x00'
response = self.reply(sequence,ACK)
logging.debug('ACK to %s: %r' % (self.client_ident(), response))
self.send_queue.append(response)
def get_client_from_nick(self,nick):
try:
clients = dict(self.server.clients)
for client_nick in clients:
if client_nick == nick:
return self.server.clients[nick]
for client_nick in self.channel.clients:
if client_nick == nick:
return self.channel.clients[nick]
except KeyError:
pass
# if not found, return self
logging.info('[%s] WARNING: Could not find client: %s (returning self)' % (self.client_ident(), nick))
return self
def check_quark_format(self,quark):
a = re.compile("^challenge\-[0-9]{4}\-[0-9]{10,11}[.][0-9]{2}$")
if a.match(quark):
return True
else:
return False
def geolocate(self, ip):
iso_code=''
country=''
city=''
try:
response = reader.city(ip)
if response.country.iso_code!=None:
iso_code=str(response.country.iso_code)
if response.country.name!=None:
country=str(response.country.name)
#if response.city.name!=None:
#city=str(response.city.name)
if (response.subdivisions.most_specific.name=="Barcelona" or
response.subdivisions.most_specific.name=="Tarragona" or
response.subdivisions.most_specific.name=="Lleida" or
response.subdivisions.most_specific.name=="Girona"):
iso_code="Catalonia"
country="Catalonia"
except:
pass
return iso_code,country,city
def parse(self, data):
response = ''
logging.debug('[PARSE] from %s: %r' % (self.client_ident(), data))
length=int(data[0:4].encode('hex'),16)
if (len(data)<length-4): return()
sequence=0
if (length >= 4):
sequence=int(data[4:8].encode('hex'),16)
if (length >= 8):
command=int(data[8:12].encode('hex'),16)
if (command==0):
command = "connect"
params = sequence
if (command==1):
command = "auth"
nicklen=int(data[12:16].encode('hex'),16)
nick=data[16:16+nicklen]
passwordlen=int(data[16+nicklen:16+nicklen+4].encode('hex'),16)
password=data[20+nicklen:20+nicklen+passwordlen]
port=int(data[20+nicklen+passwordlen:24+nicklen+passwordlen].encode('hex'),16)
if len(data) > 24+nicklen+passwordlen:
version=int(data[24+nicklen+passwordlen:28+nicklen+passwordlen].encode('hex'),16)
else:
version=0
params=nick,password,port,version,sequence
if (command==2):
if self.nick==None: return()
command = "motd"
params = sequence
if (command==3):
if self.nick==None: return()
command="list"
params = sequence
if (command==4):
if self.nick==None: return()
command="users"
params = sequence
if (command==5):
if self.nick==None: return()
command="join"
channellen=int(data[12:16].encode('hex'),16)
channel=data[16:16+channellen]
params = channel,sequence
if (command==6):
if self.nick==None: return()
command="status"
status=int(data[12:16].encode('hex'),16)
params = status,sequence
if (command==7):
if self.nick==None: return()
command="privmsg"
msglen=int(data[12:16].encode('hex'),16)
msg=data[16:16+msglen]
params = msg,sequence
if (command==8):
if self.nick==None: return()
command="challenge"
nicklen=int(data[12:16].encode('hex'),16)
nick=data[16:16+nicklen]
channellen=int(data[16+nicklen:16+nicklen+4].encode('hex'),16)
channel=data[20+nicklen:20+nicklen+channellen]
params = nick,channel,sequence
if (command==9):
if self.nick==None: return()
command="accept"
nicklen=int(data[12:16].encode('hex'),16)
nick=data[16:16+nicklen]
channellen=int(data[16+nicklen:16+nicklen+4].encode('hex'),16)
channel=data[20+nicklen:20+nicklen+channellen]
params = nick,channel,sequence
if (command==0xa):
if self.nick==None: return()
command="decline"
nicklen=int(data[12:16].encode('hex'),16)
nick=data[16:16+nicklen]
params = nick,sequence
if (command==0xb):
command="getpeer"
quarklen=int(data[12:16].encode('hex'),16)
quark=data[16:16+quarklen]
fbaport=int(data[16+quarklen:16+quarklen+4].encode('hex'),16)
params = quark,fbaport,sequence
if (command==0xc):
command="getnicks"
quarklen=int(data[12:16].encode('hex'),16)
quark=data[16:16+quarklen]
params = quark,sequence
if (command==0xf):
command="fba_privmsg"
quarklen=int(data[12:16].encode('hex'),16)
quark=data[16:16+quarklen]
msglen=int(data[16+quarklen:16+quarklen+4].encode('hex'),16)
msg=data[20+quarklen:20+quarklen+msglen]
params = quark,msg,sequence
if (command==0x10):
if self.nick==None: return()
command="watch"
nicklen=int(data[12:16].encode('hex'),16)
nick=data[16:16+nicklen]
params = nick,sequence
if (command==0x11):
command="savestate"
quarklen=int(data[12:16].encode('hex'),16)
quark=data[16:16+quarklen]
block1=data[16+quarklen:20+quarklen]
block2=data[20+quarklen:24+quarklen]
#buflen=int(data[24+quarklen:24+quarklen+4].encode('hex'),16)
#gamebuf=data[28+quarklen:28+quarklen+buflen]
gamebuf=data[24+quarklen:length+4]
params = quark,block1,block2,gamebuf,sequence
if (command==0x12):
command="gamebuffer"
quarklen=int(data[12:16].encode('hex'),16)
quark=data[16:16+quarklen]
#buflen=int(data[16+quarklen:16+quarklen+4].encode('hex'),16)
#gamebuf=data[20+quarklen:20+quarklen+buflen]
gamebuf=data[20+quarklen:length+4]
params = quark,gamebuf,sequence
if (command==0x13):
command="ggpotv"
quarklen=int(data[12:16].encode('hex'),16)
quark=data[16:16+quarklen]
gamebuf=data[20+quarklen:length+4]
params = quark,gamebuf,sequence
if (command==0x14):
command="spectator"
quarklen=int(data[12:16].encode('hex'),16)
quark=data[16:16+quarklen]
params = quark,sequence
if (command==0x1c):
if self.nick==None: return()
command="cancel"
nicklen=int(data[12:16].encode('hex'),16)
nick=data[16:16+nicklen]
params = nick,sequence
if command in ["join", "challenge", "decline", "cancel", "accept", "getnicks", "watch", "spectator"]:
logging.info('[%s] SEQUENCE: %d COMMAND: %s %s' % (self.client_ident(),sequence,command,params[0]))
elif command in ["savestate", "list", "users", "ggpotv"]:
logging.debug('[%s] SEQUENCE: %d COMMAND: %s' % (self.client_ident(),sequence,command))
else:
logging.info('[%s] SEQUENCE: %d COMMAND: %s' % (self.client_ident(),sequence,command))
try:
handler = getattr(self, 'handle_%s' % (command), None)
if not handler:
logging.info('[%s] No handler for command: %s. Full line: %r' % (self.client_ident(), command, data))
if self.nick==None: return()
command="unknown"
params = sequence
handler = getattr(self, 'handle_%s' % (command), None)
response = handler(params)
except AttributeError, e:
raise e
logging.error('[%s] ERROR (1) %s in command %s' % (self.client_ident(), e, command))
except GGPOError, e:
response = '[%s] ERROR (2) %s %s in command %s' % (self.client_ident(), e.code, e.value, command)
logging.error('%s' % (response))
except Exception, e:
response = '[%s] ERROR (3) %s in command %s' % (self.client_ident(), repr(e), command)
logging.error('%s' % (response))
raise
if (len(data) > length+4 ):
pdu=data[length+4:]
self.parse(pdu)
return response
def handle(self):
logging.info('[%s] Client connected' % (self.client_ident(), ))
data=''
while True:
try:
ready_to_read, ready_to_write, in_error = select.select([self.request], [], [], 0.1)
except Exception, e:
logging.debug('[%s] ERROR: %s' % (self.client_ident(), e))
break
# Write any commands to the client
while self.send_queue:
msg = self.send_queue.pop(0)
#logging.debug('[SEND] to %s: %r' % (self.client_ident(), msg))
try:
self.request.send(msg)
except:
logging.info('[%s] Can\'t send data. Finishing ' % (self.client_ident(), ))
self.finish()
# See if the client has any commands for us.
if len(ready_to_read) == 1 and ready_to_read[0] == self.request:
try:
dataread=self.request.recv(16384)
data+=dataread
if not dataread:
break
#logging.debug('[RECV] from %s: %r' % (self.client_ident(), data))
while (len(data)-4 > int(data[0:4].encode('hex'),16)):
length=int(data[0:4].encode('hex'),16)
response = self.parse(data[0:length+4])
data=data[length+4:]
if len(data)-4 == int(data[0:4].encode('hex'),16):
response = self.parse(data)
data=''
if response:
logging.debug('<<<<<<>>>>>to %s: %r' % (self.client_ident(), response))
#self.request.send(response)
except Exception, e:
logging.info('[%s] Can\'t read data. Finishing. ERROR: %s' % (self.client_ident(), repr(e)))
self.finish()
self.request.close()
def get_peer_from_quark(self, quark):
"""
Returns a GGPOClient object representing our FBA peer's ggpofba connection, or self if not found
"""
connections = dict(self.server.connections)
for host in connections:
try:
client = self.server.connections[host]
if client.clienttype=="player" and client.quark==quark and client.host!=self.host:
return client
except KeyError:
pass
return self
def get_myclient_from_quark(self, quark):
"""
Returns a GGPOClient object representing our own client connection, or self if not found
"""
try:
quarkobject = self.server.quarks[quark]
if quarkobject.p1client!=None and self.nick!=None:
if quarkobject.p1client.nick == self.nick:
return quarkobject.p1client
if quarkobject.p2client!=None and self.nick!=None:
if quarkobject.p2client.nick == self.nick:
return quarkobject.p2client
except KeyError:
pass
clients = dict(self.server.clients)
for nick in clients:
client = self.get_client_from_nick(nick)
if client.clienttype=="client" and client.quark==quark and client.host[0]==self.host[0]:
return client
return self
def get_myclient_from_quark_and_peer(self, quark, peer):
"""
Returns a GGPOClient object representing our own client connection, or self if not found
"""
clients = dict(self.server.clients)
for nick in clients:
client = self.get_client_from_nick(nick)
if client.clienttype=="client" and client.quark==quark and client.nick!=peer.nick:
return client
return self
def handle_fba_privmsg(self, params):
"""
Handle sending messages inside the FBA emulator.
"""
quark, msg, sequence = params
# send the ACK to the client
#self.send_ack(sequence)
peer=self.get_peer_from_quark(quark)
# send the in-game chat messages to the client
# this helps people using experimental blitter that can't see the OSD text
try:
quarkobject = self.server.quarks[quark]
if quarkobject.p1.nick==self.nick:
mypeer = quarkobject.p2client
myself = quarkobject.p1client
else:
mypeer = quarkobject.p1client
myself = quarkobject.p2client
negseq=4294967294 #'\xff\xff\xff\xfe'
response = self.reply(negseq,self.sizepad("System")+self.sizepad('GAME: <'+self.nick+'> '+msg))
logging.debug('to %s: %r' % (mypeer.client_ident(), response))
mypeer.send_queue.append(response)
logging.debug('to %s: %r' % (myself.client_ident(), response))
myself.send_queue.append(response)
except:
pass
negseq=4294967288 #'\xff\xff\xff\xf8'
pdu=self.sizepad(quark)
pdu+=self.sizepad(self.nick)
pdu+=self.sizepad(msg)
response = self.reply(negseq,pdu)
logging.debug('to %s: %r' % (peer.client_ident(), response))
peer.send_queue.append(response)
logging.debug('to %s: %r' % (self.client_ident(), response))
self.send_queue.append(response)
def handle_gamebuffer(self, params):
quark, gamebuf, sequence = params
negseq=4294967284 #'\xff\xff\xff\xf4'
pdu=gamebuf
response = self.reply(negseq,pdu)
connections = dict(self.server.connections)
for host in connections:
try:
client = self.server.connections[host]
if client.clienttype=="spectator" and client.quark==quark and client.side==0:
logging.debug('to %s: %r' % (client.client_ident(), response))
client.send_queue.append(response)
client.side=3
except KeyError:
pass
# record match for future broadcast
try:
quarkobject = self.server.quarks[quark]
except KeyError:
return()
if quarkobject.p1.nick==None and quarkobject.p2.nick==None and quarkobject.channel.name=='lobby':
self.finish()
return()
if self.check_quark_format(quark) and quarkobject.recorded == False:
quarkobject.recorded=True
# match started successfully, reset useports on both clients:
quarkobject.p1client.useports=False
quarkobject.p2client.useports=False
quarkobject.p1client.warnmsg=''
quarkobject.p2client.warnmsg=''
# store player nicknames
date = datetime.datetime.today().strftime("%Y-%m-%d %H:%M:%S")
conn = dbconnect()
cursor = conn.cursor()
sql = "INSERT INTO quarks (quark, player1, player2, channel, date, realtime_views, saved_views, p1_country, p2_country, duration) VALUES ("+PARAM+","+PARAM+","+PARAM+","+PARAM+","+PARAM+",0,0,"+PARAM+","+PARAM+",-1)"
try:
cursor.execute(sql, [quark, quarkobject.p1.nick, quarkobject.p2.nick, quarkobject.channel.name, date, quarkobject.p1client.cc, quarkobject.p2client.cc])
conn.commit()
except:
# close the connection if we can't add the quark into the db
conn.close()
self.finish()
return()
conn.close()
# store initial savestate (gamebuffer)
quarkfile = os.path.join(os.path.realpath(os.path.dirname(sys.argv[0])),'quarks', 'quark-'+quark+'-gamebuffer.fs')
if not os.path.exists(quarkfile):
try:
os.mkdir(os.path.dirname(quarkfile))
except:
pass
f=open(quarkfile, 'wb')
f.write(response)
f.close()
def handle_savestate(self, params):
quark, block1, block2, gamebuf, sequence = params
# send ACK to the player
self.send_ack(sequence)
negseq=4294967283 #'\xff\xff\xff\xf3'
pdu=block2+block1+gamebuf
response = self.reply(negseq,pdu)
connections = dict(self.server.connections)
for host in connections:
try:
client = self.server.connections[host]
if client.clienttype=="spectator" and client.quark==quark and client.side==3:
logging.debug('to %s: %r' % (client.client_ident(), response))
client.send_queue.append(response)
except KeyError:
pass
# record match for future broadcast
try:
quarkobject = self.server.quarks[quark]
except KeyError:
return()
if self.check_quark_format(quark) and quarkobject.recorded == True:
quarkfile = os.path.join(os.path.realpath(os.path.dirname(sys.argv[0])),'quarks', 'quark-'+quark+'-savestate.fs')
if not os.path.exists(quarkfile):
try:
os.mkdir(os.path.dirname(quarkfile))
except:
pass
try:
f=open(quarkfile, 'ab')
f.write(response)
f.close()
except IOError:
logging.debug('[%s] IOError in command savestate' % (self.client_ident()))
def handle_getnicks(self, params):
quark, sequence = params
# to replay a saved quark
try:
quarkobject = self.server.quarks[quark]
except KeyError:
# make sure the quark format is valid
if not self.check_quark_format(quark):
return()
dbfile = os.path.join(os.path.realpath(os.path.dirname(sys.argv[0])),'db', 'ggposrv.sqlite3')
if not os.path.exists(dbfile):
return()
conn = dbconnect()
cursor = conn.cursor()
sql = "SELECT player1, player2, channel FROM quarks WHERE quark=" + PARAM
cursor.execute(sql, [(quark)])
player1,player2,channel=cursor.fetchone()
conn.close()
if channel=='':
channel="lobby"
if channel=='ssf2t':
channel="ssf2xj"
if player1=='' and player2=='':
return()
# if the quark file is not present in the local cache, retrieve it from S3
quarkfile = os.path.join(os.path.realpath(os.path.dirname(sys.argv[0])),'quarks', 'quark-'+quark+'-gamebuffer.fs')
if not os.path.exists(quarkfile):
bucket_name = 'fightcade.quarks'
conn = boto.connect_s3()
bucket = conn.get_bucket(bucket_name, validate=False)
tarball = 'quark-'+quark+'.tar'
k = bucket.get_key(tarball)
tarball_fullpath = os.path.join(os.path.realpath(os.path.dirname(sys.argv[0])),'quarks',tarball)
k.get_contents_to_filename(tarball_fullpath)
tar = tarfile.open(tarball_fullpath)
tar.extractall(path=os.path.join(os.path.realpath(os.path.dirname(sys.argv[0])),'quarks'))
tar.close()
os.remove(tarball_fullpath)
# we keep the quark in the local cache & remove it from S3
k.delete()
pdu='\x00\x00\x00\x00'
pdu+=self.sizepad(player1)
pdu+=self.sizepad(player2)
pdu+='\x00\x00\x00\x00'
pdu+=self.pad2hex(0)
response = self.reply(sequence,pdu)
logging.debug('to %s: %r' % (self.client_ident(), response))
self.request.send(response)
# now broadcast the quark to the client
f=open(quarkfile, 'rb')
response = f.read()
f.close()
logging.debug('to %s: %r' % (self.client_ident(), response))
self.request.send(response)
self.side=3
quarkfile = os.path.join(os.path.realpath(os.path.dirname(sys.argv[0])),'quarks', 'quark-'+quark+'-savestate.fs')
if not os.path.exists(quarkfile):
quarkfile = os.path.join(os.path.realpath(os.path.dirname(sys.argv[0])),'quarks', 'quark-'+quark+'-savestate.fs.gz')
if not os.path.exists(quarkfile):
return()
if quarkfile.endswith(".gz"):
f=gzip.open(quarkfile)
else:
f=open(quarkfile)
try:
CHUNKSIZE=self.server.channels[channel].chunksize
except:
CHUNKSIZE=1096
response = f.read(CHUNKSIZE)
while (response):
time.sleep(0.8)
try:
logging.debug('to %s: %r' % (self.client_ident(), response))
self.request.send(response)
except:
logging.debug('[%s]: spectator disconnected from broadcast' % (self.client_ident()))
break
response = f.read(CHUNKSIZE)
f.close()
self.finish()
return()
i=0
while True:
if (quarkobject.p1 != None and quarkobject.p2 != None) or i>=30:
break
i=i+1
time.sleep(1)
pdu='\x00\x00\x00\x00'
if (i<30):
pdu+=self.sizepad(quarkobject.p1.nick)
pdu+=self.sizepad(quarkobject.p2.nick)
else:
# avoid crashing fba if we can't get our peer
pdu+='\x00\x00\x00\x00'
pdu+='\x00\x00\x00\x00'
pdu+='\x00\x00\x00\x00'
if self.clienttype=="player":
pdu+=self.pad2hex(len(quarkobject.spectators))
else:
pdu+=self.pad2hex(len(quarkobject.spectators)+1)
response = self.reply(sequence,pdu)
logging.debug('to %s: %r' % (self.client_ident(), response))
self.send_queue.append(response)
if self.clienttype=="player":
# call auto_spectate() to record the game
logging.debug('[%s] calling AUTO-SPECTATE' % (self.client_ident()))
self.auto_spectate(quark)
# announce the match to the public
myself=self.get_myclient_from_quark(quark)
myself.previous_status=myself.status
myself.status=2
params = 2,0
myself.handle_status(params)
def handle_getpeer(self, params):
quark, fbaport, sequence = params
if replayonly:
self.finish()
return()
# send ack to the client's ggpofba
self.send_ack(sequence)
self.clienttype="player"
self.quark=quark
self.fbaport=fbaport
quarkobject = self.server.quarks.setdefault(quark, GGPOQuark(quark))
myself=self.get_myclient_from_quark(quark)
if self!=myself:
self.side=myself.side
self.nick=myself.nick
self.useports=myself.useports
self.channel=myself.channel
quarkobject.channel=myself.channel
if quarkobject.p1!=None and quarkobject.p2!=None:
logging.info('[%s] getpeer in a full quark: go away' % (self.client_ident()))
self.finish()
return
i=0
while True:
i=i+1
time.sleep(0.8)
peer=self.get_peer_from_quark(quark)
if peer!=self or i>=30:
break
if peer==self:
logging.info('[%s] couldn\'t find peer: %s' % (self.client_ident() , peer.client_ident()))
self.finish()
return()
else:
logging.debug('[%s] found peer: %s [my fbaport: %d ; peer fbaport: %d]' % (self.client_ident() , peer.client_ident(), self.fbaport, peer.fbaport))
# fix for clients with multiple public ip addresses
if self==myself:
myself=self.get_myclient_from_quark_and_peer(quark,peer)
if self!=myself:
self.side=myself.side
self.nick=myself.nick
self.useports=myself.useports
self.channel=myself.channel
quarkobject.channel=myself.channel
logging.info('[%s] My client and I have different IP addresses: %s' % (self.client_ident(), myself.client_ident()))
else:
logging.info('[%s] ERROR: couldn\'t find my client. Aborting.' % (self.client_ident()))
self.finish()
return()
selfchallenge=False
if self.side==1 and quarkobject.p1==None:
quarkobject.p1=self
quarkobject.p1client=myself
elif self.side==2 and quarkobject.p2==None:
quarkobject.p2=self
quarkobject.p2client=myself
else:
# you are challenging yourself
if (quarkobject.p1==None):
quarkobject.p1=self
quarkobject.p1client=myself
if (quarkobject.p2==None):
quarkobject.p2=self
quarkobject.p2client=myself
selfchallenge=True
negseq=4294967289 #'\xff\xff\xff\xf9'
if holepunch and self.useports==False and peer.useports==False and quarkobject.useports==False:
# when UDP hole punching is enabled clients must use the udp proxy wrapper
pdu=self.sizepad("127.0.0.1")
if selfchallenge:
pdu+=self.pad2hex(7002)
else:
ip = self.host[0]
try:
port = int(quarkobject.proxyport[ip])
except:
port = 7001
pdu+=self.pad2hex(port)
if (int(self.host[1])>6009 or int(self.host[1])<6000) and myself.version < 32:
myself.useports=True
logging.debug('[%s] using holepunch with %s on quark %s (and setting useports=True)' % (self.client_ident(), peer.client_ident(), quark))
else:
if holepunch:
# if we can't do nat traversal with holepunch, try to use open ports instead
logging.info('[%s] WARNING: not using holepunch with %s on quark %s' % (self.client_ident(), peer.client_ident(), quark))
if int(self.host[1])>6009 or int(self.host[1])<6000 and myself!=None:
msg="Looks like FightCade is having problems doing NAT traversal on your connection.\n"
msg+="If you can't connect try opening GGPO ports on your router (6000 to 6009 udp)."
myself.warnmsg=msg
# warn the other peer that he's innocent:
if int(peer.host[1])<=6009 and int(peer.host[1])>=6000 and quarkobject!=None:
peer_nick = myself.nick
if peer_nick==None or peer_nick=='':
peer_nick="your peer"
msg="Looks like "+str(peer_nick)+" has problems connecting (problem is on his side, not on yours).\n"
msg+="If you can't connect with him try opening GGPO ports on your router (6000 to 6009 udp)."
if quarkobject.p1client==myself and quarkobject.p2client!=None:
quarkobject.p2client.warnmsg=msg
elif quarkobject.p2client==myself and quarkobject.p1client!=None:
quarkobject.p1client.warnmsg=msg
if int(peer.host[1])>6009 or int(peer.host[1])<6000 and quarkobject!=None:
msg="Looks like FightCade is having problems doing NAT traversal on your connection.\n"
msg+="If you can't connect try opening GGPO ports on your router (6000 to 6009 udp)."
if quarkobject.p1client==myself and quarkobject.p2client!=None:
quarkobject.p2client.warnmsg=msg
elif quarkobject.p2client==myself and quarkobject.p1client!=None:
quarkobject.p1client.warnmsg=msg
# warn the other peer that he's innocent:
if int(self.host[1])<=6009 and int(self.host[1])>=6000:
peer_nick = myself.opponent
if peer_nick==None or peer_nick=='':
peer_nick="your peer"
msg="Looks like "+str(peer_nick)+" has problems connecting (problem is on his side, not on yours).\n"
msg+="If you can't connect with him try opening GGPO ports on your router (6000 to 6009 udp)."
myself.warnmsg=msg
pdu=self.sizepad(peer.host[0])
pdu+=self.pad2hex(peer.fbaport)
if self.side==1:
pdu+=self.pad2hex(1)
else:
pdu+=self.pad2hex(0)
response = self.reply(negseq,pdu)
logging.debug('to %s: %r' % (self.client_ident(), response))
self.send_queue.append(response)
def auto_spectate(self, quark):
logging.debug('[%s] entering AUTO-SPECTATE' % (self.client_ident()))
negseq=4294967285 #'\xff\xff\xff\xf5'
pdu=''
response = self.reply(negseq,pdu)
negseq=4294967286 #'\xff\xff\xff\xf6'
pdu=self.pad2hex(1)
response+=self.reply(negseq,pdu)
# make the player's FBA send us the game data, to store it on the server
logging.debug('to %s: %r' % (self.client_ident(), response))
self.send_queue.append(response)
def handle_spectator(self,params):
quark, sequence = params
# make sure the quark format is valid
if not self.check_quark_format(quark):
self.finish()
return()
# a match can only be spectated once from the same ip
connections = dict(self.server.connections)
for host in connections:
try:
client = self.server.connections[host]
if client.clienttype=="spectator" and client.host[0]==self.host[0] and client.quark==quark:
logging.debug('[%s] kicking spectator trying to watch a duplicate quark: %s' % (self.client_ident(), quark))
self.finish()
return()
except:
pass
try:
quarkobject = self.server.quarks[quark]
except KeyError:
# to replay a saved quark
logging.debug('[%s] spectating saved quark: %s' % (self.client_ident(), quark))
# send ack to the client's ggpofba
self.send_ack(sequence)
self.clienttype="spectator"
self.quark=quark
# increment saved views on db
try:
dbfile = os.path.join(os.path.realpath(os.path.dirname(sys.argv[0])),'db', 'ggposrv.sqlite3')
conn = dbconnect()
cursor = conn.cursor()
sql = "UPDATE quarks SET saved_views=saved_views+1 WHERE quark=" + PARAM
cursor.execute(sql, [quark])
conn.commit()
conn.close()
except:
pass
return()
logging.debug('[%s] spectating real-time quark: %s' % (self.client_ident(), quark))
# send ack to the client's ggpofba
self.send_ack(sequence)
self.clienttype="spectator"
self.quark=quark
quarkobject.spectators.add(self)
negseq=4294967285 #'\xff\xff\xff\xf5'
pdu=''
response = self.reply(negseq,pdu)
negseq=4294967286 #'\xff\xff\xff\xf6'
pdu=self.pad2hex(len(quarkobject.spectators)+1)
response+=self.reply(negseq,pdu)
# this updates the number of spectators in both players FBAs
logging.debug('to %s: %r' % (quarkobject.p1.client_ident(), response))
quarkobject.p1.send_queue.append(response)
logging.debug('to %s: %r' % (quarkobject.p2.client_ident(), response))
quarkobject.p2.send_queue.append(response)
for spectator in quarkobject.spectators:
spectator.send_queue.append(response)
# increment realtime views on db
try:
dbfile = os.path.join(os.path.realpath(os.path.dirname(sys.argv[0])),'db', 'ggposrv.sqlite3')
conn = dbconnect()
cursor = conn.cursor()
sql = "UPDATE quarks SET realtime_views=realtime_views+1 WHERE quark=" + PARAM
cursor.execute(sql, [quark])
conn.commit()
conn.close()
except:
pass
def spectator_leave(self, quark):
quarkobject = self.server.quarks[quark]
quarkobject.spectators.remove(self)
negseq=4294967286 #'\xff\xff\xff\xf6'
pdu=self.pad2hex(len(quarkobject.spectators)+1)
response=self.reply(negseq,pdu)
# this updates the number of spectators in both players FBAs
try:
logging.debug('to %s: %r' % (quarkobject.p1.client_ident(), response))
quarkobject.p1.send_queue.append(response)
logging.debug('to %s: %r' % (quarkobject.p2.client_ident(), response))
quarkobject.p2.send_queue.append(response)
except:
pass
for spectator in quarkobject.spectators:
spectator.send_queue.append(response)
def handle_challenge(self, params):
nick, channel, sequence = params
client = self.get_client_from_nick(nick)
challengespam=False
timestamp = time.time()
if (timestamp-self.lastmsgtime < 0.70):
challengespam=True
# clean self.quark if it's already set and the last challenge was more than 1 min ago
if (timestamp-self.challengetime > 60 and self.quark!=None and self.status!=2):
self.quark=None
self.lastmsgtime = timestamp
# if we can't find the client, tell the user that this client has parted:
if client == self and nick!=self.nick:
negseq=4294967293 #'\xff\xff\xff\xfd'
pdu=''
pdu+='\x00\x00\x00\x01' #unk1
pdu+='\x00\x00\x00\x00' #unk2
pdu+=self.sizepad(nick)
response = self.reply(negseq,pdu)
self.send_queue.append(response)
# check that user is connected, in available state and in the same channel, and we're not playing
if (client.status==0 and client.channel==self.channel and self.channel.name==channel and self.status<2 and nick!=self.nick and client!=self and challengespam==False and self.quark==None):
self.challengetime = timestamp
# send ACK to the initiator of the challenge request
self.send_ack(sequence)
self.side=1
# send the challenge request to the challenged user
negseq=4294967292 #'\xff\xff\xff\xfc'
pdu=self.sizepad(self.nick)
pdu+=self.sizepad(self.channel.name)
response = self.reply(negseq,pdu)
logging.debug('to %s: %r' % (client.client_ident(), response))
client.send_queue.append(response)
# add the client to the challenging list
self.challenging[client.host] = client
else:
# send the NOACK to the client
response = self.reply(sequence,'\x00\x00\x00\x0a')
logging.debug('[%s] challenge NO_ACK: tried to challenge client %s (%s) but client.status=%d and self.status=%d and client.channel=%r and self.channel=%r and self.channel.name=%r and channel=%r and spam=%s' % (self.client_ident(), client.client_ident(), nick, client.status, self.status, client.channel, self.channel, self.channel.name, channel, challengespam ))
self.send_queue.append(response)
def handle_accept(self, params):
nick, channel, sequence = params
client = self.get_client_from_nick(nick)
# this must be int as we use it for quark too
timestamp = int(time.time())
# clean self.quark if it's already set and the last challenge was more than 1 min ago
if (timestamp-self.challengetime > 60 and self.quark!=None and self.status!=2):
self.quark=None
# make sure that nick has challenged the user that is doing the accept command
if self.host not in client.challenging or self.quark!=None or client.quark!=None:
# send the NOACK to the client
response = self.reply(sequence,'\x00\x00\x00\x0c')
logging.debug('[%s] accept NO_ACK: %r' % (self.client_ident(), response))
self.send_queue.append(response)
return
else:
#client.challenging.pop(self.host)
client.challenging.clear()
self.challenging.clear()
random1=random.randint(1000,9999)
random2=random.randint(10,99)
quark="challenge-"+str(random1)+"-"+str(timestamp)+"."+str(random2)
self.challengetime = timestamp
self.quark=quark
client.quark=quark
self.side=2
client.side=1
self.opponent=nick
client.opponent=self.nick
# send the quark stream uri to the user who accepted the challenge
negseq=4294967290 #'\xff\xff\xff\xfa'
pdu=''
pdu+=self.sizepad(self.opponent)
pdu+=self.sizepad(self.nick)
pdu+=self.sizepad("quark:served,"+self.channel.name+","+self.quark+","+str(listen_port))
response = self.reply(negseq,pdu)
logging.debug('to %s: %r' % (self.client_ident(), response))
self.send_queue.append(response)
# send the quark stream uri to the challenge initiator
negseq=4294967290 #'\xff\xff\xff\xfa'
pdu=''
pdu+=self.sizepad(client.nick)
pdu+=self.sizepad(client.opponent)
pdu+=self.sizepad("quark:served,"+self.channel.name+","+self.quark+","+str(listen_port))
response = self.reply(negseq,pdu)
logging.debug('to %s: %r' % (client.client_ident(), response))
client.send_queue.append(response)
def handle_decline(self, params):
nick, sequence = params
client = self.get_client_from_nick(nick)
if self.host not in client.challenging:
# send the NOACK to the client
response = self.reply(sequence,'\x00\x00\x00\x0d')
logging.debug('[%s] decline NO_ACK: %r' % (self.client_ident(), response))
self.send_queue.append(response)
return
else:
client.challenging.pop(self.host)
# send ACK to the initiator of the decline request
self.send_ack(sequence)
# inform of the decline to the initiator of the challenge
negseq=4294967291 #'\xff\xff\xff\xfb'
pdu=self.sizepad(self.nick)
#pdu+=self.sizepad(self.channel.name)
response = self.reply(negseq,pdu)
logging.debug('to %s: %r' % (client.client_ident(), response))
client.send_queue.append(response)
def handle_watch(self, params):
nick, sequence = params
client = self.get_client_from_nick(nick)
# check that user is connected, in playing state (status=2) and in the same channel
if (client.status==2 and client.channel==self.channel and client.quark!=None):
# send ACK to the user who wants to watch the running match
self.send_ack(sequence)
# send the quark stream uri to the user who wants to watch
negseq=4294967290 #'\xff\xff\xff\xfa'
pdu=''
pdu+=self.sizepad(client.nick)
pdu+=self.sizepad(client.opponent)
pdu+=self.sizepad("quark:stream,"+self.channel.name+","+str(client.quark)+","+str(listen_port))
response = self.reply(negseq,pdu)
logging.debug('to %s: %r' % (self.client_ident(), response))
self.send_queue.append(response)
else:
# send the NOACK to the client
response = self.reply(sequence,'\x00\x00\x00\x0b')
logging.debug('[%s] watch NO_ACK: %r' % (self.client_ident(), response))
self.send_queue.append(response)
def handle_cancel(self, params):
nick, sequence = params
client = self.get_client_from_nick(nick)
if client.host not in self.challenging:
# send the NOACK to the client
response = self.reply(sequence,'\x00\x00\x00\x0e')
logging.debug('[%s] cancel NO_ACK: %r' % (self.client_ident(), response))
self.send_queue.append(response)
return
else:
self.challenging.pop(client.host)
# send ACK to the challenger user who wants to cancel the challenge
self.send_ack(sequence)
# send the cancel action to the challenged user
negseq=4294967279 #'\xff\xff\xff\xef'
pdu=self.sizepad(self.nick)
response = self.reply(negseq,pdu)
client = self.get_client_from_nick(nick)
logging.debug('to %s: %r' % (client.client_ident(), response))
client.send_queue.append(response)
def handle_ggpotv(self, params):
# XXX: not sure what to do with this...
quark, gamebuf, sequence = params
self.send_ack(sequence)
def handle_unknown(self, params):
sequence = params
response = self.reply(sequence,'\x00\x00\x00\x08')
logging.debug('to %s: %r' % (self.client_ident(), response))
self.send_queue.append(response)
# kick the user out of the server
self.finish()
def handle_connect(self, params):
sequence = params
self.send_ack(sequence)
self.server.connections[self.host] = self
def handle_motd(self, params):
sequence = params
pdu='\x00\x00\x00\x00'
channel = self.channel
pdu+=self.sizepad(channel.name)
pdu+=self.sizepad(channel.topic)
if self.version >= MIN_CLIENT_VERSION:
pdu+=self.sizepad(channel.motd+self.dynamic_motd(channel.name))
else:
motd='<center>'
if self.version != 0:
motd+='You are using FightCade client version {0:.2f}'.format(self.version/100.0)+'\n'
motd+="*********************************************************\n"
motd+=" ERROR: YOUR VERSION OF FIGHTCADE CLIENT IS TOO OLD\n"
motd+=" ERROR: YOUR VERSION OF FIGHTCADE CLIENT IS TOO OLD\n"
motd+=" ERROR: YOUR VERSION OF FIGHTCADE CLIENT IS TOO OLD\n"
motd+="*********************************************************\n\n"
motd+="MINIMUM VERSION REQUIRED TO CONNECT IS: {0:.2f}".format(MIN_CLIENT_VERSION/100.0)+"\n\n"
motd+="PLEASE RE-DOWNLOAD LATEST VERSION OF FIGHTCADE NOW:\n"
motd+="\nhttp://www.fightcade.com\n\n"
motd+="*********************************************************"
motd+="</center>"
pdu+=self.sizepad(motd)
response = self.reply(sequence,pdu)
logging.debug('to %s: %r' % (self.client_ident(), response))
self.send_queue.append(response)
def kick_client(self, sequence, error=6):
# auth unsuccessful
response = self.reply(sequence,self.pad2hex(error))
logging.debug('to %s: %r' % (self.client_ident(), response))
self.send_queue.append(response)
#self.finish()
def handle_auth(self, params):
"""
Handle the initial setting of the user's nickname
"""
nick,password,port,version,sequence = params
if replayonly:
self.finish()
return()
if not nullauth:
# New connection
conn = dbconnect()
cursor = conn.cursor()
# fetch the user's salt
sql = "SELECT salt FROM users WHERE username=" + PARAM
cursor.execute(sql, [(nick)])
salt=cursor.fetchone()
if (salt==None):
# user doesn't exist into database
logging.info("[%s] user doesn't exist into database: %s" % (self.client_ident(), nick))
self.kick_client(sequence,4)
conn.close()
return
# compute the hashed password
h_password = hmac.new("GGPO-NG", password+salt[0], hashlib.sha512).hexdigest()
sql = "SELECT COUNT(username) FROM users WHERE password=" + PARAM + " AND username=" + PARAM
cursor.execute(sql, [(h_password),(nick)])
result = cursor.fetchone()
conn.close()
if (result[0] != 1):
# wrong password
logging.info("[%s] wrong password: %s" % (self.client_ident(), nick))
self.kick_client(sequence,6)
return
if nick in self.server.clients:
# Someone else is using the nick
clone = self.get_client_from_nick(nick)
if clone != self:
logging.info("[%s] someone else is using the nick: %s (%s)" % (self.client_ident(), nick, clone.client_ident()))
self.server.clients.pop(nick)
# remove the clone from channel
clone.handle_part(clone.channel.name)
clone.request.close()
self.kick_client(sequence,8)
return()
# check for multiple connections from the same ip address
same_ip=0
clone_port=0
clients = dict(self.server.clients)
try:
for client_nick in clients:
if self.server.clients[client_nick].host[0]==self.host[0]:
same_ip+=1
clone_port=self.server.clients[client_nick].port
except KeyError:
pass
#logging.info("[%s] connections from host %s -> %d" % (self.client_ident(), self.host[0], same_ip))
if (same_ip >= 2) or (same_ip == 1 and clone_port!=port):
self.nick = nick
logging.info("[%s] too many connections from host %s" % (self.client_ident(), self.host[0]))
self.kick_client(sequence,9)
return()
logging.info("[%s] LOGIN OK. VERSION: %s NICK: %s" % (self.client_ident(), str(version), nick))
self.nick = nick
self.server.clients[nick] = self
self.port = port
self.clienttype="client"
self.cc, self.country, self.city = self.geolocate(self.host[0])
self.version = version
timestamp = time.time()
self.lastmsgtime = timestamp
# auth successful
self.send_ack(sequence)
if self.host in self.server.connections:
self.server.connections.pop(self.host)
def handle_status(self, params):
status,sequence = params
# send ack to the client
if (sequence >4):
self.send_ack(sequence)
if self.status == 2 and sequence!=0 and (status>=0 and status<2) and self.opponent!=None:
# set previous_status when status is modified while playing
self.previous_status = status
return
elif (status>=0 and status<2) or (status==2 and sequence==0):
self.status = status
if (status!=2):
self.previous_status = status
else:
# do nothing if the user tries to set an invalid status
logging.info('[%s]: trying to set invalid status: %d , self.status=%d, sequence=%d, self.opponent=%s' % (self.client_ident(), status, self.status, sequence, self.opponent))
return
if self.clienttype=="client":
negseq=4294967293 #'\xff\xff\xff\xfd'
pdu2=''
pdu='\x00\x00\x00\x01'
pdu+='\x00\x00\x00\x01'
pdu+=self.sizepad(self.nick)
pdu+=self.pad2hex(self.status)
if (self.opponent!=None):
pdu+=self.sizepad(self.opponent)
else:
pdu+='\x00\x00\x00\x00'
pdu+=self.sizepad(str(self.host[0]))
pdu+='\x00\x00\x00\x00' #unk1
pdu+='\x00\x00\x00\x00' #unk2
pdu+=self.sizepad(self.city)
pdu+=self.sizepad(self.cc)
pdu+=self.sizepad(self.country)
pdu+=self.pad2hex(self.port)
if (self.opponent!=None):
client = self.get_client_from_nick(self.opponent)
pdu2+='\x00\x00\x00\x01'
pdu2+=self.sizepad(client.nick)
pdu2+=self.pad2hex(client.status)
pdu2+=self.sizepad(client.opponent)
pdu2+=self.sizepad(str(client.host[0]))
pdu2+='\x00\x00\x00\x00' #unk1
pdu2+='\x00\x00\x00\x00' #unk2
pdu2+=self.sizepad(client.city)
pdu2+=self.sizepad(client.cc)
pdu2+=self.sizepad(client.country)
pdu2+=self.pad2hex(client.port)
# fix for crappy routers that change their own public ip address to something else
pdu1='\x00\x00\x00\x01'
pdu1+='\x00\x00\x00\x01'
pdu1+=self.sizepad(self.nick)
pdu1+=self.pad2hex(self.status)
if (self.opponent!=None):
pdu1+=self.sizepad(self.opponent)
else:
pdu1+='\x00\x00\x00\x00'
pdu1+=self.sizepad("127.0.0.1")
pdu1+='\x00\x00\x00\x00' #unk1
pdu1+='\x00\x00\x00\x00' #unk2
pdu1+=self.sizepad(self.city)
pdu1+=self.sizepad(self.cc)
pdu1+=self.sizepad(self.country)
pdu1+=self.pad2hex(self.port)
self_response = self.reply(negseq,pdu1+pdu2)
self.send_queue.append(self_response)
response = self.reply(negseq,pdu+pdu2)
clients = self.channel.clients.copy()
for client in clients:
# Send message to all client in the channel
if client != self:
logging.debug('to %s: %r' % (client.client_ident(), response))
client.send_queue.append(response)
def handle_users(self, params):
sequence = params
pdu=''
i=0
# disconnect the client if it's using an older version
if (self.version < MIN_CLIENT_VERSION):
self.finish()
return()
clients = self.channel.clients.copy()
for client in clients:
i=i+1
pdu+=self.sizepad(client.nick)
pdu+=self.pad2hex(client.status) #status
if (client.opponent!=None):
pdu+=self.sizepad(client.opponent)
else:
pdu+='\x00\x00\x00\x00'
if client==self:
pdu+=self.sizepad("127.0.0.1")
else:
pdu+=self.sizepad(str(client.host[0]))
pdu+='\x00\x00\x00\x00' #unk1
pdu+='\x00\x00\x00\x00' #unk2
pdu+=self.sizepad(client.city)
pdu+=self.sizepad(client.cc)
pdu+=self.sizepad(client.country)
pdu+=self.pad2hex(client.port) # port
if (self.version >= 40):
spectators=0
if client.quark!=None:
try:
quarkobject = self.server.quarks[client.quark]
spectators=len(quarkobject.spectators)
except:
pass
pdu+=self.pad2hex(spectators)
response = self.reply(sequence,'\x00\x00\x00\x00'+self.pad2hex(i)+pdu)
logging.debug('to %s: %r' % (self.client_ident(), response))
self.send_queue.append(response)
def handle_list(self, params):
sequence = params
pdu=''
i=0
for target in sorted(self.server.channels):
i=i+1
channel = self.server.channels.get(target)
if (self.version < 32):
pdu+=self.sizepad(channel.name)
pdu+=self.sizepad(channel.rom)
topic=str(channel.topic)+" ["+str(len(channel.clients))+"]"
pdu+=self.sizepad(topic)
pdu+=self.pad2hex(i)
elif (self.version < 41):
pdu+=self.sizepad(channel.name)
pdu+=self.sizepad(channel.rom)
pdu+=self.sizepad(channel.topic)
pdu+=self.pad2hex(len(channel.clients))
pdu+=self.pad2hex(i)
else:
pdu+=self.sizepad(channel.name)
pdu+=self.sizepad(channel.rom)
pdu+=self.sizepad(channel.topic)
if channel.port==listen_port:
pdu+=self.pad2hex(len(channel.clients))
else:
# pick the value from /run/shm/ggposrv/${rom-name}.${port-number}.txt
try:
f=open("/run/shm/ggposrv/"+str(channel.name)+"."+str(channel.port)+".txt", 'r')
num_clients = int(f.read())
f.close()
except:
num_clients=0
if num_clients < 0:
num_clients=0
pdu+=self.pad2hex(num_clients)
pdu+=self.pad2hex(channel.port)
pdu+=self.pad2hex(i)
response = self.reply(sequence,'\x00\x00\x00\x00'+self.pad2hex(i)+pdu)
logging.debug('to %s: %r' % (self.client_ident(), response))
self.send_queue.append(response)
def handle_join(self, params):
"""
Handle the JOINing of a user to a channel.
"""
channel_name,sequence = params
if not channel_name in self.server.channels or self.nick==None or replayonly:
# send the NOACK to the client
response = self.reply(sequence,'\x00\x00\x00\x08')
logging.debug('[%s] JOIN NO_ACK: %r' % (self.client_ident(), response))
self.send_queue.append(response)
return()
# part from previously joined channel
self.handle_part(self.channel.name)
# Add user to the channel (create new channel if not exists)
channel = self.server.channels.setdefault(channel_name, GGPOChannel(channel_name, channel_name, channel_name))
channel.clients.add(self)
# Add channel to user's channel list
self.channel = channel
# send the ACK to the client
self.send_ack(sequence)
negseq=4294967295 #'\xff\xff\xff\xff'
response = self.reply(negseq,'')
logging.debug('CONNECITON ESTABLISHED to %s: %r' % (self.client_ident(), response))
self.send_queue.append(response)
params = self.status,0
self.handle_status(params)
# write the number of channel clients to /run/shm/ggposrv/${rom-name}.${port-number}.txt
chanfile = "/run/shm/ggposrv/"+str(channel.name)+"."+str(listen_port)+".txt"
if not os.path.exists(chanfile):
try:
os.mkdir(os.path.dirname(chanfile))
except:
pass
try:
f=open(chanfile, 'w')
f.write(str(len(channel.clients)))
f.close()
except:
pass
# hook for attendance stats, see: https://github.com/poliva/ggposrv/issues/14
if attendance:
try:
url = "http://neogeodb.com:8765/players"
payload = { "player": str(self.nick), "rom": str(self.channel.name), "country": str(self.country) }
headers = {'content-type': 'application/json'}
response = requests.post(url, data=json.dumps(payload), headers=headers)
except:
logging.debug('[%s] ERROR sending attendance stats' % self.client_ident())
def handle_privmsg(self, params):
"""
Handle sending a message to a channel.
"""
msg, sequence = params
channel = self.channel
# send the ACK to the client
self.send_ack(sequence)
# allow "System" user to send broadcast messages to *ALL* connected users
negseq=4294967294 #'\xff\xff\xff\xfe'
response = self.reply(negseq,self.sizepad(self.nick)+self.sizepad(msg))
if (self.nick=="System"):
for client in self.server.clients.values():
if client.clienttype=="client":
logging.debug('to %s: %r' % (client.client_ident(), response))
client.send_queue.append(response)
return
timestamp = time.time()
if (len(msg)>=200) and (">" not in msg):
self.spamhit += 0.75
if (len(msg)>=170) and (len(self.lastmsg)>=170) and (timestamp-self.lastmsgtime<30) and (">" not in msg):
self.spamhit += 1
if ("http" in msg) and ("http" in self.lastmsg) and (timestamp-self.lastmsgtime<30):
self.spamhit += 1
if (self.lastmsg == msg) and (len(msg) > 3) and (timestamp-self.lastmsgtime<60):
self.spamhit += 1
self.lastmsg = msg
if (timestamp-self.lastmsgtime < 0.70 or len(msg) > 700):
nick="System"
msg="Please do not spam"
negseq=4294967294 #'\xff\xff\xff\xfe'
response = self.reply(negseq,self.sizepad(nick)+self.sizepad(msg))
logging.debug('to %s: %r' % (self.client_ident(), response))
self.send_queue.append(response)
self.lastmsgtime = timestamp
self.spamhit += 1
return
self.lastmsgtime = timestamp
negseq=4294967294 #'\xff\xff\xff\xfe'
response = self.reply(negseq,self.sizepad(self.nick)+self.sizepad(msg))
self.send_queue.append(response)
# if warned 3 times, do not display his messages
if (self.spamhit >= 3):
return
clients = self.channel.clients.copy()
for client in clients:
# Send message to all client in the channel
if client != self:
logging.debug('to %s: %r' % (client.client_ident(), response))
client.send_queue.append(response)
def handle_part(self, params):
"""
Handle a client parting from channel(s).
"""
pchannel = params
# Send message to all clients in the channel user is in, and
# remove the user from the channel.
channel = self.server.channels.get(pchannel)
negseq=4294967293 #'\xff\xff\xff\xfd'
pdu=''
pdu+='\x00\x00\x00\x01' #unk1
pdu+='\x00\x00\x00\x00' #unk2
pdu+=self.sizepad(self.nick)
response = self.reply(negseq,pdu)
clients = self.channel.clients.copy()
for client in clients:
if client != self:
# Send message to all client in the channel except ourselves
logging.debug('to %s: %r' % (client.client_ident(), response))
client.send_queue.append(response)
if self in channel.clients:
channel.clients.remove(self)
# write the number of channel clients to /run/shm/ggposrv/${rom-name}.${port-number}.txt
chanfile = "/run/shm/ggposrv/"+str(channel.name)+"."+str(listen_port)+".txt"
if not os.path.exists(chanfile):
try:
os.mkdir(os.path.dirname(chanfile))
except:
pass
try:
f=open(chanfile, 'w')
f.write(str(len(channel.clients)))
f.close()
except:
pass
def get_profile_url(self, username):
username = re.sub("%", "%25", username)
username = re.sub(" ", "%20", username)
username = re.sub("#", "%23", username)
username = re.sub("\+", "%2B", username)
username = re.sub('\\\\', "%5C", username)
return 'http://www.fightcade.com/id/'+username
def dynamic_motd(self, channel):
motd=''
# generic motd
motdfile = os.path.join(os.path.realpath(os.path.dirname(sys.argv[0])),'motd', 'motd.txt')
if os.path.exists(motdfile):
f=open(motdfile, 'r')
for line in f.readlines():
motd+=line
f.close()
motd+='\n'
# channel specific motd
motdfile = os.path.join(os.path.realpath(os.path.dirname(sys.argv[0])),'motd', channel+'.txt')
if os.path.exists(motdfile):
f=open(motdfile, 'r')
for line in f.readlines():
motd+=line
f.close()
motd+='\n'
# dynamic motd
motd+='-!- FightCade server version {0:.2f}'.format(VERSION/100.0)+'\n'
if self.version > 0:
motd+='-!- You are using FightCade client version {0:.2f}'.format(self.version/100.0)+'\n'
ports=[]
for channel in self.server.channels:
port=self.server.channels[channel].port
if port not in ports:
ports.append(port)
clients = len(self.server.clients)
# write the number of clients to /run/shm/ggposrv/server-clients.${port-number}.txt
chanfile = "/run/shm/ggposrv/server-clients."+str(listen_port)+".txt"
if not os.path.exists(chanfile):
try:
os.mkdir(os.path.dirname(chanfile))
except:
pass
try:
f=open(chanfile, 'w')
f.write(str(clients))
f.close()
except:
pass
for port in ports:
if int(listen_port) != int(port):
# pick the value from /run/shm/ggposrv/server-clients.${port-number}.txt
try:
f=open("/run/shm/ggposrv/server-clients."+str(port)+".txt", 'r')
num_clients = int(f.read())
f.close()
except:
num_clients=0
clients+=num_clients
if clients==1:
motd+='-!- You are the first client on the server!\n'
else:
motd+='-!- There are '+str(clients)+' clients connected to the server.\n'
#quarks = len(self.server.quarks)
quarks=0
for quark in self.server.quarks.values():
if quark.p1!=None and quark.p2!=None and quark.p1.nick!=None and quark.p2.nick!=None:
quarks=quarks+1
# write the number of quarks to /run/shm/ggposrv/server-quarks.${port-number}.txt
quarksfile = "/run/shm/ggposrv/server-quarks."+str(listen_port)+".txt"
try:
f=open(quarksfile, 'w')
f.write(str(quarks))
f.close()
except:
pass
for port in ports:
if int(listen_port) != int(port):
# pick the value from /run/shm/ggposrv/server-quarks.${port-number}.txt
try:
f=open("/run/shm/ggposrv/server-quarks."+str(port)+".txt", 'r')
num_quarks = int(f.read())
f.close()
except:
num_quarks=0
quarks+=num_quarks
if quarks==0:
motd+='-!- At the moment no one is playing :(\n'
elif quarks==1:
motd+='-!- There is only one ongoing game.\n'
elif quarks>1:
motd+='-!- There are '+str(quarks)+' ongoing games.\n'
spectators=0
connections = dict(self.server.connections)
for host in connections:
try:
client = self.server.connections[host]
if client.clienttype=="spectator":
spectators+=1
except:
pass
# write the number of spectators to /run/shm/ggposrv/server-quarks.${port-number}.txt
specfile = "/run/shm/ggposrv/server-spectators."+str(listen_port)+".txt"
try:
f=open(specfile, 'w')
f.write(str(spectators))
f.close()
except:
pass
for port in ports:
if int(listen_port) != int(port):
# pick the value from /run/shm/ggposrv/server-spectators.${port-number}.txt
try:
f=open("/run/shm/ggposrv/server-spectators."+str(port)+".txt", 'r')
num_spectators = int(f.read())
f.close()
except:
num_spectators=0
spectators+=num_spectators
motd+='-!- There are '+str(spectators)+' spectators.\n'
motd+='\n-!- Your profile: '+self.get_profile_url(self.nick)+'\n'
if self.channel.name!='unsupported' and self.channel.name!='lobby':
motd+='-!- Game stats: http://www.fightcade.com/game/'+str(self.channel.name)+'\n'
motd+='-!- Replay browser: http://www.fightcade.com/replay\n'
return motd
def handle_dump(self):
"""
Dump internal server information for debugging purposes.
"""
print "Clients:"
for client in self.server.clients.values():
print " ", client
print " ", client.channel.name
print "Channels:"
for channel in self.server.channels.values():
print " ", channel.name, channel
for client in channel.clients:
print " ", client.nick, client
def client_ident(self):
"""
Return the client identifier as included in many command replies.
"""
return('%s@%s:%s#%s/%s(%s)' % (self.nick, self.host[0], self.host[1], self.channel.name, self.clienttype, self.quark))
def finish(self):
"""
The client conection is finished. Do some cleanup to ensure that the
client doesn't linger around in any channel or the client list.
"""
logging.info('[%s] Client disconnected' % (self.client_ident()))
if self in self.channel.clients:
# Client is gone without properly QUITing or PARTing this
# channel.
clients = self.channel.clients.copy()
for client in clients:
# if the gone client was playing against someone, update his status
if (client.opponent==self.nick):
client.opponent=None
#self.channel.clients.remove(self)
self.handle_part(self.channel.name)
logging.debug("[%s] removing myself from channel" % (self.client_ident()))
if self.nick in self.server.clients and self.clienttype=="client":
self.server.clients.pop(self.nick)
logging.debug("[%s] removing myself from server clients" % (self.client_ident()))
if self.clienttype=="player":
# return the client to non-playing state when the emulator closes
myself=self.get_myclient_from_quark(self.quark)
logging.info("[%s] cleaning: %s" % (self.client_ident(), myself.client_ident()))
myself.side=0
myself.opponent=None
myself.quark=None
if (myself.previous_status!=None and myself.previous_status!=2):
myself.status=myself.previous_status
else:
myself.status=0
myself.previous_status=None
params = myself.status,0
myself.handle_status(params)
try:
quarkobject = self.server.quarks[self.quark]
# try to clean our peer's client too
if quarkobject.p1==self and quarkobject.p2!=None:
mypeer = self.get_client_from_nick(quarkobject.p2.nick)
elif quarkobject.p2==self and quarkobject.p1!=None:
mypeer = self.get_client_from_nick(quarkobject.p1.nick)
else:
mypeer = self
mypeer.side=0
mypeer.opponent=None
if (mypeer != self):
mypeer.quark=None
if (mypeer.previous_status!=None and mypeer.previous_status!=2):
mypeer.status=mypeer.previous_status
else:
mypeer.status=0
mypeer.previous_status=None
params = mypeer.status,0
mypeer.handle_status(params)
# if connection didn't succeed, send the warning message to both peers
nick="System"
negseq=4294967294 #'\xff\xff\xff\xfe'
if myself.warnmsg!='' and myself.clienttype=="client" and myself.host[0]!=mypeer.host[0]:
response = self.reply(negseq,self.sizepad(str(nick))+self.sizepad(str(myself.warnmsg)))
myself.send_queue.append(response)
logging.debug("[%s] sending warnmsg to %s : %s" % (self.client_ident(), myself.client_ident(), myself.warnmsg.replace('\n', ' ') ))
myself.warnmsg=''
if mypeer.warnmsg!='' and mypeer.clienttype=="client" and myself.host[0]!=mypeer.host[0]:
response = self.reply(negseq,self.sizepad(str(nick))+self.sizepad(str(mypeer.warnmsg)))
mypeer.send_queue.append(response)
logging.debug("[%s] sending warnmsg to %s : %s" % (self.client_ident(), mypeer.client_ident(), mypeer.warnmsg.replace('\n', ' ') ))
mypeer.warnmsg=''
# remove quark if we are a player that closes ggpofba
if quarkobject.p1==self or quarkobject.p2==self and self.quark!=None:
# this will kill the emulators to avoid assertion failed errors with future players
# produces an ugly "guru meditation" error on the peer's FBA, but lets the player
# do another game without having to cross challenge
# --- comenting it for now, as it freezes the windows client :/
#logging.info("[%s] killing both FBAs" % (self.client_ident()))
#quarkobject.p1.send_queue.append('\xff\xff\x00\x00\xde\xad')
#quarkobject.p2.send_queue.append('\xff\xff\x00\x00\xde\xad')
logging.debug("[%s] removing quark: %s" % (self.client_ident(), self.quark))
self.server.quarks.pop(self.quark)
# compress replay data
quarkfile = os.path.join(os.path.realpath(os.path.dirname(sys.argv[0])),'quarks', 'quark-'+str(quarkobject.quark)+'-savestate.fs')
gz_quarkfile = os.path.join(os.path.realpath(os.path.dirname(sys.argv[0])),'quarks', 'quark-'+str(quarkobject.quark)+'-savestate.fs.gz')
if not os.path.exists(gz_quarkfile) and os.path.exists(quarkfile):
logging.info("[%s] compressing replay data for quark %s" % (self.client_ident(), self.quark))
try:
f_out = gzip.open(gz_quarkfile, 'wb')
f_in = open(quarkfile, 'rb')
f_out.writelines(f_in)
f_out.close()
f_in.close()
os.unlink(quarkfile)
except:
logging.error('[%s] Error compressing replay data for quark %s' % (self.client_ident(), self.quark))
# broadcast the quark id for replays
quarkfile = os.path.join(os.path.realpath(os.path.dirname(sys.argv[0])),'quarks', 'quark-'+str(quarkobject.quark)+'-gamebuffer.fs')
if os.path.exists(quarkfile):
nick="System"
msg = "To replay your match, type /replay "+str(quarkobject.quark)+"@"+str(quarkobject.channel.name)
negseq=4294967294 #'\xff\xff\xff\xfe'
response = self.reply(negseq,self.sizepad(str(nick))+self.sizepad(str(msg)))
logging.debug('to %s: %r' % (self.client_ident(), response))
if (quarkobject.p1client!=None):
quarkobject.p1client.send_queue.append(response)
if (quarkobject.p2client!=None):
quarkobject.p2client.send_queue.append(response)
# update the duration
end_date = datetime.datetime.today().strftime("%Y-%m-%d %H:%M:%S")
rdate1 = datetime.datetime.strptime(end_date, "%Y-%m-%d %H:%M:%S")
dbfile = os.path.join(os.path.realpath(os.path.dirname(sys.argv[0])),'db', 'ggposrv.sqlite3')
conn = dbconnect()
cursor = conn.cursor()
sql = "SELECT date FROM quarks WHERE quark=" + PARAM
try:
cursor.execute(sql, [(quarkobject.quark)])
start_date=cursor.fetchone()[0]
mdate1 = datetime.datetime.strptime(str(start_date), "%Y-%m-%d %H:%M:%S")
duration = int((rdate1-mdate1).total_seconds())
sql = "UPDATE quarks SET duration="+PARAM+" WHERE quark=" + PARAM
cursor.execute(sql, [duration, quarkobject.quark])
conn.commit()
except:
logging.error("[%s] ERROR updating duration" % (self.client_ident()))
conn.close()
if quarkobject.p1==self and self.quark!=None and quarkobject.p2!=None:
logging.debug("[%s] killing peer connection: %s" % (self.client_ident(), quarkobject.p2.client_ident()))
quarkobject.p2.request.close()
if quarkobject.p2==self and self.quark!=None and quarkobject.p1!=None:
logging.debug("[%s] killing peer connection: %s" % (self.client_ident(), quarkobject.p1.client_ident()))
quarkobject.p1.request.close()
except KeyError, AttributeError:
pass
if self.clienttype=="spectator":
# this client is an spectator
try:
logging.debug("[%s] spectator leaving quark %s" % (self.client_ident(), self.quark))
self.spectator_leave(self.quark)
except KeyError, AttributeError:
pass
if self.host in self.server.connections:
self.server.connections.pop(self.host)
logging.debug("[%s] removing myself from server connections" % (self.client_ident()))
logging.debug('[%s] Connection finished' % (self.client_ident()))
self.request.close()
def __repr__(self):
"""
Return a user-readable description of the client
"""
return('<%s %s@%s>' % (
self.__class__.__name__,
self.nick,
self.host[0],
)
)
class GGPOServer(SocketServer.ThreadingMixIn, SocketServer.TCPServer):
daemon_threads = True
allow_reuse_address = True
def __init__(self, server_address, RequestHandlerClass):
self.servername = 'localhost'
self.channels = {} # Existing channels (GGPOChannel instances) by channelname
self.channels['1941']=GGPOChannel("1941", "1941", '1941 - Counter Attack (World)')
self.channels['1944']=GGPOChannel("1944", "1944", '1944 - the loop master (000620 USA)')
self.channels['19xx']=GGPOChannel("19xx", "19xx", '19XX - the war against destiny (951207 USA)', '', 376)
self.channels['2020bb']=GGPOChannel("2020bb", "2020bb", '2020 Super Baseball (set 1)', '', 1096)
self.channels['3countb']=GGPOChannel("3countb", "3countb", '3 Count Bout')
#self.channels['3wonders']=GGPOChannel("3wonders", "3wonders", 'Three Wonders (wonder 3 910520 etc)')
self.channels['agallet']=GGPOChannel("agallet", "agallet", 'Air Gallet')
self.channels['alpham2']=GGPOChannel("alpham2", "alpham2", 'Alpha Mission II')
self.channels['androdun']=GGPOChannel("androdun", "androdun", 'Andro Dunos')
self.channels['aodk']=GGPOChannel("aodk", "aodk", 'Aggressors of Dark Kombat')
self.channels['aof2']=GGPOChannel("aof2", "aof2", 'Art of Fighting 2 (set 1)')
self.channels['aof3']=GGPOChannel("aof3", "aof3", 'Art of Fighting 3 - the path of the warrior', '', 1096)
self.channels['aof']=GGPOChannel("aof", "aof", 'Art of Fighting')
self.channels['armwar']=GGPOChannel("armwar", "armwar", 'Armored Warriors (941024 Europe)')
self.channels['avsp']=GGPOChannel("avsp", "avsp", 'Alien vs Predator (940520 Euro)', '', 376)
self.channels['bangbead']=GGPOChannel("bangbead", "bangbead", 'Bang Bead', '', 1096)
self.channels['batcir']=GGPOChannel("batcir", "batcir", 'Battle Circuit (970319 Euro)')
self.channels['bjourney']=GGPOChannel("bjourney", "bjourney", "Blue's Journey")
self.channels['blazstar']=GGPOChannel("blazstar", "blazstar", 'Blazing Star')
self.channels['breakers']=GGPOChannel("breakers", "breakers", 'Breakers')
self.channels['breakrev']=GGPOChannel("breakrev", "breakrev", 'Breakers Revenge', '', 1096)
self.channels['bstars2']=GGPOChannel("bstars2", "bstars2", 'Baseball Stars 2')
self.channels['burningf']=GGPOChannel("burningf", "burningf", 'Burning Fight (set 1)')
self.channels['captcomm']=GGPOChannel("captcomm", "captcomm", 'Captain Commando (911014 other country)', '', 736)
self.channels['cawing']=GGPOChannel("cawing", "cawing", 'Carrier Air Wing (U.S. navy 901012 etc)')
self.channels['crsword']=GGPOChannel("crsword", "crsword", 'Crossed Swords')
self.channels['csclub']=GGPOChannel("csclub", "csclub", 'Capcom Sports Club (970722 Euro)', '', 376)
self.channels['cthd2003']=GGPOChannel("cthd2003", "cthd2003", 'Crouching Tiger Hidden Dragon 2003 (bootleg)')
self.channels['cyberlip']=GGPOChannel("cyberlip", "cyberlip", 'Cyber-Lip')
self.channels['cybots']=GGPOChannel("cybots", "cybots", 'Cyberbots - fullmetal madness (950424 Euro)', '', 376)
self.channels['ddonpach']=GGPOChannel("ddonpach", "ddonpach", 'DoDonPachi (1997 2/5 master ver, international)', '', 376)
self.channels['ddsom']=GGPOChannel("ddsom", "ddsom", 'Dungeons & Dragons - shadow over mystara (960619 Euro)', '', 376)
self.channels['ddtod']=GGPOChannel("ddtod", "ddtod", 'Dungeons & Dragons - tower of doom (940412 Euro)')
self.channels['dimahoo']=GGPOChannel("dimahoo", "dimahoo", 'Dimahoo (000121 Euro)')
self.channels['dino']=GGPOChannel("dino", "dino", 'Cadillacs & Dinosaurs (930201 etc)', '', 496)
self.channels['donpachi']=GGPOChannel("donpachi", "donpachi", 'DonPachi (ver. 1.01 1995/05/11, U.S.A)')
self.channels['doubledr']=GGPOChannel("doubledr", "doubledr", 'Double Dragon', '', 1096)
self.channels['ecofghtr']=GGPOChannel("ecofghtr", "ecofghtr", 'Eco Fighters (931203 etc)')
self.channels['eightman']=GGPOChannel("eightman", "eightman", 'Eight Man')
self.channels['esprade']=GGPOChannel("esprade", "esprade", 'ESP Ra.De. (1998 4/22 international ver.)', '', 376)
self.channels['fatfursp']=GGPOChannel("fatfursp", "fatfursp", 'Fatal Fury Special (set 1)', '', 1096)
self.channels['fatfury1']=GGPOChannel("fatfury1", "fatfury1", 'Fatal Fury - king of fighters')
self.channels['fatfury2']=GGPOChannel("fatfury2", "fatfury2", 'Fatal Fury 2')
self.channels['fatfury3']=GGPOChannel("fatfury3", "fatfury3", 'Fatal Fury 3 - road to the final victory')
self.channels['fbfrenzy']=GGPOChannel("fbfrenzy", "fbfrenzy", 'Football Frenzy')
self.channels['feversos']=GGPOChannel("feversos", "feversos", 'Fever SOS (International ver. Fri Sep 25 1998)')
self.channels['ffight']=GGPOChannel("ffight", "ffight", 'Final Fight (World)', '', 736)
self.channels['fightfev']=GGPOChannel("fightfev", "fightfev", 'Fight Fever (set 1)')
self.channels['flipshot']=GGPOChannel("flipshot", "flipshot", 'Battle Flip Shot')
self.channels['forgottn']=GGPOChannel("forgottn", "forgottn", 'Forgotten Worlds (US)')
self.channels['gaia']=GGPOChannel("gaia", "gaia", 'Gaia Crusaders')
self.channels['galaxyfg']=GGPOChannel("galaxyfg", "galaxyfg", 'Galaxy Fight - universal warriors')
self.channels['ganryu']=GGPOChannel("ganryu", "ganryu", 'Ganryu')
self.channels['garou']=GGPOChannel("garou", "garou", 'Garou - mark of the wolves (set 1)', '', 1096)
#self.channels['garouo']=GGPOChannel("garouo", "garouo", 'Garou - mark of the wolves (set 2)')
self.channels['ghostlop']=GGPOChannel("ghostlop", "ghostlop", 'Ghostlop [Prototype]')
self.channels['ghouls']=GGPOChannel("ghouls", "ghouls", "Ghouls'n Ghosts (World)", '', 736)
self.channels['gigawing']=GGPOChannel("gigawing", "gigawing", 'Giga Wing (990222 USA)')
self.channels['goalx3']=GGPOChannel("goalx3", "goalx3", 'Goal! Goal! Goal!')
self.channels['gowcaizr']=GGPOChannel("gowcaizr", "gowcaizr", 'Voltage Fighter - Gowcaizer')
self.channels['grdians']=GGPOChannel("grdians", "grdians", 'Guardians')
self.channels['guwange']=GGPOChannel("guwange", "guwange", 'Guwange (Japan, 1999 6/24 master ver.)')
self.channels['hsf2']=GGPOChannel("hsf2", "hsf2", 'Hyper Street Fighter 2: The Anniversary Edition (040202 Asia)', '', 376)
#self.channels['jojobane']=GGPOChannel("jojobane", "jojobane", "JoJo's Bizarre Adventure", '', 496)
self.channels['jojoban']=GGPOChannel("jojoban", "jojoban", "JoJo's Bizarre Adventure", '', 496)
self.channels['jojon']=GGPOChannel("jojon", "jojon", "JoJo's Venture")
self.channels['kabukikl']=GGPOChannel("kabukikl", "kabukikl", 'Kabuki Klash - far east of eden', '', 1096)
self.channels['karatblz']=GGPOChannel("karatblz", "karatblz", "Karate Blazers")
self.channels['karnovr']=GGPOChannel("karnovr", "karnovr", "Karnov's Revenge", '', 1096)
self.channels['kf2k5uni']=GGPOChannel("kf2k5uni", "kf2k5uni", 'King of Fighters 10th Anniversary 2005 Unique (bootleg of kof2002)', '', 1096, 7002)
self.channels['kizuna']=GGPOChannel("kizuna", "kizuna", 'Kizuna Encounter - super tag battle', '', 1096)
self.channels['knights']=GGPOChannel("knights", "knights", 'Knights of the Round (911127 etc)')
#self.channels['kod']=GGPOChannel("kod", "kod", 'King of Dragons (910711 etc)')
self.channels['kodu']=GGPOChannel("kodu", "kodu", 'King of Dragons (US 910910)')
self.channels['kof2000']=GGPOChannel("kof2000", "kof2000", 'King of Fighters 2000', '', 1096, 7002)
self.channels['kof2001']=GGPOChannel("kof2001", "kof2001", 'King of Fighters 2001 (set 1)', '', 1096, 7002)
self.channels['kof2002']=GGPOChannel("kof2002", "kof2002", 'King of Fighters 2002 - challenge to ultimate battle', '', 1096, 7002)
self.channels['kof2003']=GGPOChannel("kof2003", "kof2003", 'King of Fighters 2003 (MVS)', '', 1096, 7002)
self.channels['kof2k4se']=GGPOChannel("kof2k4se", "kof2k4se", 'King of Fighters Special Edition 2004 (bootleg of kof2002)', '', 1096, 7002)
self.channels['kof94']=GGPOChannel("kof94", "kof94", "King of Fighters '94", '', 1096, 7002)
self.channels['kof95']=GGPOChannel("kof95", "kof95", "King of Fighters '95 (set 1)", '', 1096, 7002)
self.channels['kof96']=GGPOChannel("kof96", "kof96", "King of Fighters '96 (set 1)", '', 1096, 7002)
self.channels['kof97']=GGPOChannel("kof97", "kof97", "King of Fighters '97 (set 1)", '', 1096, 7002)
#self.channels['kof98-2']=GGPOChannel("kof98-2", "kof98", "King of Fighters '98 (Room 2)")
#self.channels['kof98-3']=GGPOChannel("kof98-3", "kof98", "King of Fighters '98 (Room 3)")
#self.channels['kof98']=GGPOChannel("kof98", "kof98", "King of Fighters '98 (Room 1)")
self.channels['kof98']=GGPOChannel("kof98", "kof98", "King of Fighters '98", '', 1096, 7002)
self.channels['kof99']=GGPOChannel("kof99", "kof99", "King of Fighters '99 - millennium battle (set 1)", '', 1096, 7002)
self.channels['kotm2']=GGPOChannel("kotm2", "kotm2", 'King of the Monsters 2 - the next thing')
self.channels['kotm']=GGPOChannel("kotm", "kotm", 'King of the Monsters (set 1)')
#self.channels['kov']=GGPOChannel("kov", "kov", 'Knights of Valour')
self.channels['lastblad']=GGPOChannel("lastblad", "lastblad", 'Last Blade (set 1)')
self.channels['lastbld2']=GGPOChannel("lastbld2", "lastbld2", 'Last Blade 2', '', 1096)
self.channels['lbowling']=GGPOChannel("lbowling", "lbowling", 'League Bowling', '', 1096)
self.channels['lobby']=GGPOChannel("lobby", '', "The Lobby", '', 1096)
self.channels['lresort']=GGPOChannel("lresort", "lresort", 'Last Resort')
self.channels['magdrop2']=GGPOChannel("magdrop2", "magdrop2", 'Magical Drop II')
self.channels['magdrop3']=GGPOChannel("magdrop3", "magdrop3", 'Magical Drop III', '', 1096)
self.channels['matrim']=GGPOChannel("matrim", "matrim", 'Power Instinct Matrimelee', '', 1096)
self.channels['megaman2']=GGPOChannel("megaman2", "megaman2", 'Mega Man 2 - the power fighters (960708 USA)', '', 376)
self.channels['mercs']=GGPOChannel("mercs", "mercs", 'Mercs (900302 etc)')
self.channels['miexchng']=GGPOChannel("miexchng", "miexchng", 'Money Puzzle Exchanger', '', 1096)
self.channels['mmancp2u']=GGPOChannel("mmancp2u", "mmancp2u", 'Mega Man - The Power Battle (951006 USA, SAMPLE Version)', '', 376)
self.channels['mmatrix']=GGPOChannel("mmatrix", "mmatrix", 'Mars Matrix (000412 USA)')
self.channels['mpang']=GGPOChannel("mpang", "mpang", 'Mighty! Pang (001010 USA)')
self.channels['ms5plus']=GGPOChannel("ms5plus", "ms5plus", 'Metal Slug 5 Plus (bootleg)', '', 1096)
self.channels['msh']=GGPOChannel("msh", "msh", 'Marvel Super Heroes (951024 Euro)')
self.channels['mshvsf']=GGPOChannel("mshvsf", "mshvsf", 'Marvel Super Heroes vs Street Fighter (970625 Euro)', '', 376)
self.channels['mslug2']=GGPOChannel("mslug2", "mslug2", 'Metal Slug 2 - super vehicle-001/II')
self.channels['mslug3']=GGPOChannel("mslug3", "mslug3", 'Metal Slug 3', '', 1096)
self.channels['mslug4']=GGPOChannel("mslug4", "mslug4", 'Metal Slug 4', '', 1096)
self.channels['mslug3b6']=GGPOChannel("mslug3b6", "mslug3b6", 'Metal Slug 6 [Bootleg, bootleg of "Metal Slug 3"]', '', 1096)
self.channels['mslug']=GGPOChannel("mslug", "mslug", 'Metal Slug - super vehicle-001', '', 1096)
self.channels['mslugx']=GGPOChannel("mslugx", "mslugx", 'Metal Slug X - super vehicle-001', '', 1096)
self.channels['msword']=GGPOChannel("msword", "msword", 'Magic Sword - heroic fantasy (25.07.1990 other country)')
self.channels['mutnat']=GGPOChannel("mutnat", "mutnat", 'Mutation Nation')
self.channels['mvsc']=GGPOChannel("mvsc", "mvsc", 'Marvel vs Capcom - clash of super heroes (980112 Euro)', '', 376)
self.channels['ncombat']=GGPOChannel("ncombat", "ncombat", 'Ninja Combat (set 1)')
self.channels['ncommand']=GGPOChannel("ncommand", "ncommand", 'Ninja Commando')
self.channels['neobombe']=GGPOChannel("neobombe", "neobombe", 'Neo Bomberman', '', 1096)
self.channels['neocup98']=GGPOChannel("neocup98", "neocup98", "Neo-Geo Cup '98 - the road to the victory")
#self.channels['neodrift']=GGPOChannel("neodrift", "neodrift", 'Neo Drift Out - new technology')
self.channels['ninjamas']=GGPOChannel("ninjamas", "ninjamas", "Ninja Master's haoh ninpo cho", '', 1096)
self.channels['nitd']=GGPOChannel("nitd", "nitd", 'Nightmare in the Dark')
#self.channels['nwarr']=GGPOChannel("nwarr", "nwarr", "Night Warriors - darkstalkers' revenge (950316 Euro)")
#self.channels['orlegend']=GGPOChannel("orlegend", "orlegend", 'Oriental Legend')
self.channels['overtop']=GGPOChannel("overtop", "overtop", 'OverTop')
self.channels['panicbom']=GGPOChannel("panicbom", "panicbom", 'Panic Bomber')
self.channels['pbobbl2n']=GGPOChannel("pbobbl2n", "pbobbl2n", 'Puzzle Bobble 2', '', 1096)
self.channels['pbobblen']=GGPOChannel("pbobblen", "pbobblen", 'Puzzle Bobble (set 1)')
self.channels['pdrift']=GGPOChannel("pdrift", "pdrift", 'Power Drift (World, Rev A)')
self.channels['pgoal']=GGPOChannel("pgoal", "pgoal", 'Pleasure Goal - 5 on 5 mini soccer')
self.channels['preisle2']=GGPOChannel("preisle2", "preisle2", 'Prehistoric Isle 2')
self.channels['progear']=GGPOChannel("progear", "progear", 'Progear (010117 USA)', '', 376)
self.channels['pspikes2']=GGPOChannel("pspikes2", "pspikes2", 'Power Spikes II', '', 1096)
self.channels['pulstar']=GGPOChannel("pulstar", "pulstar", 'Pulstar')
self.channels['punisher']=GGPOChannel("punisher", "punisher", 'The Punisher (930422 etc)', '', 496)
self.channels['pzloop2']=GGPOChannel("pzloop2", "pzloop2", 'Puzz Loop 2 (010302 Euro)')
self.channels['ragnagrd']=GGPOChannel("ragnagrd", "ragnagrd", 'Ragnagard')
self.channels['rbff1']=GGPOChannel("rbff1", "rbff1", 'Real Bout Fatal Fury', '', 1096)
self.channels['rbff2']=GGPOChannel("rbff2", "rbff2", 'Real Bout Fatal Fury 2 - the newcomers (set 1)', '', 1096)
self.channels['rbffspec']=GGPOChannel("rbffspec", "rbffspec", 'Real Bout Fatal Fury Special', '', 1096)
self.channels['redeartn']=GGPOChannel("redeartn", "redeartn", 'Red Earth')
#self.channels['ridhero']=GGPOChannel("ridhero", "ridhero", 'Riding Hero (set 1)')
self.channels['ringdest']=GGPOChannel("ringdest", "ringdest", 'Ring of Destruction - slammasters II (940902 Euro)', '', 496)
self.channels['rotd']=GGPOChannel("rotd", "rotd", 'Rage of the Dragons', '', 1096)
self.channels['s1945p']=GGPOChannel("s1945p", "s1945p", 'Strikers 1945 plus')
#self.channels['sailormn']=GGPOChannel("sailormn", "sailormn", 'Pretty Soldier Sailor Moon (version 95/03/22B)')
self.channels['samsh5sp']=GGPOChannel("samsh5sp", "samsh5sp", 'Samurai Shodown V Special (set 1, uncensored)', '', 1096)
self.channels['samsho2']=GGPOChannel("samsho2", "samsho2", 'Samurai Shodown II', '', 1096)
self.channels['samsho3']=GGPOChannel("samsho3", "samsho3", 'Samurai Shodown III (set 1)')
self.channels['samsho4']=GGPOChannel("samsho4", "samsho4", "Samurai Shodown IV - Amakusa's revenge", '', 1096)
self.channels['samsho5']=GGPOChannel("samsho5", "samsho5", 'Samurai Shodown V (set 1)')
self.channels['samsho']=GGPOChannel("samsho", "samsho", 'Samurai Shodown')
self.channels['savagere']=GGPOChannel("savagere", "savagere", 'Savage Reign')
self.channels['sdodgeb']=GGPOChannel("sdodgeb", "sdodgeb", 'Super Dodge Ball', '', 1096)
self.channels['sengoku2']=GGPOChannel("sengoku2", "sengoku2", 'Sengoku 2', '', 1096)
self.channels['sengoku3']=GGPOChannel("sengoku3", "sengoku3", 'Sengoku 3')
self.channels['sengoku']=GGPOChannel("sengoku", "sengoku", 'Sengoku (set 1)')
self.channels['sf2']=GGPOChannel("sf2", "sf2", "Street Fighter II - the world warrior (910522 etc)", '', 736)
self.channels['sf2ce']=GGPOChannel("sf2ce", "sf2ce", "Street Fighter II' - champion edition (street fighter 2' 920313 etc)", '', 736)
self.channels['sf2hf']=GGPOChannel("sf2hf", "sf2hf", "Street Fighter II' - hyper fighting (street fighter 2' T 921209 ETC)", '', 736)
self.channels['sf2koryu']=GGPOChannel("sf2koryu", "sf2koryu", "Street Fighter II' - champion edition (Hack - kouryu) [Bootleg]", '', 736)
self.channels['sfa2']=GGPOChannel("sfa2", "sfa2", 'Street Fighter Alpha 2 (960306 USA)', '', 376)
self.channels['sfa3']=GGPOChannel("sfa3", "sfa3:sfa3u", 'Street Fighter Alpha 3 (980904 Euro)', '', 376)
self.channels['sfa']=GGPOChannel("sfa", "sfa", "Street Fighter Alpha - warriors' dreams (950727 Euro)", '', 376)
self.channels['sfiii2n']=GGPOChannel("sfiii2n", "sfiii2n", 'Street Fighter III 2nd Impact: Giant Attack (Asia 970930, NO CD)', '', 496)
self.channels['sfiii3n']=GGPOChannel("sfiii3n", "sfiii3n", 'Street Fighter III 3rd Strike: Fight for the Future (Japan 990512, NO CD)', '', 496)
self.channels['sfiii3an']=GGPOChannel("sfiii3an", "sfiii3an", 'Street Fighter III 4th Strike (Hack) [bootleg]')
#self.channels['sfiii3']=GGPOChannel("sfiii3", "sfiii3n", "Street Fighter Tres")
#self.channels['sfiii']=GGPOChannel("sfiii", "sfiii", 'Street Fighter III: New Generation (Japan 970204)')
self.channels['sfiiin']=GGPOChannel("sfiiin", "sfiiin", 'Street Fighter III: New Generation (Asia 970204, NO CD)', '', 496)
self.channels['sfz2aa']=GGPOChannel("sfz2aa", "sfz2aa", 'Street Fighter Zero 2 Alpha (960826 Asia)')
#self.channels['sfz2a']=GGPOChannel("sfz2a", "sfz2aa", "Street Fighter Alpha 2 Gold")
self.channels['sgemf']=GGPOChannel("sgemf", "sgemf", 'Super Gem Fighter Mini Mix (970904 USA)', '', 376)
self.channels['shocktr2']=GGPOChannel("shocktr2", "shocktr2", 'Shock Troopers - 2nd squad')
self.channels['shocktro']=GGPOChannel("shocktro", "shocktro", 'Shock Troopers (set 1)')
self.channels['slammast']=GGPOChannel("slammast", "slammast", 'Saturday Night Slam Masters (Slam Masters 930713 etc)', '', 496)
#self.channels['snowbros']=GGPOChannel("snowbros", "snowbros", 'Snow Bros. - Nick & Tom (set 1)', '', 736)
self.channels['sonicwi2']=GGPOChannel("sonicwi2", "sonicwi2", 'Aero Fighters 2')
self.channels['sonicwi3']=GGPOChannel("sonicwi3", "sonicwi3", 'Aero Fighters 3')
self.channels['spf2t']=GGPOChannel("spf2t", "spf2t", 'Super Puzzle Fighter II Turbo (Super Puzzle Fighter 2 Turbo 960620 USA)', '', 376)
self.channels['spinmast']=GGPOChannel("spinmast", "spinmast", 'Spin Master')
self.channels['ssf2']=GGPOChannel("ssf2", "ssf2", 'Super Street Fighter II - the new challengers (super street fighter 2 930911 etc)', '', 376)
#self.channels['ssf2t']=GGPOChannel("ssf2t", "ssf2t", 'Super Street Fighter II Turbo (super street fighter 2 X 940223 etc)', '', 376)
self.channels['ssf2xj']=GGPOChannel("ssf2xj", "ssf2xj", 'Super Street Fighter II X - grand master challenge (super street fighter 2 X 940223 Japan)', '', 376)
self.channels['ssideki2']=GGPOChannel("ssideki2", "ssideki2", 'Super Sidekicks 2 - the world championship', '', 1096)
self.channels['ssideki3']=GGPOChannel("ssideki3", "ssideki3", 'Super Sidekicks 3 - the next glory', '', 1096)
self.channels['ssideki4']=GGPOChannel("ssideki4", "ssideki4", 'The Ultimate 11 - SNK football championship')
self.channels['ssideki']=GGPOChannel("ssideki", "ssideki", 'Super Sidekicks')
self.channels['strhoop']=GGPOChannel("strhoop", "strhoop", 'Street Hoop', '', 1096)
self.channels['strider']=GGPOChannel("strider", "strider", 'Strider (US set 1)')
#self.channels['svc']=GGPOChannel("svc", "svc", 'SvC Chaos - SNK vs Capcom (MVS)', '', 1096)
self.channels['svcplus']=GGPOChannel("svcplus", "svcplus", 'SvC Chaos - SNK vs Capcom Plus (bootleg, set 1)', '', 1096)
self.channels['theroes']=GGPOChannel("theroes", "theroes", 'Thunder Heroes')
self.channels['tophuntr']=GGPOChannel("tophuntr", "tophuntr", 'Top Hunter - Roddy & Cathy (set 1)')
self.channels['turfmast']=GGPOChannel("turfmast", "turfmast", 'Neo Turf Masters', '', 1096)
self.channels['twinspri']=GGPOChannel("twinspri", "twinspri", 'Twinklestar Sprites', '', 1096)
self.channels['tws96']=GGPOChannel("tws96", "tws96", "Tecmo World Soccer '96", '', 1096)
self.channels['unsupported']=GGPOChannel("unsupported", "unsupported", "Unsupported Games", '', 1096)
self.channels['uopoko']=GGPOChannel("uopoko", "uopoko", 'Puzzle Uo Poko (International)')
self.channels['vampjr1']=GGPOChannel("vampjr1", "vampjr1", "Vampire - the night warriors (940630 Japan)")
self.channels['varth']=GGPOChannel("varth", "varth", 'Varth - operation thunderstorm (920714 etc)')
self.channels['vhunt2']=GGPOChannel("vhunt2", "vhunt2", 'Vampire Hunter 2 - darkstalkers revenge (970929 Japan)')
self.channels['vhuntjr2']=GGPOChannel("vhuntjr2", "vhuntjr2", "Vampire Hunter - darkstalkers' revenge (950302 Japan)")
self.channels['vsav2']=GGPOChannel("vsav2", "vsav2", 'Vampire Savior 2 - the lord of vampire (970913 Japan)', '', 376)
self.channels['vsav']=GGPOChannel("vsav", "vsav", 'Vampire Savior - the lord of vampire (970519 Euro)', '', 376)
self.channels['wakuwak7']=GGPOChannel("wakuwak7", "wakuwak7", 'Waku Waku 7', '', 1096)
self.channels['wh1']=GGPOChannel("wh1", "wh1", 'World Heroes (set 1)')
self.channels['wh2']=GGPOChannel("wh2", "wh2", 'World Heroes 2')
self.channels['wh2j']=GGPOChannel("wh2j", "wh2j", 'World Heroes 2 Jet (set 1)', '', 1096)
self.channels['whp']=GGPOChannel("whp", "whp", 'World Heroes Perfect', '', 1096)
self.channels['willow']=GGPOChannel("willow", "willow", 'Willow (US)', '', 736)
self.channels['wjammers']=GGPOChannel("wjammers", "wjammers", 'Windjammers - flying disc game', '', 1096)
self.channels['wof']=GGPOChannel("wof", "wof", 'Warriors of Fate (921002 etc)')
self.channels['wonder3']=GGPOChannel("wonder3", "wonder3", 'Wonder 3 (910520 Japan)')
self.channels['xmcota']=GGPOChannel("xmcota", "xmcota", 'X-Men - children of the atom (950105 Euro)', '', 376)
self.channels['xmvsf']=GGPOChannel("xmvsf", "xmvsf", 'X-Men vs Street Fighter (961004 Euro)', '', 376)
self.channels['zupapa']=GGPOChannel("zupapa", "zupapa", 'Zupapa!')
self.clients = {} # Connected authenticated clients (GGPOClient instances) by nickname
self.connections = {} # Connected unauthenticated clients (GGPOClient instances) by host
self.quarks = {} # quark games (GGPOQuark instances) by quark
SocketServer.TCPServer.__init__(self, server_address, RequestHandlerClass)
class RendezvousUDPServer(SocketServer.ThreadingMixIn, SocketServer.UDPServer):
def __init__(self, server_address, MyUDPHandler):
self.quarkqueue = {}
SocketServer.UDPServer.__init__(self, server_address, MyUDPHandler)
class MyUDPHandler(SocketServer.BaseRequestHandler):
"""
This class works similar to the TCP handler class, except that
self.request consists of a pair of data and client socket, and since
there is no connection the client address must be given explicitly
when sending data back via sendto().
"""
def __init__(self, request, client_address, server):
self.quark=''
SocketServer.BaseRequestHandler.__init__(self, request, client_address, server)
def addr2bytes(self, addr ):
"""Convert an address pair to a hash."""
host, port = addr
try:
host = socket.gethostbyname( host )
except (socket.gaierror, socket.error):
raise ValueError, "invalid host"
try:
port = int(port)
except ValueError:
raise ValueError, "invalid port"
bytes = socket.inet_aton( host )
bytes += struct.pack( "H", port )
return bytes
def handle(self):
data = self.request[0].strip()
sockfd = self.request[1]
if "useports" in data:
quark=data.split("/")[1]
quarkobject = ggposerver.quarks.setdefault(quark, GGPOQuark(quark))
quarkobject.useports=True
#ggposerver.quarks[quark].useports=True
logging.info("[%s:%d] HOLEPUNCH FAILED for quark %s, will use ports" % (self.client_address[0], self.client_address[1], quark))
if data != "ok" and "useports" not in data:
try:
self.quark, port = data.split('/')
except ValueError:
self.quark = data
port=7001
clientip=self.client_address[0]
quarkobject = ggposerver.quarks.setdefault(self.quark, GGPOQuark(self.quark))
quarkobject.proxyport[clientip]=port
sockfd.sendto( "ok "+self.quark, self.client_address )
logging.info("[%s:%d] HOLEPUNCH request received for quark: %s" % (self.client_address[0], self.client_address[1], self.quark))
try:
a, b = self.server.quarkqueue[self.quark], self.client_address
sockfd.sendto( self.addr2bytes(a), b )
sockfd.sendto( self.addr2bytes(b), a )
logging.info("HOLEPUNCH linked: %s" % self.quark)
del self.server.quarkqueue[self.quark]
except KeyError:
if self.quark!='':
self.server.quarkqueue[self.quark] = self.client_address
class Daemon:
"""
Daemonize the current process (detach it from the console).
"""
def __init__(self):
# Fork a child and end the parent (detach from parent)
try:
pid = os.fork()
if pid > 0:
sys.exit(0) # End parent
except OSError, e:
sys.stderr.write("fork #1 failed: %d (%s)\n" % (e.errno, e.strerror))
sys.exit(-2)
# Change some defaults so the daemon doesn't tie up dirs, etc.
os.setsid()
os.umask(0)
# Fork a child and end parent (so init now owns process)
try:
pid = os.fork()
if pid > 0:
try:
f = file('ggposrv.pid', 'w')
f.write(str(pid))
f.close()
except IOError, e:
logging.error(e)
sys.stderr.write(repr(e))
sys.exit(0) # End parent
except OSError, e:
sys.stderr.write("fork #2 failed: %d (%s)\n" % (e.errno, e.strerror))
sys.exit(-2)
# Close STDIN, STDOUT so we don't tie up the controlling terminal
for fd in (0, 1):
try:
os.close(fd)
except OSError:
pass
if __name__ == "__main__":
global ggposerver
print "-!- FightCade server version {0:.2f}".format(VERSION/100.0)
print "-!- (c) 2014-2015 Pau Oliva Fora (@pof) "
#
# Parameter parsing
#
parser = optparse.OptionParser()
parser.set_usage(sys.argv[0] + " [option]")
parser.add_option("--start", dest="start", action="store_true", default=True, help="Start ggposrv (default)")
parser.add_option("--stop", dest="stop", action="store_true", default=False, help="Stop ggposrv")
parser.add_option("--restart", dest="restart", action="store_true", default=False, help="Restart ggposrv")
parser.add_option("-a", "--address", dest="listen_address", action="store", default='0.0.0.0', help="IP to listen on")
parser.add_option("-p", "--port", dest="listen_port", action="store", default='7000', help="Port to listen on (default=7000)")
parser.add_option("-o", "--logfile", dest="logfile", action="store", default="ggposrv.log", help="log file location")
parser.add_option("-V", "--verbose", dest="verbose", action="store_true", default=False, help="Be verbose (show lots of output)")
parser.add_option("-l", "--log-stdout", dest="log_stdout", action="store_true", default=False, help="Also log to stdout")
parser.add_option("-f", "--foreground", dest="foreground", action="store_true", default=False, help="Do not go into daemon mode.")
parser.add_option("-H", "--http", dest="httpserver", action="store_true", default=False, help="Start debug http server")
parser.add_option("-u", "--udpholepunch", dest="udpholepunch", action="store_true", default=False, help="Use UDP hole punching.")
parser.add_option("-r", "--replay", dest="replay", action="store_true", default=False, help="Use the server only for replaying quarks.")
parser.add_option("-n", "--nullauth", dest="nullauth", action="store_true", default=False, help="Accept all login/password combinations.")
parser.add_option("-t", "--attendance", dest="attendance", action="store_true", default=False, help="Send attendance stats")
(options, args) = parser.parse_args()
holepunch=options.udpholepunch
replayonly=options.replay
nullauth=options.nullauth
listen_port=int(options.listen_port)
attendance=options.attendance
#logfile = os.path.join(os.path.realpath(os.path.dirname(sys.argv[0])),'ggposrv.log')
logfile = options.logfile
#
# Logging
#
if options.verbose:
loglevel = logging.DEBUG
else:
loglevel = logging.INFO
log = logging.basicConfig(
level=loglevel,
format='%(asctime)s:%(levelname)s:%(message)s',
filename=logfile,
filemode='a')
#
# Handle start/stop/restart commands.
#
if options.stop or options.restart:
print "-!- Stopping ggposrv"
logging.info("Stopping ggposrv")
pid = None
try:
f = file('ggposrv.pid', 'r')
pid = int(f.readline())
f.close()
os.unlink('ggposrv.pid')
except ValueError, e:
sys.stderr.write('Error in pid file `ggposrv.pid`. Aborting\n')
sys.exit(-1)
except IOError, e:
pass
if pid:
os.kill(pid, 15)
else:
sys.stderr.write('ggposrv not running or no PID file found\n')
if not options.restart:
sys.exit(0)
print "-!- Starting ggposrv"
logging.info("Starting ggposrv")
logging.debug("logfile = %s" % (logfile))
if options.log_stdout:
console = logging.StreamHandler()
formatter = logging.Formatter('[%(levelname)s] %(message)s')
console.setFormatter(formatter)
console.setLevel(logging.DEBUG)
logging.getLogger('').addHandler(console)
if options.verbose:
logging.info("We're being verbose")
# set the thread stack size, by default it uses the operating system value found by 'ulimit -s' = 8192 kbytes (8Mb).
# the minimum value here is 32768 (32kB), and ideally should be multiple of 4096. For example 524288 = 512kb
# With the default value of 8192 kB (8Mb) I can create around 300 threads. Setting it to 2Mb and see what happens..
threading.stack_size(4096*512)
#
# Go into daemon mode
#
if not options.foreground:
Daemon()
#
# Start server
#
try:
if holepunch:
punchserver = RendezvousUDPServer((options.listen_address, int(options.listen_port)), MyUDPHandler)
logging.info('Starting holepunch on %s:%s/udp' % (options.listen_address, options.listen_port))
t = threading.Thread(target=punchserver.serve_forever)
t.daemon = True
t.start()
ggposerver = GGPOServer((options.listen_address, int(options.listen_port)), GGPOClient)
logging.info('Starting ggposrv on %s:%s/tcp' % (options.listen_address, options.listen_port))
if options.httpserver:
webserver = HTTPServer((options.listen_address, listen_port+1000), GGPOHttpHandler)
logging.info('Starting http server on %s:%s/tcp' % (options.listen_address, str(listen_port+1000)))
t2=threading.Thread(target=webserver.serve_forever)
t2.daemon=True
t2.start()
ggposerver.serve_forever()
except socket.error, e:
logging.error(repr(e))
sys.exit(-2)
except:
traceback.print_exc(file=open("ggposrv-errors.log","a"))