let black do it's magic

This commit is contained in:
Hay1tsme
2023-03-09 11:38:58 -05:00
parent fa7206848c
commit a76bb94eb1
150 changed files with 8474 additions and 4843 deletions
+1 -1
View File
@@ -4,4 +4,4 @@ from core.aimedb import AimedbFactory
from core.title import TitleServlet from core.title import TitleServlet
from core.utils import Utils from core.utils import Utils
from core.mucha import MuchaServlet from core.mucha import MuchaServlet
from core.frontend import FrontendServlet from core.frontend import FrontendServlet
+115 -52
View File
@@ -8,17 +8,18 @@ from logging.handlers import TimedRotatingFileHandler
from core.config import CoreConfig from core.config import CoreConfig
from core.data import Data from core.data import Data
class AimedbProtocol(Protocol): class AimedbProtocol(Protocol):
AIMEDB_RESPONSE_CODES = { AIMEDB_RESPONSE_CODES = {
"felica_lookup": 0x03, "felica_lookup": 0x03,
"lookup": 0x06, "lookup": 0x06,
"log": 0x0a, "log": 0x0A,
"campaign": 0x0c, "campaign": 0x0C,
"touch": 0x0e, "touch": 0x0E,
"lookup2": 0x10, "lookup2": 0x10,
"felica_lookup2": 0x12, "felica_lookup2": 0x12,
"log2": 0x14, "log2": 0x14,
"hello": 0x65 "hello": 0x65,
} }
request_list: Dict[int, Any] = {} request_list: Dict[int, Any] = {}
@@ -30,14 +31,14 @@ class AimedbProtocol(Protocol):
if core_cfg.aimedb.key == "": if core_cfg.aimedb.key == "":
self.logger.error("!!!KEY NOT SET!!!") self.logger.error("!!!KEY NOT SET!!!")
exit(1) exit(1)
self.request_list[0x01] = self.handle_felica_lookup self.request_list[0x01] = self.handle_felica_lookup
self.request_list[0x04] = self.handle_lookup self.request_list[0x04] = self.handle_lookup
self.request_list[0x05] = self.handle_register self.request_list[0x05] = self.handle_register
self.request_list[0x09] = self.handle_log self.request_list[0x09] = self.handle_log
self.request_list[0x0b] = self.handle_campaign self.request_list[0x0B] = self.handle_campaign
self.request_list[0x0d] = self.handle_touch self.request_list[0x0D] = self.handle_touch
self.request_list[0x0f] = self.handle_lookup2 self.request_list[0x0F] = self.handle_lookup2
self.request_list[0x11] = self.handle_felica_lookup2 self.request_list[0x11] = self.handle_felica_lookup2
self.request_list[0x13] = self.handle_log2 self.request_list[0x13] = self.handle_log2
self.request_list[0x64] = self.handle_hello self.request_list[0x64] = self.handle_hello
@@ -53,8 +54,10 @@ class AimedbProtocol(Protocol):
self.logger.debug(f"{self.transport.getPeer().host} Connected") self.logger.debug(f"{self.transport.getPeer().host} Connected")
def connectionLost(self, reason) -> None: def connectionLost(self, reason) -> None:
self.logger.debug(f"{self.transport.getPeer().host} Disconnected - {reason.value}") self.logger.debug(
f"{self.transport.getPeer().host} Disconnected - {reason.value}"
)
def dataReceived(self, data: bytes) -> None: def dataReceived(self, data: bytes) -> None:
cipher = AES.new(self.config.aimedb.key.encode(), AES.MODE_ECB) cipher = AES.new(self.config.aimedb.key.encode(), AES.MODE_ECB)
@@ -66,7 +69,7 @@ class AimedbProtocol(Protocol):
self.logger.debug(f"{self.transport.getPeer().host} wrote {decrypted.hex()}") self.logger.debug(f"{self.transport.getPeer().host} wrote {decrypted.hex()}")
if not decrypted[1] == 0xa1 and not decrypted[0] == 0x3e: if not decrypted[1] == 0xA1 and not decrypted[0] == 0x3E:
self.logger.error(f"Bad magic") self.logger.error(f"Bad magic")
return None return None
@@ -90,30 +93,46 @@ class AimedbProtocol(Protocol):
except ValueError as e: except ValueError as e:
self.logger.error(f"Failed to encrypt {resp.hex()} because {e}") self.logger.error(f"Failed to encrypt {resp.hex()} because {e}")
return None return None
def handle_campaign(self, data: bytes) -> bytes: def handle_campaign(self, data: bytes) -> bytes:
self.logger.info(f"campaign from {self.transport.getPeer().host}") self.logger.info(f"campaign from {self.transport.getPeer().host}")
ret = struct.pack("<5H", 0xa13e, 0x3087, self.AIMEDB_RESPONSE_CODES["campaign"], 0x0200, 0x0001) ret = struct.pack(
"<5H",
0xA13E,
0x3087,
self.AIMEDB_RESPONSE_CODES["campaign"],
0x0200,
0x0001,
)
return self.append_padding(ret) return self.append_padding(ret)
def handle_hello(self, data: bytes) -> bytes: def handle_hello(self, data: bytes) -> bytes:
self.logger.info(f"hello from {self.transport.getPeer().host}") self.logger.info(f"hello from {self.transport.getPeer().host}")
ret = struct.pack("<5H", 0xa13e, 0x3087, self.AIMEDB_RESPONSE_CODES["hello"], 0x0020, 0x0001) ret = struct.pack(
"<5H", 0xA13E, 0x3087, self.AIMEDB_RESPONSE_CODES["hello"], 0x0020, 0x0001
)
return self.append_padding(ret) return self.append_padding(ret)
def handle_lookup(self, data: bytes) -> bytes: def handle_lookup(self, data: bytes) -> bytes:
luid = data[0x20: 0x2a].hex() luid = data[0x20:0x2A].hex()
user_id = self.data.card.get_user_id_from_card(access_code=luid) user_id = self.data.card.get_user_id_from_card(access_code=luid)
if user_id is None: user_id = -1 if user_id is None:
user_id = -1
self.logger.info(f"lookup from {self.transport.getPeer().host}: luid {luid} -> user_id {user_id}") self.logger.info(
f"lookup from {self.transport.getPeer().host}: luid {luid} -> user_id {user_id}"
)
ret = struct.pack("<5H", 0xa13e, 0x3087, self.AIMEDB_RESPONSE_CODES["lookup"], 0x0130, 0x0001) ret = struct.pack(
"<5H", 0xA13E, 0x3087, self.AIMEDB_RESPONSE_CODES["lookup"], 0x0130, 0x0001
)
ret += bytes(0x20 - len(ret)) ret += bytes(0x20 - len(ret))
if user_id is None: ret += struct.pack("<iH", -1, 0) if user_id is None:
else: ret += struct.pack("<l", user_id) ret += struct.pack("<iH", -1, 0)
else:
ret += struct.pack("<l", user_id)
return self.append_padding(ret) return self.append_padding(ret)
def handle_lookup2(self, data: bytes) -> bytes: def handle_lookup2(self, data: bytes) -> bytes:
@@ -125,66 +144,98 @@ class AimedbProtocol(Protocol):
return bytes(ret) return bytes(ret)
def handle_felica_lookup(self, data: bytes) -> bytes: def handle_felica_lookup(self, data: bytes) -> bytes:
idm = data[0x20: 0x28].hex() idm = data[0x20:0x28].hex()
pmm = data[0x28: 0x30].hex() pmm = data[0x28:0x30].hex()
access_code = self.data.card.to_access_code(idm) access_code = self.data.card.to_access_code(idm)
self.logger.info(f"felica_lookup from {self.transport.getPeer().host}: idm {idm} pmm {pmm} -> access_code {access_code}") self.logger.info(
f"felica_lookup from {self.transport.getPeer().host}: idm {idm} pmm {pmm} -> access_code {access_code}"
)
ret = struct.pack("<5H", 0xa13e, 0x3087, self.AIMEDB_RESPONSE_CODES["felica_lookup"], 0x0030, 0x0001) ret = struct.pack(
"<5H",
0xA13E,
0x3087,
self.AIMEDB_RESPONSE_CODES["felica_lookup"],
0x0030,
0x0001,
)
ret += bytes(26) ret += bytes(26)
ret += bytes.fromhex(access_code) ret += bytes.fromhex(access_code)
return self.append_padding(ret) return self.append_padding(ret)
def handle_felica_lookup2(self, data: bytes) -> bytes: def handle_felica_lookup2(self, data: bytes) -> bytes:
idm = data[0x30: 0x38].hex() idm = data[0x30:0x38].hex()
pmm = data[0x38: 0x40].hex() pmm = data[0x38:0x40].hex()
access_code = self.data.card.to_access_code(idm) access_code = self.data.card.to_access_code(idm)
user_id = self.data.card.get_user_id_from_card(access_code=access_code) user_id = self.data.card.get_user_id_from_card(access_code=access_code)
if user_id is None: user_id = -1 if user_id is None:
user_id = -1
self.logger.info(f"felica_lookup2 from {self.transport.getPeer().host}: idm {idm} ipm {pmm} -> access_code {access_code} user_id {user_id}") self.logger.info(
f"felica_lookup2 from {self.transport.getPeer().host}: idm {idm} ipm {pmm} -> access_code {access_code} user_id {user_id}"
)
ret = struct.pack("<5H", 0xa13e, 0x3087, self.AIMEDB_RESPONSE_CODES["felica_lookup2"], 0x0140, 0x0001) ret = struct.pack(
"<5H",
0xA13E,
0x3087,
self.AIMEDB_RESPONSE_CODES["felica_lookup2"],
0x0140,
0x0001,
)
ret += bytes(22) ret += bytes(22)
ret += struct.pack("<lq", user_id, -1) # first -1 is ext_id, 3rd is access code ret += struct.pack("<lq", user_id, -1) # first -1 is ext_id, 3rd is access code
ret += bytes.fromhex(access_code) ret += bytes.fromhex(access_code)
ret += struct.pack("<l", 1) ret += struct.pack("<l", 1)
return self.append_padding(ret) return self.append_padding(ret)
def handle_touch(self, data: bytes) -> bytes: def handle_touch(self, data: bytes) -> bytes:
self.logger.info(f"touch from {self.transport.getPeer().host}") self.logger.info(f"touch from {self.transport.getPeer().host}")
ret = struct.pack("<5H", 0xa13e, 0x3087, self.AIMEDB_RESPONSE_CODES["touch"], 0x0050, 0x0001) ret = struct.pack(
"<5H", 0xA13E, 0x3087, self.AIMEDB_RESPONSE_CODES["touch"], 0x0050, 0x0001
)
ret += bytes(5) ret += bytes(5)
ret += struct.pack("<3H", 0x6f, 0, 1) ret += struct.pack("<3H", 0x6F, 0, 1)
return self.append_padding(ret) return self.append_padding(ret)
def handle_register(self, data: bytes) -> bytes: def handle_register(self, data: bytes) -> bytes:
luid = data[0x20: 0x2a].hex() luid = data[0x20:0x2A].hex()
if self.config.server.allow_user_registration: if self.config.server.allow_user_registration:
user_id = self.data.user.create_user() user_id = self.data.user.create_user()
if user_id is None: if user_id is None:
user_id = -1 user_id = -1
self.logger.error("Failed to register user!") self.logger.error("Failed to register user!")
else: else:
card_id = self.data.card.create_card(user_id, luid) card_id = self.data.card.create_card(user_id, luid)
if card_id is None: if card_id is None:
user_id = -1 user_id = -1
self.logger.error("Failed to register card!") self.logger.error("Failed to register card!")
self.logger.info(f"register from {self.transport.getPeer().host}: luid {luid} -> user_id {user_id}") self.logger.info(
f"register from {self.transport.getPeer().host}: luid {luid} -> user_id {user_id}"
)
else: else:
self.logger.info(f"register from {self.transport.getPeer().host} blocked!: luid {luid}") self.logger.info(
f"register from {self.transport.getPeer().host} blocked!: luid {luid}"
)
user_id = -1 user_id = -1
ret = struct.pack("<5H", 0xa13e, 0x3087, self.AIMEDB_RESPONSE_CODES["lookup"], 0x0030, 0x0001 if user_id > -1 else 0) ret = struct.pack(
"<5H",
0xA13E,
0x3087,
self.AIMEDB_RESPONSE_CODES["lookup"],
0x0030,
0x0001 if user_id > -1 else 0,
)
ret += bytes(0x20 - len(ret)) ret += bytes(0x20 - len(ret))
ret += struct.pack("<l", user_id) ret += struct.pack("<l", user_id)
@@ -193,42 +244,54 @@ class AimedbProtocol(Protocol):
def handle_log(self, data: bytes) -> bytes: def handle_log(self, data: bytes) -> bytes:
# TODO: Save aimedb logs # TODO: Save aimedb logs
self.logger.info(f"log from {self.transport.getPeer().host}") self.logger.info(f"log from {self.transport.getPeer().host}")
ret = struct.pack("<5H", 0xa13e, 0x3087, self.AIMEDB_RESPONSE_CODES["log"], 0x0020, 0x0001) ret = struct.pack(
"<5H", 0xA13E, 0x3087, self.AIMEDB_RESPONSE_CODES["log"], 0x0020, 0x0001
)
return self.append_padding(ret) return self.append_padding(ret)
def handle_log2(self, data: bytes) -> bytes: def handle_log2(self, data: bytes) -> bytes:
self.logger.info(f"log2 from {self.transport.getPeer().host}") self.logger.info(f"log2 from {self.transport.getPeer().host}")
ret = struct.pack("<5H", 0xa13e, 0x3087, self.AIMEDB_RESPONSE_CODES["log2"], 0x0040, 0x0001) ret = struct.pack(
"<5H", 0xA13E, 0x3087, self.AIMEDB_RESPONSE_CODES["log2"], 0x0040, 0x0001
)
ret += bytes(22) ret += bytes(22)
ret += struct.pack("H", 1) ret += struct.pack("H", 1)
return self.append_padding(ret) return self.append_padding(ret)
class AimedbFactory(Factory): class AimedbFactory(Factory):
protocol = AimedbProtocol protocol = AimedbProtocol
def __init__(self, cfg: CoreConfig) -> None: def __init__(self, cfg: CoreConfig) -> None:
self.config = cfg self.config = cfg
log_fmt_str = "[%(asctime)s] Aimedb | %(levelname)s | %(message)s" log_fmt_str = "[%(asctime)s] Aimedb | %(levelname)s | %(message)s"
log_fmt = logging.Formatter(log_fmt_str) log_fmt = logging.Formatter(log_fmt_str)
self.logger = logging.getLogger("aimedb") self.logger = logging.getLogger("aimedb")
fileHandler = TimedRotatingFileHandler("{0}/{1}.log".format(self.config.server.log_dir, "aimedb"), when="d", backupCount=10) fileHandler = TimedRotatingFileHandler(
"{0}/{1}.log".format(self.config.server.log_dir, "aimedb"),
when="d",
backupCount=10,
)
fileHandler.setFormatter(log_fmt) fileHandler.setFormatter(log_fmt)
consoleHandler = logging.StreamHandler() consoleHandler = logging.StreamHandler()
consoleHandler.setFormatter(log_fmt) consoleHandler.setFormatter(log_fmt)
self.logger.addHandler(fileHandler) self.logger.addHandler(fileHandler)
self.logger.addHandler(consoleHandler) self.logger.addHandler(consoleHandler)
self.logger.setLevel(self.config.aimedb.loglevel) self.logger.setLevel(self.config.aimedb.loglevel)
coloredlogs.install(level=cfg.aimedb.loglevel, logger=self.logger, fmt=log_fmt_str) coloredlogs.install(
level=cfg.aimedb.loglevel, logger=self.logger, fmt=log_fmt_str
)
if self.config.aimedb.key == "": if self.config.aimedb.key == "":
self.logger.error("Please set 'key' field in your config file.") self.logger.error("Please set 'key' field in your config file.")
exit(1) exit(1)
self.logger.info(f"Ready on port {self.config.aimedb.port}") self.logger.info(f"Ready on port {self.config.aimedb.port}")
def buildProtocol(self, addr): def buildProtocol(self, addr):
return AimedbProtocol(self.config) return AimedbProtocol(self.config)
+126 -62
View File
@@ -16,8 +16,9 @@ from core.data import Data
from core.utils import Utils from core.utils import Utils
from core.const import * from core.const import *
class AllnetServlet: class AllnetServlet:
def __init__(self, core_cfg: CoreConfig, cfg_folder: str): def __init__(self, core_cfg: CoreConfig, cfg_folder: str):
super().__init__() super().__init__()
self.config = core_cfg self.config = core_cfg
self.config_folder = cfg_folder self.config_folder = cfg_folder
@@ -27,35 +28,45 @@ class AllnetServlet:
self.logger = logging.getLogger("allnet") self.logger = logging.getLogger("allnet")
if not hasattr(self.logger, "initialized"): if not hasattr(self.logger, "initialized"):
log_fmt_str = "[%(asctime)s] Allnet | %(levelname)s | %(message)s" log_fmt_str = "[%(asctime)s] Allnet | %(levelname)s | %(message)s"
log_fmt = logging.Formatter(log_fmt_str) log_fmt = logging.Formatter(log_fmt_str)
fileHandler = TimedRotatingFileHandler("{0}/{1}.log".format(self.config.server.log_dir, "allnet"), when="d", backupCount=10) fileHandler = TimedRotatingFileHandler(
"{0}/{1}.log".format(self.config.server.log_dir, "allnet"),
when="d",
backupCount=10,
)
fileHandler.setFormatter(log_fmt) fileHandler.setFormatter(log_fmt)
consoleHandler = logging.StreamHandler() consoleHandler = logging.StreamHandler()
consoleHandler.setFormatter(log_fmt) consoleHandler.setFormatter(log_fmt)
self.logger.addHandler(fileHandler) self.logger.addHandler(fileHandler)
self.logger.addHandler(consoleHandler) self.logger.addHandler(consoleHandler)
self.logger.setLevel(core_cfg.allnet.loglevel) self.logger.setLevel(core_cfg.allnet.loglevel)
coloredlogs.install(level=core_cfg.allnet.loglevel, logger=self.logger, fmt=log_fmt_str) coloredlogs.install(
level=core_cfg.allnet.loglevel, logger=self.logger, fmt=log_fmt_str
)
self.logger.initialized = True self.logger.initialized = True
plugins = Utils.get_all_titles() plugins = Utils.get_all_titles()
if len(plugins) == 0: if len(plugins) == 0:
self.logger.error("No games detected!") self.logger.error("No games detected!")
for _, mod in plugins.items(): for _, mod in plugins.items():
if hasattr(mod.index, "get_allnet_info"): if hasattr(mod.index, "get_allnet_info"):
for code in mod.game_codes: for code in mod.game_codes:
enabled, uri, host = mod.index.get_allnet_info(code, self.config, self.config_folder) enabled, uri, host = mod.index.get_allnet_info(
code, self.config, self.config_folder
)
if enabled: if enabled:
self.uri_registry[code] = (uri, host) self.uri_registry[code] = (uri, host)
self.logger.info(f"Allnet serving {len(self.uri_registry)} games on port {core_cfg.allnet.port}") self.logger.info(
f"Allnet serving {len(self.uri_registry)} games on port {core_cfg.allnet.port}"
)
def handle_poweron(self, request: Request, _: Dict): def handle_poweron(self, request: Request, _: Dict):
request_ip = request.getClientAddress().host request_ip = request.getClientAddress().host
@@ -67,14 +78,22 @@ class AllnetServlet:
req = AllnetPowerOnRequest(req_dict[0]) req = AllnetPowerOnRequest(req_dict[0])
# Validate the request. Currently we only validate the fields we plan on using # Validate the request. Currently we only validate the fields we plan on using
if not req.game_id or not req.ver or not req.token or not req.serial or not req.ip: if (
raise AllnetRequestException(f"Bad auth request params from {request_ip} - {vars(req)}") not req.game_id
or not req.ver
or not req.token
or not req.serial
or not req.ip
):
raise AllnetRequestException(
f"Bad auth request params from {request_ip} - {vars(req)}"
)
except AllnetRequestException as e: except AllnetRequestException as e:
if e.message != "": if e.message != "":
self.logger.error(e) self.logger.error(e)
return b"" return b""
if req.format_ver == 3: if req.format_ver == 3:
resp = AllnetPowerOnResponse3(req.token) resp = AllnetPowerOnResponse3(req.token)
else: else:
@@ -83,26 +102,32 @@ class AllnetServlet:
self.logger.debug(f"Allnet request: {vars(req)}") self.logger.debug(f"Allnet request: {vars(req)}")
if req.game_id not in self.uri_registry: if req.game_id not in self.uri_registry:
msg = f"Unrecognised game {req.game_id} attempted allnet auth from {request_ip}." msg = f"Unrecognised game {req.game_id} attempted allnet auth from {request_ip}."
self.data.base.log_event("allnet", "ALLNET_AUTH_UNKNOWN_GAME", logging.WARN, msg) self.data.base.log_event(
"allnet", "ALLNET_AUTH_UNKNOWN_GAME", logging.WARN, msg
)
self.logger.warn(msg) self.logger.warn(msg)
resp.stat = 0 resp.stat = 0
return self.dict_to_http_form_string([vars(resp)]) return self.dict_to_http_form_string([vars(resp)])
resp.uri, resp.host = self.uri_registry[req.game_id] resp.uri, resp.host = self.uri_registry[req.game_id]
machine = self.data.arcade.get_machine(req.serial) machine = self.data.arcade.get_machine(req.serial)
if machine is None and not self.config.server.allow_unregistered_serials: if machine is None and not self.config.server.allow_unregistered_serials:
msg = f"Unrecognised serial {req.serial} attempted allnet auth from {request_ip}." msg = f"Unrecognised serial {req.serial} attempted allnet auth from {request_ip}."
self.data.base.log_event("allnet", "ALLNET_AUTH_UNKNOWN_SERIAL", logging.WARN, msg) self.data.base.log_event(
"allnet", "ALLNET_AUTH_UNKNOWN_SERIAL", logging.WARN, msg
)
self.logger.warn(msg) self.logger.warn(msg)
resp.stat = 0 resp.stat = 0
return self.dict_to_http_form_string([vars(resp)]) return self.dict_to_http_form_string([vars(resp)])
if machine is not None: if machine is not None:
arcade = self.data.arcade.get_arcade(machine["arcade"]) arcade = self.data.arcade.get_arcade(machine["arcade"])
country = arcade["country"] if machine["country"] is None else machine["country"] country = (
arcade["country"] if machine["country"] is None else machine["country"]
)
if country is None: if country is None:
country = AllnetCountryCode.JAPAN.value country = AllnetCountryCode.JAPAN.value
@@ -111,16 +136,30 @@ class AllnetServlet:
resp.allnet_id = machine["id"] resp.allnet_id = machine["id"]
resp.name = arcade["name"] if arcade["name"] is not None else "" resp.name = arcade["name"] if arcade["name"] is not None else ""
resp.nickname = arcade["nickname"] if arcade["nickname"] is not None else "" resp.nickname = arcade["nickname"] if arcade["nickname"] is not None else ""
resp.region0 = arcade["region_id"] if arcade["region_id"] is not None else AllnetJapanRegionId.AICHI.value resp.region0 = (
resp.region_name0 = arcade["country"] if arcade["country"] is not None else AllnetCountryCode.JAPAN.value arcade["region_id"]
resp.region_name1 = arcade["state"] if arcade["state"] is not None else AllnetJapanRegionId.AICHI.name if arcade["region_id"] is not None
else AllnetJapanRegionId.AICHI.value
)
resp.region_name0 = (
arcade["country"]
if arcade["country"] is not None
else AllnetCountryCode.JAPAN.value
)
resp.region_name1 = (
arcade["state"]
if arcade["state"] is not None
else AllnetJapanRegionId.AICHI.name
)
resp.region_name2 = arcade["city"] if arcade["city"] is not None else "" resp.region_name2 = arcade["city"] if arcade["city"] is not None else ""
resp.client_timezone = arcade["timezone"] if arcade["timezone"] is not None else "+0900" resp.client_timezone = (
arcade["timezone"] if arcade["timezone"] is not None else "+0900"
)
int_ver = req.ver.replace(".", "") int_ver = req.ver.replace(".", "")
resp.uri = resp.uri.replace("$v", int_ver) resp.uri = resp.uri.replace("$v", int_ver)
resp.host = resp.host.replace("$v", int_ver) resp.host = resp.host.replace("$v", int_ver)
msg = f"{req.serial} authenticated from {request_ip}: {req.game_id} v{req.ver}" msg = f"{req.serial} authenticated from {request_ip}: {req.game_id} v{req.ver}"
self.data.base.log_event("allnet", "ALLNET_AUTH_SUCCESS", logging.INFO, msg) self.data.base.log_event("allnet", "ALLNET_AUTH_SUCCESS", logging.INFO, msg)
self.logger.info(msg) self.logger.info(msg)
@@ -139,8 +178,10 @@ class AllnetServlet:
# Validate the request. Currently we only validate the fields we plan on using # Validate the request. Currently we only validate the fields we plan on using
if not req.game_id or not req.ver or not req.serial: if not req.game_id or not req.ver or not req.serial:
raise AllnetRequestException(f"Bad download request params from {request_ip} - {vars(req)}") raise AllnetRequestException(
f"Bad download request params from {request_ip} - {vars(req)}"
)
except AllnetRequestException as e: except AllnetRequestException as e:
if e.message != "": if e.message != "":
self.logger.error(e) self.logger.error(e)
@@ -149,8 +190,8 @@ class AllnetServlet:
resp = AllnetDownloadOrderResponse() resp = AllnetDownloadOrderResponse()
if not self.config.allnet.allow_online_updates: if not self.config.allnet.allow_online_updates:
return self.dict_to_http_form_string([vars(resp)]) return self.dict_to_http_form_string([vars(resp)])
else: # TODO: Actual dlorder response else: # TODO: Actual dlorder response
return self.dict_to_http_form_string([vars(resp)]) return self.dict_to_http_form_string([vars(resp)])
def handle_billing_request(self, request: Request, _: Dict): def handle_billing_request(self, request: Request, _: Dict):
@@ -159,10 +200,10 @@ class AllnetServlet:
if req_dict is None: if req_dict is None:
self.logger.error(f"Failed to parse request {request.content.getvalue()}") self.logger.error(f"Failed to parse request {request.content.getvalue()}")
return b"" return b""
self.logger.debug(f"request {req_dict}") self.logger.debug(f"request {req_dict}")
rsa = RSA.import_key(open(self.config.billing.signing_key, 'rb').read()) rsa = RSA.import_key(open(self.config.billing.signing_key, "rb").read())
signer = PKCS1_v1_5.new(rsa) signer = PKCS1_v1_5.new(rsa)
digest = SHA.new() digest = SHA.new()
@@ -178,30 +219,34 @@ class AllnetServlet:
machine = self.data.arcade.get_machine(kc_serial) machine = self.data.arcade.get_machine(kc_serial)
if machine is None and not self.config.server.allow_unregistered_serials: if machine is None and not self.config.server.allow_unregistered_serials:
msg = f"Unrecognised serial {kc_serial} attempted billing checkin from {request_ip} for game {kc_game}." msg = f"Unrecognised serial {kc_serial} attempted billing checkin from {request_ip} for game {kc_game}."
self.data.base.log_event("allnet", "BILLING_CHECKIN_NG_SERIAL", logging.WARN, msg) self.data.base.log_event(
"allnet", "BILLING_CHECKIN_NG_SERIAL", logging.WARN, msg
)
self.logger.warn(msg) self.logger.warn(msg)
resp = BillingResponse("", "", "", "") resp = BillingResponse("", "", "", "")
resp.result = "1" resp.result = "1"
return self.dict_to_http_form_string([vars(resp)]) return self.dict_to_http_form_string([vars(resp)])
msg = f"Billing checkin from {request.getClientIP()}: game {kc_game} keychip {kc_serial} playcount " \ msg = (
f"Billing checkin from {request.getClientIP()}: game {kc_game} keychip {kc_serial} playcount "
f"{kc_playcount} billing_type {kc_billigtype} nearfull {kc_nearfull} playlimit {kc_playlimit}" f"{kc_playcount} billing_type {kc_billigtype} nearfull {kc_nearfull} playlimit {kc_playlimit}"
)
self.logger.info(msg) self.logger.info(msg)
self.data.base.log_event('billing', 'BILLING_CHECKIN_OK', logging.INFO, msg) self.data.base.log_event("billing", "BILLING_CHECKIN_OK", logging.INFO, msg)
while kc_playcount > kc_playlimit: while kc_playcount > kc_playlimit:
kc_playlimit += 1024 kc_playlimit += 1024
kc_nearfull += 1024 kc_nearfull += 1024
playlimit = kc_playlimit playlimit = kc_playlimit
nearfull = kc_nearfull + (kc_billigtype * 0x00010000) nearfull = kc_nearfull + (kc_billigtype * 0x00010000)
digest.update(playlimit.to_bytes(4, 'little') + kc_serial_bytes) digest.update(playlimit.to_bytes(4, "little") + kc_serial_bytes)
playlimit_sig = signer.sign(digest).hex() playlimit_sig = signer.sign(digest).hex()
digest = SHA.new() digest = SHA.new()
digest.update(nearfull.to_bytes(4, 'little') + kc_serial_bytes) digest.update(nearfull.to_bytes(4, "little") + kc_serial_bytes)
nearfull_sig = signer.sign(digest).hex() nearfull_sig = signer.sign(digest).hex()
# TODO: playhistory # TODO: playhistory
@@ -222,16 +267,16 @@ class AllnetServlet:
def kvp_to_dict(self, kvp: List[str]) -> List[Dict[str, Any]]: def kvp_to_dict(self, kvp: List[str]) -> List[Dict[str, Any]]:
ret: List[Dict[str, Any]] = [] ret: List[Dict[str, Any]] = []
for x in kvp: for x in kvp:
items = x.split('&') items = x.split("&")
tmp = {} tmp = {}
for item in items: for item in items:
kvp = item.split('=') kvp = item.split("=")
if len(kvp) == 2: if len(kvp) == 2:
tmp[kvp[0]] = kvp[1] tmp[kvp[0]] = kvp[1]
ret.append(tmp) ret.append(tmp)
return ret return ret
def billing_req_to_dict(self, data: bytes): def billing_req_to_dict(self, data: bytes):
@@ -241,8 +286,8 @@ class AllnetServlet:
try: try:
decomp = zlib.decompressobj(-zlib.MAX_WBITS) decomp = zlib.decompressobj(-zlib.MAX_WBITS)
unzipped = decomp.decompress(data) unzipped = decomp.decompress(data)
sections = unzipped.decode('ascii').split('\r\n') sections = unzipped.decode("ascii").split("\r\n")
return self.kvp_to_dict(sections) return self.kvp_to_dict(sections)
except Exception as e: except Exception as e:
@@ -252,33 +297,38 @@ class AllnetServlet:
def allnet_req_to_dict(self, data: str) -> Optional[List[Dict[str, Any]]]: def allnet_req_to_dict(self, data: str) -> Optional[List[Dict[str, Any]]]:
""" """
Parses an allnet request string into a python dictionary Parses an allnet request string into a python dictionary
""" """
try: try:
zipped = base64.b64decode(data) zipped = base64.b64decode(data)
unzipped = zlib.decompress(zipped) unzipped = zlib.decompress(zipped)
sections = unzipped.decode('utf-8').split('\r\n') sections = unzipped.decode("utf-8").split("\r\n")
return self.kvp_to_dict(sections) return self.kvp_to_dict(sections)
except Exception as e: except Exception as e:
self.logger.error(f"allnet_req_to_dict: {e} while parsing {data}") self.logger.error(f"allnet_req_to_dict: {e} while parsing {data}")
return None return None
def dict_to_http_form_string(self, data:List[Dict[str, Any]], crlf: bool = False, trailing_newline: bool = True) -> Optional[str]: def dict_to_http_form_string(
self,
data: List[Dict[str, Any]],
crlf: bool = False,
trailing_newline: bool = True,
) -> Optional[str]:
""" """
Takes a python dictionary and parses it into an allnet response string Takes a python dictionary and parses it into an allnet response string
""" """
try: try:
urlencode = "" urlencode = ""
for item in data: for item in data:
for k,v in item.items(): for k, v in item.items():
urlencode += f"{k}={v}&" urlencode += f"{k}={v}&"
if crlf: if crlf:
urlencode = urlencode[:-1] + "\r\n" urlencode = urlencode[:-1] + "\r\n"
else: else:
urlencode = urlencode[:-1] + "\n" urlencode = urlencode[:-1] + "\n"
if not trailing_newline: if not trailing_newline:
if crlf: if crlf:
urlencode = urlencode[:-2] urlencode = urlencode[:-2]
@@ -286,23 +336,24 @@ class AllnetServlet:
urlencode = urlencode[:-1] urlencode = urlencode[:-1]
return urlencode return urlencode
except Exception as e: except Exception as e:
self.logger.error(f"dict_to_http_form_string: {e} while parsing {data}") self.logger.error(f"dict_to_http_form_string: {e} while parsing {data}")
return None return None
class AllnetPowerOnRequest():
class AllnetPowerOnRequest:
def __init__(self, req: Dict) -> None: def __init__(self, req: Dict) -> None:
if req is None: if req is None:
raise AllnetRequestException("Request processing failed") raise AllnetRequestException("Request processing failed")
self.game_id: str = req["game_id"] if "game_id" in req else "" self.game_id: str = req["game_id"] if "game_id" in req else ""
self.ver: str = req["ver"] if "ver" in req else "" self.ver: str = req["ver"] if "ver" in req else ""
self.serial: str = req["serial"] if "serial" in req else "" self.serial: str = req["serial"] if "serial" in req else ""
self.ip: str = req["ip"] if "ip" in req else "" self.ip: str = req["ip"] if "ip" in req else ""
self.firm_ver: str = req["firm_ver"] if "firm_ver" in req else "" self.firm_ver: str = req["firm_ver"] if "firm_ver" in req else ""
self.boot_ver: str = req["boot_ver"] if "boot_ver" in req else "" self.boot_ver: str = req["boot_ver"] if "boot_ver" in req else ""
self.encode: str = req["encode"] if "encode" in req else "" self.encode: str = req["encode"] if "encode" in req else ""
try: try:
self.hops = int(req["hops"]) if "hops" in req else 0 self.hops = int(req["hops"]) if "hops" in req else 0
self.format_ver = int(req["format_ver"]) if "format_ver" in req else 2 self.format_ver = int(req["format_ver"]) if "format_ver" in req else 2
@@ -310,7 +361,8 @@ class AllnetPowerOnRequest():
except ValueError as e: except ValueError as e:
raise AllnetRequestException(f"Failed to parse int: {e}") raise AllnetRequestException(f"Failed to parse int: {e}")
class AllnetPowerOnResponse3():
class AllnetPowerOnResponse3:
def __init__(self, token) -> None: def __init__(self, token) -> None:
self.stat = 1 self.stat = 1
self.uri = "" self.uri = ""
@@ -326,12 +378,15 @@ class AllnetPowerOnResponse3():
self.country = "JPN" self.country = "JPN"
self.allnet_id = "123" self.allnet_id = "123"
self.client_timezone = "+0900" self.client_timezone = "+0900"
self.utc_time = datetime.now(tz=pytz.timezone('UTC')).strftime("%Y-%m-%dT%H:%M:%SZ") self.utc_time = datetime.now(tz=pytz.timezone("UTC")).strftime(
"%Y-%m-%dT%H:%M:%SZ"
)
self.setting = "" self.setting = ""
self.res_ver = "3" self.res_ver = "3"
self.token = str(token) self.token = str(token)
class AllnetPowerOnResponse2():
class AllnetPowerOnResponse2:
def __init__(self) -> None: def __init__(self) -> None:
self.stat = 1 self.stat = 1
self.uri = "" self.uri = ""
@@ -355,23 +410,31 @@ class AllnetPowerOnResponse2():
self.timezone = "+0900" self.timezone = "+0900"
self.res_class = "PowerOnResponseV2" self.res_class = "PowerOnResponseV2"
class AllnetDownloadOrderRequest():
class AllnetDownloadOrderRequest:
def __init__(self, req: Dict) -> None: def __init__(self, req: Dict) -> None:
self.game_id = req["game_id"] if "game_id" in req else "" self.game_id = req["game_id"] if "game_id" in req else ""
self.ver = req["ver"] if "ver" in req else "" self.ver = req["ver"] if "ver" in req else ""
self.serial = req["serial"] if "serial" in req else "" self.serial = req["serial"] if "serial" in req else ""
self.encode = req["encode"] if "encode" in req else "" self.encode = req["encode"] if "encode" in req else ""
class AllnetDownloadOrderResponse():
class AllnetDownloadOrderResponse:
def __init__(self, stat: int = 1, serial: str = "", uri: str = "null") -> None: def __init__(self, stat: int = 1, serial: str = "", uri: str = "null") -> None:
self.stat = stat self.stat = stat
self.serial = serial self.serial = serial
self.uri = uri self.uri = uri
class BillingResponse():
def __init__(self, playlimit: str = "", playlimit_sig: str = "", nearfull: str = "", nearfull_sig: str = "",
playhistory: str = "000000/0:000000/0:000000/0") -> None:
class BillingResponse:
def __init__(
self,
playlimit: str = "",
playlimit_sig: str = "",
nearfull: str = "",
nearfull_sig: str = "",
playhistory: str = "000000/0:000000/0:000000/0",
) -> None:
self.result = "0" self.result = "0"
self.waitime = "100" self.waitime = "100"
self.linelimit = "1" self.linelimit = "1"
@@ -383,10 +446,11 @@ class BillingResponse():
self.nearfullsig = nearfull_sig self.nearfullsig = nearfull_sig
self.fixlogincnt = "0" self.fixlogincnt = "0"
self.fixinterval = "5" self.fixinterval = "5"
self.playhistory = playhistory self.playhistory = playhistory
# playhistory -> YYYYMM/C:... # playhistory -> YYYYMM/C:...
# YYYY -> 4 digit year, MM -> 2 digit month, C -> Playcount during that period # YYYY -> 4 digit year, MM -> 2 digit month, C -> Playcount during that period
class AllnetRequestException(Exception): class AllnetRequestException(Exception):
def __init__(self, message="") -> None: def __init__(self, message="") -> None:
self.message = message self.message = message
+159 -56
View File
@@ -1,33 +1,47 @@
import logging, os import logging, os
from typing import Any from typing import Any
class ServerConfig: class ServerConfig:
def __init__(self, parent_config: "CoreConfig") -> None: def __init__(self, parent_config: "CoreConfig") -> None:
self.__config = parent_config self.__config = parent_config
@property @property
def listen_address(self) -> str: def listen_address(self) -> str:
return CoreConfig.get_config_field(self.__config, 'core', 'server', 'listen_address', default='127.0.0.1') return CoreConfig.get_config_field(
self.__config, "core", "server", "listen_address", default="127.0.0.1"
)
@property @property
def allow_user_registration(self) -> bool: def allow_user_registration(self) -> bool:
return CoreConfig.get_config_field(self.__config, 'core', 'server', 'allow_user_registration', default=True) return CoreConfig.get_config_field(
self.__config, "core", "server", "allow_user_registration", default=True
)
@property @property
def allow_unregistered_serials(self) -> bool: def allow_unregistered_serials(self) -> bool:
return CoreConfig.get_config_field(self.__config, 'core', 'server', 'allow_unregistered_serials', default=True) return CoreConfig.get_config_field(
self.__config, "core", "server", "allow_unregistered_serials", default=True
)
@property @property
def name(self) -> str: def name(self) -> str:
return CoreConfig.get_config_field(self.__config, 'core', 'server', 'name', default="ARTEMiS") return CoreConfig.get_config_field(
self.__config, "core", "server", "name", default="ARTEMiS"
)
@property @property
def is_develop(self) -> bool: def is_develop(self) -> bool:
return CoreConfig.get_config_field(self.__config, 'core', 'server', 'is_develop', default=True) return CoreConfig.get_config_field(
self.__config, "core", "server", "is_develop", default=True
)
@property @property
def log_dir(self) -> str: def log_dir(self) -> str:
return CoreConfig.get_config_field(self.__config, 'core', 'server', 'log_dir', default='logs') return CoreConfig.get_config_field(
self.__config, "core", "server", "log_dir", default="logs"
)
class TitleConfig: class TitleConfig:
def __init__(self, parent_config: "CoreConfig") -> None: def __init__(self, parent_config: "CoreConfig") -> None:
@@ -35,15 +49,24 @@ class TitleConfig:
@property @property
def loglevel(self) -> int: def loglevel(self) -> int:
return CoreConfig.str_to_loglevel(CoreConfig.get_config_field(self.__config, 'core', 'title', 'loglevel', default="info")) return CoreConfig.str_to_loglevel(
CoreConfig.get_config_field(
self.__config, "core", "title", "loglevel", default="info"
)
)
@property @property
def hostname(self) -> str: def hostname(self) -> str:
return CoreConfig.get_config_field(self.__config, 'core', 'title', 'hostname', default="localhost") return CoreConfig.get_config_field(
self.__config, "core", "title", "hostname", default="localhost"
)
@property @property
def port(self) -> int: def port(self) -> int:
return CoreConfig.get_config_field(self.__config, 'core', 'title', 'port', default=8080) return CoreConfig.get_config_field(
self.__config, "core", "title", "port", default=8080
)
class DatabaseConfig: class DatabaseConfig:
def __init__(self, parent_config: "CoreConfig") -> None: def __init__(self, parent_config: "CoreConfig") -> None:
@@ -51,43 +74,70 @@ class DatabaseConfig:
@property @property
def host(self) -> str: def host(self) -> str:
return CoreConfig.get_config_field(self.__config, 'core', 'database', 'host', default="localhost") return CoreConfig.get_config_field(
self.__config, "core", "database", "host", default="localhost"
)
@property @property
def username(self) -> str: def username(self) -> str:
return CoreConfig.get_config_field(self.__config, 'core', 'database', 'username', default='aime') return CoreConfig.get_config_field(
self.__config, "core", "database", "username", default="aime"
)
@property @property
def password(self) -> str: def password(self) -> str:
return CoreConfig.get_config_field(self.__config, 'core', 'database', 'password', default='aime') return CoreConfig.get_config_field(
self.__config, "core", "database", "password", default="aime"
)
@property @property
def name(self) -> str: def name(self) -> str:
return CoreConfig.get_config_field(self.__config, 'core', 'database', 'name', default='aime') return CoreConfig.get_config_field(
self.__config, "core", "database", "name", default="aime"
)
@property @property
def port(self) -> int: def port(self) -> int:
return CoreConfig.get_config_field(self.__config, 'core', 'database', 'port', default=3306) return CoreConfig.get_config_field(
self.__config, "core", "database", "port", default=3306
)
@property @property
def protocol(self) -> str: def protocol(self) -> str:
return CoreConfig.get_config_field(self.__config, 'core', 'database', 'type', default="mysql") return CoreConfig.get_config_field(
self.__config, "core", "database", "type", default="mysql"
)
@property @property
def sha2_password(self) -> bool: def sha2_password(self) -> bool:
return CoreConfig.get_config_field(self.__config, 'core', 'database', 'sha2_password', default=False) return CoreConfig.get_config_field(
self.__config, "core", "database", "sha2_password", default=False
)
@property @property
def loglevel(self) -> int: def loglevel(self) -> int:
return CoreConfig.str_to_loglevel(CoreConfig.get_config_field(self.__config, 'core', 'database', 'loglevel', default="info")) return CoreConfig.str_to_loglevel(
CoreConfig.get_config_field(
self.__config, "core", "database", "loglevel", default="info"
)
)
@property @property
def user_table_autoincrement_start(self) -> int: def user_table_autoincrement_start(self) -> int:
return CoreConfig.get_config_field(self.__config, 'core', 'database', 'user_table_autoincrement_start', default=10000) return CoreConfig.get_config_field(
self.__config,
"core",
"database",
"user_table_autoincrement_start",
default=10000,
)
@property @property
def memcached_host(self) -> str: def memcached_host(self) -> str:
return CoreConfig.get_config_field(self.__config, 'core', 'database', 'memcached_host', default="localhost") return CoreConfig.get_config_field(
self.__config, "core", "database", "memcached_host", default="localhost"
)
class FrontendConfig: class FrontendConfig:
def __init__(self, parent_config: "CoreConfig") -> None: def __init__(self, parent_config: "CoreConfig") -> None:
@@ -95,15 +145,24 @@ class FrontendConfig:
@property @property
def enable(self) -> int: def enable(self) -> int:
return CoreConfig.get_config_field(self.__config, 'core', 'frontend', 'enable', default=False) return CoreConfig.get_config_field(
self.__config, "core", "frontend", "enable", default=False
)
@property @property
def port(self) -> int: def port(self) -> int:
return CoreConfig.get_config_field(self.__config, 'core', 'frontend', 'port', default=8090) return CoreConfig.get_config_field(
self.__config, "core", "frontend", "port", default=8090
)
@property @property
def loglevel(self) -> int: def loglevel(self) -> int:
return CoreConfig.str_to_loglevel(CoreConfig.get_config_field(self.__config, 'core', 'frontend', 'loglevel', default="info")) return CoreConfig.str_to_loglevel(
CoreConfig.get_config_field(
self.__config, "core", "frontend", "loglevel", default="info"
)
)
class AllnetConfig: class AllnetConfig:
def __init__(self, parent_config: "CoreConfig") -> None: def __init__(self, parent_config: "CoreConfig") -> None:
@@ -111,15 +170,24 @@ class AllnetConfig:
@property @property
def loglevel(self) -> int: def loglevel(self) -> int:
return CoreConfig.str_to_loglevel(CoreConfig.get_config_field(self.__config, 'core', 'allnet', 'loglevel', default="info")) return CoreConfig.str_to_loglevel(
CoreConfig.get_config_field(
self.__config, "core", "allnet", "loglevel", default="info"
)
)
@property @property
def port(self) -> int: def port(self) -> int:
return CoreConfig.get_config_field(self.__config, 'core', 'allnet', 'port', default=80) return CoreConfig.get_config_field(
self.__config, "core", "allnet", "port", default=80
)
@property @property
def allow_online_updates(self) -> int: def allow_online_updates(self) -> int:
return CoreConfig.get_config_field(self.__config, 'core', 'allnet', 'allow_online_updates', default=False) return CoreConfig.get_config_field(
self.__config, "core", "allnet", "allow_online_updates", default=False
)
class BillingConfig: class BillingConfig:
def __init__(self, parent_config: "CoreConfig") -> None: def __init__(self, parent_config: "CoreConfig") -> None:
@@ -127,35 +195,53 @@ class BillingConfig:
@property @property
def port(self) -> int: def port(self) -> int:
return CoreConfig.get_config_field(self.__config, 'core', 'billing', 'port', default=8443) return CoreConfig.get_config_field(
self.__config, "core", "billing", "port", default=8443
)
@property @property
def ssl_key(self) -> str: def ssl_key(self) -> str:
return CoreConfig.get_config_field(self.__config, 'core', 'billing', 'ssl_key', default="cert/server.key") return CoreConfig.get_config_field(
self.__config, "core", "billing", "ssl_key", default="cert/server.key"
)
@property @property
def ssl_cert(self) -> str: def ssl_cert(self) -> str:
return CoreConfig.get_config_field(self.__config, 'core', 'billing', 'ssl_cert', default="cert/server.pem") return CoreConfig.get_config_field(
self.__config, "core", "billing", "ssl_cert", default="cert/server.pem"
)
@property @property
def signing_key(self) -> str: def signing_key(self) -> str:
return CoreConfig.get_config_field(self.__config, 'core', 'billing', 'signing_key', default="cert/billing.key") return CoreConfig.get_config_field(
self.__config, "core", "billing", "signing_key", default="cert/billing.key"
)
class AimedbConfig: class AimedbConfig:
def __init__(self, parent_config: "CoreConfig") -> None: def __init__(self, parent_config: "CoreConfig") -> None:
self.__config = parent_config self.__config = parent_config
@property @property
def loglevel(self) -> int: def loglevel(self) -> int:
return CoreConfig.str_to_loglevel(CoreConfig.get_config_field(self.__config, 'core', 'aimedb', 'loglevel', default="info")) return CoreConfig.str_to_loglevel(
CoreConfig.get_config_field(
self.__config, "core", "aimedb", "loglevel", default="info"
)
)
@property @property
def port(self) -> int: def port(self) -> int:
return CoreConfig.get_config_field(self.__config, 'core', 'aimedb', 'port', default=22345) return CoreConfig.get_config_field(
self.__config, "core", "aimedb", "port", default=22345
)
@property @property
def key(self) -> str: def key(self) -> str:
return CoreConfig.get_config_field(self.__config, 'core', 'aimedb', 'key', default="") return CoreConfig.get_config_field(
self.__config, "core", "aimedb", "key", default=""
)
class MuchaConfig: class MuchaConfig:
def __init__(self, parent_config: "CoreConfig") -> None: def __init__(self, parent_config: "CoreConfig") -> None:
@@ -163,27 +249,42 @@ class MuchaConfig:
@property @property
def enable(self) -> int: def enable(self) -> int:
return CoreConfig.get_config_field(self.__config, 'core', 'mucha', 'enable', default=False) return CoreConfig.get_config_field(
self.__config, "core", "mucha", "enable", default=False
)
@property @property
def loglevel(self) -> int: def loglevel(self) -> int:
return CoreConfig.str_to_loglevel(CoreConfig.get_config_field(self.__config, 'core', 'mucha', 'loglevel', default="info")) return CoreConfig.str_to_loglevel(
CoreConfig.get_config_field(
self.__config, "core", "mucha", "loglevel", default="info"
)
)
@property @property
def hostname(self) -> str: def hostname(self) -> str:
return CoreConfig.get_config_field(self.__config, 'core', 'mucha', 'hostname', default="localhost") return CoreConfig.get_config_field(
self.__config, "core", "mucha", "hostname", default="localhost"
)
@property @property
def port(self) -> int: def port(self) -> int:
return CoreConfig.get_config_field(self.__config, 'core', 'mucha', 'port', default=8444) return CoreConfig.get_config_field(
self.__config, "core", "mucha", "port", default=8444
)
@property @property
def ssl_cert(self) -> str: def ssl_cert(self) -> str:
return CoreConfig.get_config_field(self.__config, 'core', 'mucha', 'ssl_cert', default="cert/server.pem") return CoreConfig.get_config_field(
self.__config, "core", "mucha", "ssl_cert", default="cert/server.pem"
)
@property @property
def signing_key(self) -> str: def signing_key(self) -> str:
return CoreConfig.get_config_field(self.__config, 'core', 'mucha', 'signing_key', default="cert/billing.key") return CoreConfig.get_config_field(
self.__config, "core", "mucha", "signing_key", default="cert/billing.key"
)
class CoreConfig(dict): class CoreConfig(dict):
def __init__(self) -> None: def __init__(self) -> None:
@@ -200,20 +301,22 @@ class CoreConfig(dict):
def str_to_loglevel(cls, level_str: str): def str_to_loglevel(cls, level_str: str):
if level_str.lower() == "error": if level_str.lower() == "error":
return logging.ERROR return logging.ERROR
elif level_str.lower().startswith("warn"): # Fits warn or warning elif level_str.lower().startswith("warn"): # Fits warn or warning
return logging.WARN return logging.WARN
elif level_str.lower() == "debug": elif level_str.lower() == "debug":
return logging.DEBUG return logging.DEBUG
else: else:
return logging.INFO return logging.INFO
@classmethod @classmethod
def get_config_field(cls, __config: dict, module, *path: str, default: Any = "") -> Any: def get_config_field(
envKey = f'CFG_{module}_' cls, __config: dict, module, *path: str, default: Any = ""
) -> Any:
envKey = f"CFG_{module}_"
for arg in path: for arg in path:
envKey += arg + '_' envKey += arg + "_"
if envKey.endswith('_'): if envKey.endswith("_"):
envKey = envKey[:-1] envKey = envKey[:-1]
if envKey in os.environ: if envKey in os.environ:
+8 -3
View File
@@ -1,6 +1,7 @@
from enum import Enum from enum import Enum
class MainboardPlatformCodes():
class MainboardPlatformCodes:
RINGEDGE = "AALE" RINGEDGE = "AALE"
RINGWIDE = "AAML" RINGWIDE = "AAML"
NU = "AAVE" NU = "AAVE"
@@ -8,7 +9,8 @@ class MainboardPlatformCodes():
ALLS_UX = "ACAE" ALLS_UX = "ACAE"
ALLS_HX = "ACAX" ALLS_HX = "ACAX"
class MainboardRevisions():
class MainboardRevisions:
RINGEDGE = 1 RINGEDGE = 1
RINGEDGE2 = 2 RINGEDGE2 = 2
@@ -26,12 +28,14 @@ class MainboardRevisions():
ALLS_UX2 = 2 ALLS_UX2 = 2
ALLS_HX2 = 12 ALLS_HX2 = 12
class KeychipPlatformsCodes():
class KeychipPlatformsCodes:
RING = "A72E" RING = "A72E"
NU = ("A60E", "A60E", "A60E") NU = ("A60E", "A60E", "A60E")
NUSX = ("A61X", "A69X") NUSX = ("A61X", "A69X")
ALLS = "A63E" ALLS = "A63E"
class AllnetCountryCode(Enum): class AllnetCountryCode(Enum):
JAPAN = "JPN" JAPAN = "JPN"
UNITED_STATES = "USA" UNITED_STATES = "USA"
@@ -41,6 +45,7 @@ class AllnetCountryCode(Enum):
TAIWAN = "TWN" TAIWAN = "TWN"
CHINA = "CHN" CHINA = "CHN"
class AllnetJapanRegionId(Enum): class AllnetJapanRegionId(Enum):
NONE = 0 NONE = 0
AICHI = 1 AICHI = 1
+1 -1
View File
@@ -1,2 +1,2 @@
from core.data.database import Data from core.data.database import Data
from core.data.cache import cached from core.data.cache import cached
+8 -6
View File
@@ -1,4 +1,3 @@
from typing import Any, Callable from typing import Any, Callable
from functools import wraps from functools import wraps
import hashlib import hashlib
@@ -6,15 +5,17 @@ import pickle
import logging import logging
from core.config import CoreConfig from core.config import CoreConfig
cfg:CoreConfig = None # type: ignore cfg: CoreConfig = None # type: ignore
# Make memcache optional # Make memcache optional
try: try:
import pylibmc # type: ignore import pylibmc # type: ignore
has_mc = True has_mc = True
except ModuleNotFoundError: except ModuleNotFoundError:
has_mc = False has_mc = False
def cached(lifetime: int=10, extra_key: Any=None) -> Callable:
def cached(lifetime: int = 10, extra_key: Any = None) -> Callable:
def _cached(func: Callable) -> Callable: def _cached(func: Callable) -> Callable:
if has_mc: if has_mc:
hostname = "127.0.0.1" hostname = "127.0.0.1"
@@ -22,11 +23,10 @@ def cached(lifetime: int=10, extra_key: Any=None) -> Callable:
hostname = cfg.database.memcached_host hostname = cfg.database.memcached_host
memcache = pylibmc.Client([hostname], binary=True) memcache = pylibmc.Client([hostname], binary=True)
memcache.behaviors = {"tcp_nodelay": True, "ketama": True} memcache.behaviors = {"tcp_nodelay": True, "ketama": True}
@wraps(func) @wraps(func)
def wrapper(*args: Any, **kwargs: Any) -> Any: def wrapper(*args: Any, **kwargs: Any) -> Any:
if lifetime is not None: if lifetime is not None:
# Hash function args # Hash function args
items = kwargs.items() items = kwargs.items()
hashable_args = (args[1:], sorted(list(items))) hashable_args = (args[1:], sorted(list(items)))
@@ -41,7 +41,7 @@ def cached(lifetime: int=10, extra_key: Any=None) -> Callable:
except pylibmc.Error as e: except pylibmc.Error as e:
logging.getLogger("database").error(f"Memcache failed: {e}") logging.getLogger("database").error(f"Memcache failed: {e}")
result = None result = None
if result is not None: if result is not None:
logging.getLogger("database").debug(f"Cache hit: {result}") logging.getLogger("database").debug(f"Cache hit: {result}")
return result return result
@@ -55,7 +55,9 @@ def cached(lifetime: int=10, extra_key: Any=None) -> Callable:
memcache.set(cache_key, result, lifetime) memcache.set(cache_key, result, lifetime)
return result return result
else: else:
@wraps(func) @wraps(func)
def wrapper(*args: Any, **kwargs: Any) -> Any: def wrapper(*args: Any, **kwargs: Any) -> Any:
return func(*args, **kwargs) return func(*args, **kwargs)
+88 -49
View File
@@ -13,6 +13,7 @@ from core.config import CoreConfig
from core.data.schema import * from core.data.schema import *
from core.utils import Utils from core.utils import Utils
class Data: class Data:
def __init__(self, cfg: CoreConfig) -> None: def __init__(self, cfg: CoreConfig) -> None:
self.config = cfg self.config = cfg
@@ -22,7 +23,7 @@ class Data:
self.__url = f"{self.config.database.protocol}://{self.config.database.username}:{passwd.hex()}@{self.config.database.host}/{self.config.database.name}?charset=utf8mb4" self.__url = f"{self.config.database.protocol}://{self.config.database.username}:{passwd.hex()}@{self.config.database.host}/{self.config.database.name}?charset=utf8mb4"
else: else:
self.__url = f"{self.config.database.protocol}://{self.config.database.username}:{self.config.database.password}@{self.config.database.host}/{self.config.database.name}?charset=utf8mb4" self.__url = f"{self.config.database.protocol}://{self.config.database.username}:{self.config.database.password}@{self.config.database.host}/{self.config.database.name}?charset=utf8mb4"
self.__engine = create_engine(self.__url, pool_recycle=3600) self.__engine = create_engine(self.__url, pool_recycle=3600)
session = sessionmaker(bind=self.__engine, autoflush=True, autocommit=True) session = sessionmaker(bind=self.__engine, autoflush=True, autocommit=True)
self.session = scoped_session(session) self.session = scoped_session(session)
@@ -38,11 +39,15 @@ class Data:
self.logger = logging.getLogger("database") self.logger = logging.getLogger("database")
# Prevent the logger from adding handlers multiple times # Prevent the logger from adding handlers multiple times
if not getattr(self.logger, 'handler_set', None): if not getattr(self.logger, "handler_set", None):
fileHandler = TimedRotatingFileHandler("{0}/{1}.log".format(self.config.server.log_dir, "db"), encoding="utf-8", fileHandler = TimedRotatingFileHandler(
when="d", backupCount=10) "{0}/{1}.log".format(self.config.server.log_dir, "db"),
encoding="utf-8",
when="d",
backupCount=10,
)
fileHandler.setFormatter(log_fmt) fileHandler.setFormatter(log_fmt)
consoleHandler = logging.StreamHandler() consoleHandler = logging.StreamHandler()
consoleHandler.setFormatter(log_fmt) consoleHandler.setFormatter(log_fmt)
@@ -50,8 +55,10 @@ class Data:
self.logger.addHandler(consoleHandler) self.logger.addHandler(consoleHandler)
self.logger.setLevel(self.config.database.loglevel) self.logger.setLevel(self.config.database.loglevel)
coloredlogs.install(cfg.database.loglevel, logger=self.logger, fmt=log_fmt_str) coloredlogs.install(
self.logger.handler_set = True # type: ignore cfg.database.loglevel, logger=self.logger, fmt=log_fmt_str
)
self.logger.handler_set = True # type: ignore
def create_database(self): def create_database(self):
self.logger.info("Creating databases...") self.logger.info("Creating databases...")
@@ -60,24 +67,32 @@ class Data:
except SQLAlchemyError as e: except SQLAlchemyError as e:
self.logger.error(f"Failed to create databases! {e}") self.logger.error(f"Failed to create databases! {e}")
return return
games = Utils.get_all_titles() games = Utils.get_all_titles()
for game_dir, game_mod in games.items(): for game_dir, game_mod in games.items():
try: try:
title_db = game_mod.database(self.config) title_db = game_mod.database(self.config)
metadata.create_all(self.__engine.connect()) metadata.create_all(self.__engine.connect())
self.base.set_schema_ver(game_mod.current_schema_version, game_mod.game_codes[0]) self.base.set_schema_ver(
game_mod.current_schema_version, game_mod.game_codes[0]
)
except Exception as e: except Exception as e:
self.logger.warning(f"Could not load database schema from {game_dir} - {e}") self.logger.warning(
f"Could not load database schema from {game_dir} - {e}"
)
self.logger.info(f"Setting base_schema_ver to {self.schema_ver_latest}") self.logger.info(f"Setting base_schema_ver to {self.schema_ver_latest}")
self.base.set_schema_ver(self.schema_ver_latest) self.base.set_schema_ver(self.schema_ver_latest)
self.logger.info(f"Setting user auto_incrememnt to {self.config.database.user_table_autoincrement_start}") self.logger.info(
self.user.reset_autoincrement(self.config.database.user_table_autoincrement_start) f"Setting user auto_incrememnt to {self.config.database.user_table_autoincrement_start}"
)
self.user.reset_autoincrement(
self.config.database.user_table_autoincrement_start
)
def recreate_database(self): def recreate_database(self):
self.logger.info("Dropping all databases...") self.logger.info("Dropping all databases...")
self.base.execute("SET FOREIGN_KEY_CHECKS=0") self.base.execute("SET FOREIGN_KEY_CHECKS=0")
@@ -86,61 +101,79 @@ class Data:
except SQLAlchemyError as e: except SQLAlchemyError as e:
self.logger.error(f"Failed to drop databases! {e}") self.logger.error(f"Failed to drop databases! {e}")
return return
for root, dirs, files in os.walk("./titles"): for root, dirs, files in os.walk("./titles"):
for dir in dirs: for dir in dirs:
if not dir.startswith("__"): if not dir.startswith("__"):
try: try:
mod = importlib.import_module(f"titles.{dir}") mod = importlib.import_module(f"titles.{dir}")
try: try:
title_db = mod.database(self.config) title_db = mod.database(self.config)
metadata.drop_all(self.__engine.connect()) metadata.drop_all(self.__engine.connect())
except Exception as e: except Exception as e:
self.logger.warning(f"Could not load database schema from {dir} - {e}") self.logger.warning(
f"Could not load database schema from {dir} - {e}"
)
except ImportError as e: except ImportError as e:
self.logger.warning(f"Failed to load database schema dir {dir} - {e}") self.logger.warning(
f"Failed to load database schema dir {dir} - {e}"
)
break break
self.base.execute("SET FOREIGN_KEY_CHECKS=1") self.base.execute("SET FOREIGN_KEY_CHECKS=1")
self.create_database() self.create_database()
def migrate_database(self, game: str, version: int, action: str) -> None: def migrate_database(self, game: str, version: int, action: str) -> None:
old_ver = self.base.get_schema_ver(game) old_ver = self.base.get_schema_ver(game)
sql = "" sql = ""
if old_ver is None: if old_ver is None:
self.logger.error(f"Schema for game {game} does not exist, did you run the creation script?") self.logger.error(
return f"Schema for game {game} does not exist, did you run the creation script?"
)
if old_ver == version:
self.logger.info(f"Schema for game {game} is already version {old_ver}, nothing to do")
return
if not os.path.exists(f"core/data/schema/versions/{game.upper()}_{version}_{action}.sql"):
self.logger.error(f"Could not find {action} script {game.upper()}_{version}_{action}.sql in core/data/schema/versions folder")
return return
with open(f"core/data/schema/versions/{game.upper()}_{version}_{action}.sql", "r", encoding="utf-8") as f: if old_ver == version:
self.logger.info(
f"Schema for game {game} is already version {old_ver}, nothing to do"
)
return
if not os.path.exists(
f"core/data/schema/versions/{game.upper()}_{version}_{action}.sql"
):
self.logger.error(
f"Could not find {action} script {game.upper()}_{version}_{action}.sql in core/data/schema/versions folder"
)
return
with open(
f"core/data/schema/versions/{game.upper()}_{version}_{action}.sql",
"r",
encoding="utf-8",
) as f:
sql = f.read() sql = f.read()
result = self.base.execute(sql) result = self.base.execute(sql)
if result is None: if result is None:
self.logger.error("Error execuing sql script!") self.logger.error("Error execuing sql script!")
return None return None
result = self.base.set_schema_ver(version, game) result = self.base.set_schema_ver(version, game)
if result is None: if result is None:
self.logger.error("Error setting version in schema_version table!") self.logger.error("Error setting version in schema_version table!")
return None return None
self.logger.info(f"Successfully migrated {game} to schema version {version}") self.logger.info(f"Successfully migrated {game} to schema version {version}")
def create_owner(self, email: Optional[str] = None) -> None: def create_owner(self, email: Optional[str] = None) -> None:
pw = ''.join(secrets.choice(string.ascii_letters + string.digits) for i in range(20)) pw = "".join(
secrets.choice(string.ascii_letters + string.digits) for i in range(20)
)
hash = bcrypt.hashpw(pw.encode(), bcrypt.gensalt()) hash = bcrypt.hashpw(pw.encode(), bcrypt.gensalt())
user_id = self.user.create_user(email=email, permission=255, password=hash) user_id = self.user.create_user(email=email, permission=255, password=hash)
@@ -153,32 +186,38 @@ class Data:
self.logger.error(f"Failed to create card for owner with id {user_id}") self.logger.error(f"Failed to create card for owner with id {user_id}")
return return
self.logger.warn(f"Successfully created owner with email {email}, access code 00000000000000000000, and password {pw} Make sure to change this password and assign a real card ASAP!") self.logger.warn(
f"Successfully created owner with email {email}, access code 00000000000000000000, and password {pw} Make sure to change this password and assign a real card ASAP!"
)
def migrate_card(self, old_ac: str, new_ac: str, should_force: bool) -> None: def migrate_card(self, old_ac: str, new_ac: str, should_force: bool) -> None:
if old_ac == new_ac: if old_ac == new_ac:
self.logger.error("Both access codes are the same!") self.logger.error("Both access codes are the same!")
return return
new_card = self.card.get_card_by_access_code(new_ac) new_card = self.card.get_card_by_access_code(new_ac)
if new_card is None: if new_card is None:
self.card.update_access_code(old_ac, new_ac) self.card.update_access_code(old_ac, new_ac)
return return
if not should_force: if not should_force:
self.logger.warn(f"Card already exists for access code {new_ac} (id {new_card['id']}). If you wish to continue, rerun with the '--force' flag."\ self.logger.warn(
f" All exiting data on the target card {new_ac} will be perminently erased and replaced with data from card {old_ac}.") f"Card already exists for access code {new_ac} (id {new_card['id']}). If you wish to continue, rerun with the '--force' flag."
f" All exiting data on the target card {new_ac} will be perminently erased and replaced with data from card {old_ac}."
)
return return
self.logger.info(f"All exiting data on the target card {new_ac} will be perminently erased and replaced with data from card {old_ac}.") self.logger.info(
f"All exiting data on the target card {new_ac} will be perminently erased and replaced with data from card {old_ac}."
)
self.card.delete_card(new_card["id"]) self.card.delete_card(new_card["id"])
self.card.update_access_code(old_ac, new_ac) self.card.update_access_code(old_ac, new_ac)
hanging_user = self.user.get_user(new_card["user"]) hanging_user = self.user.get_user(new_card["user"])
if hanging_user["password"] is None: if hanging_user["password"] is None:
self.logger.info(f"Delete hanging user {hanging_user['id']}") self.logger.info(f"Delete hanging user {hanging_user['id']}")
self.user.delete_user(hanging_user['id']) self.user.delete_user(hanging_user["id"])
def delete_hanging_users(self) -> None: def delete_hanging_users(self) -> None:
""" """
Finds and deletes users that have not registered for the webui that have no cards assocated with them. Finds and deletes users that have not registered for the webui that have no cards assocated with them.
@@ -186,13 +225,13 @@ class Data:
unreg_users = self.user.get_unregistered_users() unreg_users = self.user.get_unregistered_users()
if unreg_users is None: if unreg_users is None:
self.logger.error("Error occoured finding unregistered users") self.logger.error("Error occoured finding unregistered users")
for user in unreg_users: for user in unreg_users:
cards = self.card.get_user_cards(user['id']) cards = self.card.get_user_cards(user["id"])
if cards is None: if cards is None:
self.logger.error(f"Error getting cards for user {user['id']}") self.logger.error(f"Error getting cards for user {user['id']}")
continue continue
if not cards: if not cards:
self.logger.info(f"Delete hanging user {user['id']}") self.logger.info(f"Delete hanging user {user['id']}")
self.user.delete_user(user['id']) self.user.delete_user(user["id"])
+1 -1
View File
@@ -3,4 +3,4 @@ from core.data.schema.card import CardData
from core.data.schema.base import BaseData, metadata from core.data.schema.base import BaseData, metadata
from core.data.schema.arcade import ArcadeData from core.data.schema.arcade import ArcadeData
__all__ = ["UserData", "CardData", "BaseData", "metadata", "ArcadeData"] __all__ = ["UserData", "CardData", "BaseData", "metadata", "ArcadeData"]
+133 -74
View File
@@ -14,131 +14,186 @@ arcade = Table(
metadata, metadata,
Column("id", Integer, primary_key=True, nullable=False), Column("id", Integer, primary_key=True, nullable=False),
Column("name", String(255)), Column("name", String(255)),
Column("nickname", String(255)), Column("nickname", String(255)),
Column("country", String(3)), Column("country", String(3)),
Column("country_id", Integer), Column("country_id", Integer),
Column("state", String(255)), Column("state", String(255)),
Column("city", String(255)), Column("city", String(255)),
Column("region_id", Integer), Column("region_id", Integer),
Column("timezone", String(255)), Column("timezone", String(255)),
mysql_charset='utf8mb4' mysql_charset="utf8mb4",
) )
machine = Table( machine = Table(
"machine", "machine",
metadata, metadata,
Column("id", Integer, primary_key=True, nullable=False), Column("id", Integer, primary_key=True, nullable=False),
Column("arcade", ForeignKey("arcade.id", ondelete="cascade", onupdate="cascade"), nullable=False), Column(
"arcade",
ForeignKey("arcade.id", ondelete="cascade", onupdate="cascade"),
nullable=False,
),
Column("serial", String(15), nullable=False), Column("serial", String(15), nullable=False),
Column("board", String(15)), Column("board", String(15)),
Column("game", String(4)), Column("game", String(4)),
Column("country", String(3)), # overwrites if not null Column("country", String(3)), # overwrites if not null
Column("timezone", String(255)), Column("timezone", String(255)),
Column("ota_enable", Boolean), Column("ota_enable", Boolean),
Column("is_cab", Boolean), Column("is_cab", Boolean),
mysql_charset='utf8mb4' mysql_charset="utf8mb4",
) )
arcade_owner = Table( arcade_owner = Table(
'arcade_owner', "arcade_owner",
metadata, metadata,
Column('user', Integer, ForeignKey("aime_user.id", ondelete="cascade", onupdate="cascade"), nullable=False), Column(
Column('arcade', Integer, ForeignKey("arcade.id", ondelete="cascade", onupdate="cascade"), nullable=False), "user",
Column('permissions', Integer, nullable=False), Integer,
PrimaryKeyConstraint('user', 'arcade', name='arcade_owner_pk'), ForeignKey("aime_user.id", ondelete="cascade", onupdate="cascade"),
mysql_charset='utf8mb4' nullable=False,
),
Column(
"arcade",
Integer,
ForeignKey("arcade.id", ondelete="cascade", onupdate="cascade"),
nullable=False,
),
Column("permissions", Integer, nullable=False),
PrimaryKeyConstraint("user", "arcade", name="arcade_owner_pk"),
mysql_charset="utf8mb4",
) )
class ArcadeData(BaseData): class ArcadeData(BaseData):
def get_machine(self, serial: str = None, id: int = None) -> Optional[Dict]: def get_machine(self, serial: str = None, id: int = None) -> Optional[Dict]:
if serial is not None: if serial is not None:
serial = serial.replace("-", "") serial = serial.replace("-", "")
if len(serial) == 11: if len(serial) == 11:
sql = machine.select(machine.c.serial.like(f"{serial}%")) sql = machine.select(machine.c.serial.like(f"{serial}%"))
elif len(serial) == 15: elif len(serial) == 15:
sql = machine.select(machine.c.serial == serial) sql = machine.select(machine.c.serial == serial)
else: else:
self.logger.error(f"{__name__ }: Malformed serial {serial}") self.logger.error(f"{__name__ }: Malformed serial {serial}")
return None return None
elif id is not None: elif id is not None:
sql = machine.select(machine.c.id == id) sql = machine.select(machine.c.id == id)
else: else:
self.logger.error(f"{__name__ }: Need either serial or ID to look up!") self.logger.error(f"{__name__ }: Need either serial or ID to look up!")
return None return None
result = self.execute(sql) result = self.execute(sql)
if result is None: return None if result is None:
return None
return result.fetchone() return result.fetchone()
def put_machine(self, arcade_id: int, serial: str = "", board: str = None, game: str = None, is_cab: bool = False) -> Optional[int]: def put_machine(
self,
arcade_id: int,
serial: str = "",
board: str = None,
game: str = None,
is_cab: bool = False,
) -> Optional[int]:
if arcade_id: if arcade_id:
self.logger.error(f"{__name__ }: Need arcade id!") self.logger.error(f"{__name__ }: Need arcade id!")
return None return None
sql = machine.insert().values(arcade = arcade_id, keychip = serial, board = board, game = game, is_cab = is_cab) sql = machine.insert().values(
arcade=arcade_id, keychip=serial, board=board, game=game, is_cab=is_cab
result = self.execute(sql)
if result is None: return None
return result.lastrowid
def set_machine_serial(self, machine_id: int, serial: str) -> None:
result = self.execute(machine.update(machine.c.id == machine_id).values(keychip = serial))
if result is None:
self.logger.error(f"Failed to update serial for machine {machine_id} -> {serial}")
return result.lastrowid
def set_machine_boardid(self, machine_id: int, boardid: str) -> None:
result = self.execute(machine.update(machine.c.id == machine_id).values(board = boardid))
if result is None:
self.logger.error(f"Failed to update board id for machine {machine_id} -> {boardid}")
def get_arcade(self, id: int) -> Optional[Dict]:
sql = arcade.select(arcade.c.id == id)
result = self.execute(sql)
if result is None: return None
return result.fetchone()
def put_arcade(self, name: str, nickname: str = None, country: str = "JPN", country_id: int = 1,
state: str = "", city: str = "", regional_id: int = 1) -> Optional[int]:
if nickname is None: nickname = name
sql = arcade.insert().values(name = name, nickname = nickname, country = country, country_id = country_id,
state = state, city = city, regional_id = regional_id)
result = self.execute(sql)
if result is None: return None
return result.lastrowid
def get_arcade_owners(self, arcade_id: int) -> Optional[Dict]:
sql = select(arcade_owner).where(arcade_owner.c.arcade==arcade_id)
result = self.execute(sql)
if result is None: return None
return result.fetchall()
def add_arcade_owner(self, arcade_id: int, user_id: int) -> None:
sql = insert(arcade_owner).values(
arcade=arcade_id,
user=user_id
) )
result = self.execute(sql) result = self.execute(sql)
if result is None: return None if result is None:
return None
return result.lastrowid return result.lastrowid
def format_serial(self, platform_code: str, platform_rev: int, serial_num: int, append: int = 4152) -> str: def set_machine_serial(self, machine_id: int, serial: str) -> None:
return f"{platform_code}{platform_rev:02d}A{serial_num:04d}{append:04d}" # 0x41 = A, 0x52 = R result = self.execute(
machine.update(machine.c.id == machine_id).values(keychip=serial)
)
if result is None:
self.logger.error(
f"Failed to update serial for machine {machine_id} -> {serial}"
)
return result.lastrowid
def set_machine_boardid(self, machine_id: int, boardid: str) -> None:
result = self.execute(
machine.update(machine.c.id == machine_id).values(board=boardid)
)
if result is None:
self.logger.error(
f"Failed to update board id for machine {machine_id} -> {boardid}"
)
def get_arcade(self, id: int) -> Optional[Dict]:
sql = arcade.select(arcade.c.id == id)
result = self.execute(sql)
if result is None:
return None
return result.fetchone()
def put_arcade(
self,
name: str,
nickname: str = None,
country: str = "JPN",
country_id: int = 1,
state: str = "",
city: str = "",
regional_id: int = 1,
) -> Optional[int]:
if nickname is None:
nickname = name
sql = arcade.insert().values(
name=name,
nickname=nickname,
country=country,
country_id=country_id,
state=state,
city=city,
regional_id=regional_id,
)
result = self.execute(sql)
if result is None:
return None
return result.lastrowid
def get_arcade_owners(self, arcade_id: int) -> Optional[Dict]:
sql = select(arcade_owner).where(arcade_owner.c.arcade == arcade_id)
result = self.execute(sql)
if result is None:
return None
return result.fetchall()
def add_arcade_owner(self, arcade_id: int, user_id: int) -> None:
sql = insert(arcade_owner).values(arcade=arcade_id, user=user_id)
result = self.execute(sql)
if result is None:
return None
return result.lastrowid
def format_serial(
self, platform_code: str, platform_rev: int, serial_num: int, append: int = 4152
) -> str:
return f"{platform_code}{platform_rev:02d}A{serial_num:04d}{append:04d}" # 0x41 = A, 0x52 = R
def validate_keychip_format(self, serial: str) -> bool: def validate_keychip_format(self, serial: str) -> bool:
serial = serial.replace("-", "") serial = serial.replace("-", "")
if len(serial) != 11 or len(serial) != 15: if len(serial) != 11 or len(serial) != 15:
self.logger.error(f"Serial validate failed: Incorrect length for {serial} (len {len(serial)})") self.logger.error(
f"Serial validate failed: Incorrect length for {serial} (len {len(serial)})"
)
return False return False
platform_code = serial[:4] platform_code = serial[:4]
platform_rev = serial[4:6] platform_rev = serial[4:6]
const_a = serial[6] const_a = serial[6]
@@ -150,11 +205,15 @@ class ArcadeData(BaseData):
return False return False
if len(append) != 0 or len(append) != 4: if len(append) != 0 or len(append) != 4:
self.logger.error(f"Serial validate failed: {serial} had malformed append {append}") self.logger.error(
f"Serial validate failed: {serial} had malformed append {append}"
)
return False return False
if len(num) != 4: if len(num) != 4:
self.logger.error(f"Serial validate failed: {serial} had malformed number {num}") self.logger.error(
f"Serial validate failed: {serial} had malformed number {num}"
)
return False return False
return True return True
+38 -24
View File
@@ -19,7 +19,7 @@ schema_ver = Table(
metadata, metadata,
Column("game", String(4), primary_key=True, nullable=False), Column("game", String(4), primary_key=True, nullable=False),
Column("version", Integer, nullable=False, server_default="1"), Column("version", Integer, nullable=False, server_default="1"),
mysql_charset='utf8mb4' mysql_charset="utf8mb4",
) )
event_log = Table( event_log = Table(
@@ -32,16 +32,17 @@ event_log = Table(
Column("message", String(1000), nullable=False), Column("message", String(1000), nullable=False),
Column("details", JSON, nullable=False), Column("details", JSON, nullable=False),
Column("when_logged", TIMESTAMP, nullable=False, server_default=func.now()), Column("when_logged", TIMESTAMP, nullable=False, server_default=func.now()),
mysql_charset='utf8mb4' mysql_charset="utf8mb4",
) )
class BaseData():
class BaseData:
def __init__(self, cfg: CoreConfig, conn: Connection) -> None: def __init__(self, cfg: CoreConfig, conn: Connection) -> None:
self.config = cfg self.config = cfg
self.conn = conn self.conn = conn
self.logger = logging.getLogger("database") self.logger = logging.getLogger("database")
def execute(self, sql: str, opts: Dict[str, Any]={}) -> Optional[CursorResult]: def execute(self, sql: str, opts: Dict[str, Any] = {}) -> Optional[CursorResult]:
res = None res = None
try: try:
@@ -51,7 +52,7 @@ class BaseData():
except SQLAlchemyError as e: except SQLAlchemyError as e:
self.logger.error(f"SQLAlchemy error {e}") self.logger.error(f"SQLAlchemy error {e}")
return None return None
except UnicodeEncodeError as e: except UnicodeEncodeError as e:
self.logger.error(f"UnicodeEncodeError error {e}") self.logger.error(f"UnicodeEncodeError error {e}")
return None return None
@@ -63,7 +64,7 @@ class BaseData():
except SQLAlchemyError as e: except SQLAlchemyError as e:
self.logger.error(f"SQLAlchemy error {e}") self.logger.error(f"SQLAlchemy error {e}")
return None return None
except UnicodeEncodeError as e: except UnicodeEncodeError as e:
self.logger.error(f"UnicodeEncodeError error {e}") self.logger.error(f"UnicodeEncodeError error {e}")
return None return None
@@ -73,58 +74,71 @@ class BaseData():
raise raise
return res return res
def generate_id(self) -> int: def generate_id(self) -> int:
""" """
Generate a random 5-7 digit id Generate a random 5-7 digit id
""" """
return randrange(10000, 9999999) return randrange(10000, 9999999)
def get_schema_ver(self, game: str) -> Optional[int]: def get_schema_ver(self, game: str) -> Optional[int]:
sql = select(schema_ver).where(schema_ver.c.game == game) sql = select(schema_ver).where(schema_ver.c.game == game)
result = self.execute(sql) result = self.execute(sql)
if result is None: if result is None:
return None return None
row = result.fetchone() row = result.fetchone()
if row is None: if row is None:
return None return None
return row["version"] return row["version"]
def set_schema_ver(self, ver: int, game: str = "CORE") -> Optional[int]: def set_schema_ver(self, ver: int, game: str = "CORE") -> Optional[int]:
sql = insert(schema_ver).values(game = game, version = ver) sql = insert(schema_ver).values(game=game, version=ver)
conflict = sql.on_duplicate_key_update(version = ver) conflict = sql.on_duplicate_key_update(version=ver)
result = self.execute(conflict) result = self.execute(conflict)
if result is None: if result is None:
self.logger.error(f"Failed to update schema version for game {game} (v{ver})") self.logger.error(
f"Failed to update schema version for game {game} (v{ver})"
)
return None return None
return result.lastrowid return result.lastrowid
def log_event(self, system: str, type: str, severity: int, message: str, details: Dict = {}) -> Optional[int]: def log_event(
sql = event_log.insert().values(system = system, type = type, severity = severity, message = message, details = json.dumps(details)) self, system: str, type: str, severity: int, message: str, details: Dict = {}
) -> Optional[int]:
sql = event_log.insert().values(
system=system,
type=type,
severity=severity,
message=message,
details=json.dumps(details),
)
result = self.execute(sql) result = self.execute(sql)
if result is None: if result is None:
self.logger.error(f"{__name__}: Failed to insert event into event log! system = {system}, type = {type}, severity = {severity}, message = {message}") self.logger.error(
f"{__name__}: Failed to insert event into event log! system = {system}, type = {type}, severity = {severity}, message = {message}"
)
return None return None
return result.lastrowid return result.lastrowid
def get_event_log(self, entries: int = 100) -> Optional[List[Dict]]: def get_event_log(self, entries: int = 100) -> Optional[List[Dict]]:
sql = event_log.select().limit(entries).all() sql = event_log.select().limit(entries).all()
result = self.execute(sql) result = self.execute(sql)
if result is None: return None if result is None:
return None
return result.fetchall() return result.fetchall()
def fix_bools(self, data: Dict) -> Dict: def fix_bools(self, data: Dict) -> Dict:
for k,v in data.items(): for k, v in data.items():
if type(v) == str and v.lower() == "true": if type(v) == str and v.lower() == "true":
data[k] = True data[k] = True
elif type(v) == str and v.lower() == "false": elif type(v) == str and v.lower() == "false":
data[k] = False data[k] = False
return data return data
+29 -16
View File
@@ -8,55 +8,67 @@ from sqlalchemy.engine import Row
from core.data.schema.base import BaseData, metadata from core.data.schema.base import BaseData, metadata
aime_card = Table( aime_card = Table(
'aime_card', "aime_card",
metadata, metadata,
Column("id", Integer, primary_key=True, nullable=False), Column("id", Integer, primary_key=True, nullable=False),
Column("user", ForeignKey("aime_user.id", ondelete="cascade", onupdate="cascade"), nullable=False), Column(
"user",
ForeignKey("aime_user.id", ondelete="cascade", onupdate="cascade"),
nullable=False,
),
Column("access_code", String(20)), Column("access_code", String(20)),
Column("created_date", TIMESTAMP, server_default=func.now()), Column("created_date", TIMESTAMP, server_default=func.now()),
Column("last_login_date", TIMESTAMP, onupdate=func.now()), Column("last_login_date", TIMESTAMP, onupdate=func.now()),
Column("is_locked", Boolean, server_default="0"), Column("is_locked", Boolean, server_default="0"),
Column("is_banned", Boolean, server_default="0"), Column("is_banned", Boolean, server_default="0"),
UniqueConstraint("user", "access_code", name="aime_card_uk"), UniqueConstraint("user", "access_code", name="aime_card_uk"),
mysql_charset='utf8mb4' mysql_charset="utf8mb4",
) )
class CardData(BaseData): class CardData(BaseData):
def get_card_by_access_code(self, access_code: str) -> Optional[Row]: def get_card_by_access_code(self, access_code: str) -> Optional[Row]:
sql = aime_card.select(aime_card.c.access_code == access_code) sql = aime_card.select(aime_card.c.access_code == access_code)
result = self.execute(sql) result = self.execute(sql)
if result is None: return None if result is None:
return None
return result.fetchone() return result.fetchone()
def get_card_by_id(self, card_id: int) -> Optional[Row]: def get_card_by_id(self, card_id: int) -> Optional[Row]:
sql = aime_card.select(aime_card.c.id == card_id) sql = aime_card.select(aime_card.c.id == card_id)
result = self.execute(sql) result = self.execute(sql)
if result is None: return None if result is None:
return None
return result.fetchone() return result.fetchone()
def update_access_code(self, old_ac: str, new_ac: str) -> None: def update_access_code(self, old_ac: str, new_ac: str) -> None:
sql = aime_card.update(aime_card.c.access_code == old_ac).values(access_code = new_ac) sql = aime_card.update(aime_card.c.access_code == old_ac).values(
access_code=new_ac
)
result = self.execute(sql) result = self.execute(sql)
if result is None: if result is None:
self.logger.error(f"Failed to change card access code from {old_ac} to {new_ac}") self.logger.error(
f"Failed to change card access code from {old_ac} to {new_ac}"
)
def get_user_id_from_card(self, access_code: str) -> Optional[int]: def get_user_id_from_card(self, access_code: str) -> Optional[int]:
""" """
Given a 20 digit access code as a string, get the user id associated with that card Given a 20 digit access code as a string, get the user id associated with that card
""" """
card = self.get_card_by_access_code(access_code) card = self.get_card_by_access_code(access_code)
if card is None: return None if card is None:
return None
return int(card["user"]) return int(card["user"])
def delete_card(self, card_id: int) -> None: def delete_card(self, card_id: int) -> None:
sql = aime_card.delete(aime_card.c.id == card_id) sql = aime_card.delete(aime_card.c.id == card_id)
result = self.execute(sql) result = self.execute(sql)
if result is None: if result is None:
self.logger.error(f"Failed to delete card with id {card_id}") self.logger.error(f"Failed to delete card with id {card_id}")
def get_user_cards(self, aime_id: int) -> Optional[List[Row]]: def get_user_cards(self, aime_id: int) -> Optional[List[Row]]:
@@ -65,17 +77,18 @@ class CardData(BaseData):
""" """
sql = aime_card.select(aime_card.c.user == aime_id) sql = aime_card.select(aime_card.c.user == aime_id)
result = self.execute(sql) result = self.execute(sql)
if result is None: return None if result is None:
return None
return result.fetchall() return result.fetchall()
def create_card(self, user_id: int, access_code: str) -> Optional[int]: def create_card(self, user_id: int, access_code: str) -> Optional[int]:
""" """
Given a aime_user id and a 20 digit access code as a string, create a card and return the ID if successful Given a aime_user id and a 20 digit access code as a string, create a card and return the ID if successful
""" """
sql = aime_card.insert().values(user=user_id, access_code=access_code) sql = aime_card.insert().values(user=user_id, access_code=access_code)
result = self.execute(sql) result = self.execute(sql)
if result is None: return None if result is None:
return None
return result.lastrowid return result.lastrowid
def to_access_code(self, luid: str) -> str: def to_access_code(self, luid: str) -> str:
@@ -88,4 +101,4 @@ class CardData(BaseData):
""" """
Given a 20 digit access code as a string, return the 16 hex character luid Given a 20 digit access code as a string, return the 16 hex character luid
""" """
return f'{int(access_code):0{16}x}' return f"{int(access_code):0{16}x}"
+38 -29
View File
@@ -17,72 +17,81 @@ aime_user = Table(
Column("username", String(25), unique=True), Column("username", String(25), unique=True),
Column("email", String(255), unique=True), Column("email", String(255), unique=True),
Column("password", String(255)), Column("password", String(255)),
Column("permissions", Integer), Column("permissions", Integer),
Column("created_date", TIMESTAMP, server_default=func.now()), Column("created_date", TIMESTAMP, server_default=func.now()),
Column("last_login_date", TIMESTAMP, onupdate=func.now()), Column("last_login_date", TIMESTAMP, onupdate=func.now()),
Column("suspend_expire_time", TIMESTAMP), Column("suspend_expire_time", TIMESTAMP),
mysql_charset='utf8mb4' mysql_charset="utf8mb4",
) )
class PermissionBits(Enum): class PermissionBits(Enum):
PermUser = 1 PermUser = 1
PermMod = 2 PermMod = 2
PermSysAdmin = 4 PermSysAdmin = 4
class UserData(BaseData): class UserData(BaseData):
def create_user(self, id: int = None, username: str = None, email: str = None, password: str = None, permission: int = 1) -> Optional[int]: def create_user(
self,
id: int = None,
username: str = None,
email: str = None,
password: str = None,
permission: int = 1,
) -> Optional[int]:
if id is None: if id is None:
sql = insert(aime_user).values( sql = insert(aime_user).values(
username=username, username=username,
email=email, email=email,
password=password, password=password,
permissions=permission permissions=permission,
) )
else: else:
sql = insert(aime_user).values( sql = insert(aime_user).values(
id=id, id=id,
username=username, username=username,
email=email, email=email,
password=password, password=password,
permissions=permission permissions=permission,
) )
conflict = sql.on_duplicate_key_update( conflict = sql.on_duplicate_key_update(
username=username, username=username, email=email, password=password, permissions=permission
email=email,
password=password,
permissions=permission
) )
result = self.execute(conflict) result = self.execute(conflict)
if result is None: return None if result is None:
return None
return result.lastrowid return result.lastrowid
def get_user(self, user_id: int) -> Optional[Row]: def get_user(self, user_id: int) -> Optional[Row]:
sql = select(aime_user).where(aime_user.c.id == user_id) sql = select(aime_user).where(aime_user.c.id == user_id)
result = self.execute(sql) result = self.execute(sql)
if result is None: return False if result is None:
return False
return result.fetchone() return result.fetchone()
def check_password(self, user_id: int, passwd: bytes = None) -> bool: def check_password(self, user_id: int, passwd: bytes = None) -> bool:
usr = self.get_user(user_id) usr = self.get_user(user_id)
if usr is None: return False if usr is None:
if usr['password'] is None:
return False return False
return bcrypt.checkpw(passwd, usr['password'].encode()) if usr["password"] is None:
return False
return bcrypt.checkpw(passwd, usr["password"].encode())
def reset_autoincrement(self, ai_value: int) -> None: def reset_autoincrement(self, ai_value: int) -> None:
# ALTER TABLE isn't in sqlalchemy so we do this the ugly way # ALTER TABLE isn't in sqlalchemy so we do this the ugly way
sql = f"ALTER TABLE aime_user AUTO_INCREMENT={ai_value}" sql = f"ALTER TABLE aime_user AUTO_INCREMENT={ai_value}"
self.execute(sql) self.execute(sql)
def delete_user(self, user_id: int) -> None: def delete_user(self, user_id: int) -> None:
sql = aime_user.delete(aime_user.c.id == user_id) sql = aime_user.delete(aime_user.c.id == user_id)
result = self.execute(sql) result = self.execute(sql)
if result is None: if result is None:
self.logger.error(f"Failed to delete user with id {user_id}") self.logger.error(f"Failed to delete user with id {user_id}")
def get_unregistered_users(self) -> List[Row]: def get_unregistered_users(self) -> List[Row]:
@@ -92,6 +101,6 @@ class UserData(BaseData):
sql = select(aime_user).where(aime_user.c.password == None) sql = select(aime_user).where(aime_user.c.password == None)
result = self.execute(sql) result = self.execute(sql)
if result is None: if result is None:
return None return None
return result.fetchall() return result.fetchall()
+77 -40
View File
@@ -14,11 +14,13 @@ from core.config import CoreConfig
from core.data import Data from core.data import Data
from core.utils import Utils from core.utils import Utils
class IUserSession(Interface): class IUserSession(Interface):
userId = Attribute("User's ID") userId = Attribute("User's ID")
current_ip = Attribute("User's current ip address") current_ip = Attribute("User's current ip address")
permissions = Attribute("User's permission level") permissions = Attribute("User's permission level")
@implementer(IUserSession) @implementer(IUserSession)
class UserSession(object): class UserSession(object):
def __init__(self, session): def __init__(self, session):
@@ -26,10 +28,11 @@ class UserSession(object):
self.current_ip = "0.0.0.0" self.current_ip = "0.0.0.0"
self.permissions = 0 self.permissions = 0
class FrontendServlet(resource.Resource): class FrontendServlet(resource.Resource):
def getChild(self, name: bytes, request: Request): def getChild(self, name: bytes, request: Request):
self.logger.debug(f"{request.getClientIP()} -> {name.decode()}") self.logger.debug(f"{request.getClientIP()} -> {name.decode()}")
if name == b'': if name == b"":
return self return self
return resource.Resource.getChild(self, name, request) return resource.Resource.getChild(self, name, request)
@@ -42,17 +45,23 @@ class FrontendServlet(resource.Resource):
self.game_list: List[Dict[str, str]] = [] self.game_list: List[Dict[str, str]] = []
self.children: Dict[str, Any] = {} self.children: Dict[str, Any] = {}
fileHandler = TimedRotatingFileHandler("{0}/{1}.log".format(self.config.server.log_dir, "frontend"), when="d", backupCount=10) fileHandler = TimedRotatingFileHandler(
"{0}/{1}.log".format(self.config.server.log_dir, "frontend"),
when="d",
backupCount=10,
)
fileHandler.setFormatter(log_fmt) fileHandler.setFormatter(log_fmt)
consoleHandler = logging.StreamHandler() consoleHandler = logging.StreamHandler()
consoleHandler.setFormatter(log_fmt) consoleHandler.setFormatter(log_fmt)
self.logger.addHandler(fileHandler) self.logger.addHandler(fileHandler)
self.logger.addHandler(consoleHandler) self.logger.addHandler(consoleHandler)
self.logger.setLevel(cfg.frontend.loglevel) self.logger.setLevel(cfg.frontend.loglevel)
coloredlogs.install(level=cfg.frontend.loglevel, logger=self.logger, fmt=log_fmt_str) coloredlogs.install(
level=cfg.frontend.loglevel, logger=self.logger, fmt=log_fmt_str
)
registerAdapter(UserSession, Session, IUserSession) registerAdapter(UserSession, Session, IUserSession)
fe_game = FE_Game(cfg, self.environment) fe_game = FE_Game(cfg, self.environment)
@@ -65,18 +74,26 @@ class FrontendServlet(resource.Resource):
fe_game.putChild(game_dir.encode(), game_fe) fe_game.putChild(game_dir.encode(), game_fe)
except: except:
raise raise
self.environment.globals["game_list"] = self.game_list self.environment.globals["game_list"] = self.game_list
self.putChild(b"gate", FE_Gate(cfg, self.environment)) self.putChild(b"gate", FE_Gate(cfg, self.environment))
self.putChild(b"user", FE_User(cfg, self.environment)) self.putChild(b"user", FE_User(cfg, self.environment))
self.putChild(b"game", fe_game) self.putChild(b"game", fe_game)
self.logger.info(f"Ready on port {self.config.frontend.port} serving {len(fe_game.children)} games") self.logger.info(
f"Ready on port {self.config.frontend.port} serving {len(fe_game.children)} games"
)
def render_GET(self, request): def render_GET(self, request):
self.logger.debug(f"{request.getClientIP()} -> {request.uri.decode()}") self.logger.debug(f"{request.getClientIP()} -> {request.uri.decode()}")
template = self.environment.get_template("core/frontend/index.jinja") template = self.environment.get_template("core/frontend/index.jinja")
return template.render(server_name=self.config.server.name, title=self.config.server.name, game_list=self.game_list, sesh=vars(IUserSession(request.getSession()))).encode("utf-16") return template.render(
server_name=self.config.server.name,
title=self.config.server.name,
game_list=self.game_list,
sesh=vars(IUserSession(request.getSession())),
).encode("utf-16")
class FE_Base(resource.Resource): class FE_Base(resource.Resource):
""" """
@@ -84,43 +101,51 @@ class FE_Base(resource.Resource):
Initializes the environment, data, logger, config, and sets isLeaf to true Initializes the environment, data, logger, config, and sets isLeaf to true
It is expected that game implementations of this class overwrite many of these It is expected that game implementations of this class overwrite many of these
""" """
isLeaf = True
isLeaf = True
def __init__(self, cfg: CoreConfig, environment: jinja2.Environment) -> None: def __init__(self, cfg: CoreConfig, environment: jinja2.Environment) -> None:
self.core_config = cfg self.core_config = cfg
self.data = Data(cfg) self.data = Data(cfg)
self.logger = logging.getLogger('frontend') self.logger = logging.getLogger("frontend")
self.environment = environment self.environment = environment
self.nav_name = "nav_name" self.nav_name = "nav_name"
class FE_Gate(FE_Base): class FE_Gate(FE_Base):
def render_GET(self, request: Request): def render_GET(self, request: Request):
self.logger.debug(f"{request.getClientIP()} -> {request.uri.decode()}") self.logger.debug(f"{request.getClientIP()} -> {request.uri.decode()}")
uri: str = request.uri.decode() uri: str = request.uri.decode()
sesh = request.getSession() sesh = request.getSession()
usr_sesh = IUserSession(sesh) usr_sesh = IUserSession(sesh)
if usr_sesh.userId > 0: if usr_sesh.userId > 0:
return redirectTo(b"/user", request) return redirectTo(b"/user", request)
if uri.startswith("/gate/create"): if uri.startswith("/gate/create"):
return self.create_user(request) return self.create_user(request)
if b'e' in request.args: if b"e" in request.args:
try: try:
err = int(request.args[b'e'][0].decode()) err = int(request.args[b"e"][0].decode())
except: except:
err = 0 err = 0
else: err = 0 else:
err = 0
template = self.environment.get_template("core/frontend/gate/gate.jinja")
return template.render(
title=f"{self.core_config.server.name} | Login Gate",
error=err,
sesh=vars(usr_sesh),
).encode("utf-16")
template = self.environment.get_template("core/frontend/gate/gate.jinja")
return template.render(title=f"{self.core_config.server.name} | Login Gate", error=err, sesh=vars(usr_sesh)).encode("utf-16")
def render_POST(self, request: Request): def render_POST(self, request: Request):
uri = request.uri.decode() uri = request.uri.decode()
ip = request.getClientAddress().host ip = request.getClientAddress().host
if uri == "/gate/gate.login": if uri == "/gate/gate.login":
access_code: str = request.args[b"access_code"][0].decode() access_code: str = request.args[b"access_code"][0].decode()
passwd: bytes = request.args[b"passwd"][0] passwd: bytes = request.args[b"passwd"][0]
if passwd == b"": if passwd == b"":
@@ -129,26 +154,28 @@ class FE_Gate(FE_Base):
uid = self.data.card.get_user_id_from_card(access_code) uid = self.data.card.get_user_id_from_card(access_code)
if uid is None: if uid is None:
return redirectTo(b"/gate?e=1", request) return redirectTo(b"/gate?e=1", request)
if passwd is None: if passwd is None:
sesh = self.data.user.check_password(uid) sesh = self.data.user.check_password(uid)
if sesh is not None: if sesh is not None:
return redirectTo(f"/gate/create?ac={access_code}".encode(), request) return redirectTo(
f"/gate/create?ac={access_code}".encode(), request
)
return redirectTo(b"/gate?e=1", request) return redirectTo(b"/gate?e=1", request)
if not self.data.user.check_password(uid, passwd): if not self.data.user.check_password(uid, passwd):
return redirectTo(b"/gate?e=1", request) return redirectTo(b"/gate?e=1", request)
self.logger.info(f"Successful login of user {uid} at {ip}") self.logger.info(f"Successful login of user {uid} at {ip}")
sesh = request.getSession() sesh = request.getSession()
usr_sesh = IUserSession(sesh) usr_sesh = IUserSession(sesh)
usr_sesh.userId = uid usr_sesh.userId = uid
usr_sesh.current_ip = ip usr_sesh.current_ip = ip
return redirectTo(b"/user", request) return redirectTo(b"/user", request)
elif uri == "/gate/gate.create": elif uri == "/gate/gate.create":
access_code: str = request.args[b"access_code"][0].decode() access_code: str = request.args[b"access_code"][0].decode()
username: str = request.args[b"username"][0] username: str = request.args[b"username"][0]
@@ -162,26 +189,33 @@ class FE_Gate(FE_Base):
salt = bcrypt.gensalt() salt = bcrypt.gensalt()
hashed = bcrypt.hashpw(passwd, salt) hashed = bcrypt.hashpw(passwd, salt)
result = self.data.user.create_user(uid, username, email, hashed.decode(), 1) result = self.data.user.create_user(
uid, username, email, hashed.decode(), 1
)
if result is None: if result is None:
return redirectTo(b"/gate?e=3", request) return redirectTo(b"/gate?e=3", request)
if not self.data.user.check_password(uid, passwd.encode()): if not self.data.user.check_password(uid, passwd.encode()):
return redirectTo(b"/gate", request) return redirectTo(b"/gate", request)
return redirectTo(b"/user", request) return redirectTo(b"/user", request)
else: else:
return b"" return b""
def create_user(self, request: Request): def create_user(self, request: Request):
if b'ac' not in request.args or len(request.args[b'ac'][0].decode()) != 20: if b"ac" not in request.args or len(request.args[b"ac"][0].decode()) != 20:
return redirectTo(b"/gate?e=2", request) return redirectTo(b"/gate?e=2", request)
ac = request.args[b'ac'][0].decode() ac = request.args[b"ac"][0].decode()
template = self.environment.get_template("core/frontend/gate/create.jinja") template = self.environment.get_template("core/frontend/gate/create.jinja")
return template.render(title=f"{self.core_config.server.name} | Create User", code=ac, sesh={"userId": 0}).encode("utf-16") return template.render(
title=f"{self.core_config.server.name} | Create User",
code=ac,
sesh={"userId": 0},
).encode("utf-16")
class FE_User(FE_Base): class FE_User(FE_Base):
def render_GET(self, request: Request): def render_GET(self, request: Request):
@@ -191,17 +225,20 @@ class FE_User(FE_Base):
usr_sesh = IUserSession(sesh) usr_sesh = IUserSession(sesh)
if usr_sesh.userId == 0: if usr_sesh.userId == 0:
return redirectTo(b"/gate", request) return redirectTo(b"/gate", request)
return template.render(title=f"{self.core_config.server.name} | Account", sesh=vars(usr_sesh)).encode("utf-16") return template.render(
title=f"{self.core_config.server.name} | Account", sesh=vars(usr_sesh)
).encode("utf-16")
class FE_Game(FE_Base): class FE_Game(FE_Base):
isLeaf = False isLeaf = False
children: Dict[str, Any] = {} children: Dict[str, Any] = {}
def getChild(self, name: bytes, request: Request): def getChild(self, name: bytes, request: Request):
if name == b'': if name == b"":
return self return self
return resource.Resource.getChild(self, name, request) return resource.Resource.getChild(self, name, request)
def render_GET(self, request: Request) -> bytes: def render_GET(self, request: Request) -> bytes:
return redirectTo(b"/user", request) return redirectTo(b"/user", request)
+69 -37
View File
@@ -9,25 +9,30 @@ import pytz
from core.config import CoreConfig from core.config import CoreConfig
from core.utils import Utils from core.utils import Utils
class MuchaServlet: class MuchaServlet:
def __init__(self, cfg: CoreConfig, cfg_dir: str) -> None: def __init__(self, cfg: CoreConfig, cfg_dir: str) -> None:
self.config = cfg self.config = cfg
self.config_dir = cfg_dir self.config_dir = cfg_dir
self.mucha_registry: List[str] = [] self.mucha_registry: List[str] = []
self.logger = logging.getLogger('mucha') self.logger = logging.getLogger("mucha")
log_fmt_str = "[%(asctime)s] Mucha | %(levelname)s | %(message)s" log_fmt_str = "[%(asctime)s] Mucha | %(levelname)s | %(message)s"
log_fmt = logging.Formatter(log_fmt_str) log_fmt = logging.Formatter(log_fmt_str)
fileHandler = TimedRotatingFileHandler("{0}/{1}.log".format(self.config.server.log_dir, "mucha"), when="d", backupCount=10) fileHandler = TimedRotatingFileHandler(
"{0}/{1}.log".format(self.config.server.log_dir, "mucha"),
when="d",
backupCount=10,
)
fileHandler.setFormatter(log_fmt) fileHandler.setFormatter(log_fmt)
consoleHandler = logging.StreamHandler() consoleHandler = logging.StreamHandler()
consoleHandler.setFormatter(log_fmt) consoleHandler.setFormatter(log_fmt)
self.logger.addHandler(fileHandler) self.logger.addHandler(fileHandler)
self.logger.addHandler(consoleHandler) self.logger.addHandler(consoleHandler)
self.logger.setLevel(logging.INFO) self.logger.setLevel(logging.INFO)
coloredlogs.install(level=logging.INFO, logger=self.logger, fmt=log_fmt_str) coloredlogs.install(level=logging.INFO, logger=self.logger, fmt=log_fmt_str)
@@ -35,43 +40,57 @@ class MuchaServlet:
for _, mod in all_titles.items(): for _, mod in all_titles.items():
if hasattr(mod, "index") and hasattr(mod.index, "get_mucha_info"): if hasattr(mod, "index") and hasattr(mod.index, "get_mucha_info"):
enabled, game_cd = mod.index.get_mucha_info(self.config, self.config_dir) enabled, game_cd = mod.index.get_mucha_info(
self.config, self.config_dir
)
if enabled: if enabled:
self.mucha_registry.append(game_cd) self.mucha_registry.append(game_cd)
self.logger.info(f"Serving {len(self.mucha_registry)} games on port {self.config.mucha.port}") self.logger.info(
f"Serving {len(self.mucha_registry)} games on port {self.config.mucha.port}"
)
def handle_boardauth(self, request: Request, _: Dict) -> bytes: def handle_boardauth(self, request: Request, _: Dict) -> bytes:
req_dict = self.mucha_preprocess(request.content.getvalue()) req_dict = self.mucha_preprocess(request.content.getvalue())
if req_dict is None: if req_dict is None:
self.logger.error(f"Error processing mucha request {request.content.getvalue()}") self.logger.error(
f"Error processing mucha request {request.content.getvalue()}"
)
return b"" return b""
req = MuchaAuthRequest(req_dict) req = MuchaAuthRequest(req_dict)
self.logger.debug(f"Mucha request {vars(req)}") self.logger.debug(f"Mucha request {vars(req)}")
self.logger.info(f"Boardauth request from {request.getClientAddress().host} for {req.gameVer}") self.logger.info(
f"Boardauth request from {request.getClientAddress().host} for {req.gameVer}"
)
if req.gameCd not in self.mucha_registry: if req.gameCd not in self.mucha_registry:
self.logger.warn(f"Unknown gameCd {req.gameCd}") self.logger.warn(f"Unknown gameCd {req.gameCd}")
return b"" return b""
# TODO: Decrypt S/N # TODO: Decrypt S/N
resp = MuchaAuthResponse(f"{self.config.mucha.hostname}{':' + self.config.mucha.port if self.config.server.is_develop else ''}") resp = MuchaAuthResponse(
f"{self.config.mucha.hostname}{':' + self.config.mucha.port if self.config.server.is_develop else ''}"
)
self.logger.debug(f"Mucha response {vars(resp)}") self.logger.debug(f"Mucha response {vars(resp)}")
return self.mucha_postprocess(vars(resp)) return self.mucha_postprocess(vars(resp))
def handle_updatecheck(self, request: Request, _: Dict) -> bytes: def handle_updatecheck(self, request: Request, _: Dict) -> bytes:
req_dict = self.mucha_preprocess(request.content.getvalue()) req_dict = self.mucha_preprocess(request.content.getvalue())
if req_dict is None: if req_dict is None:
self.logger.error(f"Error processing mucha request {request.content.getvalue()}") self.logger.error(
f"Error processing mucha request {request.content.getvalue()}"
)
return b"" return b""
req = MuchaUpdateRequest(req_dict) req = MuchaUpdateRequest(req_dict)
self.logger.debug(f"Mucha request {vars(req)}") self.logger.debug(f"Mucha request {vars(req)}")
self.logger.info(f"Updatecheck request from {request.getClientAddress().host} for {req.gameVer}") self.logger.info(
f"Updatecheck request from {request.getClientAddress().host} for {req.gameVer}"
)
if req.gameCd not in self.mucha_registry: if req.gameCd not in self.mucha_registry:
self.logger.warn(f"Unknown gameCd {req.gameCd}") self.logger.warn(f"Unknown gameCd {req.gameCd}")
@@ -86,14 +105,14 @@ class MuchaServlet:
def mucha_preprocess(self, data: bytes) -> Optional[Dict]: def mucha_preprocess(self, data: bytes) -> Optional[Dict]:
try: try:
ret: Dict[str, Any] = {} ret: Dict[str, Any] = {}
for x in data.decode().split('&'): for x in data.decode().split("&"):
kvp = x.split('=') kvp = x.split("=")
if len(kvp) == 2: if len(kvp) == 2:
ret[kvp[0]] = kvp[1] ret[kvp[0]] = kvp[1]
return ret return ret
except: except:
self.logger.error(f"Error processing mucha request {data}") self.logger.error(f"Error processing mucha request {data}")
return None return None
@@ -101,7 +120,7 @@ class MuchaServlet:
def mucha_postprocess(self, data: dict) -> Optional[bytes]: def mucha_postprocess(self, data: dict) -> Optional[bytes]:
try: try:
urlencode = "" urlencode = ""
for k,v in data.items(): for k, v in data.items():
urlencode += f"{k}={v}&" urlencode += f"{k}={v}&"
return urlencode.encode() return urlencode.encode()
@@ -110,36 +129,44 @@ class MuchaServlet:
self.logger.error("Error processing mucha response") self.logger.error("Error processing mucha response")
return None return None
class MuchaAuthRequest():
class MuchaAuthRequest:
def __init__(self, request: Dict) -> None: def __init__(self, request: Dict) -> None:
self.gameVer = "" if "gameVer" not in request else request["gameVer"] # gameCd + boardType + countryCd + version self.gameVer = (
self.sendDate = "" if "sendDate" not in request else request["sendDate"] # %Y%m%d "" if "gameVer" not in request else request["gameVer"]
) # gameCd + boardType + countryCd + version
self.sendDate = (
"" if "sendDate" not in request else request["sendDate"]
) # %Y%m%d
self.serialNum = "" if "serialNum" not in request else request["serialNum"] self.serialNum = "" if "serialNum" not in request else request["serialNum"]
self.gameCd = "" if "gameCd" not in request else request["gameCd"] self.gameCd = "" if "gameCd" not in request else request["gameCd"]
self.boardType = "" if "boardType" not in request else request["boardType"] self.boardType = "" if "boardType" not in request else request["boardType"]
self.boardId = "" if "boardId" not in request else request["boardId"] self.boardId = "" if "boardId" not in request else request["boardId"]
self.mac = "" if "mac" not in request else request["mac"] self.mac = "" if "mac" not in request else request["mac"]
self.placeId = "" if "placeId" not in request else request["placeId"] self.placeId = "" if "placeId" not in request else request["placeId"]
self.storeRouterIp = "" if "storeRouterIp" not in request else request["storeRouterIp"] self.storeRouterIp = (
"" if "storeRouterIp" not in request else request["storeRouterIp"]
)
self.countryCd = "" if "countryCd" not in request else request["countryCd"] self.countryCd = "" if "countryCd" not in request else request["countryCd"]
self.useToken = "" if "useToken" not in request else request["useToken"] self.useToken = "" if "useToken" not in request else request["useToken"]
self.allToken = "" if "allToken" not in request else request["allToken"] self.allToken = "" if "allToken" not in request else request["allToken"]
class MuchaAuthResponse():
class MuchaAuthResponse:
def __init__(self, mucha_url: str) -> None: def __init__(self, mucha_url: str) -> None:
self.RESULTS = "001" self.RESULTS = "001"
self.AUTH_INTERVAL = "86400" self.AUTH_INTERVAL = "86400"
self.SERVER_TIME = datetime.strftime(datetime.now(), "%Y%m%d%H%M") self.SERVER_TIME = datetime.strftime(datetime.now(), "%Y%m%d%H%M")
self.UTC_SERVER_TIME = datetime.strftime(datetime.now(pytz.UTC), "%Y%m%d%H%M") self.UTC_SERVER_TIME = datetime.strftime(datetime.now(pytz.UTC), "%Y%m%d%H%M")
self.CHARGE_URL = f"https://{mucha_url}/charge/" self.CHARGE_URL = f"https://{mucha_url}/charge/"
self.FILE_URL = f"https://{mucha_url}/file/" self.FILE_URL = f"https://{mucha_url}/file/"
self.URL_1 = f"https://{mucha_url}/url1/" self.URL_1 = f"https://{mucha_url}/url1/"
self.URL_2 = f"https://{mucha_url}/url2/" self.URL_2 = f"https://{mucha_url}/url2/"
self.URL_3 = f"https://{mucha_url}/url3/" self.URL_3 = f"https://{mucha_url}/url3/"
self.PLACE_ID = "JPN123" self.PLACE_ID = "JPN123"
self.COUNTRY_CD = "JPN" self.COUNTRY_CD = "JPN"
self.SHOP_NAME = "TestShop!" self.SHOP_NAME = "TestShop!"
self.SHOP_NICKNAME = "TestShop" self.SHOP_NICKNAME = "TestShop"
self.AREA_0 = "008" self.AREA_0 = "008"
@@ -150,7 +177,7 @@ class MuchaAuthResponse():
self.AREA_FULL_1 = "" self.AREA_FULL_1 = ""
self.AREA_FULL_2 = "" self.AREA_FULL_2 = ""
self.AREA_FULL_3 = "" self.AREA_FULL_3 = ""
self.SHOP_NAME_EN = "TestShop!" self.SHOP_NAME_EN = "TestShop!"
self.SHOP_NICKNAME_EN = "TestShop" self.SHOP_NICKNAME_EN = "TestShop"
self.AREA_0_EN = "008" self.AREA_0_EN = "008"
@@ -162,23 +189,27 @@ class MuchaAuthResponse():
self.AREA_FULL_2_EN = "" self.AREA_FULL_2_EN = ""
self.AREA_FULL_3_EN = "" self.AREA_FULL_3_EN = ""
self.PREFECTURE_ID = "1" self.PREFECTURE_ID = "1"
self.EXPIRATION_DATE = "null" self.EXPIRATION_DATE = "null"
self.USE_TOKEN = "0" self.USE_TOKEN = "0"
self.CONSUME_TOKEN = "0" self.CONSUME_TOKEN = "0"
self.DONGLE_FLG = "1" self.DONGLE_FLG = "1"
self.FORCE_BOOT = "0" self.FORCE_BOOT = "0"
class MuchaUpdateRequest():
class MuchaUpdateRequest:
def __init__(self, request: Dict) -> None: def __init__(self, request: Dict) -> None:
self.gameVer = "" if "gameVer" not in request else request["gameVer"] self.gameVer = "" if "gameVer" not in request else request["gameVer"]
self.gameCd = "" if "gameCd" not in request else request["gameCd"] self.gameCd = "" if "gameCd" not in request else request["gameCd"]
self.serialNum = "" if "serialNum" not in request else request["serialNum"] self.serialNum = "" if "serialNum" not in request else request["serialNum"]
self.countryCd = "" if "countryCd" not in request else request["countryCd"] self.countryCd = "" if "countryCd" not in request else request["countryCd"]
self.placeId = "" if "placeId" not in request else request["placeId"] self.placeId = "" if "placeId" not in request else request["placeId"]
self.storeRouterIp = "" if "storeRouterIp" not in request else request["storeRouterIp"] self.storeRouterIp = (
"" if "storeRouterIp" not in request else request["storeRouterIp"]
)
class MuchaUpdateResponse():
class MuchaUpdateResponse:
def __init__(self, game_ver: str, mucha_url: str) -> None: def __init__(self, game_ver: str, mucha_url: str) -> None:
self.RESULTS = "001" self.RESULTS = "001"
self.UPDATE_VER_1 = game_ver self.UPDATE_VER_1 = game_ver
@@ -194,7 +225,8 @@ class MuchaUpdateResponse():
self.USER_ID = "" self.USER_ID = ""
self.PASSWORD = "" self.PASSWORD = ""
class MuchaUpdateResponseStub():
class MuchaUpdateResponseStub:
def __init__(self, game_ver: str) -> None: def __init__(self, game_ver: str) -> None:
self.RESULTS = "001" self.RESULTS = "001"
self.UPDATE_VER_1 = game_ver self.UPDATE_VER_1 = game_ver
+33 -20
View File
@@ -7,8 +7,9 @@ from core.config import CoreConfig
from core.data import Data from core.data import Data
from core.utils import Utils from core.utils import Utils
class TitleServlet():
def __init__(self, core_cfg: CoreConfig, cfg_folder: str): class TitleServlet:
def __init__(self, core_cfg: CoreConfig, cfg_folder: str):
super().__init__() super().__init__()
self.config = core_cfg self.config = core_cfg
self.config_folder = cfg_folder self.config_folder = cfg_folder
@@ -18,47 +19,57 @@ class TitleServlet():
self.logger = logging.getLogger("title") self.logger = logging.getLogger("title")
if not hasattr(self.logger, "initialized"): if not hasattr(self.logger, "initialized"):
log_fmt_str = "[%(asctime)s] Title | %(levelname)s | %(message)s" log_fmt_str = "[%(asctime)s] Title | %(levelname)s | %(message)s"
log_fmt = logging.Formatter(log_fmt_str) log_fmt = logging.Formatter(log_fmt_str)
fileHandler = TimedRotatingFileHandler("{0}/{1}.log".format(self.config.server.log_dir, "title"), when="d", backupCount=10) fileHandler = TimedRotatingFileHandler(
"{0}/{1}.log".format(self.config.server.log_dir, "title"),
when="d",
backupCount=10,
)
fileHandler.setFormatter(log_fmt) fileHandler.setFormatter(log_fmt)
consoleHandler = logging.StreamHandler() consoleHandler = logging.StreamHandler()
consoleHandler.setFormatter(log_fmt) consoleHandler.setFormatter(log_fmt)
self.logger.addHandler(fileHandler) self.logger.addHandler(fileHandler)
self.logger.addHandler(consoleHandler) self.logger.addHandler(consoleHandler)
self.logger.setLevel(core_cfg.title.loglevel) self.logger.setLevel(core_cfg.title.loglevel)
coloredlogs.install(level=core_cfg.title.loglevel, logger=self.logger, fmt=log_fmt_str) coloredlogs.install(
level=core_cfg.title.loglevel, logger=self.logger, fmt=log_fmt_str
)
self.logger.initialized = True self.logger.initialized = True
plugins = Utils.get_all_titles() plugins = Utils.get_all_titles()
for folder, mod in plugins.items(): for folder, mod in plugins.items():
if hasattr(mod, "game_codes") and hasattr(mod, "index"): if hasattr(mod, "game_codes") and hasattr(mod, "index"):
should_call_setup = True should_call_setup = True
if hasattr(mod.index, "get_allnet_info"): if hasattr(mod.index, "get_allnet_info"):
for code in mod.game_codes: for code in mod.game_codes:
enabled, _, _ = mod.index.get_allnet_info(code, self.config, self.config_folder) enabled, _, _ = mod.index.get_allnet_info(
code, self.config, self.config_folder
)
if enabled: if enabled:
handler_cls = mod.index(self.config, self.config_folder) handler_cls = mod.index(self.config, self.config_folder)
if hasattr(handler_cls, "setup") and should_call_setup: if hasattr(handler_cls, "setup") and should_call_setup:
handler_cls.setup() handler_cls.setup()
should_call_setup = False should_call_setup = False
self.title_registry[code] = handler_cls self.title_registry[code] = handler_cls
else: else:
self.logger.warn(f"Game {folder} has no get_allnet_info") self.logger.warn(f"Game {folder} has no get_allnet_info")
else: else:
self.logger.error(f"{folder} missing game_code or index in __init__.py") self.logger.error(f"{folder} missing game_code or index in __init__.py")
self.logger.info(f"Serving {len(self.title_registry)} game codes on port {core_cfg.title.port}") self.logger.info(
f"Serving {len(self.title_registry)} game codes on port {core_cfg.title.port}"
)
def render_GET(self, request: Request, endpoints: dict) -> bytes: def render_GET(self, request: Request, endpoints: dict) -> bytes:
code = endpoints["game"] code = endpoints["game"]
@@ -66,7 +77,7 @@ class TitleServlet():
self.logger.warn(f"Unknown game code {code}") self.logger.warn(f"Unknown game code {code}")
request.setResponseCode(404) request.setResponseCode(404)
return b"" return b""
index = self.title_registry[code] index = self.title_registry[code]
if not hasattr(index, "render_GET"): if not hasattr(index, "render_GET"):
self.logger.warn(f"{code} does not dispatch GET") self.logger.warn(f"{code} does not dispatch GET")
@@ -74,18 +85,20 @@ class TitleServlet():
return b"" return b""
return index.render_GET(request, endpoints["version"], endpoints["endpoint"]) return index.render_GET(request, endpoints["version"], endpoints["endpoint"])
def render_POST(self, request: Request, endpoints: dict) -> bytes: def render_POST(self, request: Request, endpoints: dict) -> bytes:
code = endpoints["game"] code = endpoints["game"]
if code not in self.title_registry: if code not in self.title_registry:
self.logger.warn(f"Unknown game code {code}") self.logger.warn(f"Unknown game code {code}")
request.setResponseCode(404) request.setResponseCode(404)
return b"" return b""
index = self.title_registry[code] index = self.title_registry[code]
if not hasattr(index, "render_POST"): if not hasattr(index, "render_POST"):
self.logger.warn(f"{code} does not dispatch POST") self.logger.warn(f"{code} does not dispatch POST")
request.setResponseCode(405) request.setResponseCode(405)
return b"" return b""
return index.render_POST(request, int(endpoints["version"]), endpoints["endpoint"]) return index.render_POST(
request, int(endpoints["version"]), endpoints["endpoint"]
)
+2 -1
View File
@@ -4,13 +4,14 @@ import logging
import importlib import importlib
from os import walk from os import walk
class Utils: class Utils:
@classmethod @classmethod
def get_all_titles(cls) -> Dict[str, ModuleType]: def get_all_titles(cls) -> Dict[str, ModuleType]:
ret: Dict[str, Any] = {} ret: Dict[str, Any] = {}
for root, dirs, files in walk("titles"): for root, dirs, files in walk("titles"):
for dir in dirs: for dir in dirs:
if not dir.startswith("__"): if not dir.startswith("__"):
try: try:
mod = importlib.import_module(f"titles.{dir}") mod = importlib.import_module(f"titles.{dir}")
+20 -6
View File
@@ -4,16 +4,30 @@ from core.config import CoreConfig
from core.data import Data from core.data import Data
from os import path from os import path
if __name__=='__main__': if __name__ == "__main__":
parser = argparse.ArgumentParser(description="Database utilities") parser = argparse.ArgumentParser(description="Database utilities")
parser.add_argument("--config", "-c", type=str, help="Config folder to use", default="config") parser.add_argument(
parser.add_argument("--version", "-v", type=str, help="Version of the database to upgrade/rollback to") "--config", "-c", type=str, help="Config folder to use", default="config"
parser.add_argument("--game", "-g", type=str, help="Game code of the game who's schema will be updated/rolled back. Ex. SDFE") )
parser.add_argument(
"--version",
"-v",
type=str,
help="Version of the database to upgrade/rollback to",
)
parser.add_argument(
"--game",
"-g",
type=str,
help="Game code of the game who's schema will be updated/rolled back. Ex. SDFE",
)
parser.add_argument("--email", "-e", type=str, help="Email for the new user") parser.add_argument("--email", "-e", type=str, help="Email for the new user")
parser.add_argument("--old_ac", "-o", type=str, help="Access code to transfer from") parser.add_argument("--old_ac", "-o", type=str, help="Access code to transfer from")
parser.add_argument("--new_ac", "-n", type=str, help="Access code to transfer to") parser.add_argument("--new_ac", "-n", type=str, help="Access code to transfer to")
parser.add_argument("--force", "-f", type=bool, help="Force the action to happen") parser.add_argument("--force", "-f", type=bool, help="Force the action to happen")
parser.add_argument("action", type=str, help="DB Action, create, recreate, upgrade, or rollback") parser.add_argument(
"action", type=str, help="DB Action, create, recreate, upgrade, or rollback"
)
args = parser.parse_args() args = parser.parse_args()
cfg = CoreConfig() cfg = CoreConfig()
@@ -23,7 +37,7 @@ if __name__=='__main__':
if args.action == "create": if args.action == "create":
data.create_database() data.create_database()
elif args.action == "recreate": elif args.action == "recreate":
data.recreate_database() data.recreate_database()
+131 -43
View File
@@ -12,6 +12,7 @@ from twisted.internet import reactor, endpoints
from twisted.web.http import Request from twisted.web.http import Request
from routes import Mapper from routes import Mapper
class HttpDispatcher(resource.Resource): class HttpDispatcher(resource.Resource):
def __init__(self, cfg: CoreConfig, config_dir: str): def __init__(self, cfg: CoreConfig, config_dir: str):
super().__init__() super().__init__()
@@ -20,73 +21,142 @@ class HttpDispatcher(resource.Resource):
self.map_get = Mapper() self.map_get = Mapper()
self.map_post = Mapper() self.map_post = Mapper()
self.logger = logging.getLogger("core") self.logger = logging.getLogger("core")
self.allnet = AllnetServlet(cfg, config_dir) self.allnet = AllnetServlet(cfg, config_dir)
self.title = TitleServlet(cfg, config_dir) self.title = TitleServlet(cfg, config_dir)
self.mucha = MuchaServlet(cfg, config_dir) self.mucha = MuchaServlet(cfg, config_dir)
self.map_post.connect('allnet_ping', '/naomitest.html', controller="allnet", action='handle_naomitest', conditions=dict(method=['GET'])) self.map_post.connect(
self.map_post.connect('allnet_poweron', '/sys/servlet/PowerOn', controller="allnet", action='handle_poweron', conditions=dict(method=['POST'])) "allnet_ping",
self.map_post.connect('allnet_downloadorder', '/sys/servlet/DownloadOrder', controller="allnet", action='handle_dlorder', conditions=dict(method=['POST'])) "/naomitest.html",
self.map_post.connect('allnet_billing', '/request', controller="allnet", action='handle_billing_request', conditions=dict(method=['POST'])) controller="allnet",
self.map_post.connect('allnet_billing', '/request/', controller="allnet", action='handle_billing_request', conditions=dict(method=['POST'])) action="handle_naomitest",
conditions=dict(method=["GET"]),
)
self.map_post.connect(
"allnet_poweron",
"/sys/servlet/PowerOn",
controller="allnet",
action="handle_poweron",
conditions=dict(method=["POST"]),
)
self.map_post.connect(
"allnet_downloadorder",
"/sys/servlet/DownloadOrder",
controller="allnet",
action="handle_dlorder",
conditions=dict(method=["POST"]),
)
self.map_post.connect(
"allnet_billing",
"/request",
controller="allnet",
action="handle_billing_request",
conditions=dict(method=["POST"]),
)
self.map_post.connect(
"allnet_billing",
"/request/",
controller="allnet",
action="handle_billing_request",
conditions=dict(method=["POST"]),
)
self.map_post.connect('mucha_boardauth', '/mucha/boardauth.do', controller="mucha", action='handle_boardauth', conditions=dict(method=['POST'])) self.map_post.connect(
self.map_post.connect('mucha_updatacheck', '/mucha/updatacheck.do', controller="mucha", action='handle_updatacheck', conditions=dict(method=['POST'])) "mucha_boardauth",
"/mucha/boardauth.do",
controller="mucha",
action="handle_boardauth",
conditions=dict(method=["POST"]),
)
self.map_post.connect(
"mucha_updatacheck",
"/mucha/updatacheck.do",
controller="mucha",
action="handle_updatacheck",
conditions=dict(method=["POST"]),
)
self.map_get.connect("title_get", "/{game}/{version}/{endpoint:.*?}", controller="title", action="render_GET", conditions=dict(method=['GET']), requirements=dict(game=R"S...")) self.map_get.connect(
self.map_post.connect("title_post", "/{game}/{version}/{endpoint:.*?}", controller="title", action="render_POST", conditions=dict(method=['POST']), requirements=dict(game=R"S...")) "title_get",
"/{game}/{version}/{endpoint:.*?}",
controller="title",
action="render_GET",
conditions=dict(method=["GET"]),
requirements=dict(game=R"S..."),
)
self.map_post.connect(
"title_post",
"/{game}/{version}/{endpoint:.*?}",
controller="title",
action="render_POST",
conditions=dict(method=["POST"]),
requirements=dict(game=R"S..."),
)
def render_GET(self, request: Request) -> bytes: def render_GET(self, request: Request) -> bytes:
test = self.map_get.match(request.uri.decode()) test = self.map_get.match(request.uri.decode())
if test is None: if test is None:
self.logger.debug(f"Unknown GET endpoint {request.uri.decode()} from {request.getClientAddress().host} to port {request.getHost().port}") self.logger.debug(
f"Unknown GET endpoint {request.uri.decode()} from {request.getClientAddress().host} to port {request.getHost().port}"
)
request.setResponseCode(404) request.setResponseCode(404)
return b"Endpoint not found." return b"Endpoint not found."
return self.dispatch(test, request) return self.dispatch(test, request)
def render_POST(self, request: Request) -> bytes: def render_POST(self, request: Request) -> bytes:
test = self.map_post.match(request.uri.decode()) test = self.map_post.match(request.uri.decode())
if test is None: if test is None:
self.logger.debug(f"Unknown POST endpoint {request.uri.decode()} from {request.getClientAddress().host} to port {request.getHost().port}") self.logger.debug(
f"Unknown POST endpoint {request.uri.decode()} from {request.getClientAddress().host} to port {request.getHost().port}"
)
request.setResponseCode(404) request.setResponseCode(404)
return b"Endpoint not found." return b"Endpoint not found."
return self.dispatch(test, request) return self.dispatch(test, request)
def dispatch(self, matcher: Dict, request: Request) -> bytes: def dispatch(self, matcher: Dict, request: Request) -> bytes:
controller = getattr(self, matcher["controller"], None) controller = getattr(self, matcher["controller"], None)
if controller is None: if controller is None:
self.logger.error(f"Controller {matcher['controller']} not found via endpoint {request.uri.decode()}") self.logger.error(
f"Controller {matcher['controller']} not found via endpoint {request.uri.decode()}"
)
request.setResponseCode(404) request.setResponseCode(404)
return b"Endpoint not found." return b"Endpoint not found."
handler = getattr(controller, matcher["action"], None) handler = getattr(controller, matcher["action"], None)
if handler is None: if handler is None:
self.logger.error(f"Action {matcher['action']} not found in controller {matcher['controller']} via endpoint {request.uri.decode()}") self.logger.error(
f"Action {matcher['action']} not found in controller {matcher['controller']} via endpoint {request.uri.decode()}"
)
request.setResponseCode(404) request.setResponseCode(404)
return b"Endpoint not found." return b"Endpoint not found."
url_vars = matcher url_vars = matcher
url_vars.pop("controller") url_vars.pop("controller")
url_vars.pop("action") url_vars.pop("action")
ret = handler(request, url_vars) ret = handler(request, url_vars)
if type(ret) == str: if type(ret) == str:
return ret.encode() return ret.encode()
elif type(ret) == bytes: elif type(ret) == bytes:
return ret return ret
else: else:
return b"" return b""
if __name__ == "__main__": if __name__ == "__main__":
parser = argparse.ArgumentParser(description="ARTEMiS main entry point") parser = argparse.ArgumentParser(description="ARTEMiS main entry point")
parser.add_argument("--config", "-c", type=str, default="config", help="Configuration folder") parser.add_argument(
"--config", "-c", type=str, default="config", help="Configuration folder"
)
args = parser.parse_args() args = parser.parse_args()
if not path.exists(f"{args.config}/core.yaml"): if not path.exists(f"{args.config}/core.yaml"):
print(f"The config folder you specified ({args.config}) does not exist or does not contain core.yaml.\nDid you copy the example folder?") print(
f"The config folder you specified ({args.config}) does not exist or does not contain core.yaml.\nDid you copy the example folder?"
)
exit(1) exit(1)
cfg: CoreConfig = CoreConfig() cfg: CoreConfig = CoreConfig()
@@ -95,56 +165,74 @@ if __name__ == "__main__":
logger = logging.getLogger("core") logger = logging.getLogger("core")
log_fmt_str = "[%(asctime)s] Core | %(levelname)s | %(message)s" log_fmt_str = "[%(asctime)s] Core | %(levelname)s | %(message)s"
log_fmt = logging.Formatter(log_fmt_str) log_fmt = logging.Formatter(log_fmt_str)
fileHandler = TimedRotatingFileHandler("{0}/{1}.log".format(cfg.server.log_dir, "core"), when="d", backupCount=10) fileHandler = TimedRotatingFileHandler(
"{0}/{1}.log".format(cfg.server.log_dir, "core"), when="d", backupCount=10
)
fileHandler.setFormatter(log_fmt) fileHandler.setFormatter(log_fmt)
consoleHandler = logging.StreamHandler() consoleHandler = logging.StreamHandler()
consoleHandler.setFormatter(log_fmt) consoleHandler.setFormatter(log_fmt)
logger.addHandler(fileHandler) logger.addHandler(fileHandler)
logger.addHandler(consoleHandler) logger.addHandler(consoleHandler)
log_lv = logging.DEBUG if cfg.server.is_develop else logging.INFO log_lv = logging.DEBUG if cfg.server.is_develop else logging.INFO
logger.setLevel(log_lv) logger.setLevel(log_lv)
coloredlogs.install(level=log_lv, logger=logger, fmt=log_fmt_str) coloredlogs.install(level=log_lv, logger=logger, fmt=log_fmt_str)
if not path.exists(cfg.server.log_dir): if not path.exists(cfg.server.log_dir):
mkdir(cfg.server.log_dir) mkdir(cfg.server.log_dir)
if not access(cfg.server.log_dir, W_OK): if not access(cfg.server.log_dir, W_OK):
logger.error(f"Log directory {cfg.server.log_dir} NOT writable, please check permissions") logger.error(
f"Log directory {cfg.server.log_dir} NOT writable, please check permissions"
)
exit(1) exit(1)
if not cfg.aimedb.key: if not cfg.aimedb.key:
logger.error("!!AIMEDB KEY BLANK, SET KEY IN CORE.YAML!!") logger.error("!!AIMEDB KEY BLANK, SET KEY IN CORE.YAML!!")
exit(1) exit(1)
logger.info(f"ARTEMiS starting in {'develop' if cfg.server.is_develop else 'production'} mode")
allnet_server_str = f"tcp:{cfg.allnet.port}:interface={cfg.server.listen_address}" logger.info(
f"ARTEMiS starting in {'develop' if cfg.server.is_develop else 'production'} mode"
)
allnet_server_str = f"tcp:{cfg.allnet.port}:interface={cfg.server.listen_address}"
title_server_str = f"tcp:{cfg.title.port}:interface={cfg.server.listen_address}" title_server_str = f"tcp:{cfg.title.port}:interface={cfg.server.listen_address}"
adb_server_str = f"tcp:{cfg.aimedb.port}:interface={cfg.server.listen_address}" adb_server_str = f"tcp:{cfg.aimedb.port}:interface={cfg.server.listen_address}"
frontend_server_str = f"tcp:{cfg.frontend.port}:interface={cfg.server.listen_address}" frontend_server_str = (
f"tcp:{cfg.frontend.port}:interface={cfg.server.listen_address}"
)
billing_server_str = f"tcp:{cfg.billing.port}:interface={cfg.server.listen_address}" billing_server_str = f"tcp:{cfg.billing.port}:interface={cfg.server.listen_address}"
if cfg.server.is_develop: if cfg.server.is_develop:
billing_server_str = f"ssl:{cfg.billing.port}:interface={cfg.server.listen_address}"\ billing_server_str = (
f"ssl:{cfg.billing.port}:interface={cfg.server.listen_address}"
f":privateKey={cfg.billing.ssl_key}:certKey={cfg.billing.ssl_cert}" f":privateKey={cfg.billing.ssl_key}:certKey={cfg.billing.ssl_cert}"
)
dispatcher = HttpDispatcher(cfg, args.config) dispatcher = HttpDispatcher(cfg, args.config)
endpoints.serverFromString(reactor, allnet_server_str).listen(server.Site(dispatcher)) endpoints.serverFromString(reactor, allnet_server_str).listen(
server.Site(dispatcher)
)
endpoints.serverFromString(reactor, adb_server_str).listen(AimedbFactory(cfg)) endpoints.serverFromString(reactor, adb_server_str).listen(AimedbFactory(cfg))
if cfg.frontend.enable: if cfg.frontend.enable:
endpoints.serverFromString(reactor, frontend_server_str).listen(server.Site(FrontendServlet(cfg, args.config))) endpoints.serverFromString(reactor, frontend_server_str).listen(
server.Site(FrontendServlet(cfg, args.config))
)
if cfg.billing.port > 0: if cfg.billing.port > 0:
endpoints.serverFromString(reactor, billing_server_str).listen(server.Site(dispatcher)) endpoints.serverFromString(reactor, billing_server_str).listen(
server.Site(dispatcher)
if cfg.title.port > 0: )
endpoints.serverFromString(reactor, title_server_str).listen(server.Site(dispatcher))
if cfg.title.port > 0:
reactor.run() # type: ignore endpoints.serverFromString(reactor, title_server_str).listen(
server.Site(dispatcher)
)
reactor.run() # type: ignore
+41 -31
View File
@@ -12,56 +12,64 @@ from typing import List, Optional
from core import CoreConfig from core import CoreConfig
from core.utils import Utils from core.utils import Utils
class BaseReader():
def __init__(self, config: CoreConfig, version: int, bin_dir: Optional[str], opt_dir: Optional[str], extra: Optional[str]) -> None: class BaseReader:
def __init__(
self,
config: CoreConfig,
version: int,
bin_dir: Optional[str],
opt_dir: Optional[str],
extra: Optional[str],
) -> None:
self.logger = logging.getLogger("reader") self.logger = logging.getLogger("reader")
self.config = config self.config = config
self.bin_dir = bin_dir self.bin_dir = bin_dir
self.opt_dir = opt_dir self.opt_dir = opt_dir
self.version = version self.version = version
self.extra = extra self.extra = extra
def get_data_directories(self, directory: str) -> List[str]: def get_data_directories(self, directory: str) -> List[str]:
ret: List[str] = [] ret: List[str] = []
for root, dirs, files in os.walk(directory): for root, dirs, files in os.walk(directory):
for dir in dirs: for dir in dirs:
if re.fullmatch("[A-Z0-9]{4,4}", dir) is not None: if re.fullmatch("[A-Z0-9]{4,4}", dir) is not None:
ret.append(f"{root}/{dir}") ret.append(f"{root}/{dir}")
return ret return ret
if __name__ == "__main__": if __name__ == "__main__":
parser = argparse.ArgumentParser(description='Import Game Information') parser = argparse.ArgumentParser(description="Import Game Information")
parser.add_argument( parser.add_argument(
'--series', "--series",
action='store', action="store",
type=str, type=str,
required=True, required=True,
help='The game series we are importing.', help="The game series we are importing.",
) )
parser.add_argument( parser.add_argument(
'--version', "--version",
dest='version', dest="version",
action='store', action="store",
type=int, type=int,
required=True, required=True,
help='The game version we are importing.', help="The game version we are importing.",
) )
parser.add_argument( parser.add_argument(
'--binfolder', "--binfolder",
dest='bin', dest="bin",
action='store', action="store",
type=str, type=str,
help='Folder containing A000 base data', help="Folder containing A000 base data",
) )
parser.add_argument( parser.add_argument(
'--optfolder', "--optfolder",
dest='opt', dest="opt",
action='store', action="store",
type=str, type=str,
help='Folder containing Option data folders', help="Folder containing Option data folders",
) )
parser.add_argument( parser.add_argument(
"--config", "--config",
@@ -86,15 +94,17 @@ if __name__ == "__main__":
log_fmt = logging.Formatter(log_fmt_str) log_fmt = logging.Formatter(log_fmt_str)
logger = logging.getLogger("reader") logger = logging.getLogger("reader")
fileHandler = TimedRotatingFileHandler("{0}/{1}.log".format(config.server.log_dir, "reader"), when="d", backupCount=10) fileHandler = TimedRotatingFileHandler(
"{0}/{1}.log".format(config.server.log_dir, "reader"), when="d", backupCount=10
)
fileHandler.setFormatter(log_fmt) fileHandler.setFormatter(log_fmt)
consoleHandler = logging.StreamHandler() consoleHandler = logging.StreamHandler()
consoleHandler.setFormatter(log_fmt) consoleHandler.setFormatter(log_fmt)
logger.addHandler(fileHandler) logger.addHandler(fileHandler)
logger.addHandler(consoleHandler) logger.addHandler(consoleHandler)
log_lv = logging.DEBUG if config.server.is_develop else logging.INFO log_lv = logging.DEBUG if config.server.is_develop else logging.INFO
logger.setLevel(log_lv) logger.setLevel(log_lv)
coloredlogs.install(level=log_lv, logger=logger, fmt=log_fmt_str) coloredlogs.install(level=log_lv, logger=logger, fmt=log_fmt_str)
@@ -102,8 +112,8 @@ if __name__ == "__main__":
if args.series is None or args.version is None: if args.series is None or args.version is None:
logger.error("Game or version not specified") logger.error("Game or version not specified")
parser.print_help() parser.print_help()
exit(1) exit(1)
if args.bin is None and args.opt is None: if args.bin is None and args.opt is None:
logger.error("Must specify either bin or opt directory") logger.error("Must specify either bin or opt directory")
parser.print_help() parser.print_help()
@@ -113,7 +123,7 @@ if __name__ == "__main__":
bin_arg = args.bin[:-1] bin_arg = args.bin[:-1]
else: else:
bin_arg = args.bin bin_arg = args.bin
if args.opt is not None and (args.opt.endswith("\\") or args.opt.endswith("/")): if args.opt is not None and (args.opt.endswith("\\") or args.opt.endswith("/")):
opt_arg = args.opt[:-1] opt_arg = args.opt[:-1]
else: else:
@@ -127,5 +137,5 @@ if __name__ == "__main__":
if args.series in mod.game_codes: if args.series in mod.game_codes:
handler = mod.reader(config, args.version, bin_arg, opt_arg, args.extra) handler = mod.reader(config, args.version, bin_arg, opt_arg, args.extra)
handler.read() handler.read()
logger.info("Done") logger.info("Done")
+4 -3
View File
@@ -5,12 +5,13 @@ from titles.chuni.base import ChuniBase
from titles.chuni.const import ChuniConstants from titles.chuni.const import ChuniConstants
from titles.chuni.config import ChuniConfig from titles.chuni.config import ChuniConfig
class ChuniAir(ChuniBase): class ChuniAir(ChuniBase):
def __init__(self, core_cfg: CoreConfig, game_cfg: ChuniConfig) -> None: def __init__(self, core_cfg: CoreConfig, game_cfg: ChuniConfig) -> None:
super().__init__(core_cfg, game_cfg) super().__init__(core_cfg, game_cfg)
self.version = ChuniConstants.VER_CHUNITHM_AIR self.version = ChuniConstants.VER_CHUNITHM_AIR
def handle_get_game_setting_api_request(self, data: Dict) -> Dict: def handle_get_game_setting_api_request(self, data: Dict) -> Dict:
ret = super().handle_get_game_setting_api_request(data) ret = super().handle_get_game_setting_api_request(data)
ret["gameSetting"]["dataVersion"] = "1.10.00" ret["gameSetting"]["dataVersion"] = "1.10.00"
return ret return ret
+4 -3
View File
@@ -5,12 +5,13 @@ from titles.chuni.base import ChuniBase
from titles.chuni.const import ChuniConstants from titles.chuni.const import ChuniConstants
from titles.chuni.config import ChuniConfig from titles.chuni.config import ChuniConfig
class ChuniAirPlus(ChuniBase): class ChuniAirPlus(ChuniBase):
def __init__(self, core_cfg: CoreConfig, game_cfg: ChuniConfig) -> None: def __init__(self, core_cfg: CoreConfig, game_cfg: ChuniConfig) -> None:
super().__init__(core_cfg, game_cfg) super().__init__(core_cfg, game_cfg)
self.version = ChuniConstants.VER_CHUNITHM_AIR_PLUS self.version = ChuniConstants.VER_CHUNITHM_AIR_PLUS
def handle_get_game_setting_api_request(self, data: Dict) -> Dict: def handle_get_game_setting_api_request(self, data: Dict) -> Dict:
ret = super().handle_get_game_setting_api_request(data) ret = super().handle_get_game_setting_api_request(data)
ret["gameSetting"]["dataVersion"] = "1.15.00" ret["gameSetting"]["dataVersion"] = "1.15.00"
return ret return ret
+3 -2
View File
@@ -7,12 +7,13 @@ from titles.chuni.base import ChuniBase
from titles.chuni.const import ChuniConstants from titles.chuni.const import ChuniConstants
from titles.chuni.config import ChuniConfig from titles.chuni.config import ChuniConfig
class ChuniAmazon(ChuniBase): class ChuniAmazon(ChuniBase):
def __init__(self, core_cfg: CoreConfig, game_cfg: ChuniConfig) -> None: def __init__(self, core_cfg: CoreConfig, game_cfg: ChuniConfig) -> None:
super().__init__(core_cfg, game_cfg) super().__init__(core_cfg, game_cfg)
self.version = ChuniConstants.VER_CHUNITHM_AMAZON self.version = ChuniConstants.VER_CHUNITHM_AMAZON
def handle_get_game_setting_api_request(self, data: Dict) -> Dict: def handle_get_game_setting_api_request(self, data: Dict) -> Dict:
ret = super().handle_get_game_setting_api_request(data) ret = super().handle_get_game_setting_api_request(data)
ret["gameSetting"]["dataVersion"] = "1.30.00" ret["gameSetting"]["dataVersion"] = "1.30.00"
return ret return ret
+3 -2
View File
@@ -7,12 +7,13 @@ from titles.chuni.base import ChuniBase
from titles.chuni.const import ChuniConstants from titles.chuni.const import ChuniConstants
from titles.chuni.config import ChuniConfig from titles.chuni.config import ChuniConfig
class ChuniAmazonPlus(ChuniBase): class ChuniAmazonPlus(ChuniBase):
def __init__(self, core_cfg: CoreConfig, game_cfg: ChuniConfig) -> None: def __init__(self, core_cfg: CoreConfig, game_cfg: ChuniConfig) -> None:
super().__init__(core_cfg, game_cfg) super().__init__(core_cfg, game_cfg)
self.version = ChuniConstants.VER_CHUNITHM_AMAZON_PLUS self.version = ChuniConstants.VER_CHUNITHM_AMAZON_PLUS
def handle_get_game_setting_api_request(self, data: Dict) -> Dict: def handle_get_game_setting_api_request(self, data: Dict) -> Dict:
ret = super().handle_get_game_setting_api_request(data) ret = super().handle_get_game_setting_api_request(data)
ret["gameSetting"]["dataVersion"] = "1.35.00" ret["gameSetting"]["dataVersion"] = "1.35.00"
return ret return ret
+179 -161
View File
@@ -11,7 +11,8 @@ from titles.chuni.const import ChuniConstants
from titles.chuni.database import ChuniData from titles.chuni.database import ChuniData
from titles.chuni.config import ChuniConfig from titles.chuni.config import ChuniConfig
class ChuniBase():
class ChuniBase:
def __init__(self, core_cfg: CoreConfig, game_cfg: ChuniConfig) -> None: def __init__(self, core_cfg: CoreConfig, game_cfg: ChuniConfig) -> None:
self.core_cfg = core_cfg self.core_cfg = core_cfg
self.game_cfg = game_cfg self.game_cfg = game_cfg
@@ -22,32 +23,31 @@ class ChuniBase():
self.version = ChuniConstants.VER_CHUNITHM self.version = ChuniConstants.VER_CHUNITHM
def handle_game_login_api_request(self, data: Dict) -> Dict: def handle_game_login_api_request(self, data: Dict) -> Dict:
#self.data.base.log_event("chuni", "login", logging.INFO, {"version": self.version, "user": data["userId"]}) # self.data.base.log_event("chuni", "login", logging.INFO, {"version": self.version, "user": data["userId"]})
return { "returnCode": 1 } return {"returnCode": 1}
def handle_game_logout_api_request(self, data: Dict) -> Dict: def handle_game_logout_api_request(self, data: Dict) -> Dict:
#self.data.base.log_event("chuni", "logout", logging.INFO, {"version": self.version, "user": data["userId"]}) # self.data.base.log_event("chuni", "logout", logging.INFO, {"version": self.version, "user": data["userId"]})
return { "returnCode": 1 } return {"returnCode": 1}
def handle_get_game_charge_api_request(self, data: Dict) -> Dict: def handle_get_game_charge_api_request(self, data: Dict) -> Dict:
game_charge_list = self.data.static.get_enabled_charges(self.version) game_charge_list = self.data.static.get_enabled_charges(self.version)
charges = [] charges = []
for x in range(len(game_charge_list)): for x in range(len(game_charge_list)):
charges.append({ charges.append(
"orderId": x, {
"chargeId": game_charge_list[x]["chargeId"], "orderId": x,
"price": 1, "chargeId": game_charge_list[x]["chargeId"],
"startDate": "2017-12-05 07:00:00.0", "price": 1,
"endDate": "2099-12-31 00:00:00.0", "startDate": "2017-12-05 07:00:00.0",
"salePrice": 1, "endDate": "2099-12-31 00:00:00.0",
"saleStartDate": "2017-12-05 07:00:00.0", "salePrice": 1,
"saleEndDate": "2099-12-31 00:00:00.0" "saleStartDate": "2017-12-05 07:00:00.0",
}) "saleEndDate": "2099-12-31 00:00:00.0",
return { }
"length": len(charges), )
"gameChargeList": charges return {"length": len(charges), "gameChargeList": charges}
}
def handle_get_game_event_api_request(self, data: Dict) -> Dict: def handle_get_game_event_api_request(self, data: Dict) -> Dict:
game_events = self.data.static.get_enabled_events(self.version) game_events = self.data.static.get_enabled_events(self.version)
@@ -62,26 +62,30 @@ class ChuniBase():
event_list.append(tmp) event_list.append(tmp)
return { return {
"type": data["type"], "type": data["type"],
"length": len(event_list), "length": len(event_list),
"gameEventList": event_list "gameEventList": event_list,
} }
def handle_get_game_idlist_api_request(self, data: Dict) -> Dict: def handle_get_game_idlist_api_request(self, data: Dict) -> Dict:
return { "type": data["type"], "length": 0, "gameIdlistList": [] } return {"type": data["type"], "length": 0, "gameIdlistList": []}
def handle_get_game_message_api_request(self, data: Dict) -> Dict: def handle_get_game_message_api_request(self, data: Dict) -> Dict:
return { "type": data["type"], "length": "0", "gameMessageList": [] } return {"type": data["type"], "length": "0", "gameMessageList": []}
def handle_get_game_ranking_api_request(self, data: Dict) -> Dict: def handle_get_game_ranking_api_request(self, data: Dict) -> Dict:
return { "type": data["type"], "gameRankingList": [] } return {"type": data["type"], "gameRankingList": []}
def handle_get_game_sale_api_request(self, data: Dict) -> Dict: def handle_get_game_sale_api_request(self, data: Dict) -> Dict:
return { "type": data["type"], "length": 0, "gameSaleList": [] } return {"type": data["type"], "length": 0, "gameSaleList": []}
def handle_get_game_setting_api_request(self, data: Dict) -> Dict: def handle_get_game_setting_api_request(self, data: Dict) -> Dict:
reboot_start = datetime.strftime(datetime.now() - timedelta(hours=4), self.date_time_format) reboot_start = datetime.strftime(
reboot_end = datetime.strftime(datetime.now() - timedelta(hours=3), self.date_time_format) datetime.now() - timedelta(hours=4), self.date_time_format
)
reboot_end = datetime.strftime(
datetime.now() - timedelta(hours=3), self.date_time_format
)
return { return {
"gameSetting": { "gameSetting": {
"dataVersion": "1.00.00", "dataVersion": "1.00.00",
@@ -94,15 +98,17 @@ class ChuniBase():
"maxCountItem": 300, "maxCountItem": 300,
"maxCountMusic": 300, "maxCountMusic": 300,
}, },
"isDumpUpload": "false", "isDumpUpload": "false",
"isAou": "false", "isAou": "false",
} }
def handle_get_user_activity_api_request(self, data: Dict) -> Dict: def handle_get_user_activity_api_request(self, data: Dict) -> Dict:
user_activity_list = self.data.profile.get_profile_activity(data["userId"], data["kind"]) user_activity_list = self.data.profile.get_profile_activity(
data["userId"], data["kind"]
)
activity_list = [] activity_list = []
for activity in user_activity_list: for activity in user_activity_list:
tmp = activity._asdict() tmp = activity._asdict()
tmp.pop("user") tmp.pop("user")
@@ -111,15 +117,16 @@ class ChuniBase():
activity_list.append(tmp) activity_list.append(tmp)
return { return {
"userId": data["userId"], "userId": data["userId"],
"length": len(activity_list), "length": len(activity_list),
"kind": data["kind"], "kind": data["kind"],
"userActivityList": activity_list "userActivityList": activity_list,
} }
def handle_get_user_character_api_request(self, data: Dict) -> Dict: def handle_get_user_character_api_request(self, data: Dict) -> Dict:
characters = self.data.item.get_characters(data["userId"]) characters = self.data.item.get_characters(data["userId"])
if characters is None: return {} if characters is None:
return {}
next_idx = -1 next_idx = -1
characterList = [] characterList = []
@@ -131,15 +138,17 @@ class ChuniBase():
if len(characterList) >= int(data["maxCount"]): if len(characterList) >= int(data["maxCount"]):
break break
if len(characterList) >= int(data["maxCount"]) and len(characters) > int(data["maxCount"]) + int(data["nextIndex"]): if len(characterList) >= int(data["maxCount"]) and len(characters) > int(
data["maxCount"]
) + int(data["nextIndex"]):
next_idx = int(data["maxCount"]) + int(data["nextIndex"]) + 1 next_idx = int(data["maxCount"]) + int(data["nextIndex"]) + 1
return { return {
"userId": data["userId"], "userId": data["userId"],
"length": len(characterList), "length": len(characterList),
"nextIndex": next_idx, "nextIndex": next_idx,
"userCharacterList": characterList "userCharacterList": characterList,
} }
def handle_get_user_charge_api_request(self, data: Dict) -> Dict: def handle_get_user_charge_api_request(self, data: Dict) -> Dict:
@@ -153,21 +162,21 @@ class ChuniBase():
charge_list.append(tmp) charge_list.append(tmp)
return { return {
"userId": data["userId"], "userId": data["userId"],
"length": len(charge_list), "length": len(charge_list),
"userChargeList": charge_list "userChargeList": charge_list,
} }
def handle_get_user_course_api_request(self, data: Dict) -> Dict: def handle_get_user_course_api_request(self, data: Dict) -> Dict:
user_course_list = self.data.score.get_courses(data["userId"]) user_course_list = self.data.score.get_courses(data["userId"])
if user_course_list is None: if user_course_list is None:
return { return {
"userId": data["userId"], "userId": data["userId"],
"length": 0, "length": 0,
"nextIndex": -1, "nextIndex": -1,
"userCourseList": [] "userCourseList": [],
} }
course_list = [] course_list = []
next_idx = int(data["nextIndex"]) next_idx = int(data["nextIndex"])
max_ct = int(data["maxCount"]) max_ct = int(data["maxCount"])
@@ -180,51 +189,48 @@ class ChuniBase():
if len(user_course_list) >= max_ct: if len(user_course_list) >= max_ct:
break break
if len(user_course_list) >= max_ct: if len(user_course_list) >= max_ct:
next_idx = next_idx + max_ct next_idx = next_idx + max_ct
else: else:
next_idx = -1 next_idx = -1
return { return {
"userId": data["userId"], "userId": data["userId"],
"length": len(course_list), "length": len(course_list),
"nextIndex": next_idx, "nextIndex": next_idx,
"userCourseList": course_list "userCourseList": course_list,
} }
def handle_get_user_data_api_request(self, data: Dict) -> Dict: def handle_get_user_data_api_request(self, data: Dict) -> Dict:
p = self.data.profile.get_profile_data(data["userId"], self.version) p = self.data.profile.get_profile_data(data["userId"], self.version)
if p is None: return {} if p is None:
return {}
profile = p._asdict() profile = p._asdict()
profile.pop("id") profile.pop("id")
profile.pop("user") profile.pop("user")
profile.pop("version") profile.pop("version")
return { return {"userId": data["userId"], "userData": profile}
"userId": data["userId"],
"userData": profile
}
def handle_get_user_data_ex_api_request(self, data: Dict) -> Dict: def handle_get_user_data_ex_api_request(self, data: Dict) -> Dict:
p = self.data.profile.get_profile_data_ex(data["userId"], self.version) p = self.data.profile.get_profile_data_ex(data["userId"], self.version)
if p is None: return {} if p is None:
return {}
profile = p._asdict() profile = p._asdict()
profile.pop("id") profile.pop("id")
profile.pop("user") profile.pop("user")
profile.pop("version") profile.pop("version")
return { return {"userId": data["userId"], "userDataEx": profile}
"userId": data["userId"],
"userDataEx": profile
}
def handle_get_user_duel_api_request(self, data: Dict) -> Dict: def handle_get_user_duel_api_request(self, data: Dict) -> Dict:
user_duel_list = self.data.item.get_duels(data["userId"]) user_duel_list = self.data.item.get_duels(data["userId"])
if user_duel_list is None: return {} if user_duel_list is None:
return {}
duel_list = [] duel_list = []
for duel in user_duel_list: for duel in user_duel_list:
tmp = duel._asdict() tmp = duel._asdict()
@@ -233,18 +239,18 @@ class ChuniBase():
duel_list.append(tmp) duel_list.append(tmp)
return { return {
"userId": data["userId"], "userId": data["userId"],
"length": len(duel_list), "length": len(duel_list),
"userDuelList": duel_list "userDuelList": duel_list,
} }
def handle_get_user_favorite_item_api_request(self, data: Dict) -> Dict: def handle_get_user_favorite_item_api_request(self, data: Dict) -> Dict:
return { return {
"userId": data["userId"], "userId": data["userId"],
"length": 0, "length": 0,
"kind": data["kind"], "kind": data["kind"],
"nextIndex": -1, "nextIndex": -1,
"userFavoriteItemList": [] "userFavoriteItemList": [],
} }
def handle_get_user_favorite_music_api_request(self, data: Dict) -> Dict: def handle_get_user_favorite_music_api_request(self, data: Dict) -> Dict:
@@ -252,22 +258,23 @@ class ChuniBase():
This is handled via the webui, which we don't have right now This is handled via the webui, which we don't have right now
""" """
return { return {"userId": data["userId"], "length": 0, "userFavoriteMusicList": []}
"userId": data["userId"],
"length": 0,
"userFavoriteMusicList": []
}
def handle_get_user_item_api_request(self, data: Dict) -> Dict: def handle_get_user_item_api_request(self, data: Dict) -> Dict:
kind = int(int(data["nextIndex"]) / 10000000000) kind = int(int(data["nextIndex"]) / 10000000000)
next_idx = int(int(data["nextIndex"]) % 10000000000) next_idx = int(int(data["nextIndex"]) % 10000000000)
user_item_list = self.data.item.get_items(data["userId"], kind) user_item_list = self.data.item.get_items(data["userId"], kind)
if user_item_list is None or len(user_item_list) == 0: if user_item_list is None or len(user_item_list) == 0:
return {"userId": data["userId"], "nextIndex": -1, "itemKind": kind, "userItemList": []} return {
"userId": data["userId"],
"nextIndex": -1,
"itemKind": kind,
"userItemList": [],
}
items: list[Dict[str, Any]] = [] items: list[Dict[str, Any]] = []
for i in range(next_idx, len(user_item_list)): for i in range(next_idx, len(user_item_list)):
tmp = user_item_list[i]._asdict() tmp = user_item_list[i]._asdict()
tmp.pop("user") tmp.pop("user")
tmp.pop("id") tmp.pop("id")
@@ -277,38 +284,47 @@ class ChuniBase():
xout = kind * 10000000000 + next_idx + len(items) xout = kind * 10000000000 + next_idx + len(items)
if len(items) < int(data["maxCount"]): nextIndex = 0 if len(items) < int(data["maxCount"]):
else: nextIndex = xout nextIndex = 0
else:
nextIndex = xout
return {"userId": data["userId"], "nextIndex": nextIndex, "itemKind": kind, "length": len(items), "userItemList": items} return {
"userId": data["userId"],
"nextIndex": nextIndex,
"itemKind": kind,
"length": len(items),
"userItemList": items,
}
def handle_get_user_login_bonus_api_request(self, data: Dict) -> Dict: def handle_get_user_login_bonus_api_request(self, data: Dict) -> Dict:
""" """
Unsure how to get this to trigger... Unsure how to get this to trigger...
""" """
return { return {
"userId": data["userId"], "userId": data["userId"],
"length": 2, "length": 2,
"userLoginBonusList": [ "userLoginBonusList": [
{ {
"presetId": '10', "presetId": "10",
"bonusCount": '0', "bonusCount": "0",
"lastUpdateDate": "1970-01-01 09:00:00", "lastUpdateDate": "1970-01-01 09:00:00",
"isWatched": "true" "isWatched": "true",
}, },
{ {
"presetId": '20', "presetId": "20",
"bonusCount": '0', "bonusCount": "0",
"lastUpdateDate": "1970-01-01 09:00:00", "lastUpdateDate": "1970-01-01 09:00:00",
"isWatched": "true" "isWatched": "true",
}, },
] ],
} }
def handle_get_user_map_api_request(self, data: Dict) -> Dict: def handle_get_user_map_api_request(self, data: Dict) -> Dict:
user_map_list = self.data.item.get_maps(data["userId"]) user_map_list = self.data.item.get_maps(data["userId"])
if user_map_list is None: return {} if user_map_list is None:
return {}
map_list = [] map_list = []
for map in user_map_list: for map in user_map_list:
tmp = map._asdict() tmp = map._asdict()
@@ -317,19 +333,19 @@ class ChuniBase():
map_list.append(tmp) map_list.append(tmp)
return { return {
"userId": data["userId"], "userId": data["userId"],
"length": len(map_list), "length": len(map_list),
"userMapList": map_list "userMapList": map_list,
} }
def handle_get_user_music_api_request(self, data: Dict) -> Dict: def handle_get_user_music_api_request(self, data: Dict) -> Dict:
music_detail = self.data.score.get_scores(data["userId"]) music_detail = self.data.score.get_scores(data["userId"])
if music_detail is None: if music_detail is None:
return { return {
"userId": data["userId"], "userId": data["userId"],
"length": 0, "length": 0,
"nextIndex": -1, "nextIndex": -1,
"userMusicList": [] #240 "userMusicList": [], # 240
} }
song_list = [] song_list = []
next_idx = int(data["nextIndex"]) next_idx = int(data["nextIndex"])
@@ -340,66 +356,60 @@ class ChuniBase():
tmp = music_detail[x]._asdict() tmp = music_detail[x]._asdict()
tmp.pop("user") tmp.pop("user")
tmp.pop("id") tmp.pop("id")
for song in song_list: for song in song_list:
if song["userMusicDetailList"][0]["musicId"] == tmp["musicId"]: if song["userMusicDetailList"][0]["musicId"] == tmp["musicId"]:
found = True found = True
song["userMusicDetailList"].append(tmp) song["userMusicDetailList"].append(tmp)
song["length"] = len(song["userMusicDetailList"]) song["length"] = len(song["userMusicDetailList"])
if not found: if not found:
song_list.append({ song_list.append({"length": 1, "userMusicDetailList": [tmp]})
"length": 1,
"userMusicDetailList": [tmp]
})
if len(song_list) >= max_ct: if len(song_list) >= max_ct:
break break
if len(song_list) >= max_ct: if len(song_list) >= max_ct:
next_idx += max_ct next_idx += max_ct
else: else:
next_idx = 0 next_idx = 0
return { return {
"userId": data["userId"], "userId": data["userId"],
"length": len(song_list), "length": len(song_list),
"nextIndex": next_idx, "nextIndex": next_idx,
"userMusicList": song_list #240 "userMusicList": song_list, # 240
} }
def handle_get_user_option_api_request(self, data: Dict) -> Dict: def handle_get_user_option_api_request(self, data: Dict) -> Dict:
p = self.data.profile.get_profile_option(data["userId"]) p = self.data.profile.get_profile_option(data["userId"])
option = p._asdict() option = p._asdict()
option.pop("id") option.pop("id")
option.pop("user") option.pop("user")
return { return {"userId": data["userId"], "userGameOption": option}
"userId": data["userId"],
"userGameOption": option
}
def handle_get_user_option_ex_api_request(self, data: Dict) -> Dict: def handle_get_user_option_ex_api_request(self, data: Dict) -> Dict:
p = self.data.profile.get_profile_option_ex(data["userId"]) p = self.data.profile.get_profile_option_ex(data["userId"])
option = p._asdict() option = p._asdict()
option.pop("id") option.pop("id")
option.pop("user") option.pop("user")
return { return {"userId": data["userId"], "userGameOptionEx": option}
"userId": data["userId"],
"userGameOptionEx": option
}
def read_wtf8(self, src): def read_wtf8(self, src):
return bytes([ord(c) for c in src]).decode("utf-8") return bytes([ord(c) for c in src]).decode("utf-8")
def handle_get_user_preview_api_request(self, data: Dict) -> Dict: def handle_get_user_preview_api_request(self, data: Dict) -> Dict:
profile = self.data.profile.get_profile_preview(data["userId"], self.version) profile = self.data.profile.get_profile_preview(data["userId"], self.version)
if profile is None: return None if profile is None:
profile_character = self.data.item.get_character(data["userId"], profile["characterId"]) return None
profile_character = self.data.item.get_character(
data["userId"], profile["characterId"]
)
if profile_character is None: if profile_character is None:
chara = {} chara = {}
else: else:
@@ -408,8 +418,8 @@ class ChuniBase():
chara.pop("user") chara.pop("user")
return { return {
"userId": data["userId"], "userId": data["userId"],
# Current Login State # Current Login State
"isLogin": False, "isLogin": False,
"lastLoginDate": profile["lastPlayDate"], "lastLoginDate": profile["lastPlayDate"],
# User Profile # User Profile
@@ -421,14 +431,14 @@ class ChuniBase():
"lastGameId": profile["lastGameId"], "lastGameId": profile["lastGameId"],
"lastRomVersion": profile["lastRomVersion"], "lastRomVersion": profile["lastRomVersion"],
"lastDataVersion": profile["lastDataVersion"], "lastDataVersion": profile["lastDataVersion"],
"lastPlayDate": profile["lastPlayDate"], "lastPlayDate": profile["lastPlayDate"],
"trophyId": profile["trophyId"], "trophyId": profile["trophyId"],
"nameplateId": profile["nameplateId"], "nameplateId": profile["nameplateId"],
# Current Selected Character # Current Selected Character
"userCharacter": chara, "userCharacter": chara,
# User Game Options # User Game Options
"playerLevel": profile["playerLevel"], "playerLevel": profile["playerLevel"],
"rating": profile["rating"], "rating": profile["rating"],
"headphone": profile["headphone"], "headphone": profile["headphone"],
"chargeState": "1", "chargeState": "1",
"userNameEx": profile["userName"], "userNameEx": profile["userName"],
@@ -436,7 +446,7 @@ class ChuniBase():
def handle_get_user_recent_rating_api_request(self, data: Dict) -> Dict: def handle_get_user_recent_rating_api_request(self, data: Dict) -> Dict:
recet_rating_list = self.data.profile.get_profile_recent_rating(data["userId"]) recet_rating_list = self.data.profile.get_profile_recent_rating(data["userId"])
if recet_rating_list is None: if recet_rating_list is None:
return { return {
"userId": data["userId"], "userId": data["userId"],
"length": 0, "length": 0,
@@ -459,11 +469,8 @@ class ChuniBase():
def handle_get_user_team_api_request(self, data: Dict) -> Dict: def handle_get_user_team_api_request(self, data: Dict) -> Dict:
# TODO: Team # TODO: Team
return { return {"userId": data["userId"], "teamId": 0}
"userId": data["userId"],
"teamId": 0
}
def handle_get_team_course_setting_api_request(self, data: Dict) -> Dict: def handle_get_team_course_setting_api_request(self, data: Dict) -> Dict:
return { return {
"userId": data["userId"], "userId": data["userId"],
@@ -486,19 +493,30 @@ class ChuniBase():
if "userData" in upsert: if "userData" in upsert:
try: try:
upsert["userData"][0]["userName"] = self.read_wtf8(upsert["userData"][0]["userName"]) upsert["userData"][0]["userName"] = self.read_wtf8(
except: pass upsert["userData"][0]["userName"]
)
except:
pass
self.data.profile.put_profile_data(user_id, self.version, upsert["userData"][0]) self.data.profile.put_profile_data(
user_id, self.version, upsert["userData"][0]
)
if "userDataEx" in upsert: if "userDataEx" in upsert:
self.data.profile.put_profile_data_ex(user_id, self.version, upsert["userDataEx"][0]) self.data.profile.put_profile_data_ex(
user_id, self.version, upsert["userDataEx"][0]
)
if "userGameOption" in upsert: if "userGameOption" in upsert:
self.data.profile.put_profile_option(user_id, upsert["userGameOption"][0]) self.data.profile.put_profile_option(user_id, upsert["userGameOption"][0])
if "userGameOptionEx" in upsert: if "userGameOptionEx" in upsert:
self.data.profile.put_profile_option_ex(user_id, upsert["userGameOptionEx"][0]) self.data.profile.put_profile_option_ex(
user_id, upsert["userGameOptionEx"][0]
)
if "userRecentRatingList" in upsert: if "userRecentRatingList" in upsert:
self.data.profile.put_profile_recent_rating(user_id, upsert["userRecentRatingList"]) self.data.profile.put_profile_recent_rating(
user_id, upsert["userRecentRatingList"]
)
if "userCharacterList" in upsert: if "userCharacterList" in upsert:
for character in upsert["userCharacterList"]: for character in upsert["userCharacterList"]:
self.data.item.put_character(user_id, character) self.data.item.put_character(user_id, character)
@@ -514,7 +532,7 @@ class ChuniBase():
if "userDuelList" in upsert: if "userDuelList" in upsert:
for duel in upsert["userDuelList"]: for duel in upsert["userDuelList"]:
self.data.item.put_duel(user_id, duel) self.data.item.put_duel(user_id, duel)
if "userItemList" in upsert: if "userItemList" in upsert:
for item in upsert["userItemList"]: for item in upsert["userItemList"]:
self.data.item.put_item(user_id, item) self.data.item.put_item(user_id, item)
@@ -522,23 +540,23 @@ class ChuniBase():
if "userActivityList" in upsert: if "userActivityList" in upsert:
for activity in upsert["userActivityList"]: for activity in upsert["userActivityList"]:
self.data.profile.put_profile_activity(user_id, activity) self.data.profile.put_profile_activity(user_id, activity)
if "userChargeList" in upsert: if "userChargeList" in upsert:
for charge in upsert["userChargeList"]: for charge in upsert["userChargeList"]:
self.data.profile.put_profile_charge(user_id, charge) self.data.profile.put_profile_charge(user_id, charge)
if "userMusicDetailList" in upsert: if "userMusicDetailList" in upsert:
for song in upsert["userMusicDetailList"]: for song in upsert["userMusicDetailList"]:
self.data.score.put_score(user_id, song) self.data.score.put_score(user_id, song)
if "userPlaylogList" in upsert: if "userPlaylogList" in upsert:
for playlog in upsert["userPlaylogList"]: for playlog in upsert["userPlaylogList"]:
self.data.score.put_playlog(user_id, playlog) self.data.score.put_playlog(user_id, playlog)
if "userTeamPoint" in upsert: if "userTeamPoint" in upsert:
# TODO: team stuff # TODO: team stuff
pass pass
if "userMapAreaList" in upsert: if "userMapAreaList" in upsert:
for map_area in upsert["userMapAreaList"]: for map_area in upsert["userMapAreaList"]:
self.data.item.put_map_area(user_id, map_area) self.data.item.put_map_area(user_id, map_area)
@@ -551,22 +569,22 @@ class ChuniBase():
for emoney in upsert["userEmoneyList"]: for emoney in upsert["userEmoneyList"]:
self.data.profile.put_profile_emoney(user_id, emoney) self.data.profile.put_profile_emoney(user_id, emoney)
return { "returnCode": "1" } return {"returnCode": "1"}
def handle_upsert_user_chargelog_api_request(self, data: Dict) -> Dict: def handle_upsert_user_chargelog_api_request(self, data: Dict) -> Dict:
return { "returnCode": "1" } return {"returnCode": "1"}
def handle_upsert_client_bookkeeping_api_request(self, data: Dict) -> Dict: def handle_upsert_client_bookkeeping_api_request(self, data: Dict) -> Dict:
return { "returnCode": "1" } return {"returnCode": "1"}
def handle_upsert_client_develop_api_request(self, data: Dict) -> Dict: def handle_upsert_client_develop_api_request(self, data: Dict) -> Dict:
return { "returnCode": "1" } return {"returnCode": "1"}
def handle_upsert_client_error_api_request(self, data: Dict) -> Dict: def handle_upsert_client_error_api_request(self, data: Dict) -> Dict:
return { "returnCode": "1" } return {"returnCode": "1"}
def handle_upsert_client_setting_api_request(self, data: Dict) -> Dict: def handle_upsert_client_setting_api_request(self, data: Dict) -> Dict:
return { "returnCode": "1" } return {"returnCode": "1"}
def handle_upsert_client_testmode_api_request(self, data: Dict) -> Dict: def handle_upsert_client_testmode_api_request(self, data: Dict) -> Dict:
return { "returnCode": "1" } return {"returnCode": "1"}
+24 -11
View File
@@ -1,36 +1,49 @@
from core.config import CoreConfig from core.config import CoreConfig
from typing import Dict from typing import Dict
class ChuniServerConfig():
class ChuniServerConfig:
def __init__(self, parent_config: "ChuniConfig") -> None: def __init__(self, parent_config: "ChuniConfig") -> None:
self.__config = parent_config self.__config = parent_config
@property @property
def enable(self) -> bool: def enable(self) -> bool:
return CoreConfig.get_config_field(self.__config, 'chuni', 'server', 'enable', default=True) return CoreConfig.get_config_field(
self.__config, "chuni", "server", "enable", default=True
)
@property @property
def loglevel(self) -> int: def loglevel(self) -> int:
return CoreConfig.str_to_loglevel(CoreConfig.get_config_field(self.__config, 'chuni', 'server', 'loglevel', default="info")) return CoreConfig.str_to_loglevel(
CoreConfig.get_config_field(
self.__config, "chuni", "server", "loglevel", default="info"
)
)
class ChuniCryptoConfig():
class ChuniCryptoConfig:
def __init__(self, parent_config: "ChuniConfig") -> None: def __init__(self, parent_config: "ChuniConfig") -> None:
self.__config = parent_config self.__config = parent_config
@property @property
def keys(self) -> Dict: def keys(self) -> Dict:
""" """
in the form of: in the form of:
internal_version: [key, iv] internal_version: [key, iv]
all values are hex strings all values are hex strings
""" """
return CoreConfig.get_config_field(self.__config, 'chuni', 'crypto', 'keys', default={}) return CoreConfig.get_config_field(
self.__config, "chuni", "crypto", "keys", default={}
)
@property @property
def encrypted_only(self) -> bool: def encrypted_only(self) -> bool:
return CoreConfig.get_config_field(self.__config, 'chuni', 'crypto', 'encrypted_only', default=False) return CoreConfig.get_config_field(
self.__config, "chuni", "crypto", "encrypted_only", default=False
)
class ChuniConfig(dict): class ChuniConfig(dict):
def __init__(self) -> None: def __init__(self) -> None:
self.server = ChuniServerConfig(self) self.server = ChuniServerConfig(self)
self.crypto = ChuniCryptoConfig(self) self.crypto = ChuniCryptoConfig(self)
+17 -4
View File
@@ -1,4 +1,4 @@
class ChuniConstants(): class ChuniConstants:
GAME_CODE = "SDBT" GAME_CODE = "SDBT"
GAME_CODE_NEW = "SDHD" GAME_CODE_NEW = "SDHD"
@@ -8,7 +8,7 @@ class ChuniConstants():
VER_CHUNITHM_PLUS = 1 VER_CHUNITHM_PLUS = 1
VER_CHUNITHM_AIR = 2 VER_CHUNITHM_AIR = 2
VER_CHUNITHM_AIR_PLUS = 3 VER_CHUNITHM_AIR_PLUS = 3
VER_CHUNITHM_STAR = 4 VER_CHUNITHM_STAR = 4
VER_CHUNITHM_STAR_PLUS = 5 VER_CHUNITHM_STAR_PLUS = 5
VER_CHUNITHM_AMAZON = 6 VER_CHUNITHM_AMAZON = 6
VER_CHUNITHM_AMAZON_PLUS = 7 VER_CHUNITHM_AMAZON_PLUS = 7
@@ -18,8 +18,21 @@ class ChuniConstants():
VER_CHUNITHM_NEW = 11 VER_CHUNITHM_NEW = 11
VER_CHUNITHM_NEW_PLUS = 12 VER_CHUNITHM_NEW_PLUS = 12
VERSION_NAMES = ["Chunithm", "Chunithm+", "Chunithm Air", "Chunithm Air+", "Chunithm Star", "Chunithm Star+", "Chunithm Amazon", VERSION_NAMES = [
"Chunithm Amazon+", "Chunithm Crystal", "Chunithm Crystal+", "Chunithm Paradise", "Chunithm New!!", "Chunithm New!!+"] "Chunithm",
"Chunithm+",
"Chunithm Air",
"Chunithm Air+",
"Chunithm Star",
"Chunithm Star+",
"Chunithm Amazon",
"Chunithm Amazon+",
"Chunithm Crystal",
"Chunithm Crystal+",
"Chunithm Paradise",
"Chunithm New!!",
"Chunithm New!!+",
]
@classmethod @classmethod
def game_ver_to_string(cls, ver: int): def game_ver_to_string(cls, ver: int):
+3 -2
View File
@@ -7,12 +7,13 @@ from titles.chuni.base import ChuniBase
from titles.chuni.const import ChuniConstants from titles.chuni.const import ChuniConstants
from titles.chuni.config import ChuniConfig from titles.chuni.config import ChuniConfig
class ChuniCrystal(ChuniBase): class ChuniCrystal(ChuniBase):
def __init__(self, core_cfg: CoreConfig, game_cfg: ChuniConfig) -> None: def __init__(self, core_cfg: CoreConfig, game_cfg: ChuniConfig) -> None:
super().__init__(core_cfg, game_cfg) super().__init__(core_cfg, game_cfg)
self.version = ChuniConstants.VER_CHUNITHM_CRYSTAL self.version = ChuniConstants.VER_CHUNITHM_CRYSTAL
def handle_get_game_setting_api_request(self, data: Dict) -> Dict: def handle_get_game_setting_api_request(self, data: Dict) -> Dict:
ret = super().handle_get_game_setting_api_request(data) ret = super().handle_get_game_setting_api_request(data)
ret["gameSetting"]["dataVersion"] = "1.40.00" ret["gameSetting"]["dataVersion"] = "1.40.00"
return ret return ret
+3 -2
View File
@@ -7,12 +7,13 @@ from titles.chuni.base import ChuniBase
from titles.chuni.const import ChuniConstants from titles.chuni.const import ChuniConstants
from titles.chuni.config import ChuniConfig from titles.chuni.config import ChuniConfig
class ChuniCrystalPlus(ChuniBase): class ChuniCrystalPlus(ChuniBase):
def __init__(self, core_cfg: CoreConfig, game_cfg: ChuniConfig) -> None: def __init__(self, core_cfg: CoreConfig, game_cfg: ChuniConfig) -> None:
super().__init__(core_cfg, game_cfg) super().__init__(core_cfg, game_cfg)
self.version = ChuniConstants.VER_CHUNITHM_CRYSTAL_PLUS self.version = ChuniConstants.VER_CHUNITHM_CRYSTAL_PLUS
def handle_get_game_setting_api_request(self, data: Dict) -> Dict: def handle_get_game_setting_api_request(self, data: Dict) -> Dict:
ret = super().handle_get_game_setting_api_request(data) ret = super().handle_get_game_setting_api_request(data)
ret["gameSetting"]["dataVersion"] = "1.45.00" ret["gameSetting"]["dataVersion"] = "1.45.00"
return ret return ret
+2 -1
View File
@@ -2,6 +2,7 @@ from core.data import Data
from core.config import CoreConfig from core.config import CoreConfig
from titles.chuni.schema import * from titles.chuni.schema import *
class ChuniData(Data): class ChuniData(Data):
def __init__(self, cfg: CoreConfig) -> None: def __init__(self, cfg: CoreConfig) -> None:
super().__init__(cfg) super().__init__(cfg)
@@ -9,4 +10,4 @@ class ChuniData(Data):
self.item = ChuniItemData(cfg, self.session) self.item = ChuniItemData(cfg, self.session)
self.profile = ChuniProfileData(cfg, self.session) self.profile = ChuniProfileData(cfg, self.session)
self.score = ChuniScoreData(cfg, self.session) self.score = ChuniScoreData(cfg, self.session)
self.static = ChuniStaticData(cfg, self.session) self.static = ChuniStaticData(cfg, self.session)
+73 -49
View File
@@ -28,12 +28,15 @@ from titles.chuni.paradise import ChuniParadise
from titles.chuni.new import ChuniNew from titles.chuni.new import ChuniNew
from titles.chuni.newplus import ChuniNewPlus from titles.chuni.newplus import ChuniNewPlus
class ChuniServlet():
class ChuniServlet:
def __init__(self, core_cfg: CoreConfig, cfg_dir: str) -> None: def __init__(self, core_cfg: CoreConfig, cfg_dir: str) -> None:
self.core_cfg = core_cfg self.core_cfg = core_cfg
self.game_cfg = ChuniConfig() self.game_cfg = ChuniConfig()
if path.exists(f"{cfg_dir}/{ChuniConstants.CONFIG_NAME}"): if path.exists(f"{cfg_dir}/{ChuniConstants.CONFIG_NAME}"):
self.game_cfg.update(yaml.safe_load(open(f"{cfg_dir}/{ChuniConstants.CONFIG_NAME}"))) self.game_cfg.update(
yaml.safe_load(open(f"{cfg_dir}/{ChuniConstants.CONFIG_NAME}"))
)
self.versions = [ self.versions = [
ChuniBase(core_cfg, self.game_cfg), ChuniBase(core_cfg, self.game_cfg),
@@ -56,33 +59,47 @@ class ChuniServlet():
if not hasattr(self.logger, "inited"): if not hasattr(self.logger, "inited"):
log_fmt_str = "[%(asctime)s] Chunithm | %(levelname)s | %(message)s" log_fmt_str = "[%(asctime)s] Chunithm | %(levelname)s | %(message)s"
log_fmt = logging.Formatter(log_fmt_str) log_fmt = logging.Formatter(log_fmt_str)
fileHandler = TimedRotatingFileHandler("{0}/{1}.log".format(self.core_cfg.server.log_dir, "chuni"), encoding='utf8', fileHandler = TimedRotatingFileHandler(
when="d", backupCount=10) "{0}/{1}.log".format(self.core_cfg.server.log_dir, "chuni"),
encoding="utf8",
when="d",
backupCount=10,
)
fileHandler.setFormatter(log_fmt) fileHandler.setFormatter(log_fmt)
consoleHandler = logging.StreamHandler() consoleHandler = logging.StreamHandler()
consoleHandler.setFormatter(log_fmt) consoleHandler.setFormatter(log_fmt)
self.logger.addHandler(fileHandler) self.logger.addHandler(fileHandler)
self.logger.addHandler(consoleHandler) self.logger.addHandler(consoleHandler)
self.logger.setLevel(self.game_cfg.server.loglevel) self.logger.setLevel(self.game_cfg.server.loglevel)
coloredlogs.install(level=self.game_cfg.server.loglevel, logger=self.logger, fmt=log_fmt_str) coloredlogs.install(
level=self.game_cfg.server.loglevel, logger=self.logger, fmt=log_fmt_str
)
self.logger.inited = True self.logger.inited = True
@classmethod @classmethod
def get_allnet_info(cls, game_code: str, core_cfg: CoreConfig, cfg_dir: str) -> Tuple[bool, str, str]: def get_allnet_info(
cls, game_code: str, core_cfg: CoreConfig, cfg_dir: str
) -> Tuple[bool, str, str]:
game_cfg = ChuniConfig() game_cfg = ChuniConfig()
if path.exists(f"{cfg_dir}/{ChuniConstants.CONFIG_NAME}"): if path.exists(f"{cfg_dir}/{ChuniConstants.CONFIG_NAME}"):
game_cfg.update(yaml.safe_load(open(f"{cfg_dir}/{ChuniConstants.CONFIG_NAME}"))) game_cfg.update(
yaml.safe_load(open(f"{cfg_dir}/{ChuniConstants.CONFIG_NAME}"))
)
if not game_cfg.server.enable: if not game_cfg.server.enable:
return (False, "", "") return (False, "", "")
if core_cfg.server.is_develop: if core_cfg.server.is_develop:
return (True, f"http://{core_cfg.title.hostname}:{core_cfg.title.port}/{game_code}/$v/", "") return (
True,
f"http://{core_cfg.title.hostname}:{core_cfg.title.port}/{game_code}/$v/",
"",
)
return (True, f"http://{core_cfg.title.hostname}/{game_code}/$v/", "") return (True, f"http://{core_cfg.title.hostname}/{game_code}/$v/", "")
def render_POST(self, request: Request, version: int, url_path: str) -> bytes: def render_POST(self, request: Request, version: int, url_path: str) -> bytes:
@@ -95,67 +112,75 @@ class ChuniServlet():
internal_ver = 0 internal_ver = 0
endpoint = url_split[len(url_split) - 1] endpoint = url_split[len(url_split) - 1]
if version < 105: # 1.0 if version < 105: # 1.0
internal_ver = ChuniConstants.VER_CHUNITHM internal_ver = ChuniConstants.VER_CHUNITHM
elif version >= 105 and version < 110: # Plus elif version >= 105 and version < 110: # Plus
internal_ver = ChuniConstants.VER_CHUNITHM_PLUS internal_ver = ChuniConstants.VER_CHUNITHM_PLUS
elif version >= 110 and version < 115: # Air elif version >= 110 and version < 115: # Air
internal_ver = ChuniConstants.VER_CHUNITHM_AIR internal_ver = ChuniConstants.VER_CHUNITHM_AIR
elif version >= 115 and version < 120: # Air Plus elif version >= 115 and version < 120: # Air Plus
internal_ver = ChuniConstants.VER_CHUNITHM_AIR_PLUS internal_ver = ChuniConstants.VER_CHUNITHM_AIR_PLUS
elif version >= 120 and version < 125: # Star elif version >= 120 and version < 125: # Star
internal_ver = ChuniConstants.VER_CHUNITHM_STAR internal_ver = ChuniConstants.VER_CHUNITHM_STAR
elif version >= 125 and version < 130: # Star Plus elif version >= 125 and version < 130: # Star Plus
internal_ver = ChuniConstants.VER_CHUNITHM_STAR_PLUS internal_ver = ChuniConstants.VER_CHUNITHM_STAR_PLUS
elif version >= 130 and version < 135: # Amazon elif version >= 130 and version < 135: # Amazon
internal_ver = ChuniConstants.VER_CHUNITHM_AMAZON internal_ver = ChuniConstants.VER_CHUNITHM_AMAZON
elif version >= 135 and version < 140: # Amazon Plus elif version >= 135 and version < 140: # Amazon Plus
internal_ver = ChuniConstants.VER_CHUNITHM_AMAZON_PLUS internal_ver = ChuniConstants.VER_CHUNITHM_AMAZON_PLUS
elif version >= 140 and version < 145: # Crystal elif version >= 140 and version < 145: # Crystal
internal_ver = ChuniConstants.VER_CHUNITHM_CRYSTAL internal_ver = ChuniConstants.VER_CHUNITHM_CRYSTAL
elif version >= 145 and version < 150: # Crystal Plus elif version >= 145 and version < 150: # Crystal Plus
internal_ver = ChuniConstants.VER_CHUNITHM_CRYSTAL_PLUS internal_ver = ChuniConstants.VER_CHUNITHM_CRYSTAL_PLUS
elif version >= 150 and version < 200: # Paradise elif version >= 150 and version < 200: # Paradise
internal_ver = ChuniConstants.VER_CHUNITHM_PARADISE internal_ver = ChuniConstants.VER_CHUNITHM_PARADISE
elif version >= 200 and version < 205: # New elif version >= 200 and version < 205: # New
internal_ver = ChuniConstants.VER_CHUNITHM_NEW internal_ver = ChuniConstants.VER_CHUNITHM_NEW
elif version >= 205 and version < 210: # New Plus elif version >= 205 and version < 210: # New Plus
internal_ver = ChuniConstants.VER_CHUNITHM_NEW_PLUS internal_ver = ChuniConstants.VER_CHUNITHM_NEW_PLUS
if all(c in string.hexdigits for c in endpoint) and len(endpoint) == 32: if all(c in string.hexdigits for c in endpoint) and len(endpoint) == 32:
# If we get a 32 character long hex string, it's a hash and we're # If we get a 32 character long hex string, it's a hash and we're
# doing encrypted. The likelyhood of false positives is low but # doing encrypted. The likelyhood of false positives is low but
# technically not 0 # technically not 0
endpoint = request.getHeader("User-Agent").split("#")[0] endpoint = request.getHeader("User-Agent").split("#")[0]
try: try:
crypt = AES.new( crypt = AES.new(
bytes.fromhex(self.game_cfg.crypto.keys[str(internal_ver)][0]), bytes.fromhex(self.game_cfg.crypto.keys[str(internal_ver)][0]),
AES.MODE_CBC, AES.MODE_CBC,
bytes.fromhex(self.game_cfg.crypto.keys[str(internal_ver)][1]) bytes.fromhex(self.game_cfg.crypto.keys[str(internal_ver)][1]),
) )
req_raw = crypt.decrypt(req_raw) req_raw = crypt.decrypt(req_raw)
except: except:
self.logger.error(f"Failed to decrypt v{version} request to {endpoint} -> {req_raw}") self.logger.error(
f"Failed to decrypt v{version} request to {endpoint} -> {req_raw}"
)
return zlib.compress(b'{"stat": "0"}') return zlib.compress(b'{"stat": "0"}')
encrtped = True encrtped = True
if not encrtped and self.game_cfg.crypto.encrypted_only: if not encrtped and self.game_cfg.crypto.encrypted_only:
self.logger.error(f"Unencrypted v{version} {endpoint} request, but config is set to encrypted only: {req_raw}") self.logger.error(
f"Unencrypted v{version} {endpoint} request, but config is set to encrypted only: {req_raw}"
)
return zlib.compress(b'{"stat": "0"}') return zlib.compress(b'{"stat": "0"}')
try: try:
unzip = zlib.decompress(req_raw) unzip = zlib.decompress(req_raw)
except zlib.error as e: except zlib.error as e:
self.logger.error(f"Failed to decompress v{version} {endpoint} request -> {e}") self.logger.error(
f"Failed to decompress v{version} {endpoint} request -> {e}"
)
return b"" return b""
req_data = json.loads(unzip) req_data = json.loads(unzip)
self.logger.info(f"v{version} {endpoint} request from {request.getClientAddress().host}") self.logger.info(
f"v{version} {endpoint} request from {request.getClientAddress().host}"
)
self.logger.debug(req_data) self.logger.debug(req_data)
func_to_find = "handle_" + inflection.underscore(endpoint) + "_request" func_to_find = "handle_" + inflection.underscore(endpoint) + "_request"
@@ -163,9 +188,8 @@ class ChuniServlet():
if not hasattr(self.versions[internal_ver], func_to_find): if not hasattr(self.versions[internal_ver], func_to_find):
self.logger.warning(f"Unhandled v{version} request {endpoint}") self.logger.warning(f"Unhandled v{version} request {endpoint}")
resp = {"returnCode": 1} resp = {"returnCode": 1}
else:
else:
try: try:
handler = getattr(self.versions[internal_ver], func_to_find) handler = getattr(self.versions[internal_ver], func_to_find)
resp = handler(req_data) resp = handler(req_data)
@@ -173,23 +197,23 @@ class ChuniServlet():
except Exception as e: except Exception as e:
self.logger.error(f"Error handling v{version} method {endpoint} - {e}") self.logger.error(f"Error handling v{version} method {endpoint} - {e}")
return zlib.compress(b'{"stat": "0"}') return zlib.compress(b'{"stat": "0"}')
if resp == None: if resp == None:
resp = {'returnCode': 1} resp = {"returnCode": 1}
self.logger.debug(f"Response {resp}") self.logger.debug(f"Response {resp}")
zipped = zlib.compress(json.dumps(resp, ensure_ascii=False).encode("utf-8")) zipped = zlib.compress(json.dumps(resp, ensure_ascii=False).encode("utf-8"))
if not encrtped: if not encrtped:
return zipped return zipped
padded = pad(zipped, 16) padded = pad(zipped, 16)
crypt = AES.new( crypt = AES.new(
bytes.fromhex(self.game_cfg.crypto.keys[str(internal_ver)][0]), bytes.fromhex(self.game_cfg.crypto.keys[str(internal_ver)][0]),
AES.MODE_CBC, AES.MODE_CBC,
bytes.fromhex(self.game_cfg.crypto.keys[str(internal_ver)][1]) bytes.fromhex(self.game_cfg.crypto.keys[str(internal_ver)][1]),
) )
return crypt.encrypt(padded) return crypt.encrypt(padded)
+38 -37
View File
@@ -9,13 +9,9 @@ from titles.chuni.database import ChuniData
from titles.chuni.base import ChuniBase from titles.chuni.base import ChuniBase
from titles.chuni.config import ChuniConfig from titles.chuni.config import ChuniConfig
class ChuniNew(ChuniBase):
ITEM_TYPE = { class ChuniNew(ChuniBase):
"character": 20, ITEM_TYPE = {"character": 20, "story": 21, "card": 22}
"story": 21,
"card": 22
}
def __init__(self, core_cfg: CoreConfig, game_cfg: ChuniConfig) -> None: def __init__(self, core_cfg: CoreConfig, game_cfg: ChuniConfig) -> None:
self.core_cfg = core_cfg self.core_cfg = core_cfg
@@ -25,12 +21,20 @@ class ChuniNew(ChuniBase):
self.logger = logging.getLogger("chuni") self.logger = logging.getLogger("chuni")
self.game = ChuniConstants.GAME_CODE self.game = ChuniConstants.GAME_CODE
self.version = ChuniConstants.VER_CHUNITHM_NEW self.version = ChuniConstants.VER_CHUNITHM_NEW
def handle_get_game_setting_api_request(self, data: Dict) -> Dict: def handle_get_game_setting_api_request(self, data: Dict) -> Dict:
match_start = datetime.strftime(datetime.now() - timedelta(hours=10), self.date_time_format) match_start = datetime.strftime(
match_end = datetime.strftime(datetime.now() + timedelta(hours=10), self.date_time_format) datetime.now() - timedelta(hours=10), self.date_time_format
reboot_start = datetime.strftime(datetime.now() - timedelta(hours=11), self.date_time_format) )
reboot_end = datetime.strftime(datetime.now() - timedelta(hours=10), self.date_time_format) match_end = datetime.strftime(
datetime.now() + timedelta(hours=10), self.date_time_format
)
reboot_start = datetime.strftime(
datetime.now() - timedelta(hours=11), self.date_time_format
)
reboot_end = datetime.strftime(
datetime.now() - timedelta(hours=10), self.date_time_format
)
return { return {
"gameSetting": { "gameSetting": {
"isMaintenance": "false", "isMaintenance": "false",
@@ -52,16 +56,16 @@ class ChuniNew(ChuniBase):
"udpHolePunchUri": f"http://{self.core_cfg.title.hostname}:{self.core_cfg.title.port}/SDHD/200/ChuniServlet/", "udpHolePunchUri": f"http://{self.core_cfg.title.hostname}:{self.core_cfg.title.port}/SDHD/200/ChuniServlet/",
"reflectorUri": f"http://{self.core_cfg.title.hostname}:{self.core_cfg.title.port}/SDHD/200/ChuniServlet/", "reflectorUri": f"http://{self.core_cfg.title.hostname}:{self.core_cfg.title.port}/SDHD/200/ChuniServlet/",
}, },
"isDumpUpload": "false", "isDumpUpload": "false",
"isAou": "false", "isAou": "false",
} }
def handle_delete_token_api_request(self, data: Dict) -> Dict: def handle_delete_token_api_request(self, data: Dict) -> Dict:
return { "returnCode": "1" } return {"returnCode": "1"}
def handle_create_token_api_request(self, data: Dict) -> Dict: def handle_create_token_api_request(self, data: Dict) -> Dict:
return { "returnCode": "1" } return {"returnCode": "1"}
def handle_get_user_map_area_api_request(self, data: Dict) -> Dict: def handle_get_user_map_area_api_request(self, data: Dict) -> Dict:
user_map_areas = self.data.item.get_map_areas(data["userId"]) user_map_areas = self.data.item.get_map_areas(data["userId"])
@@ -72,32 +76,29 @@ class ChuniNew(ChuniBase):
tmp.pop("user") tmp.pop("user")
map_areas.append(tmp) map_areas.append(tmp)
return { return {"userId": data["userId"], "userMapAreaList": map_areas}
"userId": data["userId"],
"userMapAreaList": map_areas
}
def handle_get_user_symbol_chat_setting_api_request(self, data: Dict) -> Dict: def handle_get_user_symbol_chat_setting_api_request(self, data: Dict) -> Dict:
return { return {"userId": data["userId"], "symbolCharInfoList": []}
"userId": data["userId"],
"symbolCharInfoList": []
}
def handle_get_user_preview_api_request(self, data: Dict) -> Dict: def handle_get_user_preview_api_request(self, data: Dict) -> Dict:
profile = self.data.profile.get_profile_preview(data["userId"], self.version) profile = self.data.profile.get_profile_preview(data["userId"], self.version)
if profile is None: return None if profile is None:
profile_character = self.data.item.get_character(data["userId"], profile["characterId"]) return None
profile_character = self.data.item.get_character(
data["userId"], profile["characterId"]
)
if profile_character is None: if profile_character is None:
chara = {} chara = {}
else: else:
chara = profile_character._asdict() chara = profile_character._asdict()
chara.pop("id") chara.pop("id")
chara.pop("user") chara.pop("user")
data1 = { data1 = {
"userId": data["userId"], "userId": data["userId"],
# Current Login State # Current Login State
"isLogin": False, "isLogin": False,
"lastLoginDate": profile["lastPlayDate"], "lastLoginDate": profile["lastPlayDate"],
# User Profile # User Profile
@@ -109,14 +110,14 @@ class ChuniNew(ChuniBase):
"lastGameId": profile["lastGameId"], "lastGameId": profile["lastGameId"],
"lastRomVersion": profile["lastRomVersion"], "lastRomVersion": profile["lastRomVersion"],
"lastDataVersion": profile["lastDataVersion"], "lastDataVersion": profile["lastDataVersion"],
"lastPlayDate": profile["lastPlayDate"], "lastPlayDate": profile["lastPlayDate"],
"emoneyBrandId": 0, "emoneyBrandId": 0,
"trophyId": profile["trophyId"], "trophyId": profile["trophyId"],
# Current Selected Character # Current Selected Character
"userCharacter": chara, "userCharacter": chara,
# User Game Options # User Game Options
"playerLevel": profile["playerLevel"], "playerLevel": profile["playerLevel"],
"rating": profile["rating"], "rating": profile["rating"],
"headphone": profile["headphone"], "headphone": profile["headphone"],
"chargeState": 0, "chargeState": 0,
"userNameEx": "0", "userNameEx": "0",
+14 -5
View File
@@ -7,17 +7,26 @@ from titles.chuni.new import ChuniNew
from titles.chuni.const import ChuniConstants from titles.chuni.const import ChuniConstants
from titles.chuni.config import ChuniConfig from titles.chuni.config import ChuniConfig
class ChuniNewPlus(ChuniNew): class ChuniNewPlus(ChuniNew):
def __init__(self, core_cfg: CoreConfig, game_cfg: ChuniConfig) -> None: def __init__(self, core_cfg: CoreConfig, game_cfg: ChuniConfig) -> None:
super().__init__(core_cfg, game_cfg) super().__init__(core_cfg, game_cfg)
self.version = ChuniConstants.VER_CHUNITHM_NEW_PLUS self.version = ChuniConstants.VER_CHUNITHM_NEW_PLUS
def handle_get_game_setting_api_request(self, data: Dict) -> Dict: def handle_get_game_setting_api_request(self, data: Dict) -> Dict:
ret = super().handle_get_game_setting_api_request(data) ret = super().handle_get_game_setting_api_request(data)
ret["gameSetting"]["romVersion"] = "2.05.00" ret["gameSetting"]["romVersion"] = "2.05.00"
ret["gameSetting"]["dataVersion"] = "2.05.00" ret["gameSetting"]["dataVersion"] = "2.05.00"
ret["gameSetting"]["matchingUri"] = f"http://{self.core_cfg.title.hostname}:{self.core_cfg.title.port}/SDHD/205/ChuniServlet/" ret["gameSetting"][
ret["gameSetting"]["matchingUriX"] = f"http://{self.core_cfg.title.hostname}:{self.core_cfg.title.port}/SDHD/205/ChuniServlet/" "matchingUri"
ret["gameSetting"]["udpHolePunchUri"] = f"http://{self.core_cfg.title.hostname}:{self.core_cfg.title.port}/SDHD/205/ChuniServlet/" ] = f"http://{self.core_cfg.title.hostname}:{self.core_cfg.title.port}/SDHD/205/ChuniServlet/"
ret["gameSetting"]["reflectorUri"] = f"http://{self.core_cfg.title.hostname}:{self.core_cfg.title.port}/SDHD/205/ChuniServlet/" ret["gameSetting"][
"matchingUriX"
] = f"http://{self.core_cfg.title.hostname}:{self.core_cfg.title.port}/SDHD/205/ChuniServlet/"
ret["gameSetting"][
"udpHolePunchUri"
] = f"http://{self.core_cfg.title.hostname}:{self.core_cfg.title.port}/SDHD/205/ChuniServlet/"
ret["gameSetting"][
"reflectorUri"
] = f"http://{self.core_cfg.title.hostname}:{self.core_cfg.title.port}/SDHD/205/ChuniServlet/"
return ret return ret
+3 -2
View File
@@ -7,12 +7,13 @@ from titles.chuni.base import ChuniBase
from titles.chuni.const import ChuniConstants from titles.chuni.const import ChuniConstants
from titles.chuni.config import ChuniConfig from titles.chuni.config import ChuniConfig
class ChuniParadise(ChuniBase): class ChuniParadise(ChuniBase):
def __init__(self, core_cfg: CoreConfig, game_cfg: ChuniConfig) -> None: def __init__(self, core_cfg: CoreConfig, game_cfg: ChuniConfig) -> None:
super().__init__(core_cfg, game_cfg) super().__init__(core_cfg, game_cfg)
self.version = ChuniConstants.VER_CHUNITHM_PARADISE self.version = ChuniConstants.VER_CHUNITHM_PARADISE
def handle_get_game_setting_api_request(self, data: Dict) -> Dict: def handle_get_game_setting_api_request(self, data: Dict) -> Dict:
ret = super().handle_get_game_setting_api_request(data) ret = super().handle_get_game_setting_api_request(data)
ret["gameSetting"]["dataVersion"] = "1.50.00" ret["gameSetting"]["dataVersion"] = "1.50.00"
return ret return ret
+4 -3
View File
@@ -5,12 +5,13 @@ from titles.chuni.base import ChuniBase
from titles.chuni.const import ChuniConstants from titles.chuni.const import ChuniConstants
from titles.chuni.config import ChuniConfig from titles.chuni.config import ChuniConfig
class ChuniPlus(ChuniBase): class ChuniPlus(ChuniBase):
def __init__(self, core_cfg: CoreConfig, game_cfg: ChuniConfig) -> None: def __init__(self, core_cfg: CoreConfig, game_cfg: ChuniConfig) -> None:
super().__init__(core_cfg, game_cfg) super().__init__(core_cfg, game_cfg)
self.version = ChuniConstants.VER_CHUNITHM_PLUS self.version = ChuniConstants.VER_CHUNITHM_PLUS
def handle_get_game_setting_api_request(self, data: Dict) -> Dict: def handle_get_game_setting_api_request(self, data: Dict) -> Dict:
ret = super().handle_get_game_setting_api_request(data) ret = super().handle_get_game_setting_api_request(data)
ret["gameSetting"]["dataVersion"] = "1.05.00" ret["gameSetting"]["dataVersion"] = "1.05.00"
return ret return ret
+92 -61
View File
@@ -7,48 +7,60 @@ from core.config import CoreConfig
from titles.chuni.database import ChuniData from titles.chuni.database import ChuniData
from titles.chuni.const import ChuniConstants from titles.chuni.const import ChuniConstants
class ChuniReader(BaseReader): class ChuniReader(BaseReader):
def __init__(self, config: CoreConfig, version: int, bin_dir: Optional[str], opt_dir: Optional[str], extra: Optional[str]) -> None: def __init__(
self,
config: CoreConfig,
version: int,
bin_dir: Optional[str],
opt_dir: Optional[str],
extra: Optional[str],
) -> None:
super().__init__(config, version, bin_dir, opt_dir, extra) super().__init__(config, version, bin_dir, opt_dir, extra)
self.data = ChuniData(config) self.data = ChuniData(config)
try: try:
self.logger.info(f"Start importer for {ChuniConstants.game_ver_to_string(version)}") self.logger.info(
f"Start importer for {ChuniConstants.game_ver_to_string(version)}"
)
except IndexError: except IndexError:
self.logger.error(f"Invalid chunithm version {version}") self.logger.error(f"Invalid chunithm version {version}")
exit(1) exit(1)
def read(self) -> None: def read(self) -> None:
data_dirs = [] data_dirs = []
if self.bin_dir is not None: if self.bin_dir is not None:
data_dirs += self.get_data_directories(self.bin_dir) data_dirs += self.get_data_directories(self.bin_dir)
if self.opt_dir is not None: if self.opt_dir is not None:
data_dirs += self.get_data_directories(self.opt_dir) data_dirs += self.get_data_directories(self.opt_dir)
for dir in data_dirs: for dir in data_dirs:
self.logger.info(f"Read from {dir}") self.logger.info(f"Read from {dir}")
self.read_events(f"{dir}/event") self.read_events(f"{dir}/event")
self.read_music(f"{dir}/music") self.read_music(f"{dir}/music")
self.read_charges(f"{dir}/chargeItem") self.read_charges(f"{dir}/chargeItem")
self.read_avatar(f"{dir}/avatarAccessory") self.read_avatar(f"{dir}/avatarAccessory")
def read_events(self, evt_dir: str) -> None: def read_events(self, evt_dir: str) -> None:
for root, dirs, files in walk(evt_dir): for root, dirs, files in walk(evt_dir):
for dir in dirs: for dir in dirs:
if path.exists(f"{root}/{dir}/Event.xml"): if path.exists(f"{root}/{dir}/Event.xml"):
with open(f"{root}/{dir}/Event.xml", 'rb') as fp: with open(f"{root}/{dir}/Event.xml", "rb") as fp:
bytedata = fp.read() bytedata = fp.read()
strdata = bytedata.decode('UTF-8') strdata = bytedata.decode("UTF-8")
xml_root = ET.fromstring(strdata) xml_root = ET.fromstring(strdata)
for name in xml_root.findall('name'): for name in xml_root.findall("name"):
id = name.find('id').text id = name.find("id").text
name = name.find('str').text name = name.find("str").text
for substances in xml_root.findall('substances'): for substances in xml_root.findall("substances"):
event_type = substances.find('type').text event_type = substances.find("type").text
result = self.data.static.put_event(self.version, id, event_type, name) result = self.data.static.put_event(
self.version, id, event_type, name
)
if result is not None: if result is not None:
self.logger.info(f"Inserted event {id}") self.logger.info(f"Inserted event {id}")
else: else:
@@ -58,73 +70,90 @@ class ChuniReader(BaseReader):
for root, dirs, files in walk(music_dir): for root, dirs, files in walk(music_dir):
for dir in dirs: for dir in dirs:
if path.exists(f"{root}/{dir}/Music.xml"): if path.exists(f"{root}/{dir}/Music.xml"):
with open(f"{root}/{dir}/Music.xml", 'rb') as fp: with open(f"{root}/{dir}/Music.xml", "rb") as fp:
bytedata = fp.read() bytedata = fp.read()
strdata = bytedata.decode('UTF-8') strdata = bytedata.decode("UTF-8")
xml_root = ET.fromstring(strdata) xml_root = ET.fromstring(strdata)
for name in xml_root.findall('name'): for name in xml_root.findall("name"):
song_id = name.find('id').text song_id = name.find("id").text
title = name.find('str').text title = name.find("str").text
for artistName in xml_root.findall('artistName'): for artistName in xml_root.findall("artistName"):
artist = artistName.find('str').text artist = artistName.find("str").text
for genreNames in xml_root.findall('genreNames'): for genreNames in xml_root.findall("genreNames"):
for list_ in genreNames.findall('list'): for list_ in genreNames.findall("list"):
for StringID in list_.findall('StringID'): for StringID in list_.findall("StringID"):
genre = StringID.find('str').text genre = StringID.find("str").text
for jaketFile in xml_root.findall('jaketFile'): #nice typo, SEGA for jaketFile in xml_root.findall("jaketFile"): # nice typo, SEGA
jacket_path = jaketFile.find('path').text jacket_path = jaketFile.find("path").text
for fumens in xml_root.findall("fumens"):
for MusicFumenData in fumens.findall("MusicFumenData"):
fumen_path = MusicFumenData.find("file").find("path")
for fumens in xml_root.findall('fumens'):
for MusicFumenData in fumens.findall('MusicFumenData'):
fumen_path = MusicFumenData.find('file').find("path")
if fumen_path is not None: if fumen_path is not None:
chart_id = MusicFumenData.find('type').find('id').text chart_id = MusicFumenData.find("type").find("id").text
if chart_id == "4": if chart_id == "4":
level = float(xml_root.find("starDifType").text) level = float(xml_root.find("starDifType").text)
we_chara = xml_root.find("worldsEndTagName").find("str").text we_chara = (
xml_root.find("worldsEndTagName")
.find("str")
.text
)
else: else:
level = float(f"{MusicFumenData.find('level').text}.{MusicFumenData.find('levelDecimal').text}") level = float(
f"{MusicFumenData.find('level').text}.{MusicFumenData.find('levelDecimal').text}"
)
we_chara = None we_chara = None
result = self.data.static.put_music( result = self.data.static.put_music(
self.version, self.version,
song_id, song_id,
chart_id, chart_id,
title, title,
artist, artist,
level, level,
genre, genre,
jacket_path, jacket_path,
we_chara we_chara,
) )
if result is not None: if result is not None:
self.logger.info(f"Inserted music {song_id} chart {chart_id}") self.logger.info(
f"Inserted music {song_id} chart {chart_id}"
)
else: else:
self.logger.warn(f"Failed to insert music {song_id} chart {chart_id}") self.logger.warn(
f"Failed to insert music {song_id} chart {chart_id}"
)
def read_charges(self, charge_dir: str) -> None: def read_charges(self, charge_dir: str) -> None:
for root, dirs, files in walk(charge_dir): for root, dirs, files in walk(charge_dir):
for dir in dirs: for dir in dirs:
if path.exists(f"{root}/{dir}/ChargeItem.xml"): if path.exists(f"{root}/{dir}/ChargeItem.xml"):
with open(f"{root}/{dir}/ChargeItem.xml", 'rb') as fp: with open(f"{root}/{dir}/ChargeItem.xml", "rb") as fp:
bytedata = fp.read() bytedata = fp.read()
strdata = bytedata.decode('UTF-8') strdata = bytedata.decode("UTF-8")
xml_root = ET.fromstring(strdata) xml_root = ET.fromstring(strdata)
for name in xml_root.findall('name'): for name in xml_root.findall("name"):
id = name.find('id').text id = name.find("id").text
name = name.find('str').text name = name.find("str").text
expirationDays = xml_root.find('expirationDays').text expirationDays = xml_root.find("expirationDays").text
consumeType = xml_root.find('consumeType').text consumeType = xml_root.find("consumeType").text
sellingAppeal = bool(xml_root.find('sellingAppeal').text) sellingAppeal = bool(xml_root.find("sellingAppeal").text)
result = self.data.static.put_charge(self.version, id, name, expirationDays, consumeType, sellingAppeal) result = self.data.static.put_charge(
self.version,
id,
name,
expirationDays,
consumeType,
sellingAppeal,
)
if result is not None: if result is not None:
self.logger.info(f"Inserted charge {id}") self.logger.info(f"Inserted charge {id}")
@@ -135,21 +164,23 @@ class ChuniReader(BaseReader):
for root, dirs, files in walk(avatar_dir): for root, dirs, files in walk(avatar_dir):
for dir in dirs: for dir in dirs:
if path.exists(f"{root}/{dir}/AvatarAccessory.xml"): if path.exists(f"{root}/{dir}/AvatarAccessory.xml"):
with open(f"{root}/{dir}/AvatarAccessory.xml", 'rb') as fp: with open(f"{root}/{dir}/AvatarAccessory.xml", "rb") as fp:
bytedata = fp.read() bytedata = fp.read()
strdata = bytedata.decode('UTF-8') strdata = bytedata.decode("UTF-8")
xml_root = ET.fromstring(strdata) xml_root = ET.fromstring(strdata)
for name in xml_root.findall('name'): for name in xml_root.findall("name"):
id = name.find('id').text id = name.find("id").text
name = name.find('str').text name = name.find("str").text
category = xml_root.find('category').text category = xml_root.find("category").text
for image in xml_root.findall('image'): for image in xml_root.findall("image"):
iconPath = image.find('path').text iconPath = image.find("path").text
for texture in xml_root.findall('texture'): for texture in xml_root.findall("texture"):
texturePath = texture.find('path').text texturePath = texture.find("path").text
result = self.data.static.put_avatar(self.version, id, name, category, iconPath, texturePath) result = self.data.static.put_avatar(
self.version, id, name, category, iconPath, texturePath
)
if result is not None: if result is not None:
self.logger.info(f"Inserted avatarAccessory {id}") self.logger.info(f"Inserted avatarAccessory {id}")
+1 -1
View File
@@ -3,4 +3,4 @@ from titles.chuni.schema.score import ChuniScoreData
from titles.chuni.schema.item import ChuniItemData from titles.chuni.schema.item import ChuniItemData
from titles.chuni.schema.static import ChuniStaticData from titles.chuni.schema.static import ChuniStaticData
__all__ = ["ChuniProfileData", "ChuniScoreData", "ChuniItemData", "ChuniStaticData"] __all__ = ["ChuniProfileData", "ChuniScoreData", "ChuniItemData", "ChuniStaticData"]
+71 -41
View File
@@ -13,7 +13,11 @@ character = Table(
"chuni_item_character", "chuni_item_character",
metadata, metadata,
Column("id", Integer, primary_key=True, nullable=False), Column("id", Integer, primary_key=True, nullable=False),
Column("user", ForeignKey("aime_user.id", ondelete="cascade", onupdate="cascade"), nullable=False), Column(
"user",
ForeignKey("aime_user.id", ondelete="cascade", onupdate="cascade"),
nullable=False,
),
Column("characterId", Integer), Column("characterId", Integer),
Column("level", Integer), Column("level", Integer),
Column("param1", Integer), Column("param1", Integer),
@@ -26,27 +30,35 @@ character = Table(
Column("assignIllust", Integer), Column("assignIllust", Integer),
Column("exMaxLv", Integer), Column("exMaxLv", Integer),
UniqueConstraint("user", "characterId", name="chuni_item_character_uk"), UniqueConstraint("user", "characterId", name="chuni_item_character_uk"),
mysql_charset='utf8mb4' mysql_charset="utf8mb4",
) )
item = Table( item = Table(
"chuni_item_item", "chuni_item_item",
metadata, metadata,
Column("id", Integer, primary_key=True, nullable=False), Column("id", Integer, primary_key=True, nullable=False),
Column("user", ForeignKey("aime_user.id", ondelete="cascade", onupdate="cascade"), nullable=False), Column(
"user",
ForeignKey("aime_user.id", ondelete="cascade", onupdate="cascade"),
nullable=False,
),
Column("itemId", Integer), Column("itemId", Integer),
Column("itemKind", Integer), Column("itemKind", Integer),
Column("stock", Integer), Column("stock", Integer),
Column("isValid", Boolean), Column("isValid", Boolean),
UniqueConstraint("user", "itemId", "itemKind", name="chuni_item_item_uk"), UniqueConstraint("user", "itemId", "itemKind", name="chuni_item_item_uk"),
mysql_charset='utf8mb4' mysql_charset="utf8mb4",
) )
duel = Table( duel = Table(
"chuni_item_duel", "chuni_item_duel",
metadata, metadata,
Column("id", Integer, primary_key=True, nullable=False), Column("id", Integer, primary_key=True, nullable=False),
Column("user", ForeignKey("aime_user.id", ondelete="cascade", onupdate="cascade"), nullable=False), Column(
"user",
ForeignKey("aime_user.id", ondelete="cascade", onupdate="cascade"),
nullable=False,
),
Column("duelId", Integer), Column("duelId", Integer),
Column("progress", Integer), Column("progress", Integer),
Column("point", Integer), Column("point", Integer),
@@ -57,14 +69,18 @@ duel = Table(
Column("param3", Integer), Column("param3", Integer),
Column("param4", Integer), Column("param4", Integer),
UniqueConstraint("user", "duelId", name="chuni_item_duel_uk"), UniqueConstraint("user", "duelId", name="chuni_item_duel_uk"),
mysql_charset='utf8mb4' mysql_charset="utf8mb4",
) )
map = Table( map = Table(
"chuni_item_map", "chuni_item_map",
metadata, metadata,
Column("id", Integer, primary_key=True, nullable=False), Column("id", Integer, primary_key=True, nullable=False),
Column("user", ForeignKey("aime_user.id", ondelete="cascade", onupdate="cascade"), nullable=False), Column(
"user",
ForeignKey("aime_user.id", ondelete="cascade", onupdate="cascade"),
nullable=False,
),
Column("mapId", Integer), Column("mapId", Integer),
Column("position", Integer), Column("position", Integer),
Column("isClear", Boolean), Column("isClear", Boolean),
@@ -72,17 +88,21 @@ map = Table(
Column("routeNumber", Integer), Column("routeNumber", Integer),
Column("eventId", Integer), Column("eventId", Integer),
Column("rate", Integer), Column("rate", Integer),
Column("statusCount", Integer), Column("statusCount", Integer),
Column("isValid", Boolean), Column("isValid", Boolean),
UniqueConstraint("user", "mapId", name="chuni_item_map_uk"), UniqueConstraint("user", "mapId", name="chuni_item_map_uk"),
mysql_charset='utf8mb4' mysql_charset="utf8mb4",
) )
map_area = Table( map_area = Table(
"chuni_item_map_area", "chuni_item_map_area",
metadata, metadata,
Column("id", Integer, primary_key=True, nullable=False), Column("id", Integer, primary_key=True, nullable=False),
Column("user", ForeignKey("aime_user.id", ondelete="cascade", onupdate="cascade"), nullable=False), Column(
"user",
ForeignKey("aime_user.id", ondelete="cascade", onupdate="cascade"),
nullable=False,
),
Column("mapAreaId", Integer), Column("mapAreaId", Integer),
Column("rate", Integer), Column("rate", Integer),
Column("isClear", Boolean), Column("isClear", Boolean),
@@ -91,9 +111,10 @@ map_area = Table(
Column("statusCount", Integer), Column("statusCount", Integer),
Column("remainGridCount", Integer), Column("remainGridCount", Integer),
UniqueConstraint("user", "mapAreaId", name="chuni_item_map_area_uk"), UniqueConstraint("user", "mapAreaId", name="chuni_item_map_area_uk"),
mysql_charset='utf8mb4' mysql_charset="utf8mb4",
) )
class ChuniItemData(BaseData): class ChuniItemData(BaseData):
def put_character(self, user_id: int, character_data: Dict) -> Optional[int]: def put_character(self, user_id: int, character_data: Dict) -> Optional[int]:
character_data["user"] = user_id character_data["user"] = user_id
@@ -104,24 +125,26 @@ class ChuniItemData(BaseData):
conflict = sql.on_duplicate_key_update(**character_data) conflict = sql.on_duplicate_key_update(**character_data)
result = self.execute(conflict) result = self.execute(conflict)
if result is None: return None if result is None:
return None
return result.lastrowid return result.lastrowid
def get_character(self, user_id: int, character_id: int) -> Optional[Dict]: def get_character(self, user_id: int, character_id: int) -> Optional[Dict]:
sql = select(character).where(and_( sql = select(character).where(
character.c.user == user_id, and_(character.c.user == user_id, character.c.characterId == character_id)
character.c.characterId == character_id )
))
result = self.execute(sql) result = self.execute(sql)
if result is None: return None if result is None:
return None
return result.fetchone() return result.fetchone()
def get_characters(self, user_id: int) -> Optional[List[Row]]: def get_characters(self, user_id: int) -> Optional[List[Row]]:
sql = select(character).where(character.c.user == user_id) sql = select(character).where(character.c.user == user_id)
result = self.execute(sql) result = self.execute(sql)
if result is None: return None if result is None:
return None
return result.fetchall() return result.fetchall()
def put_item(self, user_id: int, item_data: Dict) -> Optional[int]: def put_item(self, user_id: int, item_data: Dict) -> Optional[int]:
@@ -133,22 +156,23 @@ class ChuniItemData(BaseData):
conflict = sql.on_duplicate_key_update(**item_data) conflict = sql.on_duplicate_key_update(**item_data)
result = self.execute(conflict) result = self.execute(conflict)
if result is None: return None if result is None:
return None
return result.lastrowid return result.lastrowid
def get_items(self, user_id: int, kind: int = None) -> Optional[List[Row]]: def get_items(self, user_id: int, kind: int = None) -> Optional[List[Row]]:
if kind is None: if kind is None:
sql = select(item).where(item.c.user == user_id) sql = select(item).where(item.c.user == user_id)
else: else:
sql = select(item).where(and_( sql = select(item).where(
item.c.user == user_id, and_(item.c.user == user_id, item.c.itemKind == kind)
item.c.itemKind == kind )
))
result = self.execute(sql) result = self.execute(sql)
if result is None: return None if result is None:
return None
return result.fetchall() return result.fetchall()
def put_duel(self, user_id: int, duel_data: Dict) -> Optional[int]: def put_duel(self, user_id: int, duel_data: Dict) -> Optional[int]:
duel_data["user"] = user_id duel_data["user"] = user_id
@@ -158,14 +182,16 @@ class ChuniItemData(BaseData):
conflict = sql.on_duplicate_key_update(**duel_data) conflict = sql.on_duplicate_key_update(**duel_data)
result = self.execute(conflict) result = self.execute(conflict)
if result is None: return None if result is None:
return None
return result.lastrowid return result.lastrowid
def get_duels(self, user_id: int) -> Optional[List[Row]]: def get_duels(self, user_id: int) -> Optional[List[Row]]:
sql = select(duel).where(duel.c.user == user_id) sql = select(duel).where(duel.c.user == user_id)
result = self.execute(sql) result = self.execute(sql)
if result is None: return None if result is None:
return None
return result.fetchall() return result.fetchall()
def put_map(self, user_id: int, map_data: Dict) -> Optional[int]: def put_map(self, user_id: int, map_data: Dict) -> Optional[int]:
@@ -177,16 +203,18 @@ class ChuniItemData(BaseData):
conflict = sql.on_duplicate_key_update(**map_data) conflict = sql.on_duplicate_key_update(**map_data)
result = self.execute(conflict) result = self.execute(conflict)
if result is None: return None if result is None:
return None
return result.lastrowid return result.lastrowid
def get_maps(self, user_id: int) -> Optional[List[Row]]: def get_maps(self, user_id: int) -> Optional[List[Row]]:
sql = select(map).where(map.c.user == user_id) sql = select(map).where(map.c.user == user_id)
result = self.execute(sql) result = self.execute(sql)
if result is None: return None if result is None:
return None
return result.fetchall() return result.fetchall()
def put_map_area(self, user_id: int, map_area_data: Dict) -> Optional[int]: def put_map_area(self, user_id: int, map_area_data: Dict) -> Optional[int]:
map_area_data["user"] = user_id map_area_data["user"] = user_id
@@ -196,12 +224,14 @@ class ChuniItemData(BaseData):
conflict = sql.on_duplicate_key_update(**map_area_data) conflict = sql.on_duplicate_key_update(**map_area_data)
result = self.execute(conflict) result = self.execute(conflict)
if result is None: return None if result is None:
return None
return result.lastrowid return result.lastrowid
def get_map_areas(self, user_id: int) -> Optional[List[Row]]: def get_map_areas(self, user_id: int) -> Optional[List[Row]]:
sql = select(map_area).where(map_area.c.user == user_id) sql = select(map_area).where(map_area.c.user == user_id)
result = self.execute(sql) result = self.execute(sql)
if result is None: return None if result is None:
return result.fetchall() return None
return result.fetchall()
+167 -82
View File
@@ -13,7 +13,11 @@ profile = Table(
"chuni_profile_data", "chuni_profile_data",
metadata, metadata,
Column("id", Integer, primary_key=True, nullable=False), Column("id", Integer, primary_key=True, nullable=False),
Column("user", ForeignKey("aime_user.id", ondelete="cascade", onupdate="cascade"), nullable=False), Column(
"user",
ForeignKey("aime_user.id", ondelete="cascade", onupdate="cascade"),
nullable=False,
),
Column("version", Integer, nullable=False), Column("version", Integer, nullable=False),
Column("exp", Integer), Column("exp", Integer),
Column("level", Integer), Column("level", Integer),
@@ -62,7 +66,7 @@ profile = Table(
Column("firstTutorialCancelNum", Integer), Column("firstTutorialCancelNum", Integer),
Column("totalAdvancedHighScore", Integer), Column("totalAdvancedHighScore", Integer),
Column("masterTutorialCancelNum", Integer), Column("masterTutorialCancelNum", Integer),
Column("ext1", Integer), # Added in chunew Column("ext1", Integer), # Added in chunew
Column("ext2", Integer), Column("ext2", Integer),
Column("ext3", Integer), Column("ext3", Integer),
Column("ext4", Integer), Column("ext4", Integer),
@@ -71,16 +75,20 @@ profile = Table(
Column("ext7", Integer), Column("ext7", Integer),
Column("ext8", Integer), Column("ext8", Integer),
Column("ext9", Integer), Column("ext9", Integer),
Column("ext10", Integer), Column("ext10", Integer),
Column("extStr1", String(255)), Column("extStr1", String(255)),
Column("extStr2", String(255)), Column("extStr2", String(255)),
Column("extLong1", Integer), Column("extLong1", Integer),
Column("extLong2", Integer), Column("extLong2", Integer),
Column("mapIconId", Integer), Column("mapIconId", Integer),
Column("compatibleCmVersion", String(25)), Column("compatibleCmVersion", String(25)),
Column("medal", Integer), Column("medal", Integer),
Column("voiceId", Integer), Column("voiceId", Integer),
Column("teamId", Integer, ForeignKey("chuni_profile_team.id", ondelete="SET NULL", onupdate="SET NULL")), Column(
"teamId",
Integer,
ForeignKey("chuni_profile_team.id", ondelete="SET NULL", onupdate="SET NULL"),
),
Column("avatarBack", Integer, server_default="0"), Column("avatarBack", Integer, server_default="0"),
Column("avatarFace", Integer, server_default="0"), Column("avatarFace", Integer, server_default="0"),
Column("eliteRankPoint", Integer, server_default="0"), Column("eliteRankPoint", Integer, server_default="0"),
@@ -121,14 +129,18 @@ profile = Table(
Column("netBattleEndState", Integer, server_default="0"), Column("netBattleEndState", Integer, server_default="0"),
Column("avatarHead", Integer, server_default="0"), Column("avatarHead", Integer, server_default="0"),
UniqueConstraint("user", "version", name="chuni_profile_profile_uk"), UniqueConstraint("user", "version", name="chuni_profile_profile_uk"),
mysql_charset='utf8mb4' mysql_charset="utf8mb4",
) )
profile_ex = Table( profile_ex = Table(
"chuni_profile_data_ex", "chuni_profile_data_ex",
metadata, metadata,
Column("id", Integer, primary_key=True, nullable=False), Column("id", Integer, primary_key=True, nullable=False),
Column("user", ForeignKey("aime_user.id", ondelete="cascade", onupdate="cascade"), nullable=False), Column(
"user",
ForeignKey("aime_user.id", ondelete="cascade", onupdate="cascade"),
nullable=False,
),
Column("version", Integer, nullable=False), Column("version", Integer, nullable=False),
Column("ext1", Integer), Column("ext1", Integer),
Column("ext2", Integer), Column("ext2", Integer),
@@ -165,14 +177,18 @@ profile_ex = Table(
Column("mapIconId", Integer), Column("mapIconId", Integer),
Column("compatibleCmVersion", String(25)), Column("compatibleCmVersion", String(25)),
UniqueConstraint("user", "version", name="chuni_profile_data_ex_uk"), UniqueConstraint("user", "version", name="chuni_profile_data_ex_uk"),
mysql_charset='utf8mb4' mysql_charset="utf8mb4",
) )
option = Table( option = Table(
"chuni_profile_option", "chuni_profile_option",
metadata, metadata,
Column("id", Integer, primary_key=True, nullable=False), Column("id", Integer, primary_key=True, nullable=False),
Column("user", ForeignKey("aime_user.id", ondelete="cascade", onupdate="cascade"), nullable=False), Column(
"user",
ForeignKey("aime_user.id", ondelete="cascade", onupdate="cascade"),
nullable=False,
),
Column("speed", Integer), Column("speed", Integer),
Column("bgInfo", Integer), Column("bgInfo", Integer),
Column("rating", Integer), Column("rating", Integer),
@@ -195,7 +211,7 @@ option = Table(
Column("successSkill", Integer), Column("successSkill", Integer),
Column("successSlideHold", Integer), Column("successSlideHold", Integer),
Column("successTapTimbre", Integer), Column("successTapTimbre", Integer),
Column("ext1", Integer), # Added in chunew Column("ext1", Integer), # Added in chunew
Column("ext2", Integer), Column("ext2", Integer),
Column("ext3", Integer), Column("ext3", Integer),
Column("ext4", Integer), Column("ext4", Integer),
@@ -224,14 +240,18 @@ option = Table(
Column("playTimingOffset", Integer, server_default="0"), Column("playTimingOffset", Integer, server_default="0"),
Column("fieldWallPosition_120", Integer, server_default="0"), Column("fieldWallPosition_120", Integer, server_default="0"),
UniqueConstraint("user", name="chuni_profile_option_uk"), UniqueConstraint("user", name="chuni_profile_option_uk"),
mysql_charset='utf8mb4' mysql_charset="utf8mb4",
) )
option_ex = Table( option_ex = Table(
"chuni_profile_option_ex", "chuni_profile_option_ex",
metadata, metadata,
Column("id", Integer, primary_key=True, nullable=False), Column("id", Integer, primary_key=True, nullable=False),
Column("user", ForeignKey("aime_user.id", ondelete="cascade", onupdate="cascade"), nullable=False), Column(
"user",
ForeignKey("aime_user.id", ondelete="cascade", onupdate="cascade"),
nullable=False,
),
Column("ext1", Integer), Column("ext1", Integer),
Column("ext2", Integer), Column("ext2", Integer),
Column("ext3", Integer), Column("ext3", Integer),
@@ -253,51 +273,69 @@ option_ex = Table(
Column("ext19", Integer), Column("ext19", Integer),
Column("ext20", Integer), Column("ext20", Integer),
UniqueConstraint("user", name="chuni_profile_option_ex_uk"), UniqueConstraint("user", name="chuni_profile_option_ex_uk"),
mysql_charset='utf8mb4' mysql_charset="utf8mb4",
) )
recent_rating = Table( recent_rating = Table(
"chuni_profile_recent_rating", "chuni_profile_recent_rating",
metadata, metadata,
Column("id", Integer, primary_key=True, nullable=False), Column("id", Integer, primary_key=True, nullable=False),
Column("user", ForeignKey("aime_user.id", ondelete="cascade", onupdate="cascade"), nullable=False), Column(
"user",
ForeignKey("aime_user.id", ondelete="cascade", onupdate="cascade"),
nullable=False,
),
Column("recentRating", JSON), Column("recentRating", JSON),
UniqueConstraint("user", name="chuni_profile_recent_rating_uk"), UniqueConstraint("user", name="chuni_profile_recent_rating_uk"),
mysql_charset='utf8mb4' mysql_charset="utf8mb4",
) )
region = Table( region = Table(
"chuni_profile_region", "chuni_profile_region",
metadata, metadata,
Column("id", Integer, primary_key=True, nullable=False), Column("id", Integer, primary_key=True, nullable=False),
Column("user", ForeignKey("aime_user.id", ondelete="cascade", onupdate="cascade"), nullable=False), Column(
"user",
ForeignKey("aime_user.id", ondelete="cascade", onupdate="cascade"),
nullable=False,
),
Column("regionId", Integer), Column("regionId", Integer),
Column("playCount", Integer), Column("playCount", Integer),
UniqueConstraint("user", "regionId", name="chuni_profile_region_uk"), UniqueConstraint("user", "regionId", name="chuni_profile_region_uk"),
mysql_charset='utf8mb4' mysql_charset="utf8mb4",
) )
activity = Table( activity = Table(
"chuni_profile_activity", "chuni_profile_activity",
metadata, metadata,
Column("id", Integer, primary_key=True, nullable=False), Column("id", Integer, primary_key=True, nullable=False),
Column("user", ForeignKey("aime_user.id", ondelete="cascade", onupdate="cascade"), nullable=False), Column(
"user",
ForeignKey("aime_user.id", ondelete="cascade", onupdate="cascade"),
nullable=False,
),
Column("kind", Integer), Column("kind", Integer),
Column("activityId", Integer), # Reminder: Change this to ID in base.py or the game will be sad Column(
"activityId", Integer
), # Reminder: Change this to ID in base.py or the game will be sad
Column("sortNumber", Integer), Column("sortNumber", Integer),
Column("param1", Integer), Column("param1", Integer),
Column("param2", Integer), Column("param2", Integer),
Column("param3", Integer), Column("param3", Integer),
Column("param4", Integer), Column("param4", Integer),
UniqueConstraint("user", "kind", "activityId", name="chuni_profile_activity_uk"), UniqueConstraint("user", "kind", "activityId", name="chuni_profile_activity_uk"),
mysql_charset='utf8mb4' mysql_charset="utf8mb4",
) )
charge = Table( charge = Table(
"chuni_profile_charge", "chuni_profile_charge",
metadata, metadata,
Column("id", Integer, primary_key=True, nullable=False), Column("id", Integer, primary_key=True, nullable=False),
Column("user", ForeignKey("aime_user.id", ondelete="cascade", onupdate="cascade"), nullable=False), Column(
"user",
ForeignKey("aime_user.id", ondelete="cascade", onupdate="cascade"),
nullable=False,
),
Column("chargeId", Integer), Column("chargeId", Integer),
Column("stock", Integer), Column("stock", Integer),
Column("purchaseDate", String(25)), Column("purchaseDate", String(25)),
@@ -306,14 +344,18 @@ charge = Table(
Column("param2", Integer), Column("param2", Integer),
Column("paramDate", String(25)), Column("paramDate", String(25)),
UniqueConstraint("user", "chargeId", name="chuni_profile_charge_uk"), UniqueConstraint("user", "chargeId", name="chuni_profile_charge_uk"),
mysql_charset='utf8mb4' mysql_charset="utf8mb4",
) )
emoney = Table( emoney = Table(
"chuni_profile_emoney", "chuni_profile_emoney",
metadata, metadata,
Column("id", Integer, primary_key=True, nullable=False), Column("id", Integer, primary_key=True, nullable=False),
Column("user", ForeignKey("aime_user.id", ondelete="cascade", onupdate="cascade"), nullable=False), Column(
"user",
ForeignKey("aime_user.id", ondelete="cascade", onupdate="cascade"),
nullable=False,
),
Column("ext1", Integer), Column("ext1", Integer),
Column("ext2", Integer), Column("ext2", Integer),
Column("ext3", Integer), Column("ext3", Integer),
@@ -321,20 +363,24 @@ emoney = Table(
Column("emoneyBrand", Integer), Column("emoneyBrand", Integer),
Column("emoneyCredit", Integer), Column("emoneyCredit", Integer),
UniqueConstraint("user", "emoneyBrand", name="chuni_profile_emoney_uk"), UniqueConstraint("user", "emoneyBrand", name="chuni_profile_emoney_uk"),
mysql_charset='utf8mb4' mysql_charset="utf8mb4",
) )
overpower = Table( overpower = Table(
"chuni_profile_overpower", "chuni_profile_overpower",
metadata, metadata,
Column("id", Integer, primary_key=True, nullable=False), Column("id", Integer, primary_key=True, nullable=False),
Column("user", ForeignKey("aime_user.id", ondelete="cascade", onupdate="cascade"), nullable=False), Column(
"user",
ForeignKey("aime_user.id", ondelete="cascade", onupdate="cascade"),
nullable=False,
),
Column("genreId", Integer), Column("genreId", Integer),
Column("difficulty", Integer), Column("difficulty", Integer),
Column("rate", Integer), Column("rate", Integer),
Column("point", Integer), Column("point", Integer),
UniqueConstraint("user", "genreId", "difficulty", name="chuni_profile_emoney_uk"), UniqueConstraint("user", "genreId", "difficulty", name="chuni_profile_emoney_uk"),
mysql_charset='utf8mb4' mysql_charset="utf8mb4",
) )
team = Table( team = Table(
@@ -343,18 +389,21 @@ team = Table(
Column("id", Integer, primary_key=True, nullable=False), Column("id", Integer, primary_key=True, nullable=False),
Column("teamName", String(255)), Column("teamName", String(255)),
Column("teamPoint", Integer), Column("teamPoint", Integer),
mysql_charset='utf8mb4' mysql_charset="utf8mb4",
) )
class ChuniProfileData(BaseData): class ChuniProfileData(BaseData):
def put_profile_data(self, aime_id: int, version: int, profile_data: Dict) -> Optional[int]: def put_profile_data(
self, aime_id: int, version: int, profile_data: Dict
) -> Optional[int]:
profile_data["user"] = aime_id profile_data["user"] = aime_id
profile_data["version"] = version profile_data["version"] = version
if "accessCode" in profile_data: if "accessCode" in profile_data:
profile_data.pop("accessCode") profile_data.pop("accessCode")
profile_data = self.fix_bools(profile_data) profile_data = self.fix_bools(profile_data)
sql = insert(profile).values(**profile_data) sql = insert(profile).values(**profile_data)
conflict = sql.on_duplicate_key_update(**profile_data) conflict = sql.on_duplicate_key_update(**profile_data)
result = self.execute(conflict) result = self.execute(conflict)
@@ -363,51 +412,64 @@ class ChuniProfileData(BaseData):
self.logger.warn(f"put_profile_data: Failed to update! aime_id: {aime_id}") self.logger.warn(f"put_profile_data: Failed to update! aime_id: {aime_id}")
return None return None
return result.lastrowid return result.lastrowid
def get_profile_preview(self, aime_id: int, version: int) -> Optional[Row]: def get_profile_preview(self, aime_id: int, version: int) -> Optional[Row]:
sql = select([profile, option]).join(option, profile.c.user == option.c.user).filter( sql = (
and_(profile.c.user == aime_id, profile.c.version == version) select([profile, option])
.join(option, profile.c.user == option.c.user)
.filter(and_(profile.c.user == aime_id, profile.c.version == version))
) )
result = self.execute(sql) result = self.execute(sql)
if result is None: return None if result is None:
return None
return result.fetchone() return result.fetchone()
def get_profile_data(self, aime_id: int, version: int) -> Optional[Row]: def get_profile_data(self, aime_id: int, version: int) -> Optional[Row]:
sql = select(profile).where(and_( sql = select(profile).where(
profile.c.user == aime_id, and_(
profile.c.version == version, profile.c.user == aime_id,
)) profile.c.version == version,
)
)
result = self.execute(sql) result = self.execute(sql)
if result is None: return None if result is None:
return None
return result.fetchone() return result.fetchone()
def put_profile_data_ex(self, aime_id: int, version: int, profile_ex_data: Dict) -> Optional[int]: def put_profile_data_ex(
self, aime_id: int, version: int, profile_ex_data: Dict
) -> Optional[int]:
profile_ex_data["user"] = aime_id profile_ex_data["user"] = aime_id
profile_ex_data["version"] = version profile_ex_data["version"] = version
if "accessCode" in profile_ex_data: if "accessCode" in profile_ex_data:
profile_ex_data.pop("accessCode") profile_ex_data.pop("accessCode")
sql = insert(profile_ex).values(**profile_ex_data) sql = insert(profile_ex).values(**profile_ex_data)
conflict = sql.on_duplicate_key_update(**profile_ex_data) conflict = sql.on_duplicate_key_update(**profile_ex_data)
result = self.execute(conflict) result = self.execute(conflict)
if result is None: if result is None:
self.logger.warn(f"put_profile_data_ex: Failed to update! aime_id: {aime_id}") self.logger.warn(
f"put_profile_data_ex: Failed to update! aime_id: {aime_id}"
)
return None return None
return result.lastrowid return result.lastrowid
def get_profile_data_ex(self, aime_id: int, version: int) -> Optional[Row]: def get_profile_data_ex(self, aime_id: int, version: int) -> Optional[Row]:
sql = select(profile_ex).where(and_( sql = select(profile_ex).where(
profile_ex.c.user == aime_id, and_(
profile_ex.c.version == version, profile_ex.c.user == aime_id,
)) profile_ex.c.version == version,
)
)
result = self.execute(sql) result = self.execute(sql)
if result is None: return None if result is None:
return None
return result.fetchone() return result.fetchone()
def put_profile_option(self, aime_id: int, option_data: Dict) -> Optional[int]: def put_profile_option(self, aime_id: int, option_data: Dict) -> Optional[int]:
option_data["user"] = aime_id option_data["user"] = aime_id
@@ -416,7 +478,9 @@ class ChuniProfileData(BaseData):
result = self.execute(conflict) result = self.execute(conflict)
if result is None: if result is None:
self.logger.warn(f"put_profile_option: Failed to update! aime_id: {aime_id}") self.logger.warn(
f"put_profile_option: Failed to update! aime_id: {aime_id}"
)
return None return None
return result.lastrowid return result.lastrowid
@@ -424,18 +488,23 @@ class ChuniProfileData(BaseData):
sql = select(option).where(option.c.user == aime_id) sql = select(option).where(option.c.user == aime_id)
result = self.execute(sql) result = self.execute(sql)
if result is None: return None if result is None:
return None
return result.fetchone() return result.fetchone()
def put_profile_option_ex(self, aime_id: int, option_ex_data: Dict) -> Optional[int]: def put_profile_option_ex(
self, aime_id: int, option_ex_data: Dict
) -> Optional[int]:
option_ex_data["user"] = aime_id option_ex_data["user"] = aime_id
sql = insert(option_ex).values(**option_ex_data) sql = insert(option_ex).values(**option_ex_data)
conflict = sql.on_duplicate_key_update(**option_ex_data) conflict = sql.on_duplicate_key_update(**option_ex_data)
result = self.execute(conflict) result = self.execute(conflict)
if result is None: if result is None:
self.logger.warn(f"put_profile_option_ex: Failed to update! aime_id: {aime_id}") self.logger.warn(
f"put_profile_option_ex: Failed to update! aime_id: {aime_id}"
)
return None return None
return result.lastrowid return result.lastrowid
@@ -443,27 +512,32 @@ class ChuniProfileData(BaseData):
sql = select(option_ex).where(option_ex.c.user == aime_id) sql = select(option_ex).where(option_ex.c.user == aime_id)
result = self.execute(sql) result = self.execute(sql)
if result is None: return None if result is None:
return None
return result.fetchone() return result.fetchone()
def put_profile_recent_rating(self, aime_id: int, recent_rating_data: List[Dict]) -> Optional[int]: def put_profile_recent_rating(
self, aime_id: int, recent_rating_data: List[Dict]
) -> Optional[int]:
sql = insert(recent_rating).values( sql = insert(recent_rating).values(
user = aime_id, user=aime_id, recentRating=recent_rating_data
recentRating = recent_rating_data
) )
conflict = sql.on_duplicate_key_update(recentRating = recent_rating_data) conflict = sql.on_duplicate_key_update(recentRating=recent_rating_data)
result = self.execute(conflict) result = self.execute(conflict)
if result is None: if result is None:
self.logger.warn(f"put_profile_recent_rating: Failed to update! aime_id: {aime_id}") self.logger.warn(
f"put_profile_recent_rating: Failed to update! aime_id: {aime_id}"
)
return None return None
return result.lastrowid return result.lastrowid
def get_profile_recent_rating(self, aime_id: int) -> Optional[Row]: def get_profile_recent_rating(self, aime_id: int) -> Optional[Row]:
sql = select(recent_rating).where(recent_rating.c.user == aime_id) sql = select(recent_rating).where(recent_rating.c.user == aime_id)
result = self.execute(sql) result = self.execute(sql)
if result is None: return None if result is None:
return None
return result.fetchone() return result.fetchone()
def put_profile_activity(self, aime_id: int, activity_data: Dict) -> Optional[int]: def put_profile_activity(self, aime_id: int, activity_data: Dict) -> Optional[int]:
@@ -471,35 +545,39 @@ class ChuniProfileData(BaseData):
activity_data["user"] = aime_id activity_data["user"] = aime_id
activity_data["activityId"] = activity_data["id"] activity_data["activityId"] = activity_data["id"]
activity_data.pop("id") activity_data.pop("id")
sql = insert(activity).values(**activity_data) sql = insert(activity).values(**activity_data)
conflict = sql.on_duplicate_key_update(**activity_data) conflict = sql.on_duplicate_key_update(**activity_data)
result = self.execute(conflict) result = self.execute(conflict)
if result is None: if result is None:
self.logger.warn(f"put_profile_activity: Failed to update! aime_id: {aime_id}") self.logger.warn(
f"put_profile_activity: Failed to update! aime_id: {aime_id}"
)
return None return None
return result.lastrowid return result.lastrowid
def get_profile_activity(self, aime_id: int, kind: int) -> Optional[List[Row]]: def get_profile_activity(self, aime_id: int, kind: int) -> Optional[List[Row]]:
sql = select(activity).where(and_( sql = select(activity).where(
activity.c.user == aime_id, and_(activity.c.user == aime_id, activity.c.kind == kind)
activity.c.kind == kind )
))
result = self.execute(sql) result = self.execute(sql)
if result is None: return None if result is None:
return None
return result.fetchall() return result.fetchall()
def put_profile_charge(self, aime_id: int, charge_data: Dict) -> Optional[int]: def put_profile_charge(self, aime_id: int, charge_data: Dict) -> Optional[int]:
charge_data["user"] = aime_id charge_data["user"] = aime_id
sql = insert(charge).values(**charge_data) sql = insert(charge).values(**charge_data)
conflict = sql.on_duplicate_key_update(**charge_data) conflict = sql.on_duplicate_key_update(**charge_data)
result = self.execute(conflict) result = self.execute(conflict)
if result is None: if result is None:
self.logger.warn(f"put_profile_charge: Failed to update! aime_id: {aime_id}") self.logger.warn(
f"put_profile_charge: Failed to update! aime_id: {aime_id}"
)
return None return None
return result.lastrowid return result.lastrowid
@@ -507,9 +585,10 @@ class ChuniProfileData(BaseData):
sql = select(charge).where(charge.c.user == aime_id) sql = select(charge).where(charge.c.user == aime_id)
result = self.execute(sql) result = self.execute(sql)
if result is None: return None if result is None:
return None
return result.fetchall() return result.fetchall()
def add_profile_region(self, aime_id: int, region_id: int) -> Optional[int]: def add_profile_region(self, aime_id: int, region_id: int) -> Optional[int]:
pass pass
@@ -523,29 +602,35 @@ class ChuniProfileData(BaseData):
conflict = sql.on_duplicate_key_update(**emoney_data) conflict = sql.on_duplicate_key_update(**emoney_data)
result = self.execute(conflict) result = self.execute(conflict)
if result is None: return None if result is None:
return None
return result.lastrowid return result.lastrowid
def get_profile_emoney(self, aime_id: int) -> Optional[List[Row]]: def get_profile_emoney(self, aime_id: int) -> Optional[List[Row]]:
sql = select(emoney).where(emoney.c.user == aime_id) sql = select(emoney).where(emoney.c.user == aime_id)
result = self.execute(sql) result = self.execute(sql)
if result is None: return None if result is None:
return None
return result.fetchall() return result.fetchall()
def put_profile_overpower(self, aime_id: int, overpower_data: Dict) -> Optional[int]: def put_profile_overpower(
self, aime_id: int, overpower_data: Dict
) -> Optional[int]:
overpower_data["user"] = aime_id overpower_data["user"] = aime_id
sql = insert(overpower).values(**overpower_data) sql = insert(overpower).values(**overpower_data)
conflict = sql.on_duplicate_key_update(**overpower_data) conflict = sql.on_duplicate_key_update(**overpower_data)
result = self.execute(conflict) result = self.execute(conflict)
if result is None: return None if result is None:
return None
return result.lastrowid return result.lastrowid
def get_profile_overpower(self, aime_id: int) -> Optional[List[Row]]: def get_profile_overpower(self, aime_id: int) -> Optional[List[Row]]:
sql = select(overpower).where(overpower.c.user == aime_id) sql = select(overpower).where(overpower.c.user == aime_id)
result = self.execute(sql) result = self.execute(sql)
if result is None: return None if result is None:
return None
return result.fetchall() return result.fetchall()
+34 -15
View File
@@ -13,7 +13,11 @@ course = Table(
"chuni_score_course", "chuni_score_course",
metadata, metadata,
Column("id", Integer, primary_key=True, nullable=False), Column("id", Integer, primary_key=True, nullable=False),
Column("user", ForeignKey("aime_user.id", ondelete="cascade", onupdate="cascade"), nullable=False), Column(
"user",
ForeignKey("aime_user.id", ondelete="cascade", onupdate="cascade"),
nullable=False,
),
Column("courseId", Integer), Column("courseId", Integer),
Column("classId", Integer), Column("classId", Integer),
Column("playCount", Integer), Column("playCount", Integer),
@@ -33,14 +37,18 @@ course = Table(
Column("orderId", Integer), Column("orderId", Integer),
Column("playerRating", Integer), Column("playerRating", Integer),
UniqueConstraint("user", "courseId", name="chuni_score_course_uk"), UniqueConstraint("user", "courseId", name="chuni_score_course_uk"),
mysql_charset='utf8mb4' mysql_charset="utf8mb4",
) )
best_score = Table( best_score = Table(
"chuni_score_best", "chuni_score_best",
metadata, metadata,
Column("id", Integer, primary_key=True, nullable=False), Column("id", Integer, primary_key=True, nullable=False),
Column("user", ForeignKey("aime_user.id", ondelete="cascade", onupdate="cascade"), nullable=False), Column(
"user",
ForeignKey("aime_user.id", ondelete="cascade", onupdate="cascade"),
nullable=False,
),
Column("musicId", Integer), Column("musicId", Integer),
Column("level", Integer), Column("level", Integer),
Column("playCount", Integer), Column("playCount", Integer),
@@ -60,14 +68,18 @@ best_score = Table(
Column("ext1", Integer), Column("ext1", Integer),
Column("theoryCount", Integer), Column("theoryCount", Integer),
UniqueConstraint("user", "musicId", "level", name="chuni_score_best_uk"), UniqueConstraint("user", "musicId", "level", name="chuni_score_best_uk"),
mysql_charset='utf8mb4' mysql_charset="utf8mb4",
) )
playlog = Table( playlog = Table(
"chuni_score_playlog", "chuni_score_playlog",
metadata, metadata,
Column("id", Integer, primary_key=True, nullable=False), Column("id", Integer, primary_key=True, nullable=False),
Column("user", ForeignKey("aime_user.id", ondelete="cascade", onupdate="cascade"), nullable=False), Column(
"user",
ForeignKey("aime_user.id", ondelete="cascade", onupdate="cascade"),
nullable=False,
),
Column("orderId", Integer), Column("orderId", Integer),
Column("sortNumber", Integer), Column("sortNumber", Integer),
Column("placeId", Integer), Column("placeId", Integer),
@@ -122,15 +134,17 @@ playlog = Table(
Column("charaIllustId", Integer), Column("charaIllustId", Integer),
Column("romVersion", String(255)), Column("romVersion", String(255)),
Column("judgeHeaven", Integer), Column("judgeHeaven", Integer),
mysql_charset='utf8mb4' mysql_charset="utf8mb4",
) )
class ChuniScoreData(BaseData): class ChuniScoreData(BaseData):
def get_courses(self, aime_id: int) -> Optional[Row]: def get_courses(self, aime_id: int) -> Optional[Row]:
sql = select(course).where(course.c.user == aime_id) sql = select(course).where(course.c.user == aime_id)
result = self.execute(sql) result = self.execute(sql)
if result is None: return None if result is None:
return None
return result.fetchall() return result.fetchall()
def put_course(self, aime_id: int, course_data: Dict) -> Optional[int]: def put_course(self, aime_id: int, course_data: Dict) -> Optional[int]:
@@ -141,16 +155,18 @@ class ChuniScoreData(BaseData):
conflict = sql.on_duplicate_key_update(**course_data) conflict = sql.on_duplicate_key_update(**course_data)
result = self.execute(conflict) result = self.execute(conflict)
if result is None: return None if result is None:
return None
return result.lastrowid return result.lastrowid
def get_scores(self, aime_id: int) -> Optional[Row]: def get_scores(self, aime_id: int) -> Optional[Row]:
sql = select(best_score).where(best_score.c.user == aime_id) sql = select(best_score).where(best_score.c.user == aime_id)
result = self.execute(sql) result = self.execute(sql)
if result is None: return None if result is None:
return None
return result.fetchall() return result.fetchall()
def put_score(self, aime_id: int, score_data: Dict) -> Optional[int]: def put_score(self, aime_id: int, score_data: Dict) -> Optional[int]:
score_data["user"] = aime_id score_data["user"] = aime_id
score_data = self.fix_bools(score_data) score_data = self.fix_bools(score_data)
@@ -159,16 +175,18 @@ class ChuniScoreData(BaseData):
conflict = sql.on_duplicate_key_update(**score_data) conflict = sql.on_duplicate_key_update(**score_data)
result = self.execute(conflict) result = self.execute(conflict)
if result is None: return None if result is None:
return None
return result.lastrowid return result.lastrowid
def get_playlogs(self, aime_id: int) -> Optional[Row]: def get_playlogs(self, aime_id: int) -> Optional[Row]:
sql = select(playlog).where(playlog.c.user == aime_id) sql = select(playlog).where(playlog.c.user == aime_id)
result = self.execute(sql) result = self.execute(sql)
if result is None: return None if result is None:
return None
return result.fetchall() return result.fetchall()
def put_playlog(self, aime_id: int, playlog_data: Dict) -> Optional[int]: def put_playlog(self, aime_id: int, playlog_data: Dict) -> Optional[int]:
playlog_data["user"] = aime_id playlog_data["user"] = aime_id
playlog_data = self.fix_bools(playlog_data) playlog_data = self.fix_bools(playlog_data)
@@ -177,5 +195,6 @@ class ChuniScoreData(BaseData):
conflict = sql.on_duplicate_key_update(**playlog_data) conflict = sql.on_duplicate_key_update(**playlog_data)
result = self.execute(conflict) result = self.execute(conflict)
if result is None: return None if result is None:
return None
return result.lastrowid return result.lastrowid
+133 -89
View File
@@ -19,7 +19,7 @@ events = Table(
Column("name", String(255)), Column("name", String(255)),
Column("enabled", Boolean, server_default="1"), Column("enabled", Boolean, server_default="1"),
UniqueConstraint("version", "eventId", name="chuni_static_events_uk"), UniqueConstraint("version", "eventId", name="chuni_static_events_uk"),
mysql_charset='utf8mb4' mysql_charset="utf8mb4",
) )
music = Table( music = Table(
@@ -30,13 +30,13 @@ music = Table(
Column("songId", Integer), Column("songId", Integer),
Column("chartId", Integer), Column("chartId", Integer),
Column("title", String(255)), Column("title", String(255)),
Column("artist", String(255)), Column("artist", String(255)),
Column("level", Float), Column("level", Float),
Column("genre", String(255)), Column("genre", String(255)),
Column("jacketPath", String(255)), Column("jacketPath", String(255)),
Column("worldsEndTag", String(7)), Column("worldsEndTag", String(7)),
UniqueConstraint("version", "songId", "chartId", name="chuni_static_music_uk"), UniqueConstraint("version", "songId", "chartId", name="chuni_static_music_uk"),
mysql_charset='utf8mb4' mysql_charset="utf8mb4",
) )
charge = Table( charge = Table(
@@ -51,7 +51,7 @@ charge = Table(
Column("sellingAppeal", Boolean), Column("sellingAppeal", Boolean),
Column("enabled", Boolean, server_default="1"), Column("enabled", Boolean, server_default="1"),
UniqueConstraint("version", "chargeId", name="chuni_static_charge_uk"), UniqueConstraint("version", "chargeId", name="chuni_static_charge_uk"),
mysql_charset='utf8mb4' mysql_charset="utf8mb4",
) )
avatar = Table( avatar = Table(
@@ -65,159 +65,203 @@ avatar = Table(
Column("iconPath", String(255)), Column("iconPath", String(255)),
Column("texturePath", String(255)), Column("texturePath", String(255)),
UniqueConstraint("version", "avatarAccessoryId", name="chuni_static_avatar_uk"), UniqueConstraint("version", "avatarAccessoryId", name="chuni_static_avatar_uk"),
mysql_charset='utf8mb4' mysql_charset="utf8mb4",
) )
class ChuniStaticData(BaseData): class ChuniStaticData(BaseData):
def put_event(self, version: int, event_id: int, type: int, name: str) -> Optional[int]: def put_event(
self, version: int, event_id: int, type: int, name: str
) -> Optional[int]:
sql = insert(events).values( sql = insert(events).values(
version = version, version=version, eventId=event_id, type=type, name=name
eventId = event_id,
type = type,
name = name
) )
conflict = sql.on_duplicate_key_update( conflict = sql.on_duplicate_key_update(name=name)
name = name
)
result = self.execute(conflict) result = self.execute(conflict)
if result is None: return None if result is None:
return None
return result.lastrowid return result.lastrowid
def update_event(self, version: int, event_id: int, enabled: bool) -> Optional[bool]: def update_event(
sql = events.update(and_(events.c.version == version, events.c.eventId == event_id)).values( self, version: int, event_id: int, enabled: bool
enabled = enabled ) -> Optional[bool]:
) sql = events.update(
and_(events.c.version == version, events.c.eventId == event_id)
).values(enabled=enabled)
result = self.execute(sql) result = self.execute(sql)
if result is None: if result is None:
self.logger.warn(f"update_event: failed to update event! version: {version}, event_id: {event_id}, enabled: {enabled}") self.logger.warn(
f"update_event: failed to update event! version: {version}, event_id: {event_id}, enabled: {enabled}"
)
return None return None
event = self.get_event(version, event_id) event = self.get_event(version, event_id)
if event is None: if event is None:
self.logger.warn(f"update_event: failed to fetch event {event_id} after updating") self.logger.warn(
f"update_event: failed to fetch event {event_id} after updating"
)
return None return None
return event["enabled"] return event["enabled"]
def get_event(self, version: int, event_id: int) -> Optional[Row]: def get_event(self, version: int, event_id: int) -> Optional[Row]:
sql = select(events).where(and_(events.c.version == version, events.c.eventId == event_id)) sql = select(events).where(
and_(events.c.version == version, events.c.eventId == event_id)
)
result = self.execute(sql) result = self.execute(sql)
if result is None: return None if result is None:
return None
return result.fetchone() return result.fetchone()
def get_enabled_events(self, version: int) -> Optional[List[Row]]: def get_enabled_events(self, version: int) -> Optional[List[Row]]:
sql = select(events).where(and_(events.c.version == version, events.c.enabled == True)) sql = select(events).where(
and_(events.c.version == version, events.c.enabled == True)
)
result = self.execute(sql) result = self.execute(sql)
if result is None: return None if result is None:
return None
return result.fetchall() return result.fetchall()
def get_events(self, version: int) -> Optional[List[Row]]: def get_events(self, version: int) -> Optional[List[Row]]:
sql = select(events).where(events.c.version == version) sql = select(events).where(events.c.version == version)
result = self.execute(sql) result = self.execute(sql)
if result is None: return None if result is None:
return None
return result.fetchall() return result.fetchall()
def put_music(self, version: int, song_id: int, chart_id: int, title: int, artist: str,
level: float, genre: str, jacketPath: str, we_tag: str) -> Optional[int]:
def put_music(
self,
version: int,
song_id: int,
chart_id: int,
title: int,
artist: str,
level: float,
genre: str,
jacketPath: str,
we_tag: str,
) -> Optional[int]:
sql = insert(music).values( sql = insert(music).values(
version = version, version=version,
songId = song_id, songId=song_id,
chartId = chart_id, chartId=chart_id,
title = title, title=title,
artist = artist, artist=artist,
level = level, level=level,
genre = genre, genre=genre,
jacketPath = jacketPath, jacketPath=jacketPath,
worldsEndTag = we_tag, worldsEndTag=we_tag,
) )
conflict = sql.on_duplicate_key_update( conflict = sql.on_duplicate_key_update(
title = title, title=title,
artist = artist, artist=artist,
level = level, level=level,
genre = genre, genre=genre,
jacketPath = jacketPath, jacketPath=jacketPath,
worldsEndTag = we_tag, worldsEndTag=we_tag,
) )
result = self.execute(conflict) result = self.execute(conflict)
if result is None: return None if result is None:
return None
return result.lastrowid return result.lastrowid
def put_charge(self, version: int, charge_id: int, name: str, expiration_days: int, def put_charge(
consume_type: int, selling_appeal: bool) -> Optional[int]: self,
version: int,
charge_id: int,
name: str,
expiration_days: int,
consume_type: int,
selling_appeal: bool,
) -> Optional[int]:
sql = insert(charge).values( sql = insert(charge).values(
version = version, version=version,
chargeId = charge_id, chargeId=charge_id,
name = name, name=name,
expirationDays = expiration_days, expirationDays=expiration_days,
consumeType = consume_type, consumeType=consume_type,
sellingAppeal = selling_appeal, sellingAppeal=selling_appeal,
) )
conflict = sql.on_duplicate_key_update( conflict = sql.on_duplicate_key_update(
name = name, name=name,
expirationDays = expiration_days, expirationDays=expiration_days,
consumeType = consume_type, consumeType=consume_type,
sellingAppeal = selling_appeal, sellingAppeal=selling_appeal,
) )
result = self.execute(conflict) result = self.execute(conflict)
if result is None: return None if result is None:
return None
return result.lastrowid return result.lastrowid
def get_enabled_charges(self, version: int) -> Optional[List[Row]]: def get_enabled_charges(self, version: int) -> Optional[List[Row]]:
sql = select(charge).where(and_( sql = select(charge).where(
charge.c.version == version, and_(charge.c.version == version, charge.c.enabled == True)
charge.c.enabled == True )
))
result = self.execute(sql) result = self.execute(sql)
if result is None: return None if result is None:
return None
return result.fetchall() return result.fetchall()
def get_charges(self, version: int) -> Optional[List[Row]]: def get_charges(self, version: int) -> Optional[List[Row]]:
sql = select(charge).where(charge.c.version == version) sql = select(charge).where(charge.c.version == version)
result = self.execute(sql) result = self.execute(sql)
if result is None: return None if result is None:
return None
return result.fetchall() return result.fetchall()
def get_music_chart(self, version: int, song_id: int, chart_id: int) -> Optional[List[Row]]: def get_music_chart(
sql = select(music).where(and_( self, version: int, song_id: int, chart_id: int
music.c.version == version, ) -> Optional[List[Row]]:
music.c.songId == song_id, sql = select(music).where(
music.c.chartId == chart_id and_(
)) music.c.version == version,
music.c.songId == song_id,
music.c.chartId == chart_id,
)
)
result = self.execute(sql) result = self.execute(sql)
if result is None: return None if result is None:
return None
return result.fetchone() return result.fetchone()
def put_avatar(self, version: int, avatarAccessoryId: int, name: str, category: int, iconPath: str, texturePath: str) -> Optional[int]: def put_avatar(
self,
version: int,
avatarAccessoryId: int,
name: str,
category: int,
iconPath: str,
texturePath: str,
) -> Optional[int]:
sql = insert(avatar).values( sql = insert(avatar).values(
version = version, version=version,
avatarAccessoryId = avatarAccessoryId, avatarAccessoryId=avatarAccessoryId,
name = name, name=name,
category = category, category=category,
iconPath = iconPath, iconPath=iconPath,
texturePath = texturePath, texturePath=texturePath,
) )
conflict = sql.on_duplicate_key_update( conflict = sql.on_duplicate_key_update(
name = name, name=name,
category = category, category=category,
iconPath = iconPath, iconPath=iconPath,
texturePath = texturePath, texturePath=texturePath,
) )
result = self.execute(conflict) result = self.execute(conflict)
if result is None: return None if result is None:
return None
return result.lastrowid return result.lastrowid
+4 -3
View File
@@ -5,12 +5,13 @@ from titles.chuni.base import ChuniBase
from titles.chuni.const import ChuniConstants from titles.chuni.const import ChuniConstants
from titles.chuni.config import ChuniConfig from titles.chuni.config import ChuniConfig
class ChuniStar(ChuniBase): class ChuniStar(ChuniBase):
def __init__(self, core_cfg: CoreConfig, game_cfg: ChuniConfig) -> None: def __init__(self, core_cfg: CoreConfig, game_cfg: ChuniConfig) -> None:
super().__init__(core_cfg, game_cfg) super().__init__(core_cfg, game_cfg)
self.version = ChuniConstants.VER_CHUNITHM_STAR self.version = ChuniConstants.VER_CHUNITHM_STAR
def handle_get_game_setting_api_request(self, data: Dict) -> Dict: def handle_get_game_setting_api_request(self, data: Dict) -> Dict:
ret = super().handle_get_game_setting_api_request(data) ret = super().handle_get_game_setting_api_request(data)
ret["gameSetting"]["dataVersion"] = "1.20.00" ret["gameSetting"]["dataVersion"] = "1.20.00"
return ret return ret
+4 -3
View File
@@ -5,12 +5,13 @@ from titles.chuni.base import ChuniBase
from titles.chuni.const import ChuniConstants from titles.chuni.const import ChuniConstants
from titles.chuni.config import ChuniConfig from titles.chuni.config import ChuniConfig
class ChuniStarPlus(ChuniBase): class ChuniStarPlus(ChuniBase):
def __init__(self, core_cfg: CoreConfig, game_cfg: ChuniConfig) -> None: def __init__(self, core_cfg: CoreConfig, game_cfg: ChuniConfig) -> None:
super().__init__(core_cfg, game_cfg) super().__init__(core_cfg, game_cfg)
self.version = ChuniConstants.VER_CHUNITHM_STAR_PLUS self.version = ChuniConstants.VER_CHUNITHM_STAR_PLUS
def handle_get_game_setting_api_request(self, data: Dict) -> Dict: def handle_get_game_setting_api_request(self, data: Dict) -> Dict:
ret = super().handle_get_game_setting_api_request(data) ret = super().handle_get_game_setting_api_request(data)
ret["gameSetting"]["dataVersion"] = "1.25.00" ret["gameSetting"]["dataVersion"] = "1.25.00"
return ret return ret
+17 -27
View File
@@ -10,12 +10,14 @@ from titles.cm.const import CardMakerConstants
from titles.cm.config import CardMakerConfig from titles.cm.config import CardMakerConfig
class CardMakerBase(): class CardMakerBase:
def __init__(self, core_cfg: CoreConfig, game_cfg: CardMakerConfig) -> None: def __init__(self, core_cfg: CoreConfig, game_cfg: CardMakerConfig) -> None:
self.core_cfg = core_cfg self.core_cfg = core_cfg
self.game_cfg = game_cfg self.game_cfg = game_cfg
self.date_time_format = "%Y-%m-%d %H:%M:%S" self.date_time_format = "%Y-%m-%d %H:%M:%S"
self.date_time_format_ext = "%Y-%m-%d %H:%M:%S.%f" # needs to be lopped off at [:-5] self.date_time_format_ext = (
"%Y-%m-%d %H:%M:%S.%f" # needs to be lopped off at [:-5]
)
self.date_time_format_short = "%Y-%m-%d" self.date_time_format_short = "%Y-%m-%d"
self.logger = logging.getLogger("cardmaker") self.logger = logging.getLogger("cardmaker")
self.game = CardMakerConstants.GAME_CODE self.game = CardMakerConstants.GAME_CODE
@@ -31,27 +33,19 @@ class CardMakerBase():
return { return {
"length": 3, "length": 3,
"gameConnectList": [ "gameConnectList": [
{ {"modelKind": 0, "type": 1, "titleUri": f"{uri}/SDHD/200/"},
"modelKind": 0, {"modelKind": 1, "type": 1, "titleUri": f"{uri}/SDEZ/120/"},
"type": 1, {"modelKind": 2, "type": 1, "titleUri": f"{uri}/SDDT/130/"},
"titleUri": f"{uri}/SDHD/200/" ],
},
{
"modelKind": 1,
"type": 1,
"titleUri": f"{uri}/SDEZ/120/"
},
{
"modelKind": 2,
"type": 1,
"titleUri": f"{uri}/SDDT/130/"
}
]
} }
def handle_get_game_setting_api_request(self, data: Dict) -> Dict: def handle_get_game_setting_api_request(self, data: Dict) -> Dict:
reboot_start = date.strftime(datetime.now() + timedelta(hours=3), self.date_time_format) reboot_start = date.strftime(
reboot_end = date.strftime(datetime.now() + timedelta(hours=4), self.date_time_format) datetime.now() + timedelta(hours=3), self.date_time_format
)
reboot_end = date.strftime(
datetime.now() + timedelta(hours=4), self.date_time_format
)
return { return {
"gameSetting": { "gameSetting": {
@@ -67,18 +61,14 @@ class CardMakerBase():
"maxCountCard": 100, "maxCountCard": 100,
"watermark": False, "watermark": False,
"isMaintenance": False, "isMaintenance": False,
"isBackgroundDistribute": False "isBackgroundDistribute": False,
}, },
"isDumpUpload": False, "isDumpUpload": False,
"isAou": False "isAou": False,
} }
def handle_get_client_bookkeeping_api_request(self, data: Dict) -> Dict: def handle_get_client_bookkeeping_api_request(self, data: Dict) -> Dict:
return { return {"placeId": data["placeId"], "length": 0, "clientBookkeepingList": []}
"placeId": data["placeId"],
"length": 0,
"clientBookkeepingList": []
}
def handle_upsert_client_setting_api_request(self, data: Dict) -> Dict: def handle_upsert_client_setting_api_request(self, data: Dict) -> Dict:
return {"returnCode": 1, "apiName": "UpsertClientSettingApi"} return {"returnCode": 1, "apiName": "UpsertClientSettingApi"}
+1 -1
View File
@@ -22,7 +22,7 @@ class CardMaker136(CardMakerBase):
uri = f"http://{self.core_cfg.title.hostname}:{self.core_cfg.title.port}" uri = f"http://{self.core_cfg.title.hostname}:{self.core_cfg.title.port}"
else: else:
uri = f"http://{self.core_cfg.title.hostname}" uri = f"http://{self.core_cfg.title.hostname}"
ret["gameConnectList"][0]["titleUri"] = f"{uri}/SDHD/205/" ret["gameConnectList"][0]["titleUri"] = f"{uri}/SDHD/205/"
ret["gameConnectList"][1]["titleUri"] = f"{uri}/SDEZ/125/" ret["gameConnectList"][1]["titleUri"] = f"{uri}/SDEZ/125/"
ret["gameConnectList"][2]["titleUri"] = f"{uri}/SDDT/135/" ret["gameConnectList"][2]["titleUri"] = f"{uri}/SDDT/135/"
+9 -3
View File
@@ -1,17 +1,23 @@
from core.config import CoreConfig from core.config import CoreConfig
class CardMakerServerConfig(): class CardMakerServerConfig:
def __init__(self, parent_config: "CardMakerConfig") -> None: def __init__(self, parent_config: "CardMakerConfig") -> None:
self.__config = parent_config self.__config = parent_config
@property @property
def enable(self) -> bool: def enable(self) -> bool:
return CoreConfig.get_config_field(self.__config, 'cardmaker', 'server', 'enable', default=True) return CoreConfig.get_config_field(
self.__config, "cardmaker", "server", "enable", default=True
)
@property @property
def loglevel(self) -> int: def loglevel(self) -> int:
return CoreConfig.str_to_loglevel(CoreConfig.get_config_field(self.__config, 'cardmaker', 'server', 'loglevel', default="info")) return CoreConfig.str_to_loglevel(
CoreConfig.get_config_field(
self.__config, "cardmaker", "server", "loglevel", default="info"
)
)
class CardMakerConfig(dict): class CardMakerConfig(dict):
+1 -1
View File
@@ -1,4 +1,4 @@
class CardMakerConstants(): class CardMakerConstants:
GAME_CODE = "SDED" GAME_CODE = "SDED"
CONFIG_NAME = "cardmaker.yaml" CONFIG_NAME = "cardmaker.yaml"
+29 -14
View File
@@ -18,23 +18,29 @@ from titles.cm.base import CardMakerBase
from titles.cm.cm136 import CardMaker136 from titles.cm.cm136 import CardMaker136
class CardMakerServlet(): class CardMakerServlet:
def __init__(self, core_cfg: CoreConfig, cfg_dir: str) -> None: def __init__(self, core_cfg: CoreConfig, cfg_dir: str) -> None:
self.core_cfg = core_cfg self.core_cfg = core_cfg
self.game_cfg = CardMakerConfig() self.game_cfg = CardMakerConfig()
if path.exists(f"{cfg_dir}/{CardMakerConstants.CONFIG_NAME}"): if path.exists(f"{cfg_dir}/{CardMakerConstants.CONFIG_NAME}"):
self.game_cfg.update(yaml.safe_load(open(f"{cfg_dir}/{CardMakerConstants.CONFIG_NAME}"))) self.game_cfg.update(
yaml.safe_load(open(f"{cfg_dir}/{CardMakerConstants.CONFIG_NAME}"))
)
self.versions = [ self.versions = [
CardMakerBase(core_cfg, self.game_cfg), CardMakerBase(core_cfg, self.game_cfg),
CardMaker136(core_cfg, self.game_cfg) CardMaker136(core_cfg, self.game_cfg),
] ]
self.logger = logging.getLogger("cardmaker") self.logger = logging.getLogger("cardmaker")
log_fmt_str = "[%(asctime)s] Card Maker | %(levelname)s | %(message)s" log_fmt_str = "[%(asctime)s] Card Maker | %(levelname)s | %(message)s"
log_fmt = logging.Formatter(log_fmt_str) log_fmt = logging.Formatter(log_fmt_str)
fileHandler = TimedRotatingFileHandler("{0}/{1}.log".format(self.core_cfg.server.log_dir, "cardmaker"), encoding='utf8', fileHandler = TimedRotatingFileHandler(
when="d", backupCount=10) "{0}/{1}.log".format(self.core_cfg.server.log_dir, "cardmaker"),
encoding="utf8",
when="d",
backupCount=10,
)
fileHandler.setFormatter(log_fmt) fileHandler.setFormatter(log_fmt)
@@ -45,20 +51,29 @@ class CardMakerServlet():
self.logger.addHandler(consoleHandler) self.logger.addHandler(consoleHandler)
self.logger.setLevel(self.game_cfg.server.loglevel) self.logger.setLevel(self.game_cfg.server.loglevel)
coloredlogs.install(level=self.game_cfg.server.loglevel, coloredlogs.install(
logger=self.logger, fmt=log_fmt_str) level=self.game_cfg.server.loglevel, logger=self.logger, fmt=log_fmt_str
)
@classmethod @classmethod
def get_allnet_info(cls, game_code: str, core_cfg: CoreConfig, cfg_dir: str) -> Tuple[bool, str, str]: def get_allnet_info(
cls, game_code: str, core_cfg: CoreConfig, cfg_dir: str
) -> Tuple[bool, str, str]:
game_cfg = CardMakerConfig() game_cfg = CardMakerConfig()
if path.exists(f"{cfg_dir}/{CardMakerConstants.CONFIG_NAME}"): if path.exists(f"{cfg_dir}/{CardMakerConstants.CONFIG_NAME}"):
game_cfg.update(yaml.safe_load(open(f"{cfg_dir}/{CardMakerConstants.CONFIG_NAME}"))) game_cfg.update(
yaml.safe_load(open(f"{cfg_dir}/{CardMakerConstants.CONFIG_NAME}"))
)
if not game_cfg.server.enable: if not game_cfg.server.enable:
return (False, "", "") return (False, "", "")
if core_cfg.server.is_develop: if core_cfg.server.is_develop:
return (True, f"http://{core_cfg.title.hostname}:{core_cfg.title.port}/{game_code}/$v/", "") return (
True,
f"http://{core_cfg.title.hostname}:{core_cfg.title.port}/{game_code}/$v/",
"",
)
return (True, f"http://{core_cfg.title.hostname}/{game_code}/$v/", "") return (True, f"http://{core_cfg.title.hostname}/{game_code}/$v/", "")
@@ -86,7 +101,8 @@ class CardMakerServlet():
except zlib.error as e: except zlib.error as e:
self.logger.error( self.logger.error(
f"Failed to decompress v{version} {endpoint} request -> {e}") f"Failed to decompress v{version} {endpoint} request -> {e}"
)
return zlib.compress(b'{"stat": "0"}') return zlib.compress(b'{"stat": "0"}')
req_data = json.loads(unzip) req_data = json.loads(unzip)
@@ -104,12 +120,11 @@ class CardMakerServlet():
resp = handler(req_data) resp = handler(req_data)
except Exception as e: except Exception as e:
self.logger.error( self.logger.error(f"Error handling v{version} method {endpoint} - {e}")
f"Error handling v{version} method {endpoint} - {e}")
return zlib.compress(b'{"stat": "0"}') return zlib.compress(b'{"stat": "0"}')
if resp is None: if resp is None:
resp = {'returnCode': 1} resp = {"returnCode": 1}
self.logger.info(f"Response {resp}") self.logger.info(f"Response {resp}")
+32 -15
View File
@@ -15,14 +15,21 @@ from titles.ongeki.config import OngekiConfig
class CardMakerReader(BaseReader): class CardMakerReader(BaseReader):
def __init__(self, config: CoreConfig, version: int, bin_dir: Optional[str], def __init__(
opt_dir: Optional[str], extra: Optional[str]) -> None: self,
config: CoreConfig,
version: int,
bin_dir: Optional[str],
opt_dir: Optional[str],
extra: Optional[str],
) -> None:
super().__init__(config, version, bin_dir, opt_dir, extra) super().__init__(config, version, bin_dir, opt_dir, extra)
self.ongeki_data = OngekiData(config) self.ongeki_data = OngekiData(config)
try: try:
self.logger.info( self.logger.info(
f"Start importer for {CardMakerConstants.game_ver_to_string(version)}") f"Start importer for {CardMakerConstants.game_ver_to_string(version)}"
)
except IndexError: except IndexError:
self.logger.error(f"Invalid Card Maker version {version}") self.logger.error(f"Invalid Card Maker version {version}")
exit(1) exit(1)
@@ -30,7 +37,7 @@ class CardMakerReader(BaseReader):
def read(self) -> None: def read(self) -> None:
static_datas = { static_datas = {
"static_gachas.csv": "read_ongeki_gacha_csv", "static_gachas.csv": "read_ongeki_gacha_csv",
"static_gacha_cards.csv": "read_ongeki_gacha_card_csv" "static_gacha_cards.csv": "read_ongeki_gacha_card_csv",
} }
data_dirs = [] data_dirs = []
@@ -41,7 +48,9 @@ class CardMakerReader(BaseReader):
read_csv = getattr(CardMakerReader, func) read_csv = getattr(CardMakerReader, func)
read_csv(self, f"{self.bin_dir}/MU3/{file}") read_csv(self, f"{self.bin_dir}/MU3/{file}")
else: else:
self.logger.warn(f"Couldn't find {file} file in {self.bin_dir}, skipping") self.logger.warn(
f"Couldn't find {file} file in {self.bin_dir}, skipping"
)
if self.opt_dir is not None: if self.opt_dir is not None:
data_dirs += self.get_data_directories(self.opt_dir) data_dirs += self.get_data_directories(self.opt_dir)
@@ -64,7 +73,7 @@ class CardMakerReader(BaseReader):
row["kind"], row["kind"],
type=row["type"], type=row["type"],
isCeiling=True if row["isCeiling"] == "1" else False, isCeiling=True if row["isCeiling"] == "1" else False,
maxSelectPoint=row["maxSelectPoint"] maxSelectPoint=row["maxSelectPoint"],
) )
self.logger.info(f"Added gacha {row['gachaId']}") self.logger.info(f"Added gacha {row['gachaId']}")
@@ -81,7 +90,7 @@ class CardMakerReader(BaseReader):
rarity=row["rarity"], rarity=row["rarity"],
weight=row["weight"], weight=row["weight"],
isPickup=True if row["isPickup"] == "1" else False, isPickup=True if row["isPickup"] == "1" else False,
isSelect=True if row["isSelect"] == "1" else False isSelect=True if row["isSelect"] == "1" else False,
) )
self.logger.info(f"Added card {row['cardId']} to gacha") self.logger.info(f"Added card {row['cardId']} to gacha")
@@ -95,7 +104,7 @@ class CardMakerReader(BaseReader):
"Pickup": "Pickup", "Pickup": "Pickup",
"RecoverFiveShotFlag": "BonusRestored", "RecoverFiveShotFlag": "BonusRestored",
"Free": "Free", "Free": "Free",
"FreeSR": "Free" "FreeSR": "Free",
} }
for root, dirs, files in os.walk(base_dir): for root, dirs, files in os.walk(base_dir):
@@ -104,13 +113,19 @@ class CardMakerReader(BaseReader):
with open(f"{root}/{dir}/Gacha.xml", "r", encoding="utf-8") as f: with open(f"{root}/{dir}/Gacha.xml", "r", encoding="utf-8") as f:
troot = ET.fromstring(f.read()) troot = ET.fromstring(f.read())
name = troot.find('Name').find('str').text name = troot.find("Name").find("str").text
gacha_id = int(troot.find('Name').find('id').text) gacha_id = int(troot.find("Name").find("id").text)
# skip already existing gachas # skip already existing gachas
if self.ongeki_data.static.get_gacha( if (
OngekiConstants.VER_ONGEKI_BRIGHT_MEMORY, gacha_id) is not None: self.ongeki_data.static.get_gacha(
self.logger.info(f"Gacha {gacha_id} already added, skipping") OngekiConstants.VER_ONGEKI_BRIGHT_MEMORY, gacha_id
)
is not None
):
self.logger.info(
f"Gacha {gacha_id} already added, skipping"
)
continue continue
# 1140 is the first bright memory gacha # 1140 is the first bright memory gacha
@@ -120,7 +135,8 @@ class CardMakerReader(BaseReader):
version = OngekiConstants.VER_ONGEKI_BRIGHT_MEMORY version = OngekiConstants.VER_ONGEKI_BRIGHT_MEMORY
gacha_kind = OngekiConstants.CM_GACHA_KINDS[ gacha_kind = OngekiConstants.CM_GACHA_KINDS[
type_to_kind[troot.find('Type').text]].value type_to_kind[troot.find("Type").text]
].value
# hardcode which gachas get "Select Gacha" with 33 points # hardcode which gachas get "Select Gacha" with 33 points
is_ceiling, max_select_point = 0, 0 is_ceiling, max_select_point = 0, 0
@@ -134,5 +150,6 @@ class CardMakerReader(BaseReader):
name, name,
gacha_kind, gacha_kind,
isCeiling=is_ceiling, isCeiling=is_ceiling,
maxSelectPoint=max_select_point) maxSelectPoint=max_select_point,
)
self.logger.info(f"Added gacha {gacha_id}") self.logger.info(f"Added gacha {gacha_id}")
+1 -1
View File
@@ -7,4 +7,4 @@ index = CxbServlet
database = CxbData database = CxbData
reader = CxbReader reader = CxbReader
game_codes = [CxbConstants.GAME_CODE] game_codes = [CxbConstants.GAME_CODE]
current_schema_version = 1 current_schema_version = 1

Some files were not shown because too many files have changed in this diff Show More