import argparse import traceback from flask import Flask, request, redirect, Response, make_response from bemani.protocol import EAmuseProtocol from bemani.backend import Dispatch, UnrecognizedPCBIDException from bemani.data import Config, Data from bemani.utils.config import load_config as base_load_config, register_games as base_register_games app = Flask(__name__) config = Config() @app.route('/', defaults={'path': ''}, methods=['GET']) @app.route('/', methods=['GET']) def receive_healthcheck(path: str) -> Response: global config redirect_uri = config.server.redirect if redirect_uri is None: # Return a standard status OKAY message. return Response("Services OK.") else: # Redirect to the configured location. return redirect(redirect_uri, code=308) # type: ignore @app.route('/', defaults={'path': ''}, methods=['POST']) @app.route('/', methods=['POST']) def receive_request(path: str) -> Response: proto = EAmuseProtocol() remote_address = request.headers.get('x-remote-address', None) compression = request.headers.get('x-compress', None) encryption = request.headers.get('x-eamuse-info', None) req = proto.decode( compression, encryption, request.data, ) if req is None: # Nothing to do here return Response("Unrecognized packet!", 500) if req.name in {'soapenv:Envelope', 'soap:Envelope', 'methodCall'}: # We get lots of spam from random bots trying to SOAP # us up, so ignore this shit. return Response("Unrecognized packet!", 500) # Create and format config global config requestconfig = config.clone() requestconfig['client'] = { 'address': remote_address or request.remote_addr, } dataprovider = Data(requestconfig) try: dispatch = Dispatch(requestconfig, dataprovider, True) resp = dispatch.handle(req) if resp is None: # Nothing to do here dataprovider.local.network.put_event( 'unhandled_packet', { 'request': str(req), }, ) return Response("No response generated", 404) data = proto.encode( compression, encryption, resp, ) response = make_response(data) # Some old clients are case-sensitive, even though http spec says these # shouldn't matter, so capitalize correctly. if compression: response.headers['X-Compress'] = compression else: response.headers['X-Compress'] = 'none' if encryption: response.headers['X-Eamuse-Info'] = encryption return response except UnrecognizedPCBIDException as e: dataprovider.local.network.put_event( 'unauthorized_pcbid', { 'pcbid': e.pcbid, 'model': e.model, 'ip': e.ip, }, ) return Response("Unauthorized client", 403) except Exception: stack = traceback.format_exc() print(stack) dataprovider.local.network.put_event( 'exception', { 'service': 'xrpc', 'request': str(req), 'traceback': stack, }, ) return Response("Crash when handling packet!", 500) finally: dataprovider.close() def register_games() -> None: global config base_register_games(config) def load_config(filename: str) -> None: global config base_load_config(filename, config) if __name__ == '__main__': parser = argparse.ArgumentParser(description="A backend services provider for eAmusement games") parser.add_argument("-p", "--port", help="Port to listen on. Defaults to 80", type=int, default=80) parser.add_argument("-c", "--config", help="Core configuration. Defaults to server.yaml", type=str, default="server.yaml") parser.add_argument("-r", "--profile", help="Turn on profiling for services, writing CProfile data to the currenct directory", action="store_true") parser.add_argument("-o", "--read-only", action="store_true", help="Force the database into read-only mode.") args = parser.parse_args() # Set up global configuration, overriding config port for convenience in debugging. load_config(args.config) config['server']['port'] = args.port if args.read_only: config['database']['read_only'] = True # Register game handlers register_games() if args.profile: from werkzeug.contrib.profiler import ProfilerMiddleware app.wsgi_app = ProfilerMiddleware(app.wsgi_app, profile_dir='.') # type: ignore # Run the app app.run(host='0.0.0.0', port=args.port, debug=True)