mirror of
https://github.com/573dev/gfdm-server.git
synced 2026-03-21 17:54:19 -05:00
Back to parity with old code
This commit is contained in:
parent
f34716a1c3
commit
27117aa5c4
8
.gitignore
vendored
8
.gitignore
vendored
|
|
@ -102,8 +102,8 @@ ENV/
|
|||
# mypy
|
||||
.mypy_cache/
|
||||
|
||||
# logs
|
||||
logs/
|
||||
# project related things
|
||||
/logs/
|
||||
/decompiled/
|
||||
/database/
|
||||
|
||||
# Decompiled v8 code
|
||||
decompiled/
|
||||
|
|
|
|||
1
setup.py
1
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",
|
||||
|
|
|
|||
|
|
@ -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"]
|
||||
|
|
|
|||
|
|
@ -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):
|
||||
|
|
|
|||
|
|
@ -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",
|
||||
|
|
|
|||
165
v8_server/eamuse/services/cardmng.py
Normal file
165
v8_server/eamuse/services/cardmng.py
Normal file
|
|
@ -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:
|
||||
<call model="K32:J:B:A:2011033000" srcid="00010203040506070809">
|
||||
<cardmng
|
||||
cardid="E0040100DE52896C"
|
||||
cardtype="1"
|
||||
method="inquire"
|
||||
update="1"
|
||||
/>
|
||||
</call>
|
||||
|
||||
Example Response:
|
||||
<response>
|
||||
<cardmng
|
||||
refid="ADE0FE0B14AEAEFC"
|
||||
dataid="ADE0FE0B14AEAEFC"
|
||||
newflag="1"
|
||||
binded="0"
|
||||
expired="0"
|
||||
ecflag="0"
|
||||
useridflag="1"
|
||||
extidflag="1"
|
||||
/>
|
||||
</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())
|
||||
|
|
@ -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):
|
|||
<response>
|
||||
<cardutil>
|
||||
<card no="1" state="0">
|
||||
<kind __type="s8")0</kind>
|
||||
<kind __type="s8">0</kind>
|
||||
</card>
|
||||
</cardutil>
|
||||
</response>
|
||||
|
|
@ -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:
|
||||
<call model="K32:J:B:A:2011033000" srcid="00010203040506070809">
|
||||
<gameinfo method="get">
|
||||
<shop>
|
||||
<locationid __type="str">US-123</locationid>
|
||||
<cabid __type="u32">1</cabid>
|
||||
</shop>
|
||||
</gameinfo>
|
||||
</call>
|
||||
|
||||
Example Response:
|
||||
<response>
|
||||
<gameinfo expire="600"/>
|
||||
</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
|
||||
|
|
|
|||
|
|
@ -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):
|
||||
"""
|
||||
|
|
|
|||
57
v8_server/eamuse/utils/arc.py
Normal file
57
v8_server/eamuse/utils/arc.py
Normal file
|
|
@ -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("<III", data[4:16])
|
||||
|
||||
for fno in range(numfiles):
|
||||
start = 16 + (16 * fno)
|
||||
end = start + 16
|
||||
(nameoffset, fileoffset, uncompressedsize, compressedsize) = struct.unpack(
|
||||
"<IIII", data[start:end]
|
||||
)
|
||||
name = ""
|
||||
|
||||
while data[nameoffset] != 0:
|
||||
name = name + data[nameoffset : (nameoffset + 1)].decode("ascii")
|
||||
nameoffset = nameoffset + 1
|
||||
|
||||
self.__files[name] = (fileoffset, uncompressedsize, compressedsize)
|
||||
|
||||
@property
|
||||
def filenames(self) -> 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)]
|
||||
)
|
||||
|
|
@ -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()
|
||||
|
|
@ -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<uid: {self.uid}, pin: "{self.pin}">'
|
||||
return f'User<userid: {self.userid}, pin: "{self.pin}">'
|
||||
|
||||
@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<uid: "{self.uid}", user_id: {self.user_id}>'
|
||||
return f'Card<cardid: "{self.cardid}", userid: {self.userid}>'
|
||||
|
||||
|
||||
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<uid: {self.uid}, game: "{self.game}", user_id: {self.user_id}>'
|
||||
return f'ExtID<extid: {self.extid}, game: "{self.game}", userid: {self.userid}>'
|
||||
|
||||
@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<uid: {self.uid}, game: "{self.game}", version: {self.version} '
|
||||
f"user_id: {self.user_id}>"
|
||||
f'RefID<refid: {self.refid}, game: "{self.game}", version: {self.version} '
|
||||
f"userid: {self.userid}>"
|
||||
)
|
||||
|
||||
@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)
|
||||
|
|
|
|||
Binary file not shown.
|
|
@ -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)
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user