diff --git a/.gitignore b/.gitignore index 25822ce..330ab9b 100644 --- a/.gitignore +++ b/.gitignore @@ -102,8 +102,8 @@ ENV/ # mypy .mypy_cache/ -# logs -logs/ +# project related things +/logs/ +/decompiled/ +/database/ -# Decompiled v8 code -decompiled/ diff --git a/setup.py b/setup.py index e10f782..ea5defa 100644 --- a/setup.py +++ b/setup.py @@ -9,6 +9,7 @@ DOCS_DEPS = ["sphinx", "sphinx-rtd-theme", "sphinx-autoapi", "recommonmark"] CHECK_DEPS = ["isort", "flake8", "flake8-quotes", "pep8-naming", "mypy", "black"] REQUIREMENTS = [ "flask", + "flask_sqlalchemy", "watchdog", "pyopenssl", "lxml", diff --git a/v8_server/__init__.py b/v8_server/__init__.py index 6c3a479..1e70a2f 100644 --- a/v8_server/__init__.py +++ b/v8_server/__init__.py @@ -5,20 +5,14 @@ from pathlib import Path from typing import Optional, Union from flask import Flask, has_request_context, request +from flask_sqlalchemy import SQLAlchemy from v8_server.config import Development, Production -from v8_server.model.connection import Base, Database -from v8_server.model.user import User from v8_server.utils.flask import generate_secret_key from .version import __version__ -# Make sure the database has been created -with Database() as db: - Base.metadata.create_all(db.engine) - - class RequestFormatter(logging.Formatter): def format(self, record): encrypted = "None" @@ -117,9 +111,13 @@ static_dir = str(package_dir / "static") app = Flask(__name__, template_folder=template_dir, static_folder=static_dir) app.secret_key = generate_secret_key(config.SECRET_KEY_FILENAME) app.config.from_object(config) +db = SQLAlchemy(app) + +# Make sure the database has been created +db.create_all() # We need to import the views here specifically once the flask app has been initialized import v8_server.view # noqa: F401, E402 -__all__ = ["__version__", "app", "LOG_PATH"] +__all__ = ["__version__", "app", "db", "LOG_PATH"] diff --git a/v8_server/config.py b/v8_server/config.py index 75bf245..38deac6 100644 --- a/v8_server/config.py +++ b/v8_server/config.py @@ -1,13 +1,23 @@ +from pathlib import Path + + +DEV_DB_PATH = Path(__file__).parent.parent / "database" +PROD_DB_PATH = Path("/var/db") + + class Config(object): DEBUG: bool = False TESTING: bool = False DB_SERVER: str = "localhost" SECRET_KEY_FILENAME: str = "v8_server.key" + SQLALCHEMY_DATABASE_URI: str = f"sqlite+pysqlite:///{ PROD_DB_PATH / 'v8.db'}" + SQLALCHEMY_TRACK_MODIFICATIONS = False class Development(Config): DEBUG: bool = True SECRET_KEY_FILENAME: str = "dev_v8_server.key" + SQLALCHEMY_DATABASE_URI: str = f"sqlite+pysqlite:///{ DEV_DB_PATH / 'v8_dev.db'}" class Production(Config): diff --git a/v8_server/eamuse/services/__init__.py b/v8_server/eamuse/services/__init__.py index b62614d..f46bcb6 100644 --- a/v8_server/eamuse/services/__init__.py +++ b/v8_server/eamuse/services/__init__.py @@ -1,3 +1,4 @@ +from v8_server.eamuse.services.cardmng import CardMng from v8_server.eamuse.services.facility import Facility from v8_server.eamuse.services.local import Local from v8_server.eamuse.services.message import Message @@ -8,6 +9,7 @@ from v8_server.eamuse.services.services import ServiceRequest, Services, Service __all__ = [ + "CardMng", "Facility", "Local", "Message", diff --git a/v8_server/eamuse/services/cardmng.py b/v8_server/eamuse/services/cardmng.py new file mode 100644 index 0000000..3fd0d48 --- /dev/null +++ b/v8_server/eamuse/services/cardmng.py @@ -0,0 +1,165 @@ +from lxml.builder import E + +from v8_server import db +from v8_server.eamuse.services.services import ServiceRequest +from v8_server.eamuse.utils.xml import get_xml_attrib +from v8_server.model.user import Card, Profile, RefID, User + + +class CardMng(object): + """ + Handle the CardMng (Card Manage) request. + + This is for supporting eAmuse card interaction. + """ + + # List of statuses we return to the game for various card related reasons + SUCCESS = 0 + NO_PROFILE = 109 + NOT_ALLOWED = 110 + NOT_REGISTERED = 112 + INVALID_PIN = 116 + + # Methods + INQUIRE = "inquire" + GETREFID = "getrefid" + AUTHPASS = "authpass" + BINDMODEL = "bindmodel" + GETKEEPSPAN = "getkeepspan" + GETDATALIST = "getdatalist" + + @classmethod + def inquire(cls, req: ServiceRequest): + """ + Example Request: + + + + + Example Response: + + + + """ + # Grab the card id + cardid = get_xml_attrib(req.xml[0], "cardid") + + # Check if a user with this card id already exists + user = User.from_cardid(cardid) + + if user is None: + # The user doesn't exist, force system to create a new account + response = E.response(E.cardmng({"status": str(CardMng.NOT_REGISTERED)})) + else: + refid = RefID.from_userid(user.userid) + bound = Profile.from_userid(user.userid) is not None + + if refid is None: + raise Exception("RefID Should not be None here!") + + response = E.response( + E.cardmng( + { + "refid": refid.refid, + "dataid": refid.refid, + "newflag": "1", + "binded": "1" if bound else "0", + "expired": "0", + "exflag": "0", + "useridflag": "1", + "extidflag": "1", + } + ) + ) + + return response + + @classmethod + def getrefid(cls, req: ServiceRequest): + """""" + # Grab the card id and pin + cardid = get_xml_attrib(req.xml[0], "cardid") + pin = get_xml_attrib(req.xml[0], "passwd") + + # Create a new user object with the given pin + user = User(pin=pin) + db.session.add(user) + db.session.commit() + + # Create the card, tie it to the user account + card = Card(cardid=cardid, userid=user.userid) + db.session.add(card) + db.session.commit() + + # Generate the refid and return it + refid = RefID.create_with_userid(user.userid) + + return E.response(E.cardmng({"dataid": refid.refid, "refid": refid.refid})) + + @classmethod + def authpass(cls, req: ServiceRequest): + """""" + # Grab the refid and pin + refid_str = get_xml_attrib(req.xml[0], "refid") + pin = get_xml_attrib(req.xml[0], "pass") + + # Grab the refid + refid = db.session.query(RefID).filter(RefID.refid == refid_str).one_or_none() + + if refid is None: + raise Exception("RefID Is None Here!") + + # Check if the pin is valid for the user + user = refid.user + valid = user.pin == pin + + return E.response( + E.cardmng( + {"status": str(CardMng.SUCCESS if valid else CardMng.INVALID_PIN)} + ) + ) + + @classmethod + def bindmodel(cls, req: ServiceRequest): + """""" + # Grab the refid + refid_str = get_xml_attrib(req.xml[0], "refid") + refid = db.session.query(RefID).filter(RefID.refid == refid_str).one_or_none() + + if refid is None: + raise Exception("RefID is None Here!") + + # Just bind some garbage here for now + profile = Profile(refid=refid.refid, data={"data": "something"}) + db.session.add(profile) + db.session.commit() + + return E.response(E.cardmng({"dataid": refid.refid})) + + @classmethod + def getkeepspan(cls): + """ + Unclear what this method does, return an arbitrary span + """ + return E.response(E.cardmng({"keepspan": "30"})) + + @classmethod + def getdatalist(cls): + """ + Unclear what this method does, return a dummy response + """ + return E.response(E.cardmng()) diff --git a/v8_server/eamuse/services/local.py b/v8_server/eamuse/services/local.py index 64f12bd..9a5fe5a 100644 --- a/v8_server/eamuse/services/local.py +++ b/v8_server/eamuse/services/local.py @@ -22,6 +22,9 @@ class Local(object): CARDUTIL = "cardutil" CARDUTIL_CHECK = "check" + GAMEINFO = "gameinfo" + GAMEINFO_GET = "get" + @classmethod def shopinfo(cls, req: ServiceRequest) -> etree: """ @@ -125,7 +128,26 @@ class Local(object): # TODO: Figure out what this thing actually needs to send back if req.method == cls.DEMODATA_GET: - response = E.response(E.demodata({"expire": "600"})) + # response = E.response(E.demodata({"expire": "600"})) + + # try some dummy response that might have some info in it + response = E.response( + E.demodata( + E.hitchart({"nr": "3"}), + E.data( + E.musicid("133", e_type(T.s32)), + E.last1("1", e_type(T.s32)), + ), + E.data( + E.musicid("208", e_type(T.s32)), + E.last1("2", e_type(T.s32)), + ), + E.data( + E.musicid("209", e_type(T.s32)), + E.last1("3", e_type(T.s32)), + ), + ) + ) else: raise Exception( "Not sure how to handle this demodata request. " @@ -153,7 +175,7 @@ class Local(object): - + 0 @@ -171,3 +193,35 @@ class Local(object): ) return response + + @classmethod + def gameinfo(cls, req: ServiceRequest) -> etree: + """ + Handle a Gameinfo request. + + Currently unsure how to handle this, so we just return a dummy object. + + # Example Request: + + + + US-123 + 1 + + + + + Example Response: + + + + """ + if req.method == cls.GAMEINFO_GET: + response = E.response(E.gameinfo()) + else: + raise Exception( + "Not sure how to handle this gameinfo request. " + f'method "{req.method}" is unknown for request: {req}' + ) + + return response diff --git a/v8_server/eamuse/services/services.py b/v8_server/eamuse/services/services.py index 6a8e36c..126f58b 100644 --- a/v8_server/eamuse/services/services.py +++ b/v8_server/eamuse/services/services.py @@ -36,6 +36,23 @@ class ServiceType(IntEnum): CARDMNG = 6 LOCAL = 7 + # Extra for testing + # BINARY = 8 + # DLSTATUS = 9 + # EACOIN = 10 + # EEMALL = 11 + # INFO = 12 + # LOBBY = 13 + # NETLOG = 14 + # NUMBERING = 15 + # PKGLIST = 16 + # POSEVENT = 17 + # REFERENCE = 18 + # SHOPINF = 19 + # SIDMGR = 20 + # USERDATA = 21 + # USERID = 22 + class Services(object): """ diff --git a/v8_server/eamuse/utils/arc.py b/v8_server/eamuse/utils/arc.py new file mode 100644 index 0000000..2108f49 --- /dev/null +++ b/v8_server/eamuse/utils/arc.py @@ -0,0 +1,57 @@ +import struct +from typing import Dict, List, Tuple + +from v8_server.eamuse.utils.lz77 import Lz77 + + +class ARC: + """ + Class representing an `.arc` file. These are found in DDR Ace, and possibly + other games that use ESS. Given a serires of bytes, this will allow you to + query included filenames as well as read the contents of any file inside the + archive. + """ + + def __init__(self, data: bytes) -> None: + self.__files: Dict[str, Tuple[int, int, int]] = {} + self.__data = data + self.__parse_file(data) + + def __parse_file(self, data: bytes) -> None: + # Check file header + if data[0:4] != bytes([0x20, 0x11, 0x75, 0x19]): + raise Exception("Unknown file format!") + + # Grab header offsets + (_, numfiles, _) = struct.unpack(" List[str]: + return [f for f in self.__files] + + def read_file(self, filename: str) -> bytes: + (fileoffset, uncompressedsize, compressedsize) = self.__files[filename] + + if compressedsize == uncompressedsize: + # Just stored + return self.__data[fileoffset : (fileoffset + compressedsize)] + else: + # Compressed + lz77 = Lz77() + return lz77.decompress( + self.__data[fileoffset : (fileoffset + compressedsize)] + ) diff --git a/v8_server/model/connection.py b/v8_server/model/connection.py deleted file mode 100644 index 0693e62..0000000 --- a/v8_server/model/connection.py +++ /dev/null @@ -1,34 +0,0 @@ -from __future__ import annotations - -from pathlib import Path -from types import TracebackType -from typing import Optional, Type - -from sqlalchemy import create_engine -from sqlalchemy.ext.declarative import DeclarativeMeta, declarative_base -from sqlalchemy.orm import Session as AlchemySession - - -Base: DeclarativeMeta = declarative_base() - - -class Database(object): - def __init__(self, echo: bool = False) -> None: - self.uri = f"sqlite+pysqlite:///{Path(__file__).parent / 'v8.db'}" - self.engine = create_engine(self.uri, echo=echo) - self.session = AlchemySession(bind=self.engine) - - def __enter__(self) -> Database: - return self - - def __exit__( - self, - exception_type: Optional[Type[BaseException]], - exception_value: Optional[BaseException], - traceback: Optional[TracebackType], - ) -> bool: - self.session.close() - return exception_type is None - - def __del__(self) -> None: - self.engine.dispose() diff --git a/v8_server/model/user.py b/v8_server/model/user.py index 3b8e13f..8ad71ea 100644 --- a/v8_server/model/user.py +++ b/v8_server/model/user.py @@ -1,11 +1,23 @@ +from __future__ import annotations + +import random +from typing import Optional + +from flask_sqlalchemy.model import DefaultMeta from sqlalchemy import JSON, Column, ForeignKey, UniqueConstraint from sqlalchemy.orm import relationship from sqlalchemy.types import Integer, String -from v8_server.model.connection import Base +from v8_server import db -class User(Base): +BaseModel: DefaultMeta = db.Model + +DEFAULT_GAME = "GFDM" +DEFAULT_VERSION = "v8" + + +class User(BaseModel): """ Table representing a user. Each user has a Unique ID and a pin which is used with all cards associated with the user's account. @@ -13,17 +25,22 @@ class User(Base): __tablename__ = "users" - uid = Column(Integer, nullable=False, primary_key=True) + userid = Column(Integer, nullable=False, primary_key=True) pin = Column(String(4), nullable=False) cards = relationship("Card", back_populates="user") extids = relationship("ExtID", back_populates="user") refids = relationship("RefID", back_populates="user") def __repr__(self) -> str: - return f'User' + return f'User' + + @classmethod + def from_cardid(cls, cardid: str) -> Optional[User]: + card = db.session.query(Card).filter(Card.cardid == cardid).one_or_none() + return card.user if card is not None else None -class Card(Base): +class Card(BaseModel): """ Table representing a card associated with a user. Users may have zero or more cards associated with them. When a new card is used in a game, a new user will be created @@ -32,32 +49,56 @@ class Card(Base): __tablename__ = "cards" - uid = Column(String(16), nullable=False, primary_key=True) - user_id = Column(Integer, ForeignKey("users.uid"), nullable=False) + cardid = Column(String(16), nullable=False, primary_key=True) + userid = Column(Integer, ForeignKey("users.userid"), nullable=False) user = relationship("User", back_populates="cards") def __repr__(self) -> str: - return f'Card' + return f'Card' -class ExtID(Base): +class ExtID(BaseModel): """ Table representing and extid for a user across a game series. Each game series on the network gets its own extid (8 digit number) for each user. """ __tablename__ = "extids" - __table_args_ = (UniqueConstraint("game", "user_id", name="game_user_id"),) - uid = Column(Integer, nullable=False, primary_key=True) + __table_args_ = (UniqueConstraint("game", "userid", name="game_userid"),) + extid = Column(Integer, nullable=False, primary_key=True) game = Column(String(32), nullable=False) - user_id = Column(Integer, ForeignKey("users.uid"), nullable=False) + userid = Column(Integer, ForeignKey("users.userid"), nullable=False) user = relationship("User", back_populates="extids") def __repr__(self) -> str: - return f'ExtID' + return f'ExtID' + + @classmethod + def create_with_userid(cls, userid: int) -> Optional[ExtID]: + # First check if this user has an ExtID for GFDM + extid = ( + db.session.query(ExtID) + .filter(ExtID.userid == userid, ExtID.game == DEFAULT_GAME) + .one_or_none() + ) + + if extid is None: + # Create a new ExtID that is unique + while True: + extid_val = random.randint(0, 89999999) + 10000000 + count = db.session.query(ExtID).filter(ExtID.extid == extid_val).count() + if count == 0: + break + + # Use this ExtID + extid = ExtID(extid=extid_val, game=DEFAULT_GAME, userid=userid) + db.session.add(extid) + db.session.commit() + + return extid -class RefID(Base): +class RefID(BaseModel): """ Table representing a refid for a user. Each unique game on the network will need a refid for each user/game/version they have a profile for. If a user does not have @@ -70,28 +111,81 @@ class RefID(Base): __tablename__ = "refids" __table_args__ = ( - UniqueConstraint("game", "version", "user_id", name="game_version_user_id"), + UniqueConstraint("game", "version", "userid", name="game_version_userid"), ) - uid = Column(String(16), nullable=False, primary_key=True) + refid = Column(String(16), nullable=False, primary_key=True) game = Column(String(32), nullable=False) version = Column(Integer, nullable=False) - user_id = Column(Integer, ForeignKey("users.uid"), nullable=False) + userid = Column(Integer, ForeignKey("users.userid"), nullable=False) user = relationship("User", back_populates="refids") def __repr__(self) -> str: return ( - f'RefID" + f'RefID" ) + @classmethod + def from_userid(cls, userid: int) -> Optional[RefID]: + refid = ( + db.session.query(RefID) + .filter( + RefID.userid == userid, + RefID.game == DEFAULT_GAME, + RefID.version == DEFAULT_VERSION, + ) + .one_or_none() + ) -class Profile(Base): + return refid + + @classmethod + def create_with_userid(cls, userid: int) -> RefID: + # Create the ExtID + # This method will return an already existing ExtID or create a new one and + # return it. In this case we don't care what it returns + _ = ExtID.create_with_userid(userid) + + # Create a new RefID that is unique + while True: + refid_val = "".join(random.choice("0123456789ABCDEF") for _ in range(16)) + count = db.session.query(RefID).filter(RefID.refid == refid_val).count() + if count == 0: + break + + # Use our newly created RefID + refid = RefID( + refid=refid_val, game=DEFAULT_GAME, version=DEFAULT_VERSION, userid=userid + ) + db.session.add(refid) + db.session.commit() + + return refid + + +class Profile(BaseModel): """ Table for storing JSON profile blobs, indexed by refid """ __tablename__ = "profiles" - ref_id = Column( - String(16), ForeignKey("refids.uid"), nullable=False, primary_key=True + refid = Column( + String(16), ForeignKey("refids.refid"), nullable=False, primary_key=True ) data = Column(JSON, nullable=False) + + @classmethod + def from_refid(cls, refid: str) -> Optional[Profile]: + return db.session.query(Profile).filter(Profile.refid == refid).one_or_none() + + @classmethod + def from_userid(cls, userid: int) -> Optional[Profile]: + """ + Returns a user profile if it exists, or None if it doesn't + """ + refid = RefID.from_userid(userid) + + if refid is None: + return None + + return Profile.from_refid(refid.refid) diff --git a/v8_server/model/v8.db b/v8_server/model/v8.db deleted file mode 100644 index 0a43d3b..0000000 Binary files a/v8_server/model/v8.db and /dev/null differ diff --git a/v8_server/view/index.py b/v8_server/view/index.py index ad99a68..3780eb8 100644 --- a/v8_server/view/index.py +++ b/v8_server/view/index.py @@ -6,6 +6,7 @@ from sqlalchemy.orm.exc import MultipleResultsFound from v8_server import app from v8_server.eamuse.services import ( + CardMng, Facility, Local, Message, @@ -17,7 +18,6 @@ from v8_server.eamuse.services import ( ServiceType, ) from v8_server.eamuse.utils.xml import get_xml_attrib -from v8_server.model.connection import Database from v8_server.model.user import Card, ExtID, Profile, RefID, User @@ -111,184 +111,26 @@ def facility_service() -> FlaskResponse: return req.response(response) -''' -class CardStatus(object): - """ - List of statuses we return to the game for various reasons - """ - - SUCCESS = 0 - NO_PROFILE = 109 - NOT_ALLOWED = 110 - NOT_REGISTERED = 112 - INVALID_PIN = 116 - - -def create_refid(user_id: int) -> str: - with Database() as db: - # Create a new extid that is unique - while True: - e_id = random.randint(0, 89999999) + 10000000 - if db.session.query(ExtID).filter(ExtID.uid == e_id).count() == 0: - break - - # Use that ext_id - ext_id = ExtID(uid=e_id, game="GFDM", user_id=user_id) - - try: - db.session.add(ext_id) - except Exception: - # Most likely a duplicate error as this user already has an ExtID for this - # game series - pass - - # Create a new refid that is unique - while True: - r_id = "".join(random.choice("0123456789ABCDEF") for _ in range(16)) - if db.session.query(RefID).filter(RefID.uid == r_id).count() == 0: - break - - # Use that ref_id - ref_id = RefID(uid=r_id, game="GFDM", version=8, user_id=user_id) - db.session.add(ref_id) - db.session.commit() - - uid = ref_id.uid - - return uid - - -def has_profile(user_id: int) -> bool: - result = False - with Database() as db: - ref_id = ( - db.session.query(RefID) - .filter(RefID.game == "GFDM", RefID.version == 8, RefID.user_id == user_id) - .one() - ) - result = ( - db.session.query(Profile).filter(Profile.ref_id == ref_id.uid).count() != 0 - ) - return result - - -@app.route("/cardmng/service", methods=["POST"]) +@Services.route(ServiceType.CARDMNG) def cardmng() -> Tuple[bytes, Dict[str, str]]: - """ - This is for dealing with the card management - """ - xml, model_str, module, method, command = eamuse_read_xml(request) + req = ServiceRequest(request) - if method == "inquire": - card_id = get_xml_attrib(xml[0], "cardid") - - with Database() as db: - # Check if the user already exists - try: - card = db.session.query(Card).filter(Card.uid == card_id).one_or_none() - except MultipleResultsFound: - app.logger.error(f"Multiple Cards found for Card ID: {card_id}") - raise - - if card is None: - # This user doesn't exist, force the system to create a new account - response = E.response( - E.cardmng({"status": str(CardStatus.NOT_REGISTERED)}) - ) - else: - # Special handing for looking up whether the previous game's profile - # existed - user = db.session.query(User).filter(User.uid == card.user_id).one() - bound = has_profile(user.uid) - expired = False - - ref_id = ( - db.session.query(RefID) - .filter( - RefID.game == "GFDM", - RefID.version == 8, - RefID.user_id == user.uid, - ) - .one() - ) - paseli_enabled = False - - response = E.response( - E.cardmng( - { - "refid": ref_id.uid, - "dataid": ref_id.uid, - "newflag": "1", # A;ways seems to be set to 1 - "binded": "1" if bound else "0", - "expired": "1" if expired else "0", - "ecflag": "1" if paseli_enabled else "0", - "useridflag": "1", - "extidflag": "1", - } - ) - ) - elif method == "getrefid": - # Given a card_id, and a pin, register the card with the system and generate a - # new data_id/ref_id + ext_id - card_id = get_xml_attrib(xml[0], "cardid") - pin = get_xml_attrib(xml[0], "passwd") - - with Database() as db: - # Create the user object - user = User(pin=pin) - db.session.add(user) - - # We must commit to assign a uid - db.session.commit() - user_id = user.uid - - # Now insert the card, tying it to the account - card = Card(uid=card_id, user_id=user_id) - db.session.add(card) - db.session.commit() - - ref_id = create_refid(user_id) - response = E.response(E.cardmng({"dataid": ref_id, "refid": ref_id})) - elif method == "authpass": - # Given a data_id/ref_id previously found via inquire, verify the pin - ref_id = get_xml_attrib(xml[0], "refid") - pin = get_xml_attrib(xml[0], "pass") - - with Database() as db: - refid = db.session.query(RefID).filter(RefID.uid == ref_id).one() - user = ( - db.session.query(User).filter(User.uid == refid.user_id).one_or_none() - ) - - if user is not None: - valid = pin == user.pin - else: - valid = False - - response = E.response( - E.cardmng( - { - "status": str( - CardStatus.SUCCESS if valid else CardStatus.INVALID_PIN - ) - } - ) - ) - elif method == "bindmodel": - # Given a refid, bind the user's card to the current version of the game - # TODO: Not implemented right now. do it later - ref_id = get_xml_attrib(xml[0], "refid") - response = E.response(E.cardmng({"dataid": ref_id})) - elif method == "getkeepspan": - # Unclear what this method does, return an arbitrary span - response = E.response(E.cardmng({"keepspan", "30"})) - elif method == "getdatalist": - # Unclear what this method does, return a dummy response - response = base_response(module) + if req.method == CardMng.INQUIRE: + response = CardMng.inquire(req) + elif req.method == CardMng.GETREFID: + response = CardMng.getrefid(req) + elif req.method == CardMng.AUTHPASS: + response = CardMng.authpass(req) + elif req.method == CardMng.BINDMODEL: + response = CardMng.bindmodel(req) + elif req.method == CardMng.GETKEEPSPAN: + response = CardMng.getkeepspan() + elif req.method == CardMng.GETDATALIST: + response = CardMng.getdatalist() else: - response = base_response(module) - return eamuse_prepare_xml(response, request) -''' + raise Exception(f"Not sure how to handle this Cardmng Request: {req}") + + return req.response(response) @Services.route(ServiceType.LOCAL) @@ -301,6 +143,8 @@ def local_service() -> FlaskResponse: response = Local.demodata(req) elif req.module == Local.CARDUTIL: response = Local.cardutil(req) + elif req.module == Local.GAMEINFO: + response = Local.gameinfo(req) else: raise Exception(f"Not sure how to handle this Local Request: {req}") return req.response(response)