1
0
mirror of synced 2024-11-30 16:54:27 +01:00

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

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

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)

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

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:

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

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

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)

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"])

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"]

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

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

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}"

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()

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)

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

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"]
)

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}")

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()

174
index.py
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

72
read.py
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")

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

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

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

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

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"}

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)

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):

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

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

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)

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)

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",

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

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

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

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}")

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"]

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()

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()

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

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

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

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

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"}

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/"

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):

View File

@ -1,4 +1,4 @@
class CardMakerConstants(): class CardMakerConstants:
GAME_CODE = "SDED" GAME_CODE = "SDED"
CONFIG_NAME = "cardmaker.yaml" CONFIG_NAME = "cardmaker.yaml"

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}")

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}")

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

View File

@ -11,82 +11,91 @@ from titles.cxb.config import CxbConfig
from titles.cxb.const import CxbConstants from titles.cxb.const import CxbConstants
from titles.cxb.database import CxbData from titles.cxb.database import CxbData
class CxbBase():
class CxbBase:
def __init__(self, cfg: CoreConfig, game_cfg: CxbConfig) -> None: def __init__(self, cfg: CoreConfig, game_cfg: CxbConfig) -> None:
self.config = cfg # Config file self.config = cfg # Config file
self.game_config = game_cfg self.game_config = game_cfg
self.data = CxbData(cfg) # Database self.data = CxbData(cfg) # Database
self.game = CxbConstants.GAME_CODE self.game = CxbConstants.GAME_CODE
self.logger = logging.getLogger("cxb") self.logger = logging.getLogger("cxb")
self.version = CxbConstants.VER_CROSSBEATS_REV self.version = CxbConstants.VER_CROSSBEATS_REV
def handle_action_rpreq_request(self, data: Dict) -> Dict: def handle_action_rpreq_request(self, data: Dict) -> Dict:
return({}) return {}
def handle_action_hitreq_request(self, data: Dict) -> Dict: def handle_action_hitreq_request(self, data: Dict) -> Dict:
return({"data":[]}) return {"data": []}
def handle_auth_usercheck_request(self, data: Dict) -> Dict: def handle_auth_usercheck_request(self, data: Dict) -> Dict:
profile = self.data.profile.get_profile_index(0, data["usercheck"]["authid"], self.version) profile = self.data.profile.get_profile_index(
0, data["usercheck"]["authid"], self.version
)
if profile is not None: if profile is not None:
self.logger.info(f"User {data['usercheck']['authid']} has CXB profile") self.logger.info(f"User {data['usercheck']['authid']} has CXB profile")
return({"exist": "true", "logout": "true"}) return {"exist": "true", "logout": "true"}
self.logger.info(f"No profile for aime id {data['usercheck']['authid']}") self.logger.info(f"No profile for aime id {data['usercheck']['authid']}")
return({"exist": "false", "logout": "true"}) return {"exist": "false", "logout": "true"}
def handle_auth_entry_request(self, data: Dict) -> Dict: def handle_auth_entry_request(self, data: Dict) -> Dict:
self.logger.info(f"New profile for {data['entry']['authid']}") self.logger.info(f"New profile for {data['entry']['authid']}")
return({"token": data["entry"]["authid"], "uid": data["entry"]["authid"]}) return {"token": data["entry"]["authid"], "uid": data["entry"]["authid"]}
def handle_auth_login_request(self, data: Dict) -> Dict: def handle_auth_login_request(self, data: Dict) -> Dict:
profile = self.data.profile.get_profile_index(0, data["login"]["authid"], self.version) profile = self.data.profile.get_profile_index(
0, data["login"]["authid"], self.version
)
if profile is not None: if profile is not None:
self.logger.info(f"Login user {data['login']['authid']}") self.logger.info(f"Login user {data['login']['authid']}")
return({"token": data["login"]["authid"], "uid": data["login"]["authid"]}) return {"token": data["login"]["authid"], "uid": data["login"]["authid"]}
self.logger.warn(f"User {data['login']['authid']} does not have a profile") self.logger.warn(f"User {data['login']['authid']} does not have a profile")
return({}) return {}
def handle_action_loadrange_request(self, data: Dict) -> Dict: def handle_action_loadrange_request(self, data: Dict) -> Dict:
range_start = data['loadrange']['range'][0] range_start = data["loadrange"]["range"][0]
range_end = data['loadrange']['range'][1] range_end = data["loadrange"]["range"][1]
uid = data['loadrange']['uid'] uid = data["loadrange"]["uid"]
self.logger.info(f"Load data for {uid}") self.logger.info(f"Load data for {uid}")
profile = self.data.profile.get_profile(uid, self.version) profile = self.data.profile.get_profile(uid, self.version)
songs = self.data.score.get_best_scores(uid) songs = self.data.score.get_best_scores(uid)
data1 = [] data1 = []
index = [] index = []
versionindex = [] versionindex = []
for profile_index in profile: for profile_index in profile:
profile_data = profile_index["data"] profile_data = profile_index["data"]
if int(range_start) == 800000: if int(range_start) == 800000:
return({"index":range_start, "data":[], "version":10400}) return {"index": range_start, "data": [], "version": 10400}
if not ( int(range_start) <= int(profile_index[3]) <= int(range_end) ): if not (int(range_start) <= int(profile_index[3]) <= int(range_end)):
continue continue
#Prevent loading of the coupons within the profile to use the force unlock instead # Prevent loading of the coupons within the profile to use the force unlock instead
elif 500 <= int(profile_index[3]) <= 510: elif 500 <= int(profile_index[3]) <= 510:
continue continue
#Prevent loading of songs saved in the profile # Prevent loading of songs saved in the profile
elif 100000 <= int(profile_index[3]) <= 110000: elif 100000 <= int(profile_index[3]) <= 110000:
continue continue
#Prevent loading of the shop list / unlocked titles & icons saved in the profile # Prevent loading of the shop list / unlocked titles & icons saved in the profile
elif 200000 <= int(profile_index[3]) <= 210000: elif 200000 <= int(profile_index[3]) <= 210000:
continue continue
#Prevent loading of stories in the profile # Prevent loading of stories in the profile
elif 900000 <= int(profile_index[3]) <= 900200: elif 900000 <= int(profile_index[3]) <= 900200:
continue continue
else: else:
index.append(profile_index[3]) index.append(profile_index[3])
data1.append(b64encode(bytes(json.dumps(profile_data, separators=(',', ':')), 'utf-8')).decode('utf-8')) data1.append(
b64encode(
bytes(json.dumps(profile_data, separators=(",", ":")), "utf-8")
).decode("utf-8")
)
''' """
100000 = Songs 100000 = Songs
200000 = Shop 200000 = Shop
300000 = Courses 300000 = Courses
@ -96,101 +105,140 @@ class CxbBase():
700000 = rcLog 700000 = rcLog
800000 = Partners 800000 = Partners
900000 = Stories 900000 = Stories
''' """
# Coupons # Coupons
for i in range(500,510): for i in range(500, 510):
index.append(str(i)) index.append(str(i))
couponid = int(i) - 500 couponid = int(i) - 500
dataValue = [{ dataValue = [
"couponId":str(couponid), {
"couponNum":"1", "couponId": str(couponid),
"couponLog":[], "couponNum": "1",
}] "couponLog": [],
data1.append(b64encode(bytes(json.dumps(dataValue[0], separators=(',', ':')), 'utf-8')).decode('utf-8')) }
]
data1.append(
b64encode(
bytes(json.dumps(dataValue[0], separators=(",", ":")), "utf-8")
).decode("utf-8")
)
# ShopList_Title # ShopList_Title
for i in range(200000,201451): for i in range(200000, 201451):
index.append(str(i)) index.append(str(i))
shopid = int(i) - 200000 shopid = int(i) - 200000
dataValue = [{ dataValue = [
"shopId":shopid, {
"shopState":"2", "shopId": shopid,
"isDisable":"t", "shopState": "2",
"isDeleted":"f", "isDisable": "t",
"isSpecialFlag":"f" "isDeleted": "f",
}] "isSpecialFlag": "f",
data1.append(b64encode(bytes(json.dumps(dataValue[0], separators=(',', ':')), 'utf-8')).decode('utf-8')) }
]
data1.append(
b64encode(
bytes(json.dumps(dataValue[0], separators=(",", ":")), "utf-8")
).decode("utf-8")
)
#ShopList_Icon # ShopList_Icon
for i in range(202000,202264): for i in range(202000, 202264):
index.append(str(i)) index.append(str(i))
shopid = int(i) - 200000 shopid = int(i) - 200000
dataValue = [{ dataValue = [
"shopId":shopid, {
"shopState":"2", "shopId": shopid,
"isDisable":"t", "shopState": "2",
"isDeleted":"f", "isDisable": "t",
"isSpecialFlag":"f" "isDeleted": "f",
}] "isSpecialFlag": "f",
data1.append(b64encode(bytes(json.dumps(dataValue[0], separators=(',', ':')), 'utf-8')).decode('utf-8')) }
]
data1.append(
b64encode(
bytes(json.dumps(dataValue[0], separators=(",", ":")), "utf-8")
).decode("utf-8")
)
#Stories # Stories
for i in range(900000,900003): for i in range(900000, 900003):
index.append(str(i)) index.append(str(i))
storyid = int(i) - 900000 storyid = int(i) - 900000
dataValue = [{ dataValue = [
"storyId":storyid, {
"unlockState1":["t"] * 10, "storyId": storyid,
"unlockState2":["t"] * 10, "unlockState1": ["t"] * 10,
"unlockState3":["t"] * 10, "unlockState2": ["t"] * 10,
"unlockState4":["t"] * 10, "unlockState3": ["t"] * 10,
"unlockState5":["t"] * 10, "unlockState4": ["t"] * 10,
"unlockState6":["t"] * 10, "unlockState5": ["t"] * 10,
"unlockState7":["t"] * 10, "unlockState6": ["t"] * 10,
"unlockState8":["t"] * 10, "unlockState7": ["t"] * 10,
"unlockState9":["t"] * 10, "unlockState8": ["t"] * 10,
"unlockState10":["t"] * 10, "unlockState9": ["t"] * 10,
"unlockState11":["t"] * 10, "unlockState10": ["t"] * 10,
"unlockState12":["t"] * 10, "unlockState11": ["t"] * 10,
"unlockState13":["t"] * 10, "unlockState12": ["t"] * 10,
"unlockState14":["t"] * 10, "unlockState13": ["t"] * 10,
"unlockState15":["t"] * 10, "unlockState14": ["t"] * 10,
"unlockState16":["t"] * 10 "unlockState15": ["t"] * 10,
}] "unlockState16": ["t"] * 10,
data1.append(b64encode(bytes(json.dumps(dataValue[0], separators=(',', ':')), 'utf-8')).decode('utf-8')) }
]
data1.append(
b64encode(
bytes(json.dumps(dataValue[0], separators=(",", ":")), "utf-8")
).decode("utf-8")
)
for song in songs: for song in songs:
song_data = song["data"] song_data = song["data"]
songCode = [] songCode = []
songCode.append({ songCode.append(
"mcode": song_data['mcode'], {
"musicState": song_data['musicState'], "mcode": song_data["mcode"],
"playCount": song_data['playCount'], "musicState": song_data["musicState"],
"totalScore": song_data['totalScore'], "playCount": song_data["playCount"],
"highScore": song_data['highScore'], "totalScore": song_data["totalScore"],
"everHighScore": song_data['everHighScore'] if 'everHighScore' in song_data else ["0","0","0","0","0"], "highScore": song_data["highScore"],
"clearRate": song_data['clearRate'], "everHighScore": song_data["everHighScore"]
"rankPoint": song_data['rankPoint'], if "everHighScore" in song_data
"normalCR": song_data['normalCR'] if 'normalCR' in song_data else ["0","0","0","0","0"], else ["0", "0", "0", "0", "0"],
"survivalCR": song_data['survivalCR'] if 'survivalCR' in song_data else ["0","0","0","0","0"], "clearRate": song_data["clearRate"],
"ultimateCR": song_data['ultimateCR'] if 'ultimateCR' in song_data else ["0","0","0","0","0"], "rankPoint": song_data["rankPoint"],
"nohopeCR": song_data['nohopeCR'] if 'nohopeCR' in song_data else ["0","0","0","0","0"], "normalCR": song_data["normalCR"]
"combo": song_data['combo'], if "normalCR" in song_data
"coupleUserId": song_data['coupleUserId'], else ["0", "0", "0", "0", "0"],
"difficulty": song_data['difficulty'], "survivalCR": song_data["survivalCR"]
"isFullCombo": song_data['isFullCombo'], if "survivalCR" in song_data
"clearGaugeType": song_data['clearGaugeType'], else ["0", "0", "0", "0", "0"],
"fieldType": song_data['fieldType'], "ultimateCR": song_data["ultimateCR"]
"gameType": song_data['gameType'], if "ultimateCR" in song_data
"grade": song_data['grade'], else ["0", "0", "0", "0", "0"],
"unlockState": song_data['unlockState'], "nohopeCR": song_data["nohopeCR"]
"extraState": song_data['extraState'] if "nohopeCR" in song_data
}) else ["0", "0", "0", "0", "0"],
index.append(song_data['index']) "combo": song_data["combo"],
data1.append(b64encode(bytes(json.dumps(songCode[0], separators=(',', ':')), 'utf-8')).decode('utf-8')) "coupleUserId": song_data["coupleUserId"],
"difficulty": song_data["difficulty"],
"isFullCombo": song_data["isFullCombo"],
"clearGaugeType": song_data["clearGaugeType"],
"fieldType": song_data["fieldType"],
"gameType": song_data["gameType"],
"grade": song_data["grade"],
"unlockState": song_data["unlockState"],
"extraState": song_data["extraState"],
}
)
index.append(song_data["index"])
data1.append(
b64encode(
bytes(json.dumps(songCode[0], separators=(",", ":")), "utf-8")
).decode("utf-8")
)
for v in index: for v in index:
try: try:
@ -198,66 +246,81 @@ class CxbBase():
v_profile_data = v_profile["data"] v_profile_data = v_profile["data"]
versionindex.append(int(v_profile_data["appVersion"])) versionindex.append(int(v_profile_data["appVersion"]))
except: except:
versionindex.append('10400') versionindex.append("10400")
return({"index":index, "data":data1, "version":versionindex}) return {"index": index, "data": data1, "version": versionindex}
def handle_action_saveindex_request(self, data: Dict) -> Dict: def handle_action_saveindex_request(self, data: Dict) -> Dict:
save_data = data['saveindex'] save_data = data["saveindex"]
try: try:
#REV Omnimix Version Fetcher # REV Omnimix Version Fetcher
gameversion = data['saveindex']['data'][0][2] gameversion = data["saveindex"]["data"][0][2]
self.logger.warning(f"Game Version is {gameversion}") self.logger.warning(f"Game Version is {gameversion}")
except: except:
pass pass
if "10205" in gameversion:
self.logger.info(f"Saving CrossBeats REV profile for {data['saveindex']['uid']}")
#Alright.... time to bring the jank code
for value in data['saveindex']['data']:
if 'playedUserId' in value[1]:
self.data.profile.put_profile(data['saveindex']['uid'], self.version, value[0], value[1])
if 'mcode' not in value[1]:
self.data.profile.put_profile(data['saveindex']['uid'], self.version, value[0], value[1])
if 'shopId' in value:
continue
if 'mcode' in value[1] and 'musicState' in value[1]:
song_json = json.loads(value[1])
songCode = []
songCode.append({
"mcode": song_json['mcode'],
"musicState": song_json['musicState'],
"playCount": song_json['playCount'],
"totalScore": song_json['totalScore'],
"highScore": song_json['highScore'],
"clearRate": song_json['clearRate'],
"rankPoint": song_json['rankPoint'],
"combo": song_json['combo'],
"coupleUserId": song_json['coupleUserId'],
"difficulty": song_json['difficulty'],
"isFullCombo": song_json['isFullCombo'],
"clearGaugeType": song_json['clearGaugeType'],
"fieldType": song_json['fieldType'],
"gameType": song_json['gameType'],
"grade": song_json['grade'],
"unlockState": song_json['unlockState'],
"extraState": song_json['extraState'],
"index": value[0]
})
self.data.score.put_best_score(data['saveindex']['uid'], song_json['mcode'], self.version, value[0], songCode[0])
return({})
else:
self.logger.info(f"Saving CrossBeats REV Sunrise profile for {data['saveindex']['uid']}")
#Sunrise if "10205" in gameversion:
self.logger.info(
f"Saving CrossBeats REV profile for {data['saveindex']['uid']}"
)
# Alright.... time to bring the jank code
for value in data["saveindex"]["data"]:
if "playedUserId" in value[1]:
self.data.profile.put_profile(
data["saveindex"]["uid"], self.version, value[0], value[1]
)
if "mcode" not in value[1]:
self.data.profile.put_profile(
data["saveindex"]["uid"], self.version, value[0], value[1]
)
if "shopId" in value:
continue
if "mcode" in value[1] and "musicState" in value[1]:
song_json = json.loads(value[1])
songCode = []
songCode.append(
{
"mcode": song_json["mcode"],
"musicState": song_json["musicState"],
"playCount": song_json["playCount"],
"totalScore": song_json["totalScore"],
"highScore": song_json["highScore"],
"clearRate": song_json["clearRate"],
"rankPoint": song_json["rankPoint"],
"combo": song_json["combo"],
"coupleUserId": song_json["coupleUserId"],
"difficulty": song_json["difficulty"],
"isFullCombo": song_json["isFullCombo"],
"clearGaugeType": song_json["clearGaugeType"],
"fieldType": song_json["fieldType"],
"gameType": song_json["gameType"],
"grade": song_json["grade"],
"unlockState": song_json["unlockState"],
"extraState": song_json["extraState"],
"index": value[0],
}
)
self.data.score.put_best_score(
data["saveindex"]["uid"],
song_json["mcode"],
self.version,
value[0],
songCode[0],
)
return {}
else:
self.logger.info(
f"Saving CrossBeats REV Sunrise profile for {data['saveindex']['uid']}"
)
# Sunrise
try: try:
profileIndex = save_data['index'].index('0') profileIndex = save_data["index"].index("0")
except: except:
return({"data":""}) #Maybe return {"data": ""} # Maybe
profile = json.loads(save_data["data"][profileIndex]) profile = json.loads(save_data["data"][profileIndex])
aimeId = profile["aimeId"] aimeId = profile["aimeId"]
@ -265,65 +328,91 @@ class CxbBase():
for index, value in enumerate(data["saveindex"]["data"]): for index, value in enumerate(data["saveindex"]["data"]):
if int(data["saveindex"]["index"][index]) == 101: if int(data["saveindex"]["index"][index]) == 101:
self.data.profile.put_profile(aimeId, self.version, data["saveindex"]["index"][index], value) self.data.profile.put_profile(
if int(data["saveindex"]["index"][index]) >= 700000 and int(data["saveindex"]["index"][index])<= 701000: aimeId, self.version, data["saveindex"]["index"][index], value
self.data.profile.put_profile(aimeId, self.version, data["saveindex"]["index"][index], value) )
if int(data["saveindex"]["index"][index]) >= 500 and int(data["saveindex"]["index"][index]) <= 510: if (
self.data.profile.put_profile(aimeId, self.version, data["saveindex"]["index"][index], value) int(data["saveindex"]["index"][index]) >= 700000
if 'playedUserId' in value: and int(data["saveindex"]["index"][index]) <= 701000
self.data.profile.put_profile(aimeId, self.version, data["saveindex"]["index"][index], json.loads(value)) ):
if 'mcode' not in value and "normalCR" not in value: self.data.profile.put_profile(
self.data.profile.put_profile(aimeId, self.version, data["saveindex"]["index"][index], json.loads(value)) aimeId, self.version, data["saveindex"]["index"][index], value
if 'shopId' in value: )
if (
int(data["saveindex"]["index"][index]) >= 500
and int(data["saveindex"]["index"][index]) <= 510
):
self.data.profile.put_profile(
aimeId, self.version, data["saveindex"]["index"][index], value
)
if "playedUserId" in value:
self.data.profile.put_profile(
aimeId,
self.version,
data["saveindex"]["index"][index],
json.loads(value),
)
if "mcode" not in value and "normalCR" not in value:
self.data.profile.put_profile(
aimeId,
self.version,
data["saveindex"]["index"][index],
json.loads(value),
)
if "shopId" in value:
continue continue
# MusicList Index for the profile # MusicList Index for the profile
indexSongList = [] indexSongList = []
for value in data["saveindex"]["index"]: for value in data["saveindex"]["index"]:
if int(value) in range(100000,110000): if int(value) in range(100000, 110000):
indexSongList.append(value) indexSongList.append(value)
for index, value in enumerate(data["saveindex"]["data"]): for index, value in enumerate(data["saveindex"]["data"]):
if 'mcode' not in value: if "mcode" not in value:
continue continue
if 'playedUserId' in value: if "playedUserId" in value:
continue continue
data1 = json.loads(value) data1 = json.loads(value)
songCode = [] songCode = []
songCode.append({ songCode.append(
"mcode": data1['mcode'], {
"musicState": data1['musicState'], "mcode": data1["mcode"],
"playCount": data1['playCount'], "musicState": data1["musicState"],
"totalScore": data1['totalScore'], "playCount": data1["playCount"],
"highScore": data1['highScore'], "totalScore": data1["totalScore"],
"everHighScore": data1['everHighScore'], "highScore": data1["highScore"],
"clearRate": data1['clearRate'], "everHighScore": data1["everHighScore"],
"rankPoint": data1['rankPoint'], "clearRate": data1["clearRate"],
"normalCR": data1['normalCR'], "rankPoint": data1["rankPoint"],
"survivalCR": data1['survivalCR'], "normalCR": data1["normalCR"],
"ultimateCR": data1['ultimateCR'], "survivalCR": data1["survivalCR"],
"nohopeCR": data1['nohopeCR'], "ultimateCR": data1["ultimateCR"],
"combo": data1['combo'], "nohopeCR": data1["nohopeCR"],
"coupleUserId": data1['coupleUserId'], "combo": data1["combo"],
"difficulty": data1['difficulty'], "coupleUserId": data1["coupleUserId"],
"isFullCombo": data1['isFullCombo'], "difficulty": data1["difficulty"],
"clearGaugeType": data1['clearGaugeType'], "isFullCombo": data1["isFullCombo"],
"fieldType": data1['fieldType'], "clearGaugeType": data1["clearGaugeType"],
"gameType": data1['gameType'], "fieldType": data1["fieldType"],
"grade": data1['grade'], "gameType": data1["gameType"],
"unlockState": data1['unlockState'], "grade": data1["grade"],
"extraState": data1['extraState'], "unlockState": data1["unlockState"],
"index": indexSongList[i] "extraState": data1["extraState"],
}) "index": indexSongList[i],
}
)
self.data.score.put_best_score(aimeId, data1['mcode'], self.version, indexSongList[i], songCode[0]) self.data.score.put_best_score(
aimeId, data1["mcode"], self.version, indexSongList[i], songCode[0]
)
i += 1 i += 1
return({}) return {}
def handle_action_sprankreq_request(self, data: Dict) -> Dict: def handle_action_sprankreq_request(self, data: Dict) -> Dict:
uid = data['sprankreq']['uid'] uid = data["sprankreq"]["uid"]
self.logger.info(f"Get best rankings for {uid}") self.logger.info(f"Get best rankings for {uid}")
p = self.data.score.get_best_rankings(uid) p = self.data.score.get_best_rankings(uid)
@ -331,90 +420,122 @@ class CxbBase():
for rank in p: for rank in p:
if rank["song_id"] is not None: if rank["song_id"] is not None:
rankList.append({ rankList.append(
"sc": [rank["score"],rank["song_id"]], {
"rid": rank["rev_id"], "sc": [rank["score"], rank["song_id"]],
"clear": rank["clear"] "rid": rank["rev_id"],
}) "clear": rank["clear"],
}
)
else: else:
rankList.append({ rankList.append(
"sc": [rank["score"]], {
"rid": rank["rev_id"], "sc": [rank["score"]],
"clear": rank["clear"] "rid": rank["rev_id"],
}) "clear": rank["clear"],
}
)
return({ return {
"uid": data["sprankreq"]["uid"], "uid": data["sprankreq"]["uid"],
"aid": data["sprankreq"]["aid"], "aid": data["sprankreq"]["aid"],
"rank": rankList, "rank": rankList,
"rankx":[1,1,1] "rankx": [1, 1, 1],
}) }
def handle_action_getadv_request(self, data: Dict) -> Dict: def handle_action_getadv_request(self, data: Dict) -> Dict:
return({"data":[{"r":"1","i":"100300","c":"20"}]}) return {"data": [{"r": "1", "i": "100300", "c": "20"}]}
def handle_action_getmsg_request(self, data: Dict) -> Dict: def handle_action_getmsg_request(self, data: Dict) -> Dict:
return({"msgs":[]}) return {"msgs": []}
def handle_auth_logout_request(self, data: Dict) -> Dict: def handle_auth_logout_request(self, data: Dict) -> Dict:
return({"auth":True}) return {"auth": True}
def handle_action_rankreg_request(self, data: Dict) -> Dict: def handle_action_rankreg_request(self, data: Dict) -> Dict:
uid = data['rankreg']['uid'] uid = data["rankreg"]["uid"]
self.logger.info(f"Put {len(data['rankreg']['data'])} rankings for {uid}") self.logger.info(f"Put {len(data['rankreg']['data'])} rankings for {uid}")
for rid in data['rankreg']['data']: for rid in data["rankreg"]["data"]:
#REV S2 # REV S2
if "clear" in rid: if "clear" in rid:
try: try:
self.data.score.put_ranking(user_id=uid, rev_id=int(rid["rid"]), song_id=int(rid["sc"][1]), score=int(rid["sc"][0]), clear=rid["clear"]) self.data.score.put_ranking(
user_id=uid,
rev_id=int(rid["rid"]),
song_id=int(rid["sc"][1]),
score=int(rid["sc"][0]),
clear=rid["clear"],
)
except: except:
self.data.score.put_ranking(user_id=uid, rev_id=int(rid["rid"]), song_id=0, score=int(rid["sc"][0]), clear=rid["clear"]) self.data.score.put_ranking(
#REV user_id=uid,
rev_id=int(rid["rid"]),
song_id=0,
score=int(rid["sc"][0]),
clear=rid["clear"],
)
# REV
else: else:
try: try:
self.data.score.put_ranking(user_id=uid, rev_id=int(rid["rid"]), song_id=int(rid["sc"][1]), score=int(rid["sc"][0]), clear=0) self.data.score.put_ranking(
user_id=uid,
rev_id=int(rid["rid"]),
song_id=int(rid["sc"][1]),
score=int(rid["sc"][0]),
clear=0,
)
except: except:
self.data.score.put_ranking(user_id=uid, rev_id=int(rid["rid"]), song_id=0, score=int(rid["sc"][0]), clear=0) self.data.score.put_ranking(
return({}) user_id=uid,
rev_id=int(rid["rid"]),
song_id=0,
score=int(rid["sc"][0]),
clear=0,
)
return {}
def handle_action_addenergy_request(self, data: Dict) -> Dict: def handle_action_addenergy_request(self, data: Dict) -> Dict:
uid = data['addenergy']['uid'] uid = data["addenergy"]["uid"]
self.logger.info(f"Add energy to user {uid}") self.logger.info(f"Add energy to user {uid}")
profile = self.data.profile.get_profile_index(0, uid, self.version) profile = self.data.profile.get_profile_index(0, uid, self.version)
data1 = profile["data"] data1 = profile["data"]
p = self.data.item.get_energy(uid) p = self.data.item.get_energy(uid)
energy = p["energy"] energy = p["energy"]
if not p: if not p:
self.data.item.put_energy(uid, 5) self.data.item.put_energy(uid, 5)
return({ return {
"class": data1["myClass"], "class": data1["myClass"],
"granted": "5", "granted": "5",
"total": "5", "total": "5",
"threshold": "1000" "threshold": "1000",
}) }
array = [] array = []
newenergy = int(energy) + 5 newenergy = int(energy) + 5
self.data.item.put_energy(uid, newenergy) self.data.item.put_energy(uid, newenergy)
if int(energy) <= 995: if int(energy) <= 995:
array.append({ array.append(
"class": data1["myClass"], {
"granted": "5", "class": data1["myClass"],
"total": str(energy), "granted": "5",
"threshold": "1000" "total": str(energy),
}) "threshold": "1000",
}
)
else: else:
array.append({ array.append(
"class": data1["myClass"], {
"granted": "0", "class": data1["myClass"],
"total": str(energy), "granted": "0",
"threshold": "1000" "total": str(energy),
}) "threshold": "1000",
}
)
return array[0] return array[0]
def handle_action_eventreq_request(self, data: Dict) -> Dict: def handle_action_eventreq_request(self, data: Dict) -> Dict:

View File

@ -1,40 +1,60 @@
from core.config import CoreConfig from core.config import CoreConfig
class CxbServerConfig():
class CxbServerConfig:
def __init__(self, parent_config: "CxbConfig"): def __init__(self, parent_config: "CxbConfig"):
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, 'cxb', 'server', 'enable', default=True) return CoreConfig.get_config_field(
self.__config, "cxb", "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, 'cxb', 'server', 'loglevel', default="info")) return CoreConfig.str_to_loglevel(
CoreConfig.get_config_field(
self.__config, "cxb", "server", "loglevel", default="info"
)
)
@property @property
def hostname(self) -> str: def hostname(self) -> str:
return CoreConfig.get_config_field(self.__config, 'cxb', 'server', 'hostname', default="localhost") return CoreConfig.get_config_field(
self.__config, "cxb", "server", "hostname", default="localhost"
)
@property @property
def ssl_enable(self) -> bool: def ssl_enable(self) -> bool:
return CoreConfig.get_config_field(self.__config, 'cxb', 'server', 'ssl_enable', default=False) return CoreConfig.get_config_field(
self.__config, "cxb", "server", "ssl_enable", default=False
)
@property @property
def port(self) -> int: def port(self) -> int:
return CoreConfig.get_config_field(self.__config, 'cxb', 'server', 'port', default=8082) return CoreConfig.get_config_field(
self.__config, "cxb", "server", "port", default=8082
)
@property @property
def port_secure(self) -> int: def port_secure(self) -> int:
return CoreConfig.get_config_field(self.__config, 'cxb', 'server', 'port_secure', default=443) return CoreConfig.get_config_field(
self.__config, "cxb", "server", "port_secure", default=443
)
@property @property
def ssl_cert(self) -> str: def ssl_cert(self) -> str:
return CoreConfig.get_config_field(self.__config, 'cxb', 'server', 'ssl_cert', default="cert/title.crt") return CoreConfig.get_config_field(
self.__config, "cxb", "server", "ssl_cert", default="cert/title.crt"
)
@property @property
def ssl_key(self) -> str: def ssl_key(self) -> str:
return CoreConfig.get_config_field(self.__config, 'cxb', 'server', 'ssl_key', default="cert/title.key") return CoreConfig.get_config_field(
self.__config, "cxb", "server", "ssl_key", default="cert/title.key"
)
class CxbConfig(dict): class CxbConfig(dict):
def __init__(self) -> None: def __init__(self) -> None:

View File

@ -1,4 +1,4 @@
class CxbConstants(): class CxbConstants:
GAME_CODE = "SDCA" GAME_CODE = "SDCA"
CONFIG_NAME = "cxb.yaml" CONFIG_NAME = "cxb.yaml"
@ -8,8 +8,13 @@ class CxbConstants():
VER_CROSSBEATS_REV_SUNRISE_S2 = 2 VER_CROSSBEATS_REV_SUNRISE_S2 = 2
VER_CROSSBEATS_REV_SUNRISE_S2_OMNI = 3 VER_CROSSBEATS_REV_SUNRISE_S2_OMNI = 3
VERSION_NAMES = ("crossbeats REV.", "crossbeats REV. SUNRISE", "crossbeats REV. SUNRISE S2", "crossbeats REV. SUNRISE S2 Omnimix") VERSION_NAMES = (
"crossbeats REV.",
"crossbeats REV. SUNRISE",
"crossbeats REV. SUNRISE S2",
"crossbeats REV. SUNRISE S2 Omnimix",
)
@classmethod @classmethod
def game_ver_to_string(cls, ver: int): def game_ver_to_string(cls, ver: int):
return cls.VERSION_NAMES[ver] return cls.VERSION_NAMES[ver]

View File

@ -1,8 +1,8 @@
from core.data import Data from core.data import Data
from core.config import CoreConfig from core.config import CoreConfig
from titles.cxb.schema import CxbProfileData, CxbScoreData, CxbItemData, CxbStaticData from titles.cxb.schema import CxbProfileData, CxbScoreData, CxbItemData, CxbStaticData
class CxbData(Data): class CxbData(Data):
def __init__(self, cfg: CoreConfig) -> None: def __init__(self, cfg: CoreConfig) -> None:
super().__init__(cfg) super().__init__(cfg)

View File

@ -17,6 +17,7 @@ from titles.cxb.rev import CxbRev
from titles.cxb.rss1 import CxbRevSunriseS1 from titles.cxb.rss1 import CxbRevSunriseS1
from titles.cxb.rss2 import CxbRevSunriseS2 from titles.cxb.rss2 import CxbRevSunriseS2
class CxbServlet(resource.Resource): class CxbServlet(resource.Resource):
def __init__(self, core_cfg: CoreConfig, cfg_dir: str) -> None: def __init__(self, core_cfg: CoreConfig, cfg_dir: str) -> None:
self.isLeaf = True self.isLeaf = True
@ -24,62 +25,85 @@ class CxbServlet(resource.Resource):
self.core_cfg = core_cfg self.core_cfg = core_cfg
self.game_cfg = CxbConfig() self.game_cfg = CxbConfig()
if path.exists(f"{cfg_dir}/{CxbConstants.CONFIG_NAME}"): if path.exists(f"{cfg_dir}/{CxbConstants.CONFIG_NAME}"):
self.game_cfg.update(yaml.safe_load(open(f"{cfg_dir}/{CxbConstants.CONFIG_NAME}"))) self.game_cfg.update(
yaml.safe_load(open(f"{cfg_dir}/{CxbConstants.CONFIG_NAME}"))
)
self.logger = logging.getLogger("cxb") self.logger = logging.getLogger("cxb")
if not hasattr(self.logger, "inited"): if not hasattr(self.logger, "inited"):
log_fmt_str = "[%(asctime)s] CXB | %(levelname)s | %(message)s" log_fmt_str = "[%(asctime)s] CXB | %(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, "cxb"), encoding='utf8', fileHandler = TimedRotatingFileHandler(
when="d", backupCount=10) "{0}/{1}.log".format(self.core_cfg.server.log_dir, "cxb"),
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
self.versions = [ self.versions = [
CxbRev(core_cfg, self.game_cfg), CxbRev(core_cfg, self.game_cfg),
CxbRevSunriseS1(core_cfg, self.game_cfg), CxbRevSunriseS1(core_cfg, self.game_cfg),
CxbRevSunriseS2(core_cfg, self.game_cfg), CxbRevSunriseS2(core_cfg, self.game_cfg),
] ]
@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 = CxbConfig() game_cfg = CxbConfig()
if path.exists(f"{cfg_dir}/{CxbConstants.CONFIG_NAME}"): if path.exists(f"{cfg_dir}/{CxbConstants.CONFIG_NAME}"):
game_cfg.update(yaml.safe_load(open(f"{cfg_dir}/{CxbConstants.CONFIG_NAME}"))) game_cfg.update(
yaml.safe_load(open(f"{cfg_dir}/{CxbConstants.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 setup(self): def setup(self):
if self.game_cfg.server.enable: if self.game_cfg.server.enable:
endpoints.serverFromString(reactor, f"tcp:{self.game_cfg.server.port}:interface={self.core_cfg.server.listen_address}")\ endpoints.serverFromString(
.listen(server.Site(CxbServlet(self.core_cfg, self.cfg_dir))) reactor,
f"tcp:{self.game_cfg.server.port}:interface={self.core_cfg.server.listen_address}",
if self.core_cfg.server.is_develop and self.game_cfg.server.ssl_enable: ).listen(server.Site(CxbServlet(self.core_cfg, self.cfg_dir)))
endpoints.serverFromString(reactor, f"ssl:{self.game_cfg.server.port_secure}"\
f":interface={self.core_cfg.server.listen_address}:privateKey={self.game_cfg.server.ssl_key}:"\
f"certKey={self.game_cfg.server.ssl_cert}")\
.listen(server.Site(CxbServlet(self.core_cfg, self.cfg_dir)))
self.logger.info(f"Crossbeats title server ready on port {self.game_cfg.server.port} & {self.game_cfg.server.port_secure}") if self.core_cfg.server.is_develop and self.game_cfg.server.ssl_enable:
endpoints.serverFromString(
reactor,
f"ssl:{self.game_cfg.server.port_secure}"
f":interface={self.core_cfg.server.listen_address}:privateKey={self.game_cfg.server.ssl_key}:"
f"certKey={self.game_cfg.server.ssl_cert}",
).listen(server.Site(CxbServlet(self.core_cfg, self.cfg_dir)))
self.logger.info(
f"Crossbeats title server ready on port {self.game_cfg.server.port} & {self.game_cfg.server.port_secure}"
)
else: else:
self.logger.info(f"Crossbeats title server ready on port {self.game_cfg.server.port}") self.logger.info(
f"Crossbeats title server ready on port {self.game_cfg.server.port}"
)
def render_POST(self, request: Request): def render_POST(self, request: Request):
version = 0 version = 0
@ -96,21 +120,28 @@ class CxbServlet(resource.Resource):
except Exception as e: except Exception as e:
try: try:
req_json: Dict = json.loads(req_bytes.decode().replace('"', '\\"').replace("'", '"')) req_json: Dict = json.loads(
req_bytes.decode().replace('"', '\\"').replace("'", '"')
)
except Exception as f: except Exception as f:
self.logger.warn(f"Error decoding json: {e} / {f} - {req_url} - {req_bytes}") self.logger.warn(
f"Error decoding json: {e} / {f} - {req_url} - {req_bytes}"
)
return b"" return b""
if req_json == {}: if req_json == {}:
self.logger.warn(f"Empty json request to {req_url}") self.logger.warn(f"Empty json request to {req_url}")
return b"" return b""
cmd = url_split[len(url_split) - 1] cmd = url_split[len(url_split) - 1]
subcmd = list(req_json.keys())[0] subcmd = list(req_json.keys())[0]
if subcmd == "dldate": if subcmd == "dldate":
if not type(req_json["dldate"]) is dict or "filetype" not in req_json["dldate"]: if (
not type(req_json["dldate"]) is dict
or "filetype" not in req_json["dldate"]
):
self.logger.warn(f"Malformed dldate request: {req_url} {req_json}") self.logger.warn(f"Malformed dldate request: {req_url} {req_json}")
return b"" return b""
@ -119,7 +150,9 @@ class CxbServlet(resource.Resource):
version = int(filetype_split[0]) version = int(filetype_split[0])
filetype_inflect_split = inflection.underscore(filetype).split("/") filetype_inflect_split = inflection.underscore(filetype).split("/")
match = re.match("^([A-Za-z]*)(\d\d\d\d)$", filetype_split[len(filetype_split) - 1]) match = re.match(
"^([A-Za-z]*)(\d\d\d\d)$", filetype_split[len(filetype_split) - 1]
)
if match: if match:
subcmd = f"{inflection.underscore(match.group(1))}xxxx" subcmd = f"{inflection.underscore(match.group(1))}xxxx"
else: else:
@ -128,7 +161,7 @@ class CxbServlet(resource.Resource):
filetype = subcmd filetype = subcmd
func_to_find = f"handle_{cmd}_{subcmd}_request" func_to_find = f"handle_{cmd}_{subcmd}_request"
if version <= 10102: if version <= 10102:
version_string = "Rev" version_string = "Rev"
internal_ver = CxbConstants.VER_CROSSBEATS_REV internal_ver = CxbConstants.VER_CROSSBEATS_REV
@ -136,28 +169,28 @@ class CxbServlet(resource.Resource):
elif version == 10113 or version == 10103: elif version == 10113 or version == 10103:
version_string = "Rev SunriseS1" version_string = "Rev SunriseS1"
internal_ver = CxbConstants.VER_CROSSBEATS_REV_SUNRISE_S1 internal_ver = CxbConstants.VER_CROSSBEATS_REV_SUNRISE_S1
elif version >= 10114 or version == 10104: elif version >= 10114 or version == 10104:
version_string = "Rev SunriseS2" version_string = "Rev SunriseS2"
internal_ver = CxbConstants.VER_CROSSBEATS_REV_SUNRISE_S2 internal_ver = CxbConstants.VER_CROSSBEATS_REV_SUNRISE_S2
else: else:
version_string = "Base" version_string = "Base"
self.logger.info(f"{version_string} Request {req_url} -> {filetype}") self.logger.info(f"{version_string} Request {req_url} -> {filetype}")
self.logger.debug(req_json) self.logger.debug(req_json)
try: try:
handler = getattr(self.versions[internal_ver], func_to_find) handler = getattr(self.versions[internal_ver], func_to_find)
resp = handler(req_json) resp = handler(req_json)
except AttributeError as e: except AttributeError as e:
self.logger.warning(f"Unhandled {version_string} request {req_url} - {e}") self.logger.warning(f"Unhandled {version_string} request {req_url} - {e}")
resp = {} resp = {}
except Exception as e: except Exception as e:
self.logger.error(f"Error handling {version_string} method {req_url} - {e}") self.logger.error(f"Error handling {version_string} method {req_url} - {e}")
raise raise
self.logger.debug(f"{version_string} Response {resp}") self.logger.debug(f"{version_string} Response {resp}")
return json.dumps(resp, ensure_ascii=False).encode("utf-8") return json.dumps(resp, ensure_ascii=False).encode("utf-8")

View File

@ -8,13 +8,23 @@ from core.config import CoreConfig
from titles.cxb.database import CxbData from titles.cxb.database import CxbData
from titles.cxb.const import CxbConstants from titles.cxb.const import CxbConstants
class CxbReader(BaseReader): class CxbReader(BaseReader):
def __init__(self, config: CoreConfig, version: int, bin_arg: Optional[str], opt_arg: Optional[str], extra: Optional[str]) -> None: def __init__(
self,
config: CoreConfig,
version: int,
bin_arg: Optional[str],
opt_arg: Optional[str],
extra: Optional[str],
) -> None:
super().__init__(config, version, bin_arg, opt_arg, extra) super().__init__(config, version, bin_arg, opt_arg, extra)
self.data = CxbData(config) self.data = CxbData(config)
try: try:
self.logger.info(f"Start importer for {CxbConstants.game_ver_to_string(version)}") self.logger.info(
f"Start importer for {CxbConstants.game_ver_to_string(version)}"
)
except IndexError: except IndexError:
self.logger.error(f"Invalid project cxb version {version}") self.logger.error(f"Invalid project cxb version {version}")
exit(1) exit(1)
@ -28,7 +38,7 @@ class CxbReader(BaseReader):
if pull_bin_ram: if pull_bin_ram:
self.read_csv(f"{self.bin_dir}") self.read_csv(f"{self.bin_dir}")
def read_csv(self, bin_dir: str) -> None: def read_csv(self, bin_dir: str) -> None:
self.logger.info(f"Read csv from {bin_dir}") self.logger.info(f"Read csv from {bin_dir}")
@ -45,18 +55,73 @@ class CxbReader(BaseReader):
if not "N/A" in row["standard"]: if not "N/A" in row["standard"]:
self.logger.info(f"Added song {song_id} chart 0") self.logger.info(f"Added song {song_id} chart 0")
self.data.static.put_music(self.version, song_id, index, 0, title, artist, genre, int(row["standard"].replace("Standard ","").replace("N/A","0"))) self.data.static.put_music(
self.version,
song_id,
index,
0,
title,
artist,
genre,
int(
row["standard"]
.replace("Standard ", "")
.replace("N/A", "0")
),
)
if not "N/A" in row["hard"]: if not "N/A" in row["hard"]:
self.logger.info(f"Added song {song_id} chart 1") self.logger.info(f"Added song {song_id} chart 1")
self.data.static.put_music(self.version, song_id, index, 1, title, artist, genre, int(row["hard"].replace("Hard ","").replace("N/A","0"))) self.data.static.put_music(
self.version,
song_id,
index,
1,
title,
artist,
genre,
int(row["hard"].replace("Hard ", "").replace("N/A", "0")),
)
if not "N/A" in row["master"]: if not "N/A" in row["master"]:
self.logger.info(f"Added song {song_id} chart 2") self.logger.info(f"Added song {song_id} chart 2")
self.data.static.put_music(self.version, song_id, index, 2, title, artist, genre, int(row["master"].replace("Master ","").replace("N/A","0"))) self.data.static.put_music(
self.version,
song_id,
index,
2,
title,
artist,
genre,
int(
row["master"].replace("Master ", "").replace("N/A", "0")
),
)
if not "N/A" in row["unlimited"]: if not "N/A" in row["unlimited"]:
self.logger.info(f"Added song {song_id} chart 3") self.logger.info(f"Added song {song_id} chart 3")
self.data.static.put_music(self.version, song_id, index, 3, title, artist, genre, int(row["unlimited"].replace("Unlimited ","").replace("N/A","0"))) self.data.static.put_music(
self.version,
song_id,
index,
3,
title,
artist,
genre,
int(
row["unlimited"]
.replace("Unlimited ", "")
.replace("N/A", "0")
),
)
if not "N/A" in row["easy"]: if not "N/A" in row["easy"]:
self.logger.info(f"Added song {song_id} chart 4") self.logger.info(f"Added song {song_id} chart 4")
self.data.static.put_music(self.version, song_id, index, 4, title, artist, genre, int(row["easy"].replace("Easy ","").replace("N/A","0"))) self.data.static.put_music(
self.version,
song_id,
index,
4,
title,
artist,
genre,
int(row["easy"].replace("Easy ", "").replace("N/A", "0")),
)
except: except:
self.logger.warn(f"Couldn't read csv file in {self.bin_dir}, skipping") self.logger.warn(f"Couldn't read csv file in {self.bin_dir}, skipping")

View File

@ -11,155 +11,191 @@ from titles.cxb.config import CxbConfig
from titles.cxb.base import CxbBase from titles.cxb.base import CxbBase
from titles.cxb.const import CxbConstants from titles.cxb.const import CxbConstants
class CxbRev(CxbBase): class CxbRev(CxbBase):
def __init__(self, cfg: CoreConfig, game_cfg: CxbConfig) -> None: def __init__(self, cfg: CoreConfig, game_cfg: CxbConfig) -> None:
super().__init__(cfg, game_cfg) super().__init__(cfg, game_cfg)
self.version = CxbConstants.VER_CROSSBEATS_REV self.version = CxbConstants.VER_CROSSBEATS_REV
def handle_data_path_list_request(self, data: Dict) -> Dict: def handle_data_path_list_request(self, data: Dict) -> Dict:
return { "data": "" } return {"data": ""}
def handle_data_putlog_request(self, data: Dict) -> Dict: def handle_data_putlog_request(self, data: Dict) -> Dict:
if data["putlog"]["type"] == "ResultLog": if data["putlog"]["type"] == "ResultLog":
score_data = json.loads(data["putlog"]["data"]) score_data = json.loads(data["putlog"]["data"])
userid = score_data['usid'] userid = score_data["usid"]
self.data.score.put_playlog(userid, score_data['mcode'], score_data['difficulty'], score_data["score"], int(Decimal(score_data["clearrate"]) * 100), score_data["flawless"], score_data["super"], score_data["cool"], score_data["fast"], score_data["fast2"], score_data["slow"], score_data["slow2"], score_data["fail"], score_data["combo"]) self.data.score.put_playlog(
return({"data":True}) userid,
return {"data": True } score_data["mcode"],
score_data["difficulty"],
score_data["score"],
int(Decimal(score_data["clearrate"]) * 100),
score_data["flawless"],
score_data["super"],
score_data["cool"],
score_data["fast"],
score_data["fast2"],
score_data["slow"],
score_data["slow2"],
score_data["fail"],
score_data["combo"],
)
return {"data": True}
return {"data": True}
@cached(lifetime=86400) @cached(lifetime=86400)
def handle_data_music_list_request(self, data: Dict) -> Dict: def handle_data_music_list_request(self, data: Dict) -> Dict:
ret_str = "" ret_str = ""
with open(r"titles/cxb/rev_data/MusicArchiveList.csv") as music: with open(r"titles/cxb/rev_data/MusicArchiveList.csv") as music:
lines = music.readlines() lines = music.readlines()
for line in lines: for line in lines:
line_split = line.split(',') line_split = line.split(",")
ret_str += f"{line_split[0]},{line_split[1]},{line_split[2]},{line_split[3]},{line_split[4]},{line_split[5]},{line_split[6]},{line_split[7]},{line_split[8]},{line_split[9]},{line_split[10]},{line_split[11]},{line_split[12]},{line_split[13]},{line_split[14]},\r\n" ret_str += f"{line_split[0]},{line_split[1]},{line_split[2]},{line_split[3]},{line_split[4]},{line_split[5]},{line_split[6]},{line_split[7]},{line_split[8]},{line_split[9]},{line_split[10]},{line_split[11]},{line_split[12]},{line_split[13]},{line_split[14]},\r\n"
return({"data":ret_str}) return {"data": ret_str}
@cached(lifetime=86400) @cached(lifetime=86400)
def handle_data_item_list_icon_request(self, data: Dict) -> Dict: def handle_data_item_list_icon_request(self, data: Dict) -> Dict:
ret_str = "\r\n#ItemListIcon\r\n" ret_str = "\r\n#ItemListIcon\r\n"
with open(r"titles/cxb/rev_data/Item/ItemArchiveList_Icon.csv", encoding="utf-8") as item: with open(
r"titles/cxb/rev_data/Item/ItemArchiveList_Icon.csv", encoding="utf-8"
) as item:
lines = item.readlines() lines = item.readlines()
for line in lines: for line in lines:
ret_str += f"{line[:-1]}\r\n" ret_str += f"{line[:-1]}\r\n"
return({"data":ret_str}) return {"data": ret_str}
@cached(lifetime=86400) @cached(lifetime=86400)
def handle_data_item_list_skin_notes_request(self, data: Dict) -> Dict: def handle_data_item_list_skin_notes_request(self, data: Dict) -> Dict:
ret_str = "\r\n#ItemListSkinNotes\r\n" ret_str = "\r\n#ItemListSkinNotes\r\n"
with open(r"titles/cxb/rev_data/Item/ItemArchiveList_SkinNotes.csv", encoding="utf-8") as item: with open(
r"titles/cxb/rev_data/Item/ItemArchiveList_SkinNotes.csv", encoding="utf-8"
) as item:
lines = item.readlines() lines = item.readlines()
for line in lines: for line in lines:
ret_str += f"{line[:-1]}\r\n" ret_str += f"{line[:-1]}\r\n"
return({"data":ret_str}) return {"data": ret_str}
@cached(lifetime=86400) @cached(lifetime=86400)
def handle_data_item_list_skin_effect_request(self, data: Dict) -> Dict: def handle_data_item_list_skin_effect_request(self, data: Dict) -> Dict:
ret_str = "\r\n#ItemListSkinEffect\r\n" ret_str = "\r\n#ItemListSkinEffect\r\n"
with open(r"titles/cxb/rev_data/Item/ItemArchiveList_SkinEffect.csv", encoding="utf-8") as item: with open(
r"titles/cxb/rev_data/Item/ItemArchiveList_SkinEffect.csv", encoding="utf-8"
) as item:
lines = item.readlines() lines = item.readlines()
for line in lines: for line in lines:
ret_str += f"{line[:-1]}\r\n" ret_str += f"{line[:-1]}\r\n"
return({"data":ret_str}) return {"data": ret_str}
@cached(lifetime=86400) @cached(lifetime=86400)
def handle_data_item_list_skin_bg_request(self, data: Dict) -> Dict: def handle_data_item_list_skin_bg_request(self, data: Dict) -> Dict:
ret_str = "\r\n#ItemListSkinBg\r\n" ret_str = "\r\n#ItemListSkinBg\r\n"
with open(r"titles/cxb/rev_data/Item/ItemArchiveList_SkinBg.csv", encoding="utf-8") as item: with open(
r"titles/cxb/rev_data/Item/ItemArchiveList_SkinBg.csv", encoding="utf-8"
) as item:
lines = item.readlines() lines = item.readlines()
for line in lines: for line in lines:
ret_str += f"{line[:-1]}\r\n" ret_str += f"{line[:-1]}\r\n"
return({"data":ret_str}) return {"data": ret_str}
@cached(lifetime=86400) @cached(lifetime=86400)
def handle_data_item_list_title_request(self, data: Dict) -> Dict: def handle_data_item_list_title_request(self, data: Dict) -> Dict:
ret_str = "\r\n#ItemListTitle\r\n" ret_str = "\r\n#ItemListTitle\r\n"
with open(r"titles/cxb/rev_data/Item/ItemList_Title.csv", encoding="shift-jis") as item: with open(
r"titles/cxb/rev_data/Item/ItemList_Title.csv", encoding="shift-jis"
) as item:
lines = item.readlines() lines = item.readlines()
for line in lines: for line in lines:
ret_str += f"{line[:-1]}\r\n" ret_str += f"{line[:-1]}\r\n"
return({"data":ret_str}) return {"data": ret_str}
@cached(lifetime=86400) @cached(lifetime=86400)
def handle_data_shop_list_music_request(self, data: Dict) -> Dict: def handle_data_shop_list_music_request(self, data: Dict) -> Dict:
ret_str = "\r\n#ShopListMusic\r\n" ret_str = "\r\n#ShopListMusic\r\n"
with open(r"titles/cxb/rev_data/Shop/ShopList_Music.csv", encoding="shift-jis") as shop: with open(
r"titles/cxb/rev_data/Shop/ShopList_Music.csv", encoding="shift-jis"
) as shop:
lines = shop.readlines() lines = shop.readlines()
for line in lines: for line in lines:
ret_str += f"{line[:-1]}\r\n" ret_str += f"{line[:-1]}\r\n"
return({"data":ret_str}) return {"data": ret_str}
@cached(lifetime=86400) @cached(lifetime=86400)
def handle_data_shop_list_icon_request(self, data: Dict) -> Dict: def handle_data_shop_list_icon_request(self, data: Dict) -> Dict:
ret_str = "\r\n#ShopListIcon\r\n" ret_str = "\r\n#ShopListIcon\r\n"
with open(r"titles/cxb/rev_data/Shop/ShopList_Icon.csv", encoding="shift-jis") as shop: with open(
r"titles/cxb/rev_data/Shop/ShopList_Icon.csv", encoding="shift-jis"
) as shop:
lines = shop.readlines() lines = shop.readlines()
for line in lines: for line in lines:
ret_str += f"{line[:-1]}\r\n" ret_str += f"{line[:-1]}\r\n"
return({"data":ret_str}) return {"data": ret_str}
@cached(lifetime=86400) @cached(lifetime=86400)
def handle_data_shop_list_title_request(self, data: Dict) -> Dict: def handle_data_shop_list_title_request(self, data: Dict) -> Dict:
ret_str = "\r\n#ShopListTitle\r\n" ret_str = "\r\n#ShopListTitle\r\n"
with open(r"titles/cxb/rev_data/Shop/ShopList_Title.csv", encoding="shift-jis") as shop: with open(
r"titles/cxb/rev_data/Shop/ShopList_Title.csv", encoding="shift-jis"
) as shop:
lines = shop.readlines() lines = shop.readlines()
for line in lines: for line in lines:
ret_str += f"{line[:-1]}\r\n" ret_str += f"{line[:-1]}\r\n"
return({"data":ret_str}) return {"data": ret_str}
def handle_data_shop_list_skin_hud_request(self, data: Dict) -> Dict:
return({"data":""})
def handle_data_shop_list_skin_arrow_request(self, data: Dict) -> Dict:
return({"data":""})
def handle_data_shop_list_skin_hit_request(self, data: Dict) -> Dict:
return({"data":""})
@cached(lifetime=86400) def handle_data_shop_list_skin_hud_request(self, data: Dict) -> Dict:
return {"data": ""}
def handle_data_shop_list_skin_arrow_request(self, data: Dict) -> Dict:
return {"data": ""}
def handle_data_shop_list_skin_hit_request(self, data: Dict) -> Dict:
return {"data": ""}
@cached(lifetime=86400)
def handle_data_shop_list_sale_request(self, data: Dict) -> Dict: def handle_data_shop_list_sale_request(self, data: Dict) -> Dict:
ret_str = "\r\n#ShopListSale\r\n" ret_str = "\r\n#ShopListSale\r\n"
with open(r"titles/cxb/rev_data/Shop/ShopList_Sale.csv", encoding="shift-jis") as shop: with open(
r"titles/cxb/rev_data/Shop/ShopList_Sale.csv", encoding="shift-jis"
) as shop:
lines = shop.readlines() lines = shop.readlines()
for line in lines: for line in lines:
ret_str += f"{line[:-1]}\r\n" ret_str += f"{line[:-1]}\r\n"
return({"data":ret_str}) return {"data": ret_str}
def handle_data_extra_stage_list_request(self, data: Dict) -> Dict: def handle_data_extra_stage_list_request(self, data: Dict) -> Dict:
return({"data":""}) return {"data": ""}
@cached(lifetime=86400) @cached(lifetime=86400)
def handle_data_exxxxx_request(self, data: Dict) -> Dict: def handle_data_exxxxx_request(self, data: Dict) -> Dict:
extra_num = int(data["dldate"]["filetype"][-4:]) extra_num = int(data["dldate"]["filetype"][-4:])
ret_str = "" ret_str = ""
with open(fr"titles/cxb/rev_data/Ex000{extra_num}.csv", encoding="shift-jis") as stage: with open(
rf"titles/cxb/rev_data/Ex000{extra_num}.csv", encoding="shift-jis"
) as stage:
lines = stage.readlines() lines = stage.readlines()
for line in lines: for line in lines:
ret_str += f"{line[:-1]}\r\n" ret_str += f"{line[:-1]}\r\n"
return({"data":ret_str}) return {"data": ret_str}
def handle_data_bonus_list10100_request(self, data: Dict) -> Dict:
return({"data": ""})
def handle_data_free_coupon_request(self, data: Dict) -> Dict:
return({"data": ""})
@cached(lifetime=86400) def handle_data_bonus_list10100_request(self, data: Dict) -> Dict:
return {"data": ""}
def handle_data_free_coupon_request(self, data: Dict) -> Dict:
return {"data": ""}
@cached(lifetime=86400)
def handle_data_news_list_request(self, data: Dict) -> Dict: def handle_data_news_list_request(self, data: Dict) -> Dict:
ret_str = "" ret_str = ""
with open(r"titles/cxb/rev_data/NewsList.csv", encoding="UTF-8") as news: with open(r"titles/cxb/rev_data/NewsList.csv", encoding="UTF-8") as news:
lines = news.readlines() lines = news.readlines()
for line in lines: for line in lines:
ret_str += f"{line[:-1]}\r\n" ret_str += f"{line[:-1]}\r\n"
return({"data":ret_str}) return {"data": ret_str}
def handle_data_tips_request(self, data: Dict) -> Dict: def handle_data_tips_request(self, data: Dict) -> Dict:
return({"data":""}) return {"data": ""}
@cached(lifetime=86400) @cached(lifetime=86400)
def handle_data_license_request(self, data: Dict) -> Dict: def handle_data_license_request(self, data: Dict) -> Dict:
ret_str = "" ret_str = ""
@ -167,90 +203,104 @@ class CxbRev(CxbBase):
lines = lic.readlines() lines = lic.readlines()
for line in lines: for line in lines:
ret_str += f"{line[:-1]}\r\n" ret_str += f"{line[:-1]}\r\n"
return({"data":ret_str}) return {"data": ret_str}
@cached(lifetime=86400) @cached(lifetime=86400)
def handle_data_course_list_request(self, data: Dict) -> Dict: def handle_data_course_list_request(self, data: Dict) -> Dict:
ret_str = "" ret_str = ""
with open(r"titles/cxb/rev_data/Course/CourseList.csv", encoding="UTF-8") as course: with open(
r"titles/cxb/rev_data/Course/CourseList.csv", encoding="UTF-8"
) as course:
lines = course.readlines() lines = course.readlines()
for line in lines: for line in lines:
ret_str += f"{line[:-1]}\r\n" ret_str += f"{line[:-1]}\r\n"
return({"data":ret_str}) return {"data": ret_str}
@cached(lifetime=86400) @cached(lifetime=86400)
def handle_data_csxxxx_request(self, data: Dict) -> Dict: def handle_data_csxxxx_request(self, data: Dict) -> Dict:
# Removed the CSVs since the format isnt quite right # Removed the CSVs since the format isnt quite right
extra_num = int(data["dldate"]["filetype"][-4:]) extra_num = int(data["dldate"]["filetype"][-4:])
ret_str = "" ret_str = ""
with open(fr"titles/cxb/rev_data/Course/Cs000{extra_num}.csv", encoding="shift-jis") as course: with open(
rf"titles/cxb/rev_data/Course/Cs000{extra_num}.csv", encoding="shift-jis"
) as course:
lines = course.readlines() lines = course.readlines()
for line in lines: for line in lines:
ret_str += f"{line[:-1]}\r\n" ret_str += f"{line[:-1]}\r\n"
return({"data":ret_str}) return {"data": ret_str}
@cached(lifetime=86400) @cached(lifetime=86400)
def handle_data_mission_list_request(self, data: Dict) -> Dict: def handle_data_mission_list_request(self, data: Dict) -> Dict:
ret_str = "" ret_str = ""
with open(r"titles/cxb/rev_data/MissionList.csv", encoding="shift-jis") as mission: with open(
r"titles/cxb/rev_data/MissionList.csv", encoding="shift-jis"
) as mission:
lines = mission.readlines() lines = mission.readlines()
for line in lines: for line in lines:
ret_str += f"{line[:-1]}\r\n" ret_str += f"{line[:-1]}\r\n"
return({"data":ret_str}) return {"data": ret_str}
def handle_data_mission_bonus_request(self, data: Dict) -> Dict:
return({"data": ""})
def handle_data_unlimited_mission_request(self, data: Dict) -> Dict:
return({"data": ""})
@cached(lifetime=86400) def handle_data_mission_bonus_request(self, data: Dict) -> Dict:
return {"data": ""}
def handle_data_unlimited_mission_request(self, data: Dict) -> Dict:
return {"data": ""}
@cached(lifetime=86400)
def handle_data_event_list_request(self, data: Dict) -> Dict: def handle_data_event_list_request(self, data: Dict) -> Dict:
ret_str = "" ret_str = ""
with open(r"titles/cxb/rev_data/Event/EventArchiveList.csv", encoding="shift-jis") as mission: with open(
r"titles/cxb/rev_data/Event/EventArchiveList.csv", encoding="shift-jis"
) as mission:
lines = mission.readlines() lines = mission.readlines()
for line in lines: for line in lines:
ret_str += f"{line[:-1]}\r\n" ret_str += f"{line[:-1]}\r\n"
return({"data":ret_str}) return {"data": ret_str}
def handle_data_event_music_list_request(self, data: Dict) -> Dict: def handle_data_event_music_list_request(self, data: Dict) -> Dict:
return({"data": ""}) return {"data": ""}
def handle_data_event_mission_list_request(self, data: Dict) -> Dict:
return({"data": ""})
def handle_data_event_achievement_single_high_score_list_request(self, data: Dict) -> Dict:
return({"data": ""})
def handle_data_event_achievement_single_accumulation_request(self, data: Dict) -> Dict:
return({"data": ""})
def handle_data_event_ranking_high_score_list_request(self, data: Dict) -> Dict:
return({"data": ""})
def handle_data_event_ranking_accumulation_list_request(self, data: Dict) -> Dict:
return({"data": ""})
def handle_data_event_ranking_stamp_list_request(self, data: Dict) -> Dict:
return({"data": ""})
def handle_data_event_ranking_store_list_request(self, data: Dict) -> Dict:
return({"data": ""})
def handle_data_event_ranking_area_list_request(self, data: Dict) -> Dict:
return({"data": ""})
@cached(lifetime=86400) def handle_data_event_mission_list_request(self, data: Dict) -> Dict:
return {"data": ""}
def handle_data_event_achievement_single_high_score_list_request(
self, data: Dict
) -> Dict:
return {"data": ""}
def handle_data_event_achievement_single_accumulation_request(
self, data: Dict
) -> Dict:
return {"data": ""}
def handle_data_event_ranking_high_score_list_request(self, data: Dict) -> Dict:
return {"data": ""}
def handle_data_event_ranking_accumulation_list_request(self, data: Dict) -> Dict:
return {"data": ""}
def handle_data_event_ranking_stamp_list_request(self, data: Dict) -> Dict:
return {"data": ""}
def handle_data_event_ranking_store_list_request(self, data: Dict) -> Dict:
return {"data": ""}
def handle_data_event_ranking_area_list_request(self, data: Dict) -> Dict:
return {"data": ""}
@cached(lifetime=86400)
def handle_data_event_stamp_list_request(self, data: Dict) -> Dict: def handle_data_event_stamp_list_request(self, data: Dict) -> Dict:
ret_str = "" ret_str = ""
with open(r"titles/cxb/rev_data/Event/EventStampList.csv", encoding="shift-jis") as event: with open(
r"titles/cxb/rev_data/Event/EventStampList.csv", encoding="shift-jis"
) as event:
lines = event.readlines() lines = event.readlines()
for line in lines: for line in lines:
ret_str += f"{line[:-1]}\r\n" ret_str += f"{line[:-1]}\r\n"
return({"data":ret_str}) return {"data": ret_str}
def handle_data_event_stamp_map_list_csxxxx_request(self, data: Dict) -> Dict: def handle_data_event_stamp_map_list_csxxxx_request(self, data: Dict) -> Dict:
return({"data": "1,2,1,1,2,3,9,5,6,7,8,9,10,\r\n"}) return {"data": "1,2,1,1,2,3,9,5,6,7,8,9,10,\r\n"}
def handle_data_server_state_request(self, data: Dict) -> Dict: def handle_data_server_state_request(self, data: Dict) -> Dict:
return({"data": True}) return {"data": True}

View File

@ -11,128 +11,147 @@ from titles.cxb.config import CxbConfig
from titles.cxb.base import CxbBase from titles.cxb.base import CxbBase
from titles.cxb.const import CxbConstants from titles.cxb.const import CxbConstants
class CxbRevSunriseS1(CxbBase): class CxbRevSunriseS1(CxbBase):
def __init__(self, cfg: CoreConfig, game_cfg: CxbConfig) -> None: def __init__(self, cfg: CoreConfig, game_cfg: CxbConfig) -> None:
super().__init__(cfg, game_cfg) super().__init__(cfg, game_cfg)
self.version = CxbConstants.VER_CROSSBEATS_REV_SUNRISE_S1 self.version = CxbConstants.VER_CROSSBEATS_REV_SUNRISE_S1
def handle_data_path_list_request(self, data: Dict) -> Dict:
return { "data": "" }
@cached(lifetime=86400) def handle_data_path_list_request(self, data: Dict) -> Dict:
return {"data": ""}
@cached(lifetime=86400)
def handle_data_music_list_request(self, data: Dict) -> Dict: def handle_data_music_list_request(self, data: Dict) -> Dict:
ret_str = "" ret_str = ""
with open(r"titles/cxb/rss1_data/MusicArchiveList.csv") as music: with open(r"titles/cxb/rss1_data/MusicArchiveList.csv") as music:
lines = music.readlines() lines = music.readlines()
for line in lines: for line in lines:
line_split = line.split(',') line_split = line.split(",")
ret_str += f"{line_split[0]},{line_split[1]},{line_split[2]},{line_split[3]},{line_split[4]},{line_split[5]},{line_split[6]},{line_split[7]},{line_split[8]},{line_split[9]},{line_split[10]},{line_split[11]},{line_split[12]},{line_split[13]},{line_split[14]},\r\n" ret_str += f"{line_split[0]},{line_split[1]},{line_split[2]},{line_split[3]},{line_split[4]},{line_split[5]},{line_split[6]},{line_split[7]},{line_split[8]},{line_split[9]},{line_split[10]},{line_split[11]},{line_split[12]},{line_split[13]},{line_split[14]},\r\n"
return({"data":ret_str})
@cached(lifetime=86400) return {"data": ret_str}
@cached(lifetime=86400)
def handle_data_item_list_detail_request(self, data: Dict) -> Dict: def handle_data_item_list_detail_request(self, data: Dict) -> Dict:
#ItemListIcon load # ItemListIcon load
ret_str = "#ItemListIcon\r\n" ret_str = "#ItemListIcon\r\n"
with open(r"titles/cxb/rss1_data/Item/ItemList_Icon.csv", encoding="shift-jis") as item: with open(
r"titles/cxb/rss1_data/Item/ItemList_Icon.csv", encoding="shift-jis"
) as item:
lines = item.readlines() lines = item.readlines()
for line in lines: for line in lines:
ret_str += f"{line[:-1]}\r\n" ret_str += f"{line[:-1]}\r\n"
#ItemListTitle load # ItemListTitle load
ret_str += "\r\n#ItemListTitle\r\n" ret_str += "\r\n#ItemListTitle\r\n"
with open(r"titles/cxb/rss1_data/Item/ItemList_Title.csv", encoding="shift-jis") as item: with open(
r"titles/cxb/rss1_data/Item/ItemList_Title.csv", encoding="shift-jis"
) as item:
lines = item.readlines() lines = item.readlines()
for line in lines: for line in lines:
ret_str += f"{line[:-1]}\r\n" ret_str += f"{line[:-1]}\r\n"
return({"data":ret_str}) return {"data": ret_str}
@cached(lifetime=86400) @cached(lifetime=86400)
def handle_data_shop_list_detail_request(self, data: Dict) -> Dict: def handle_data_shop_list_detail_request(self, data: Dict) -> Dict:
#ShopListIcon load # ShopListIcon load
ret_str = "#ShopListIcon\r\n" ret_str = "#ShopListIcon\r\n"
with open(r"titles/cxb/rss1_data/Shop/ShopList_Icon.csv", encoding="utf-8") as shop: with open(
r"titles/cxb/rss1_data/Shop/ShopList_Icon.csv", encoding="utf-8"
) as shop:
lines = shop.readlines() lines = shop.readlines()
for line in lines: for line in lines:
ret_str += f"{line[:-1]}\r\n" ret_str += f"{line[:-1]}\r\n"
#ShopListMusic load
ret_str += "\r\n#ShopListMusic\r\n"
with open(r"titles/cxb/rss1_data/Shop/ShopList_Music.csv", encoding="utf-8") as shop:
lines = shop.readlines()
for line in lines:
ret_str += f"{line[:-1]}\r\n"
#ShopListSale load
ret_str += "\r\n#ShopListSale\r\n"
with open(r"titles/cxb/rss1_data/Shop/ShopList_Sale.csv", encoding="shift-jis") as shop:
lines = shop.readlines()
for line in lines:
ret_str += f"{line[:-1]}\r\n"
#ShopListSkinBg load
ret_str += "\r\n#ShopListSkinBg\r\n"
with open(r"titles/cxb/rss1_data/Shop/ShopList_SkinBg.csv", encoding="shift-jis") as shop:
lines = shop.readlines()
for line in lines:
ret_str += f"{line[:-1]}\r\n"
#ShopListSkinEffect load
ret_str += "\r\n#ShopListSkinEffect\r\n"
with open(r"titles/cxb/rss1_data/Shop/ShopList_SkinEffect.csv", encoding="shift-jis") as shop:
lines = shop.readlines()
for line in lines:
ret_str += f"{line[:-1]}\r\n"
#ShopListSkinNotes load
ret_str += "\r\n#ShopListSkinNotes\r\n"
with open(r"titles/cxb/rss1_data/Shop/ShopList_SkinNotes.csv", encoding="shift-jis") as shop:
lines = shop.readlines()
for line in lines:
ret_str += f"{line[:-1]}\r\n"
#ShopListTitle load
ret_str += "\r\n#ShopListTitle\r\n"
with open(r"titles/cxb/rss1_data/Shop/ShopList_Title.csv", encoding="utf-8") as shop:
lines = shop.readlines()
for line in lines:
ret_str += f"{line[:-1]}\r\n"
return({"data":ret_str})
def handle_data_extra_stage_list_request(self, data: Dict) -> Dict:
return({"data":""})
def handle_data_ex0001_request(self, data: Dict) -> Dict:
return({"data":""})
def handle_data_one_more_extra_list_request(self, data: Dict) -> Dict:
return({"data":""})
def handle_data_bonus_list10100_request(self, data: Dict) -> Dict:
return({"data":""})
def handle_data_oe0001_request(self, data: Dict) -> Dict:
return({"data":""})
def handle_data_free_coupon_request(self, data: Dict) -> Dict:
return({"data":""})
@cached(lifetime=86400) # ShopListMusic load
ret_str += "\r\n#ShopListMusic\r\n"
with open(
r"titles/cxb/rss1_data/Shop/ShopList_Music.csv", encoding="utf-8"
) as shop:
lines = shop.readlines()
for line in lines:
ret_str += f"{line[:-1]}\r\n"
# ShopListSale load
ret_str += "\r\n#ShopListSale\r\n"
with open(
r"titles/cxb/rss1_data/Shop/ShopList_Sale.csv", encoding="shift-jis"
) as shop:
lines = shop.readlines()
for line in lines:
ret_str += f"{line[:-1]}\r\n"
# ShopListSkinBg load
ret_str += "\r\n#ShopListSkinBg\r\n"
with open(
r"titles/cxb/rss1_data/Shop/ShopList_SkinBg.csv", encoding="shift-jis"
) as shop:
lines = shop.readlines()
for line in lines:
ret_str += f"{line[:-1]}\r\n"
# ShopListSkinEffect load
ret_str += "\r\n#ShopListSkinEffect\r\n"
with open(
r"titles/cxb/rss1_data/Shop/ShopList_SkinEffect.csv", encoding="shift-jis"
) as shop:
lines = shop.readlines()
for line in lines:
ret_str += f"{line[:-1]}\r\n"
# ShopListSkinNotes load
ret_str += "\r\n#ShopListSkinNotes\r\n"
with open(
r"titles/cxb/rss1_data/Shop/ShopList_SkinNotes.csv", encoding="shift-jis"
) as shop:
lines = shop.readlines()
for line in lines:
ret_str += f"{line[:-1]}\r\n"
# ShopListTitle load
ret_str += "\r\n#ShopListTitle\r\n"
with open(
r"titles/cxb/rss1_data/Shop/ShopList_Title.csv", encoding="utf-8"
) as shop:
lines = shop.readlines()
for line in lines:
ret_str += f"{line[:-1]}\r\n"
return {"data": ret_str}
def handle_data_extra_stage_list_request(self, data: Dict) -> Dict:
return {"data": ""}
def handle_data_ex0001_request(self, data: Dict) -> Dict:
return {"data": ""}
def handle_data_one_more_extra_list_request(self, data: Dict) -> Dict:
return {"data": ""}
def handle_data_bonus_list10100_request(self, data: Dict) -> Dict:
return {"data": ""}
def handle_data_oe0001_request(self, data: Dict) -> Dict:
return {"data": ""}
def handle_data_free_coupon_request(self, data: Dict) -> Dict:
return {"data": ""}
@cached(lifetime=86400)
def handle_data_news_list_request(self, data: Dict) -> Dict: def handle_data_news_list_request(self, data: Dict) -> Dict:
ret_str = "" ret_str = ""
with open(r"titles/cxb/rss1_data/NewsList.csv", encoding="UTF-8") as news: with open(r"titles/cxb/rss1_data/NewsList.csv", encoding="UTF-8") as news:
lines = news.readlines() lines = news.readlines()
for line in lines: for line in lines:
ret_str += f"{line[:-1]}\r\n" ret_str += f"{line[:-1]}\r\n"
return({"data":ret_str}) return {"data": ret_str}
def handle_data_tips_request(self, data: Dict) -> Dict: def handle_data_tips_request(self, data: Dict) -> Dict:
return({"data":""}) return {"data": ""}
def handle_data_release_info_list_request(self, data: Dict) -> Dict: def handle_data_release_info_list_request(self, data: Dict) -> Dict:
return({"data":""}) return {"data": ""}
@cached(lifetime=86400) @cached(lifetime=86400)
def handle_data_random_music_list_request(self, data: Dict) -> Dict: def handle_data_random_music_list_request(self, data: Dict) -> Dict:
ret_str = "" ret_str = ""
@ -141,10 +160,12 @@ class CxbRevSunriseS1(CxbBase):
count = 0 count = 0
for line in lines: for line in lines:
line_split = line.split(",") line_split = line.split(",")
ret_str += str(count) + "," + line_split[0] + "," + line_split[0] + ",\r\n" ret_str += (
str(count) + "," + line_split[0] + "," + line_split[0] + ",\r\n"
)
return {"data": ret_str}
return({"data":ret_str})
@cached(lifetime=86400) @cached(lifetime=86400)
def handle_data_license_request(self, data: Dict) -> Dict: def handle_data_license_request(self, data: Dict) -> Dict:
ret_str = "" ret_str = ""
@ -152,54 +173,58 @@ class CxbRevSunriseS1(CxbBase):
lines = licenses.readlines() lines = licenses.readlines()
for line in lines: for line in lines:
ret_str += f"{line[:-1]}\r\n" ret_str += f"{line[:-1]}\r\n"
return({"data":ret_str}) return {"data": ret_str}
@cached(lifetime=86400) @cached(lifetime=86400)
def handle_data_course_list_request(self, data: Dict) -> Dict: def handle_data_course_list_request(self, data: Dict) -> Dict:
ret_str = "" ret_str = ""
with open(r"titles/cxb/rss1_data/Course/CourseList.csv", encoding="UTF-8") as course: with open(
r"titles/cxb/rss1_data/Course/CourseList.csv", encoding="UTF-8"
) as course:
lines = course.readlines() lines = course.readlines()
for line in lines: for line in lines:
ret_str += f"{line[:-1]}\r\n" ret_str += f"{line[:-1]}\r\n"
return({"data":ret_str}) return {"data": ret_str}
@cached(lifetime=86400) @cached(lifetime=86400)
def handle_data_csxxxx_request(self, data: Dict) -> Dict: def handle_data_csxxxx_request(self, data: Dict) -> Dict:
extra_num = int(data["dldate"]["filetype"][-4:]) extra_num = int(data["dldate"]["filetype"][-4:])
ret_str = "" ret_str = ""
with open(fr"titles/cxb/rss1_data/Course/Cs{extra_num}.csv", encoding="shift-jis") as course: with open(
rf"titles/cxb/rss1_data/Course/Cs{extra_num}.csv", encoding="shift-jis"
) as course:
lines = course.readlines() lines = course.readlines()
for line in lines: for line in lines:
ret_str += f"{line[:-1]}\r\n" ret_str += f"{line[:-1]}\r\n"
return({"data":ret_str}) return {"data": ret_str}
def handle_data_mission_list_request(self, data: Dict) -> Dict: def handle_data_mission_list_request(self, data: Dict) -> Dict:
return({"data":""}) return {"data": ""}
def handle_data_mission_bonus_request(self, data: Dict) -> Dict: def handle_data_mission_bonus_request(self, data: Dict) -> Dict:
return({"data":""}) return {"data": ""}
def handle_data_unlimited_mission_request(self, data: Dict) -> Dict: def handle_data_unlimited_mission_request(self, data: Dict) -> Dict:
return({"data":""}) return {"data": ""}
def handle_data_partner_list_request(self, data: Dict) -> Dict: def handle_data_partner_list_request(self, data: Dict) -> Dict:
ret_str = "" ret_str = ""
# Lord forgive me for the sins I am about to commit # Lord forgive me for the sins I am about to commit
for i in range(0,10): for i in range(0, 10):
ret_str += f"80000{i},{i},{i},0,10000,,\r\n" ret_str += f"80000{i},{i},{i},0,10000,,\r\n"
ret_str += f"80000{i},{i},{i},1,10500,,\r\n" ret_str += f"80000{i},{i},{i},1,10500,,\r\n"
ret_str += f"80000{i},{i},{i},2,10500,,\r\n" ret_str += f"80000{i},{i},{i},2,10500,,\r\n"
for i in range(10,13): for i in range(10, 13):
ret_str += f"8000{i},{i},{i},0,10000,,\r\n" ret_str += f"8000{i},{i},{i},0,10000,,\r\n"
ret_str += f"8000{i},{i},{i},1,10500,,\r\n" ret_str += f"8000{i},{i},{i},1,10500,,\r\n"
ret_str += f"8000{i},{i},{i},2,10500,,\r\n" ret_str += f"8000{i},{i},{i},2,10500,,\r\n"
ret_str +="\r\n---\r\n0,150,100,100,100,100,\r\n" ret_str += "\r\n---\r\n0,150,100,100,100,100,\r\n"
for i in range(1,130): for i in range(1, 130):
ret_str +=f"{i},100,100,100,100,100,\r\n" ret_str += f"{i},100,100,100,100,100,\r\n"
ret_str += "---\r\n" ret_str += "---\r\n"
return({"data": ret_str}) return {"data": ret_str}
@cached(lifetime=86400) @cached(lifetime=86400)
def handle_data_partnerxxxx_request(self, data: Dict) -> Dict: def handle_data_partnerxxxx_request(self, data: Dict) -> Dict:
partner_num = int(data["dldate"]["filetype"][-4:]) partner_num = int(data["dldate"]["filetype"][-4:])
@ -208,50 +233,54 @@ class CxbRevSunriseS1(CxbBase):
lines = partner.readlines() lines = partner.readlines()
for line in lines: for line in lines:
ret_str += f"{line[:-1]}\r\n" ret_str += f"{line[:-1]}\r\n"
return({"data": ret_str}) return {"data": ret_str}
def handle_data_server_state_request(self, data: Dict) -> Dict: def handle_data_server_state_request(self, data: Dict) -> Dict:
return({"data": True}) return {"data": True}
def handle_data_settings_request(self, data: Dict) -> Dict: def handle_data_settings_request(self, data: Dict) -> Dict:
return({"data": "2,\r\n"}) return {"data": "2,\r\n"}
def handle_data_story_list_request(self, data: Dict) -> Dict: def handle_data_story_list_request(self, data: Dict) -> Dict:
#story id, story name, game version, start time, end time, course arc, unlock flag, song mcode for menu # story id, story name, game version, start time, end time, course arc, unlock flag, song mcode for menu
ret_str = "\r\n" ret_str = "\r\n"
ret_str += f"st0000,RISING PURPLE,10104,1464370990,4096483201,Cs1000,-1,purple,\r\n" ret_str += (
ret_str += f"st0001,REBEL YELL,10104,1467999790,4096483201,Cs1000,-1,chaset,\r\n" f"st0000,RISING PURPLE,10104,1464370990,4096483201,Cs1000,-1,purple,\r\n"
)
ret_str += (
f"st0001,REBEL YELL,10104,1467999790,4096483201,Cs1000,-1,chaset,\r\n"
)
ret_str += f"st0002,REMNANT,10104,1502127790,4096483201,Cs1000,-1,overcl,\r\n" ret_str += f"st0002,REMNANT,10104,1502127790,4096483201,Cs1000,-1,overcl,\r\n"
return({"data": ret_str}) return {"data": ret_str}
def handle_data_stxxxx_request(self, data: Dict) -> Dict: def handle_data_stxxxx_request(self, data: Dict) -> Dict:
story_num = int(data["dldate"]["filetype"][-4:]) story_num = int(data["dldate"]["filetype"][-4:])
ret_str = "" ret_str = ""
for i in range(1,11): for i in range(1, 11):
ret_str +=f"{i},st000{story_num}_{i-1},,,,,,,,,,,,,,,,1,,-1,1,\r\n" ret_str += f"{i},st000{story_num}_{i-1},,,,,,,,,,,,,,,,1,,-1,1,\r\n"
return({"data": ret_str}) return {"data": ret_str}
def handle_data_event_stamp_list_request(self, data: Dict) -> Dict: def handle_data_event_stamp_list_request(self, data: Dict) -> Dict:
return({"data":"Cs1032,1,1,1,1,1,1,1,1,1,1,\r\n"}) return {"data": "Cs1032,1,1,1,1,1,1,1,1,1,1,\r\n"}
def handle_data_premium_list_request(self, data: Dict) -> Dict: def handle_data_premium_list_request(self, data: Dict) -> Dict:
return({"data": "1,,,,10,,,,,99,,,,,,,,,100,,\r\n"}) return {"data": "1,,,,10,,,,,99,,,,,,,,,100,,\r\n"}
def handle_data_event_list_request(self, data: Dict) -> Dict: def handle_data_event_list_request(self, data: Dict) -> Dict:
return({"data":""}) return {"data": ""}
def handle_data_event_detail_list_request(self, data: Dict) -> Dict: def handle_data_event_detail_list_request(self, data: Dict) -> Dict:
event_id = data["dldate"]["filetype"].split("/")[2] event_id = data["dldate"]["filetype"].split("/")[2]
if "EventStampMapListCs1002" in event_id: if "EventStampMapListCs1002" in event_id:
return({"data":"1,2,1,1,2,3,9,5,6,7,8,9,10,\r\n"}) return {"data": "1,2,1,1,2,3,9,5,6,7,8,9,10,\r\n"}
elif "EventStampList" in event_id: elif "EventStampList" in event_id:
return({"data":"Cs1002,1,1,1,1,1,1,1,1,1,1,\r\n"}) return {"data": "Cs1002,1,1,1,1,1,1,1,1,1,1,\r\n"}
else: else:
return({"data":""}) return {"data": ""}
def handle_data_event_stamp_map_list_csxxxx_request(self, data: Dict) -> Dict: def handle_data_event_stamp_map_list_csxxxx_request(self, data: Dict) -> Dict:
event_id = data["dldate"]["filetype"].split("/")[2] event_id = data["dldate"]["filetype"].split("/")[2]
if "EventStampMapListCs1002" in event_id: if "EventStampMapListCs1002" in event_id:
return({"data":"1,2,1,1,2,3,9,5,6,7,8,9,10,\r\n"}) return {"data": "1,2,1,1,2,3,9,5,6,7,8,9,10,\r\n"}
else: else:
return({"data":""}) return {"data": ""}

View File

@ -11,128 +11,147 @@ from titles.cxb.config import CxbConfig
from titles.cxb.base import CxbBase from titles.cxb.base import CxbBase
from titles.cxb.const import CxbConstants from titles.cxb.const import CxbConstants
class CxbRevSunriseS2(CxbBase): class CxbRevSunriseS2(CxbBase):
def __init__(self, cfg: CoreConfig, game_cfg: CxbConfig) -> None: def __init__(self, cfg: CoreConfig, game_cfg: CxbConfig) -> None:
super().__init__(cfg, game_cfg) super().__init__(cfg, game_cfg)
self.version = CxbConstants.VER_CROSSBEATS_REV_SUNRISE_S2_OMNI self.version = CxbConstants.VER_CROSSBEATS_REV_SUNRISE_S2_OMNI
def handle_data_path_list_request(self, data: Dict) -> Dict:
return { "data": "" }
@cached(lifetime=86400) def handle_data_path_list_request(self, data: Dict) -> Dict:
return {"data": ""}
@cached(lifetime=86400)
def handle_data_music_list_request(self, data: Dict) -> Dict: def handle_data_music_list_request(self, data: Dict) -> Dict:
ret_str = "" ret_str = ""
with open(r"titles/cxb/rss2_data/MusicArchiveList.csv") as music: with open(r"titles/cxb/rss2_data/MusicArchiveList.csv") as music:
lines = music.readlines() lines = music.readlines()
for line in lines: for line in lines:
line_split = line.split(',') line_split = line.split(",")
ret_str += f"{line_split[0]},{line_split[1]},{line_split[2]},{line_split[3]},{line_split[4]},{line_split[5]},{line_split[6]},{line_split[7]},{line_split[8]},{line_split[9]},{line_split[10]},{line_split[11]},{line_split[12]},{line_split[13]},{line_split[14]},\r\n" ret_str += f"{line_split[0]},{line_split[1]},{line_split[2]},{line_split[3]},{line_split[4]},{line_split[5]},{line_split[6]},{line_split[7]},{line_split[8]},{line_split[9]},{line_split[10]},{line_split[11]},{line_split[12]},{line_split[13]},{line_split[14]},\r\n"
return({"data":ret_str})
@cached(lifetime=86400) return {"data": ret_str}
@cached(lifetime=86400)
def handle_data_item_list_detail_request(self, data: Dict) -> Dict: def handle_data_item_list_detail_request(self, data: Dict) -> Dict:
#ItemListIcon load # ItemListIcon load
ret_str = "#ItemListIcon\r\n" ret_str = "#ItemListIcon\r\n"
with open(r"titles/cxb/rss2_data/Item/ItemList_Icon.csv", encoding="utf-8") as item: with open(
r"titles/cxb/rss2_data/Item/ItemList_Icon.csv", encoding="utf-8"
) as item:
lines = item.readlines() lines = item.readlines()
for line in lines: for line in lines:
ret_str += f"{line[:-1]}\r\n" ret_str += f"{line[:-1]}\r\n"
#ItemListTitle load # ItemListTitle load
ret_str += "\r\n#ItemListTitle\r\n" ret_str += "\r\n#ItemListTitle\r\n"
with open(r"titles/cxb/rss2_data/Item/ItemList_Title.csv", encoding="utf-8") as item: with open(
r"titles/cxb/rss2_data/Item/ItemList_Title.csv", encoding="utf-8"
) as item:
lines = item.readlines() lines = item.readlines()
for line in lines: for line in lines:
ret_str += f"{line[:-1]}\r\n" ret_str += f"{line[:-1]}\r\n"
return({"data":ret_str}) return {"data": ret_str}
@cached(lifetime=86400) @cached(lifetime=86400)
def handle_data_shop_list_detail_request(self, data: Dict) -> Dict: def handle_data_shop_list_detail_request(self, data: Dict) -> Dict:
#ShopListIcon load # ShopListIcon load
ret_str = "#ShopListIcon\r\n" ret_str = "#ShopListIcon\r\n"
with open(r"titles/cxb/rss2_data/Shop/ShopList_Icon.csv", encoding="utf-8") as shop: with open(
r"titles/cxb/rss2_data/Shop/ShopList_Icon.csv", encoding="utf-8"
) as shop:
lines = shop.readlines() lines = shop.readlines()
for line in lines: for line in lines:
ret_str += f"{line[:-1]}\r\n" ret_str += f"{line[:-1]}\r\n"
#ShopListMusic load
ret_str += "\r\n#ShopListMusic\r\n"
with open(r"titles/cxb/rss2_data/Shop/ShopList_Music.csv", encoding="utf-8") as shop:
lines = shop.readlines()
for line in lines:
ret_str += f"{line[:-1]}\r\n"
#ShopListSale load
ret_str += "\r\n#ShopListSale\r\n"
with open(r"titles/cxb/rss2_data/Shop/ShopList_Sale.csv", encoding="shift-jis") as shop:
lines = shop.readlines()
for line in lines:
ret_str += f"{line[:-1]}\r\n"
#ShopListSkinBg load
ret_str += "\r\n#ShopListSkinBg\r\n"
with open(r"titles/cxb/rss2_data/Shop/ShopList_SkinBg.csv", encoding="shift-jis") as shop:
lines = shop.readlines()
for line in lines:
ret_str += f"{line[:-1]}\r\n"
#ShopListSkinEffect load
ret_str += "\r\n#ShopListSkinEffect\r\n"
with open(r"titles/cxb/rss2_data/Shop/ShopList_SkinEffect.csv", encoding="shift-jis") as shop:
lines = shop.readlines()
for line in lines:
ret_str += f"{line[:-1]}\r\n"
#ShopListSkinNotes load
ret_str += "\r\n#ShopListSkinNotes\r\n"
with open(r"titles/cxb/rss2_data/Shop/ShopList_SkinNotes.csv", encoding="shift-jis") as shop:
lines = shop.readlines()
for line in lines:
ret_str += f"{line[:-1]}\r\n"
#ShopListTitle load
ret_str += "\r\n#ShopListTitle\r\n"
with open(r"titles/cxb/rss2_data/Shop/ShopList_Title.csv", encoding="utf-8") as shop:
lines = shop.readlines()
for line in lines:
ret_str += f"{line[:-1]}\r\n"
return({"data":ret_str})
def handle_data_extra_stage_list_request(self, data: Dict) -> Dict:
return({"data":""})
def handle_data_ex0001_request(self, data: Dict) -> Dict:
return({"data":""})
def handle_data_one_more_extra_list_request(self, data: Dict) -> Dict:
return({"data":""})
def handle_data_bonus_list10100_request(self, data: Dict) -> Dict:
return({"data":""})
def handle_data_oe0001_request(self, data: Dict) -> Dict:
return({"data":""})
def handle_data_free_coupon_request(self, data: Dict) -> Dict:
return({"data":""})
@cached(lifetime=86400) # ShopListMusic load
ret_str += "\r\n#ShopListMusic\r\n"
with open(
r"titles/cxb/rss2_data/Shop/ShopList_Music.csv", encoding="utf-8"
) as shop:
lines = shop.readlines()
for line in lines:
ret_str += f"{line[:-1]}\r\n"
# ShopListSale load
ret_str += "\r\n#ShopListSale\r\n"
with open(
r"titles/cxb/rss2_data/Shop/ShopList_Sale.csv", encoding="shift-jis"
) as shop:
lines = shop.readlines()
for line in lines:
ret_str += f"{line[:-1]}\r\n"
# ShopListSkinBg load
ret_str += "\r\n#ShopListSkinBg\r\n"
with open(
r"titles/cxb/rss2_data/Shop/ShopList_SkinBg.csv", encoding="shift-jis"
) as shop:
lines = shop.readlines()
for line in lines:
ret_str += f"{line[:-1]}\r\n"
# ShopListSkinEffect load
ret_str += "\r\n#ShopListSkinEffect\r\n"
with open(
r"titles/cxb/rss2_data/Shop/ShopList_SkinEffect.csv", encoding="shift-jis"
) as shop:
lines = shop.readlines()
for line in lines:
ret_str += f"{line[:-1]}\r\n"
# ShopListSkinNotes load
ret_str += "\r\n#ShopListSkinNotes\r\n"
with open(
r"titles/cxb/rss2_data/Shop/ShopList_SkinNotes.csv", encoding="shift-jis"
) as shop:
lines = shop.readlines()
for line in lines:
ret_str += f"{line[:-1]}\r\n"
# ShopListTitle load
ret_str += "\r\n#ShopListTitle\r\n"
with open(
r"titles/cxb/rss2_data/Shop/ShopList_Title.csv", encoding="utf-8"
) as shop:
lines = shop.readlines()
for line in lines:
ret_str += f"{line[:-1]}\r\n"
return {"data": ret_str}
def handle_data_extra_stage_list_request(self, data: Dict) -> Dict:
return {"data": ""}
def handle_data_ex0001_request(self, data: Dict) -> Dict:
return {"data": ""}
def handle_data_one_more_extra_list_request(self, data: Dict) -> Dict:
return {"data": ""}
def handle_data_bonus_list10100_request(self, data: Dict) -> Dict:
return {"data": ""}
def handle_data_oe0001_request(self, data: Dict) -> Dict:
return {"data": ""}
def handle_data_free_coupon_request(self, data: Dict) -> Dict:
return {"data": ""}
@cached(lifetime=86400)
def handle_data_news_list_request(self, data: Dict) -> Dict: def handle_data_news_list_request(self, data: Dict) -> Dict:
ret_str = "" ret_str = ""
with open(r"titles/cxb/rss2_data/NewsList.csv", encoding="UTF-8") as news: with open(r"titles/cxb/rss2_data/NewsList.csv", encoding="UTF-8") as news:
lines = news.readlines() lines = news.readlines()
for line in lines: for line in lines:
ret_str += f"{line[:-1]}\r\n" ret_str += f"{line[:-1]}\r\n"
return({"data":ret_str}) return {"data": ret_str}
def handle_data_tips_request(self, data: Dict) -> Dict: def handle_data_tips_request(self, data: Dict) -> Dict:
return({"data":""}) return {"data": ""}
def handle_data_release_info_list_request(self, data: Dict) -> Dict: def handle_data_release_info_list_request(self, data: Dict) -> Dict:
return({"data":""}) return {"data": ""}
@cached(lifetime=86400) @cached(lifetime=86400)
def handle_data_random_music_list_request(self, data: Dict) -> Dict: def handle_data_random_music_list_request(self, data: Dict) -> Dict:
ret_str = "" ret_str = ""
@ -141,10 +160,12 @@ class CxbRevSunriseS2(CxbBase):
count = 0 count = 0
for line in lines: for line in lines:
line_split = line.split(",") line_split = line.split(",")
ret_str += str(count) + "," + line_split[0] + "," + line_split[0] + ",\r\n" ret_str += (
str(count) + "," + line_split[0] + "," + line_split[0] + ",\r\n"
)
return {"data": ret_str}
return({"data":ret_str})
@cached(lifetime=86400) @cached(lifetime=86400)
def handle_data_license_request(self, data: Dict) -> Dict: def handle_data_license_request(self, data: Dict) -> Dict:
ret_str = "" ret_str = ""
@ -152,54 +173,58 @@ class CxbRevSunriseS2(CxbBase):
lines = licenses.readlines() lines = licenses.readlines()
for line in lines: for line in lines:
ret_str += f"{line[:-1]}\r\n" ret_str += f"{line[:-1]}\r\n"
return({"data":ret_str}) return {"data": ret_str}
@cached(lifetime=86400) @cached(lifetime=86400)
def handle_data_course_list_request(self, data: Dict) -> Dict: def handle_data_course_list_request(self, data: Dict) -> Dict:
ret_str = "" ret_str = ""
with open(r"titles/cxb/rss2_data/Course/CourseList.csv", encoding="UTF-8") as course: with open(
r"titles/cxb/rss2_data/Course/CourseList.csv", encoding="UTF-8"
) as course:
lines = course.readlines() lines = course.readlines()
for line in lines: for line in lines:
ret_str += f"{line[:-1]}\r\n" ret_str += f"{line[:-1]}\r\n"
return({"data":ret_str}) return {"data": ret_str}
@cached(lifetime=86400) @cached(lifetime=86400)
def handle_data_csxxxx_request(self, data: Dict) -> Dict: def handle_data_csxxxx_request(self, data: Dict) -> Dict:
extra_num = int(data["dldate"]["filetype"][-4:]) extra_num = int(data["dldate"]["filetype"][-4:])
ret_str = "" ret_str = ""
with open(fr"titles/cxb/rss2_data/Course/Cs{extra_num}.csv", encoding="shift-jis") as course: with open(
rf"titles/cxb/rss2_data/Course/Cs{extra_num}.csv", encoding="shift-jis"
) as course:
lines = course.readlines() lines = course.readlines()
for line in lines: for line in lines:
ret_str += f"{line[:-1]}\r\n" ret_str += f"{line[:-1]}\r\n"
return({"data":ret_str}) return {"data": ret_str}
def handle_data_mission_list_request(self, data: Dict) -> Dict: def handle_data_mission_list_request(self, data: Dict) -> Dict:
return({"data":""}) return {"data": ""}
def handle_data_mission_bonus_request(self, data: Dict) -> Dict: def handle_data_mission_bonus_request(self, data: Dict) -> Dict:
return({"data":""}) return {"data": ""}
def handle_data_unlimited_mission_request(self, data: Dict) -> Dict: def handle_data_unlimited_mission_request(self, data: Dict) -> Dict:
return({"data":""}) return {"data": ""}
def handle_data_partner_list_request(self, data: Dict) -> Dict: def handle_data_partner_list_request(self, data: Dict) -> Dict:
ret_str = "" ret_str = ""
# Lord forgive me for the sins I am about to commit # Lord forgive me for the sins I am about to commit
for i in range(0,10): for i in range(0, 10):
ret_str += f"80000{i},{i},{i},0,10000,,\r\n" ret_str += f"80000{i},{i},{i},0,10000,,\r\n"
ret_str += f"80000{i},{i},{i},1,10500,,\r\n" ret_str += f"80000{i},{i},{i},1,10500,,\r\n"
ret_str += f"80000{i},{i},{i},2,10500,,\r\n" ret_str += f"80000{i},{i},{i},2,10500,,\r\n"
for i in range(10,13): for i in range(10, 13):
ret_str += f"8000{i},{i},{i},0,10000,,\r\n" ret_str += f"8000{i},{i},{i},0,10000,,\r\n"
ret_str += f"8000{i},{i},{i},1,10500,,\r\n" ret_str += f"8000{i},{i},{i},1,10500,,\r\n"
ret_str += f"8000{i},{i},{i},2,10500,,\r\n" ret_str += f"8000{i},{i},{i},2,10500,,\r\n"
ret_str +="\r\n---\r\n0,150,100,100,100,100,\r\n" ret_str += "\r\n---\r\n0,150,100,100,100,100,\r\n"
for i in range(1,130): for i in range(1, 130):
ret_str +=f"{i},100,100,100,100,100,\r\n" ret_str += f"{i},100,100,100,100,100,\r\n"
ret_str += "---\r\n" ret_str += "---\r\n"
return({"data": ret_str}) return {"data": ret_str}
@cached(lifetime=86400) @cached(lifetime=86400)
def handle_data_partnerxxxx_request(self, data: Dict) -> Dict: def handle_data_partnerxxxx_request(self, data: Dict) -> Dict:
partner_num = int(data["dldate"]["filetype"][-4:]) partner_num = int(data["dldate"]["filetype"][-4:])
@ -208,55 +233,65 @@ class CxbRevSunriseS2(CxbBase):
lines = partner.readlines() lines = partner.readlines()
for line in lines: for line in lines:
ret_str += f"{line[:-1]}\r\n" ret_str += f"{line[:-1]}\r\n"
return({"data": ret_str}) return {"data": ret_str}
def handle_data_server_state_request(self, data: Dict) -> Dict: def handle_data_server_state_request(self, data: Dict) -> Dict:
return({"data": True}) return {"data": True}
def handle_data_settings_request(self, data: Dict) -> Dict: def handle_data_settings_request(self, data: Dict) -> Dict:
return({"data": "2,\r\n"}) return {"data": "2,\r\n"}
def handle_data_story_list_request(self, data: Dict) -> Dict: def handle_data_story_list_request(self, data: Dict) -> Dict:
#story id, story name, game version, start time, end time, course arc, unlock flag, song mcode for menu # story id, story name, game version, start time, end time, course arc, unlock flag, song mcode for menu
ret_str = "\r\n" ret_str = "\r\n"
ret_str += f"st0000,RISING PURPLE,10104,1464370990,4096483201,Cs1000,-1,purple,\r\n" ret_str += (
ret_str += f"st0001,REBEL YELL,10104,1467999790,4096483201,Cs1000,-1,chaset,\r\n" f"st0000,RISING PURPLE,10104,1464370990,4096483201,Cs1000,-1,purple,\r\n"
)
ret_str += (
f"st0001,REBEL YELL,10104,1467999790,4096483201,Cs1000,-1,chaset,\r\n"
)
ret_str += f"st0002,REMNANT,10104,1502127790,4096483201,Cs1000,-1,overcl,\r\n" ret_str += f"st0002,REMNANT,10104,1502127790,4096483201,Cs1000,-1,overcl,\r\n"
return({"data": ret_str}) return {"data": ret_str}
def handle_data_stxxxx_request(self, data: Dict) -> Dict: def handle_data_stxxxx_request(self, data: Dict) -> Dict:
story_num = int(data["dldate"]["filetype"][-4:]) story_num = int(data["dldate"]["filetype"][-4:])
ret_str = "" ret_str = ""
# Each stories appears to have 10 pieces based on the wiki but as on how they are set.... no clue # Each stories appears to have 10 pieces based on the wiki but as on how they are set.... no clue
for i in range(1,11): for i in range(1, 11):
ret_str +=f"{i},st000{story_num}_{i-1},,,,,,,,,,,,,,,,1,,-1,1,\r\n" ret_str += f"{i},st000{story_num}_{i-1},,,,,,,,,,,,,,,,1,,-1,1,\r\n"
return({"data": ret_str}) return {"data": ret_str}
def handle_data_event_stamp_list_request(self, data: Dict) -> Dict: def handle_data_event_stamp_list_request(self, data: Dict) -> Dict:
return({"data":"Cs1002,1,1,1,1,1,1,1,1,1,1,\r\n"}) return {"data": "Cs1002,1,1,1,1,1,1,1,1,1,1,\r\n"}
def handle_data_premium_list_request(self, data: Dict) -> Dict: def handle_data_premium_list_request(self, data: Dict) -> Dict:
return({"data": "1,,,,10,,,,,99,,,,,,,,,100,,\r\n"}) return {"data": "1,,,,10,,,,,99,,,,,,,,,100,,\r\n"}
def handle_data_event_list_request(self, data: Dict) -> Dict: def handle_data_event_list_request(self, data: Dict) -> Dict:
return({"data":"Cs4001,0,10000,1601510400,1604188799,1,nv2006,1,\r\nCs4005,0,10000,1609459200,1615766399,1,nv2006,1,\r\n"}) return {
"data": "Cs4001,0,10000,1601510400,1604188799,1,nv2006,1,\r\nCs4005,0,10000,1609459200,1615766399,1,nv2006,1,\r\n"
}
def handle_data_event_detail_list_request(self, data: Dict) -> Dict: def handle_data_event_detail_list_request(self, data: Dict) -> Dict:
event_id = data["dldate"]["filetype"].split("/")[2] event_id = data["dldate"]["filetype"].split("/")[2]
if "Cs4001" in event_id: if "Cs4001" in event_id:
return({"data":"#EventMusicList\r\n1,zonzon2,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,\r\n2,moonki,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,\r\n3,tricko,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,\r\n"}) return {
"data": "#EventMusicList\r\n1,zonzon2,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,\r\n2,moonki,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,\r\n3,tricko,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,\r\n"
}
elif "Cs4005" in event_id: elif "Cs4005" in event_id:
return({"data":"#EventMusicList\r\n2,firstl,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,\r\n2,valent,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,\r\n2,dazzli2,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,\r\n"}) return {
"data": "#EventMusicList\r\n2,firstl,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,\r\n2,valent,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,\r\n2,dazzli2,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,\r\n"
}
elif "EventStampMapListCs1002" in event_id: elif "EventStampMapListCs1002" in event_id:
return({"data":"1,2,1,1,2,3,9,5,6,7,8,9,10,\r\n"}) return {"data": "1,2,1,1,2,3,9,5,6,7,8,9,10,\r\n"}
elif "EventStampList" in event_id: elif "EventStampList" in event_id:
return({"data":"Cs1002,1,1,1,1,1,1,1,1,1,1,\r\n"}) return {"data": "Cs1002,1,1,1,1,1,1,1,1,1,1,\r\n"}
else: else:
return({"data":""}) return {"data": ""}
def handle_data_event_stamp_map_list_csxxxx_request(self, data: Dict) -> Dict: def handle_data_event_stamp_map_list_csxxxx_request(self, data: Dict) -> Dict:
event_id = data["dldate"]["filetype"].split("/")[2] event_id = data["dldate"]["filetype"].split("/")[2]
if "EventStampMapListCs1002" in event_id: if "EventStampMapListCs1002" in event_id:
return({"data":"1,2,1,1,2,3,9,5,6,7,8,9,10,\r\n"}) return {"data": "1,2,1,1,2,3,9,5,6,7,8,9,10,\r\n"}
else: else:
return({"data":""}) return {"data": ""}

View File

@ -14,32 +14,29 @@ energy = Table(
Column("user", ForeignKey("aime_user.id", ondelete="cascade"), nullable=False), Column("user", ForeignKey("aime_user.id", ondelete="cascade"), nullable=False),
Column("energy", Integer, nullable=False, server_default="0"), Column("energy", Integer, nullable=False, server_default="0"),
UniqueConstraint("user", name="cxb_rev_energy_uk"), UniqueConstraint("user", name="cxb_rev_energy_uk"),
mysql_charset='utf8mb4' mysql_charset="utf8mb4",
) )
class CxbItemData(BaseData):
def put_energy(self, user_id: int, rev_energy: int) -> Optional[int]:
sql = insert(energy).values(
user = user_id,
energy = rev_energy
)
conflict = sql.on_duplicate_key_update( class CxbItemData(BaseData):
energy = rev_energy def put_energy(self, user_id: int, rev_energy: int) -> Optional[int]:
) sql = insert(energy).values(user=user_id, energy=rev_energy)
conflict = sql.on_duplicate_key_update(energy=rev_energy)
result = self.execute(conflict) result = self.execute(conflict)
if result is None: if result is None:
self.logger.error(f"{__name__} failed to insert item! user: {user_id}, energy: {rev_energy}") self.logger.error(
f"{__name__} failed to insert item! user: {user_id}, energy: {rev_energy}"
)
return None return None
return result.lastrowid return result.lastrowid
def get_energy(self, user_id: int) -> Optional[Dict]: def get_energy(self, user_id: int) -> Optional[Dict]:
sql = energy.select( sql = energy.select(and_(energy.c.user == user_id))
and_(energy.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.fetchone() return result.fetchone()

View File

@ -16,57 +16,63 @@ profile = Table(
Column("index", Integer, nullable=False), Column("index", Integer, nullable=False),
Column("data", JSON, nullable=False), Column("data", JSON, nullable=False),
UniqueConstraint("user", "index", name="cxb_profile_uk"), UniqueConstraint("user", "index", name="cxb_profile_uk"),
mysql_charset='utf8mb4' mysql_charset="utf8mb4",
) )
class CxbProfileData(BaseData): class CxbProfileData(BaseData):
def put_profile(self, user_id: int, version: int, index: int, data: JSON) -> Optional[int]: def put_profile(
self, user_id: int, version: int, index: int, data: JSON
) -> Optional[int]:
sql = insert(profile).values( sql = insert(profile).values(
user = user_id, user=user_id, version=version, index=index, data=data
version = version,
index = index,
data = data
) )
conflict = sql.on_duplicate_key_update( conflict = sql.on_duplicate_key_update(index=index, data=data)
index = index,
data = data
)
result = self.execute(conflict) result = self.execute(conflict)
if result is None: if result is None:
self.logger.error(f"{__name__} failed to update! user: {user_id}, index: {index}, data: {data}") self.logger.error(
f"{__name__} failed to update! user: {user_id}, index: {index}, data: {data}"
)
return None return None
return result.lastrowid return result.lastrowid
def get_profile(self, aime_id: int, version: int) -> Optional[List[Dict]]: def get_profile(self, aime_id: int, version: int) -> Optional[List[Dict]]:
""" """
Given a game version and either a profile or aime id, return the profile Given a game version and either a profile or aime id, return the profile
""" """
sql = profile.select(and_( sql = profile.select(
profile.c.version == version, and_(profile.c.version == version, profile.c.user == aime_id)
profile.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 get_profile_index(self, index: int, aime_id: int = None, version: int = None) -> Optional[Dict]: def get_profile_index(
self, index: int, aime_id: int = None, version: int = None
) -> Optional[Dict]:
""" """
Given a game version and either a profile or aime id, return the profile Given a game version and either a profile or aime id, return the profile
""" """
if aime_id is not None and version is not None and index is not None: if aime_id is not None and version is not None and index is not None:
sql = profile.select(and_( sql = profile.select(
and_(
profile.c.version == version, profile.c.version == version,
profile.c.user == aime_id, profile.c.user == aime_id,
profile.c.index == index profile.c.index == index,
)) )
)
else: else:
self.logger.error(f"get_profile: Bad arguments!! aime_id {aime_id} version {version}") self.logger.error(
f"get_profile: Bad arguments!! aime_id {aime_id} version {version}"
)
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()

View File

@ -18,7 +18,7 @@ score = Table(
Column("song_index", Integer), Column("song_index", Integer),
Column("data", JSON), Column("data", JSON),
UniqueConstraint("user", "song_mcode", "song_index", name="cxb_score_uk"), UniqueConstraint("user", "song_mcode", "song_index", name="cxb_score_uk"),
mysql_charset='utf8mb4' mysql_charset="utf8mb4",
) )
playlog = Table( playlog = Table(
@ -40,7 +40,7 @@ playlog = Table(
Column("fail", Integer), Column("fail", Integer),
Column("combo", Integer), Column("combo", Integer),
Column("date_scored", TIMESTAMP, server_default=func.now()), Column("date_scored", TIMESTAMP, server_default=func.now()),
mysql_charset='utf8mb4' mysql_charset="utf8mb4",
) )
ranking = Table( ranking = Table(
@ -53,11 +53,19 @@ ranking = Table(
Column("score", Integer), Column("score", Integer),
Column("clear", Integer), Column("clear", Integer),
UniqueConstraint("user", "rev_id", name="cxb_ranking_uk"), UniqueConstraint("user", "rev_id", name="cxb_ranking_uk"),
mysql_charset='utf8mb4' mysql_charset="utf8mb4",
) )
class CxbScoreData(BaseData): class CxbScoreData(BaseData):
def put_best_score(self, user_id: int, song_mcode: str, game_version: int, song_index: int, data: JSON) -> Optional[int]: def put_best_score(
self,
user_id: int,
song_mcode: str,
game_version: int,
song_index: int,
data: JSON,
) -> Optional[int]:
""" """
Update the user's best score for a chart Update the user's best score for a chart
""" """
@ -66,22 +74,37 @@ class CxbScoreData(BaseData):
song_mcode=song_mcode, song_mcode=song_mcode,
game_version=game_version, game_version=game_version,
song_index=song_index, song_index=song_index,
data=data data=data,
) )
conflict = sql.on_duplicate_key_update( conflict = sql.on_duplicate_key_update(data=sql.inserted.data)
data = sql.inserted.data
)
result = self.execute(conflict) result = self.execute(conflict)
if result is None: if result is None:
self.logger.error(f"{__name__} failed to insert best score! profile: {user_id}, song: {song_mcode}, data: {data}") self.logger.error(
f"{__name__} failed to insert best score! profile: {user_id}, song: {song_mcode}, data: {data}"
)
return None return None
return result.lastrowid return result.lastrowid
def put_playlog(self, user_id: int, song_mcode: str, chart_id: int, score: int, clear: int, flawless: int, this_super: int, def put_playlog(
cool: int, this_fast: int, this_fast2: int, this_slow: int, this_slow2: int, fail: int, combo: int) -> Optional[int]: self,
user_id: int,
song_mcode: str,
chart_id: int,
score: int,
clear: int,
flawless: int,
this_super: int,
cool: int,
this_fast: int,
this_fast2: int,
this_slow: int,
this_slow2: int,
fail: int,
combo: int,
) -> Optional[int]:
""" """
Add an entry to the user's play log Add an entry to the user's play log
""" """
@ -99,45 +122,42 @@ class CxbScoreData(BaseData):
slow=this_slow, slow=this_slow,
slow2=this_slow2, slow2=this_slow2,
fail=fail, fail=fail,
combo=combo combo=combo,
) )
result = self.execute(sql) result = self.execute(sql)
if result is None: if result is None:
self.logger.error(f"{__name__} failed to insert playlog! profile: {user_id}, song: {song_mcode}, chart: {chart_id}") self.logger.error(
f"{__name__} failed to insert playlog! profile: {user_id}, song: {song_mcode}, chart: {chart_id}"
)
return None return None
return result.lastrowid return result.lastrowid
def put_ranking(self, user_id: int, rev_id: int, song_id: int, score: int, clear: int) -> Optional[int]: def put_ranking(
self, user_id: int, rev_id: int, song_id: int, score: int, clear: int
) -> Optional[int]:
""" """
Add an entry to the user's ranking logs Add an entry to the user's ranking logs
""" """
if song_id == 0: if song_id == 0:
sql = insert(ranking).values( sql = insert(ranking).values(
user=user_id, user=user_id, rev_id=rev_id, score=score, clear=clear
rev_id=rev_id,
score=score,
clear=clear
) )
else: else:
sql = insert(ranking).values( sql = insert(ranking).values(
user=user_id, user=user_id, rev_id=rev_id, song_id=song_id, score=score, clear=clear
rev_id=rev_id,
song_id=song_id,
score=score,
clear=clear
) )
conflict = sql.on_duplicate_key_update( conflict = sql.on_duplicate_key_update(score=score)
score = score
)
result = self.execute(conflict) result = self.execute(conflict)
if result is None: if result is None:
self.logger.error(f"{__name__} failed to insert ranking log! profile: {user_id}, score: {score}, clear: {clear}") self.logger.error(
f"{__name__} failed to insert ranking log! profile: {user_id}, score: {score}, clear: {clear}"
)
return None return None
return result.lastrowid return result.lastrowid
def get_best_score(self, user_id: int, song_mcode: int) -> Optional[Dict]: def get_best_score(self, user_id: int, song_mcode: int) -> Optional[Dict]:
@ -146,21 +166,22 @@ class CxbScoreData(BaseData):
) )
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_best_scores(self, user_id: int) -> Optional[Dict]: def get_best_scores(self, user_id: int) -> Optional[Dict]:
sql = score.select(score.c.user == user_id) sql = score.select(score.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 get_best_rankings(self, user_id: int) -> Optional[List[Dict]]: def get_best_rankings(self, user_id: int) -> Optional[List[Dict]]:
sql = ranking.select( sql = ranking.select(ranking.c.user == user_id)
ranking.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()

View File

@ -21,54 +21,75 @@ music = Table(
Column("artist", String(255)), Column("artist", String(255)),
Column("category", String(255)), Column("category", String(255)),
Column("level", Float), Column("level", Float),
UniqueConstraint("version", "songId", "chartId", "index", name="cxb_static_music_uk"), UniqueConstraint(
mysql_charset='utf8mb4' "version", "songId", "chartId", "index", name="cxb_static_music_uk"
),
mysql_charset="utf8mb4",
) )
class CxbStaticData(BaseData): class CxbStaticData(BaseData):
def put_music(self, version: int, mcode: str, index: int, chart: int, title: str, artist: str, category: str, level: float ) -> Optional[int]: def put_music(
self,
version: int,
mcode: str,
index: int,
chart: int,
title: str,
artist: str,
category: str,
level: float,
) -> Optional[int]:
sql = insert(music).values( sql = insert(music).values(
version = version, version=version,
songId = mcode, songId=mcode,
index = index, index=index,
chartId = chart, chartId=chart,
title = title, title=title,
artist = artist, artist=artist,
category = category, category=category,
level = level level=level,
) )
conflict = sql.on_duplicate_key_update( conflict = sql.on_duplicate_key_update(
title = title, title=title, artist=artist, category=category, level=level
artist = artist,
category = category,
level = level
) )
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_music(self, version: int, song_id: Optional[int] = None) -> Optional[List[Row]]: def get_music(
self, version: int, song_id: Optional[int] = None
) -> Optional[List[Row]]:
if song_id is None: if song_id is None:
sql = select(music).where(music.c.version == version) sql = select(music).where(music.c.version == version)
else: else:
sql = select(music).where(and_( sql = select(music).where(
music.c.version == version, and_(
music.c.songId == song_id, music.c.version == version,
)) music.c.songId == song_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 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()

View File

@ -7,4 +7,4 @@ index = DivaServlet
database = DivaData database = DivaData
reader = DivaReader reader = DivaReader
game_codes = [DivaConstants.GAME_CODE] game_codes = [DivaConstants.GAME_CODE]
current_schema_version = 1 current_schema_version = 1

View File

@ -1,6 +1,6 @@
import datetime import datetime
from typing import Any, List, Dict from typing import Any, List, Dict
import logging import logging
import json import json
import urllib import urllib
@ -9,34 +9,35 @@ from titles.diva.config import DivaConfig
from titles.diva.const import DivaConstants from titles.diva.const import DivaConstants
from titles.diva.database import DivaData from titles.diva.database import DivaData
class DivaBase():
class DivaBase:
def __init__(self, cfg: CoreConfig, game_cfg: DivaConfig) -> None: def __init__(self, cfg: CoreConfig, game_cfg: DivaConfig) -> None:
self.core_cfg = cfg # Config file self.core_cfg = cfg # Config file
self.game_config = game_cfg self.game_config = game_cfg
self.data = DivaData(cfg) # Database self.data = DivaData(cfg) # Database
self.date_time_format = "%Y-%m-%d %H:%M:%S" self.date_time_format = "%Y-%m-%d %H:%M:%S"
self.logger = logging.getLogger("diva") self.logger = logging.getLogger("diva")
self.game = DivaConstants.GAME_CODE self.game = DivaConstants.GAME_CODE
self.version = DivaConstants.VER_PROJECT_DIVA_ARCADE_FUTURE_TONE self.version = DivaConstants.VER_PROJECT_DIVA_ARCADE_FUTURE_TONE
dt = datetime.datetime.now() dt = datetime.datetime.now()
self.time_lut=urllib.parse.quote(dt.strftime("%Y-%m-%d %H:%M:%S:16.0")) self.time_lut = urllib.parse.quote(dt.strftime("%Y-%m-%d %H:%M:%S:16.0"))
def handle_test_request(self, data: Dict) -> Dict: def handle_test_request(self, data: Dict) -> Dict:
return "" return ""
def handle_game_init_request(self, data: Dict) -> Dict: def handle_game_init_request(self, data: Dict) -> Dict:
return ( f'' ) return f""
def handle_attend_request(self, data: Dict) -> Dict: def handle_attend_request(self, data: Dict) -> Dict:
encoded = "&" encoded = "&"
params = { params = {
'atnd_prm1': '0,1,1,0,0,0,1,0,100,0,0,0,0,0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1', "atnd_prm1": "0,1,1,0,0,0,1,0,100,0,0,0,0,0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1",
'atnd_prm2': '30,10,100,4,1,50,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1', "atnd_prm2": "30,10,100,4,1,50,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1",
'atnd_prm3': '100,0,1,1,1,1,1,1,1,1,2,3,4,1,1,1,3,4,5,1,1,1,4,5,6,1,1,1,5,6,7,4,4,4,9,10,14,5,10,10,25,20,50,30,90,5,10,10,25,20,50,30,90,5,10,10,25,20,50,30,90,5,10,10,25,20,50,30,90,5,10,10,25,20,50,30,90,10,30,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0', "atnd_prm3": "100,0,1,1,1,1,1,1,1,1,2,3,4,1,1,1,3,4,5,1,1,1,4,5,6,1,1,1,5,6,7,4,4,4,9,10,14,5,10,10,25,20,50,30,90,5,10,10,25,20,50,30,90,5,10,10,25,20,50,30,90,5,10,10,25,20,50,30,90,5,10,10,25,20,50,30,90,10,30,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0",
'atnd_lut': f'{self.time_lut}', "atnd_lut": f"{self.time_lut}",
} }
encoded += urllib.parse.urlencode(params) encoded += urllib.parse.urlencode(params)
encoded = encoded.replace("%2C", ",") encoded = encoded.replace("%2C", ",")
@ -45,42 +46,42 @@ class DivaBase():
def handle_ping_request(self, data: Dict) -> Dict: def handle_ping_request(self, data: Dict) -> Dict:
encoded = "&" encoded = "&"
params = { params = {
'ping_b_msg': f'Welcome to {self.core_cfg.server.name} network!', "ping_b_msg": f"Welcome to {self.core_cfg.server.name} network!",
'ping_m_msg': 'xxx', "ping_m_msg": "xxx",
'atnd_lut': f'{self.time_lut}', "atnd_lut": f"{self.time_lut}",
'fi_lut': f'{self.time_lut}', "fi_lut": f"{self.time_lut}",
'ci_lut': f'{self.time_lut}', "ci_lut": f"{self.time_lut}",
'qi_lut': f'{self.time_lut}', "qi_lut": f"{self.time_lut}",
'pvl_lut': '2021-05-22 12:08:16.0', "pvl_lut": "2021-05-22 12:08:16.0",
'shp_ctlg_lut': '2020-06-10 19:44:16.0', "shp_ctlg_lut": "2020-06-10 19:44:16.0",
'cstmz_itm_ctlg_lut': '2019-10-08 20:23:12.0', "cstmz_itm_ctlg_lut": "2019-10-08 20:23:12.0",
'ngwl_lut': '2019-10-08 20:23:12.0', "ngwl_lut": "2019-10-08 20:23:12.0",
'rnk_nv_lut': '2020-06-10 19:51:30.0', "rnk_nv_lut": "2020-06-10 19:51:30.0",
'rnk_ps_lut': f'{self.time_lut}', "rnk_ps_lut": f"{self.time_lut}",
'bi_lut': '2020-09-18 10:00:00.0', "bi_lut": "2020-09-18 10:00:00.0",
'cpi_lut': '2020-10-25 09:25:10.0', "cpi_lut": "2020-10-25 09:25:10.0",
'bdlol_lut': '2020-09-18 10:00:00.0', "bdlol_lut": "2020-09-18 10:00:00.0",
'p_std_hc_lut': '2019-08-01 04:00:36.0', "p_std_hc_lut": "2019-08-01 04:00:36.0",
'p_std_i_n_lut': '2019-08-01 04:00:36.0', "p_std_i_n_lut": "2019-08-01 04:00:36.0",
'pdcl_lut': '2019-08-01 04:00:36.0', "pdcl_lut": "2019-08-01 04:00:36.0",
'pnml_lut': '2019-08-01 04:00:36.0', "pnml_lut": "2019-08-01 04:00:36.0",
'cinml_lut': '2019-08-01 04:00:36.0', "cinml_lut": "2019-08-01 04:00:36.0",
'rwl_lut': '2019-08-01 04:00:36.0', "rwl_lut": "2019-08-01 04:00:36.0",
'req_inv_cmd_num': '-1,-1,-1,-1,-1,-1,-1,-1,-1,-1', "req_inv_cmd_num": "-1,-1,-1,-1,-1,-1,-1,-1,-1,-1",
'req_inv_cmd_prm1': '-1,-1,-1,-1,-1,-1,-1,-1,-1,-1', "req_inv_cmd_prm1": "-1,-1,-1,-1,-1,-1,-1,-1,-1,-1",
'req_inv_cmd_prm2': '-1,-1,-1,-1,-1,-1,-1,-1,-1,-1', "req_inv_cmd_prm2": "-1,-1,-1,-1,-1,-1,-1,-1,-1,-1",
'req_inv_cmd_prm3': '-1,-1,-1,-1,-1,-1,-1,-1,-1,-1', "req_inv_cmd_prm3": "-1,-1,-1,-1,-1,-1,-1,-1,-1,-1",
'req_inv_cmd_prm4': '-1,-1,-1,-1,-1,-1,-1,-1,-1,-1', "req_inv_cmd_prm4": "-1,-1,-1,-1,-1,-1,-1,-1,-1,-1",
'pow_save_flg': 0, "pow_save_flg": 0,
'nblss_dnt_p': 100, "nblss_dnt_p": 100,
'nblss_ltt_rl_vp': 1500, "nblss_ltt_rl_vp": 1500,
'nblss_ex_ltt_flg': 1, "nblss_ex_ltt_flg": 1,
'nblss_dnt_st_tm': "2019-07-15 12:00:00.0", "nblss_dnt_st_tm": "2019-07-15 12:00:00.0",
'nblss_dnt_ed_tm': "2019-09-17 12:00:00.0", "nblss_dnt_ed_tm": "2019-09-17 12:00:00.0",
'nblss_ltt_st_tm': "2019-09-18 12:00:00.0", "nblss_ltt_st_tm": "2019-09-18 12:00:00.0",
'nblss_ltt_ed_tm': "2019-09-22 12:00:00.0", "nblss_ltt_ed_tm": "2019-09-22 12:00:00.0",
} }
encoded += urllib.parse.urlencode(params) encoded += urllib.parse.urlencode(params)
encoded = encoded.replace("+", "%20") encoded = encoded.replace("+", "%20")
encoded = encoded.replace("%2C", ",") encoded = encoded.replace("%2C", ",")
@ -122,7 +123,7 @@ class DivaBase():
response += f"&pvl_lut={self.time_lut}" response += f"&pvl_lut={self.time_lut}"
response += f"&pv_lst={pvlist}" response += f"&pv_lst={pvlist}"
return ( response ) return response
def handle_shop_catalog_request(self, data: Dict) -> Dict: def handle_shop_catalog_request(self, data: Dict) -> Dict:
catalog = "" catalog = ""
@ -137,7 +138,21 @@ class DivaBase():
else: else:
for shop in shopList: for shop in shopList:
line = str(shop["shopId"]) + "," + str(shop['unknown_0']) + "," + shop['name'] + "," + str(shop['points']) + "," + shop['start_date'] + "," + shop['end_date'] + "," + str(shop["type"]) line = (
str(shop["shopId"])
+ ","
+ str(shop["unknown_0"])
+ ","
+ shop["name"]
+ ","
+ str(shop["points"])
+ ","
+ shop["start_date"]
+ ","
+ shop["end_date"]
+ ","
+ str(shop["type"])
)
line = urllib.parse.quote(line) + "," line = urllib.parse.quote(line) + ","
catalog += f"{urllib.parse.quote(line)}" catalog += f"{urllib.parse.quote(line)}"
@ -146,7 +161,7 @@ class DivaBase():
response = f"&shp_ctlg_lut={self.time_lut}" response = f"&shp_ctlg_lut={self.time_lut}"
response += f"&shp_ctlg={catalog[:-3]}" response += f"&shp_ctlg={catalog[:-3]}"
return ( response ) return response
def handle_buy_module_request(self, data: Dict) -> Dict: def handle_buy_module_request(self, data: Dict) -> Dict:
profile = self.data.profile.get_profile(data["pd_id"], self.version) profile = self.data.profile.get_profile(data["pd_id"], self.version)
@ -162,10 +177,7 @@ class DivaBase():
new_vcld_pts = profile["vcld_pts"] - int(data["mdl_price"]) new_vcld_pts = profile["vcld_pts"] - int(data["mdl_price"])
self.data.profile.update_profile( self.data.profile.update_profile(profile["user"], vcld_pts=new_vcld_pts)
profile["user"],
vcld_pts=new_vcld_pts
)
self.data.module.put_module(data["pd_id"], self.version, data["mdl_id"]) self.data.module.put_module(data["pd_id"], self.version, data["mdl_id"])
# generate the mdl_have string # generate the mdl_have string
@ -191,7 +203,21 @@ class DivaBase():
else: else:
for item in itemList: for item in itemList:
line = str(item["itemId"]) + "," + str(item['unknown_0']) + "," + item['name'] + "," + str(item['points']) + "," + item['start_date'] + "," + item['end_date'] + "," + str(item["type"]) line = (
str(item["itemId"])
+ ","
+ str(item["unknown_0"])
+ ","
+ item["name"]
+ ","
+ str(item["points"])
+ ","
+ item["start_date"]
+ ","
+ item["end_date"]
+ ","
+ str(item["type"])
)
line = urllib.parse.quote(line) + "," line = urllib.parse.quote(line) + ","
catalog += f"{urllib.parse.quote(line)}" catalog += f"{urllib.parse.quote(line)}"
@ -200,11 +226,13 @@ class DivaBase():
response = f"&cstmz_itm_ctlg_lut={self.time_lut}" response = f"&cstmz_itm_ctlg_lut={self.time_lut}"
response += f"&cstmz_itm_ctlg={catalog[:-3]}" response += f"&cstmz_itm_ctlg={catalog[:-3]}"
return ( response ) return response
def handle_buy_cstmz_itm_request(self, data: Dict) -> Dict: def handle_buy_cstmz_itm_request(self, data: Dict) -> Dict:
profile = self.data.profile.get_profile(data["pd_id"], self.version) profile = self.data.profile.get_profile(data["pd_id"], self.version)
item = self.data.static.get_enabled_item(self.version, int(data["cstmz_itm_id"])) item = self.data.static.get_enabled_item(
self.version, int(data["cstmz_itm_id"])
)
# make sure module is available to purchase # make sure module is available to purchase
if not item: if not item:
@ -217,15 +245,16 @@ class DivaBase():
new_vcld_pts = profile["vcld_pts"] - int(data["cstmz_itm_price"]) new_vcld_pts = profile["vcld_pts"] - int(data["cstmz_itm_price"])
# save new Vocaloid Points balance # save new Vocaloid Points balance
self.data.profile.update_profile( self.data.profile.update_profile(profile["user"], vcld_pts=new_vcld_pts)
profile["user"],
vcld_pts=new_vcld_pts self.data.customize.put_customize_item(
data["pd_id"], self.version, data["cstmz_itm_id"]
) )
self.data.customize.put_customize_item(data["pd_id"], self.version, data["cstmz_itm_id"])
# generate the cstmz_itm_have string # generate the cstmz_itm_have string
cstmz_itm_have = self.data.customize.get_customize_items_have_string(data["pd_id"], self.version) cstmz_itm_have = self.data.customize.get_customize_items_have_string(
data["pd_id"], self.version
)
response = "&shp_rslt=1" response = "&shp_rslt=1"
response += f"&cstmz_itm_id={data['cstmz_itm_id']}" response += f"&cstmz_itm_id={data['cstmz_itm_id']}"
@ -237,33 +266,33 @@ class DivaBase():
def handle_festa_info_request(self, data: Dict) -> Dict: def handle_festa_info_request(self, data: Dict) -> Dict:
encoded = "&" encoded = "&"
params = { params = {
'fi_id': '1,-1', "fi_id": "1,-1",
'fi_name': f'{self.core_cfg.server.name} Opening,xxx', "fi_name": f"{self.core_cfg.server.name} Opening,xxx",
'fi_kind': '0,0', "fi_kind": "0,0",
'fi_difficulty': '-1,-1', "fi_difficulty": "-1,-1",
'fi_pv_id_lst': 'ALL,ALL', "fi_pv_id_lst": "ALL,ALL",
'fi_attr': '7FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF,7FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF', "fi_attr": "7FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF,7FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF",
'fi_add_vp': '20,0', "fi_add_vp": "20,0",
'fi_mul_vp': '1,1', "fi_mul_vp": "1,1",
'fi_st': '2022-06-17 17:00:00.0,2014-07-08 18:10:11.0', "fi_st": "2022-06-17 17:00:00.0,2014-07-08 18:10:11.0",
'fi_et': '2029-01-01 10:00:00.0,2014-07-08 18:10:11.0', "fi_et": "2029-01-01 10:00:00.0,2014-07-08 18:10:11.0",
'fi_lut': '{self.time_lut}', "fi_lut": "{self.time_lut}",
} }
encoded += urllib.parse.urlencode(params) encoded += urllib.parse.urlencode(params)
encoded = encoded.replace("+", "%20") encoded = encoded.replace("+", "%20")
encoded = encoded.replace("%2C", ",") encoded = encoded.replace("%2C", ",")
return encoded return encoded
def handle_contest_info_request(self, data: Dict) -> Dict: def handle_contest_info_request(self, data: Dict) -> Dict:
response = "" response = ""
response += f"&ci_lut={self.time_lut}" response += f"&ci_lut={self.time_lut}"
response += "&ci_str=%2A%2A%2A,%2A%2A%2A,%2A%2A%2A,%2A%2A%2A,%2A%2A%2A,%2A%2A%2A,%2A%2A%2A,%2A%2A%2A" response += "&ci_str=%2A%2A%2A,%2A%2A%2A,%2A%2A%2A,%2A%2A%2A,%2A%2A%2A,%2A%2A%2A,%2A%2A%2A,%2A%2A%2A"
return ( response ) return response
def handle_qst_inf_request(self, data: Dict) -> Dict: def handle_qst_inf_request(self, data: Dict) -> Dict:
quest = "" quest = ""
@ -279,11 +308,31 @@ class DivaBase():
response += f"&qhi_str={quest[:-1]}" response += f"&qhi_str={quest[:-1]}"
else: else:
for quests in questList: for quests in questList:
line = str(quests["questId"]) + "," + str(quests['quest_order']) + "," + str(quests['kind']) + "," + str(quests['unknown_0']) + "," + quests['start_datetime'] + "," + quests['end_datetime'] + "," + quests["name"] + "," + str(quests["unknown_1"]) + "," + str(quests["unknown_2"]) + "," + str(quests["quest_enable"]) line = (
str(quests["questId"])
+ ","
+ str(quests["quest_order"])
+ ","
+ str(quests["kind"])
+ ","
+ str(quests["unknown_0"])
+ ","
+ quests["start_datetime"]
+ ","
+ quests["end_datetime"]
+ ","
+ quests["name"]
+ ","
+ str(quests["unknown_1"])
+ ","
+ str(quests["unknown_2"])
+ ","
+ str(quests["quest_enable"])
)
quest += f"{urllib.parse.quote(line)}%0A," quest += f"{urllib.parse.quote(line)}%0A,"
responseline = f"{quest[:-1]}," responseline = f"{quest[:-1]},"
for i in range(len(questList),59): for i in range(len(questList), 59):
responseline += "%2A%2A%2A%0A," responseline += "%2A%2A%2A%0A,"
response = "" response = ""
@ -292,44 +341,44 @@ class DivaBase():
response += "&qrai_str=%2D1%2C%2D1%2C%2D1%2C%2D1%2C%2D1%2C%2D1%2C%2D1%2C%2D1%2C%2D1%2C%2D1%2C%2D1,%2D1%2C%2D1%2C%2D1%2C%2D1%2C%2D1%2C%2D1%2C%2D1%2C%2D1%2C%2D1%2C%2D1%2C%2D1,%2D1%2C%2D1%2C%2D1%2C%2D1%2C%2D1%2C%2D1%2C%2D1%2C%2D1%2C%2D1%2C%2D1%2C%2D1" response += "&qrai_str=%2D1%2C%2D1%2C%2D1%2C%2D1%2C%2D1%2C%2D1%2C%2D1%2C%2D1%2C%2D1%2C%2D1%2C%2D1,%2D1%2C%2D1%2C%2D1%2C%2D1%2C%2D1%2C%2D1%2C%2D1%2C%2D1%2C%2D1%2C%2D1%2C%2D1,%2D1%2C%2D1%2C%2D1%2C%2D1%2C%2D1%2C%2D1%2C%2D1%2C%2D1%2C%2D1%2C%2D1%2C%2D1"
return ( response ) return response
def handle_nv_ranking_request(self, data: Dict) -> Dict: def handle_nv_ranking_request(self, data: Dict) -> Dict:
return ( f'' ) return f""
def handle_ps_ranking_request(self, data: Dict) -> Dict: def handle_ps_ranking_request(self, data: Dict) -> Dict:
return ( f'' ) return f""
def handle_ng_word_request(self, data: Dict) -> Dict: def handle_ng_word_request(self, data: Dict) -> Dict:
return ( f'' ) return f""
def handle_rmt_wp_list_request(self, data: Dict) -> Dict: def handle_rmt_wp_list_request(self, data: Dict) -> Dict:
return ( f'' ) return f""
def handle_pv_def_chr_list_request(self, data: Dict) -> Dict: def handle_pv_def_chr_list_request(self, data: Dict) -> Dict:
return ( f'' ) return f""
def handle_pv_ng_mdl_list_request(self, data: Dict) -> Dict: def handle_pv_ng_mdl_list_request(self, data: Dict) -> Dict:
return ( f'' ) return f""
def handle_cstmz_itm_ng_mdl_lst_request(self, data: Dict) -> Dict: def handle_cstmz_itm_ng_mdl_lst_request(self, data: Dict) -> Dict:
return ( f'' ) return f""
def handle_banner_info_request(self, data: Dict) -> Dict: def handle_banner_info_request(self, data: Dict) -> Dict:
return ( f'' ) return f""
def handle_banner_data_request(self, data: Dict) -> Dict: def handle_banner_data_request(self, data: Dict) -> Dict:
return ( f'' ) return f""
def handle_cm_ply_info_request(self, data: Dict) -> Dict: def handle_cm_ply_info_request(self, data: Dict) -> Dict:
return ( f'' ) return f""
def handle_pstd_h_ctrl_request(self, data: Dict) -> Dict: def handle_pstd_h_ctrl_request(self, data: Dict) -> Dict:
return ( f'' ) return f""
def handle_pstd_item_ng_lst_request(self, data: Dict) -> Dict: def handle_pstd_item_ng_lst_request(self, data: Dict) -> Dict:
return ( f'' ) return f""
def handle_pre_start_request(self, data: Dict) -> str: def handle_pre_start_request(self, data: Dict) -> str:
profile = self.data.profile.get_profile(data["aime_id"], self.version) profile = self.data.profile.get_profile(data["aime_id"], self.version)
profile_shop = self.data.item.get_shop(data["aime_id"], self.version) profile_shop = self.data.item.get_shop(data["aime_id"], self.version)
@ -372,8 +421,10 @@ class DivaBase():
return response return response
def handle_registration_request(self, data: Dict) -> Dict: def handle_registration_request(self, data: Dict) -> Dict:
self.data.profile.create_profile(self.version, data["aime_id"], data["player_name"]) self.data.profile.create_profile(
return (f"&cd_adm_result=1&pd_id={data['aime_id']}") self.version, data["aime_id"], data["player_name"]
)
return f"&cd_adm_result=1&pd_id={data['aime_id']}"
def handle_start_request(self, data: Dict) -> Dict: def handle_start_request(self, data: Dict) -> Dict:
profile = self.data.profile.get_profile(data["pd_id"], self.version) profile = self.data.profile.get_profile(data["pd_id"], self.version)
@ -384,12 +435,16 @@ class DivaBase():
mdl_have = "F" * 250 mdl_have = "F" * 250
# generate the mdl_have string if "unlock_all_modules" is disabled # generate the mdl_have string if "unlock_all_modules" is disabled
if not self.game_config.mods.unlock_all_modules: if not self.game_config.mods.unlock_all_modules:
mdl_have = self.data.module.get_modules_have_string(data["pd_id"], self.version) mdl_have = self.data.module.get_modules_have_string(
data["pd_id"], self.version
)
cstmz_itm_have = "F" * 250 cstmz_itm_have = "F" * 250
# generate the cstmz_itm_have string if "unlock_all_items" is disabled # generate the cstmz_itm_have string if "unlock_all_items" is disabled
if not self.game_config.mods.unlock_all_items: if not self.game_config.mods.unlock_all_items:
cstmz_itm_have = self.data.customize.get_customize_items_have_string(data["pd_id"], self.version) cstmz_itm_have = self.data.customize.get_customize_items_have_string(
data["pd_id"], self.version
)
response = f"&pd_id={data['pd_id']}" response = f"&pd_id={data['pd_id']}"
response += "&start_result=1" response += "&start_result=1"
@ -452,15 +507,16 @@ class DivaBase():
response += f"&mdl_eqp_ary={mdl_eqp_ary}" response += f"&mdl_eqp_ary={mdl_eqp_ary}"
response += f"&c_itm_eqp_ary={c_itm_eqp_ary}" response += f"&c_itm_eqp_ary={c_itm_eqp_ary}"
response += f"&ms_itm_flg_ary={ms_itm_flg_ary}" response += f"&ms_itm_flg_ary={ms_itm_flg_ary}"
return ( response ) return response
def handle_pd_unlock_request(self, data: Dict) -> Dict: def handle_pd_unlock_request(self, data: Dict) -> Dict:
return ( f'' ) return f""
def handle_spend_credit_request(self, data: Dict) -> Dict: def handle_spend_credit_request(self, data: Dict) -> Dict:
profile = self.data.profile.get_profile(data["pd_id"], self.version) profile = self.data.profile.get_profile(data["pd_id"], self.version)
if profile is None: return if profile is None:
return
response = "" response = ""
@ -471,10 +527,16 @@ class DivaBase():
response += f"&lv_efct_id={profile['lv_efct_id']}" response += f"&lv_efct_id={profile['lv_efct_id']}"
response += f"&lv_plt_id={profile['lv_plt_id']}" response += f"&lv_plt_id={profile['lv_plt_id']}"
return ( response ) return response
def _get_pv_pd_result(self, song: int, pd_db_song: Dict, pd_db_ranking: Dict, def _get_pv_pd_result(
pd_db_customize: Dict, edition: int) -> str: self,
song: int,
pd_db_song: Dict,
pd_db_ranking: Dict,
pd_db_customize: Dict,
edition: int,
) -> str:
""" """
Helper function to generate the pv_result string for every song, ranking and edition Helper function to generate the pv_result string for every song, ranking and edition
""" """
@ -483,7 +545,7 @@ class DivaBase():
# make sure there are enough max scores to calculate a ranking # make sure there are enough max scores to calculate a ranking
if pd_db_ranking["ranking"] != 0: if pd_db_ranking["ranking"] != 0:
global_ranking = pd_db_ranking["ranking"] global_ranking = pd_db_ranking["ranking"]
# pv_no # pv_no
pv_result = f"{song}," pv_result = f"{song},"
# edition # edition
@ -513,7 +575,7 @@ class DivaBase():
f"{pd_db_customize['chsld_se']}," f"{pd_db_customize['chsld_se']},"
f"{pd_db_customize['sldtch_se']}" f"{pd_db_customize['sldtch_se']}"
) )
pv_result += f"{module_eqp}," pv_result += f"{module_eqp},"
pv_result += f"{customize_eqp}," pv_result += f"{customize_eqp},"
pv_result += f"{customize_flag}," pv_result += f"{customize_flag},"
@ -537,21 +599,35 @@ class DivaBase():
if int(song) > 0: if int(song) > 0:
# the request do not send a edition so just perform a query best score and ranking for each edition. # the request do not send a edition so just perform a query best score and ranking for each edition.
# 0=ORIGINAL, 1=EXTRA # 0=ORIGINAL, 1=EXTRA
pd_db_song_0 = self.data.score.get_best_user_score(data["pd_id"], int(song), data["difficulty"], edition=0) pd_db_song_0 = self.data.score.get_best_user_score(
pd_db_song_1 = self.data.score.get_best_user_score(data["pd_id"], int(song), data["difficulty"], edition=1) data["pd_id"], int(song), data["difficulty"], edition=0
)
pd_db_song_1 = self.data.score.get_best_user_score(
data["pd_id"], int(song), data["difficulty"], edition=1
)
pd_db_ranking_0, pd_db_ranking_1 = None, None pd_db_ranking_0, pd_db_ranking_1 = None, None
if pd_db_song_0: if pd_db_song_0:
pd_db_ranking_0 = self.data.score.get_global_ranking(data["pd_id"], int(song), data["difficulty"], edition=0) pd_db_ranking_0 = self.data.score.get_global_ranking(
data["pd_id"], int(song), data["difficulty"], edition=0
)
if pd_db_song_1: if pd_db_song_1:
pd_db_ranking_1 = self.data.score.get_global_ranking(data["pd_id"], int(song), data["difficulty"], edition=1) pd_db_ranking_1 = self.data.score.get_global_ranking(
data["pd_id"], int(song), data["difficulty"], edition=1
)
pd_db_customize = self.data.pv_customize.get_pv_customize(
data["pd_id"], int(song)
)
pd_db_customize = self.data.pv_customize.get_pv_customize(data["pd_id"], int(song))
# generate the pv_result string with the ORIGINAL edition and the EXTRA edition appended # generate the pv_result string with the ORIGINAL edition and the EXTRA edition appended
pv_result = self._get_pv_pd_result(int(song), pd_db_song_0, pd_db_ranking_0, pd_db_customize, edition=0) pv_result = self._get_pv_pd_result(
pv_result += "," + self._get_pv_pd_result(int(song), pd_db_song_1, pd_db_ranking_1, pd_db_customize, edition=1) int(song), pd_db_song_0, pd_db_ranking_0, pd_db_customize, edition=0
)
pv_result += "," + self._get_pv_pd_result(
int(song), pd_db_song_1, pd_db_ranking_1, pd_db_customize, edition=1
)
self.logger.debug(f"pv_result = {pv_result}") self.logger.debug(f"pv_result = {pv_result}")
@ -565,13 +641,12 @@ class DivaBase():
response += "&pdddt_flg=0" response += "&pdddt_flg=0"
response += f"&pdddt_tm={self.time_lut}" response += f"&pdddt_tm={self.time_lut}"
return ( response ) return response
def handle_stage_start_request(self, data: Dict) -> Dict: def handle_stage_start_request(self, data: Dict) -> Dict:
return ( f'' ) return f""
def handle_stage_result_request(self, data: Dict) -> Dict: def handle_stage_result_request(self, data: Dict) -> Dict:
profile = self.data.profile.get_profile(data["pd_id"], self.version) profile = self.data.profile.get_profile(data["pd_id"], self.version)
pd_song_list = data["stg_ply_pv_id"].split(",") pd_song_list = data["stg_ply_pv_id"].split(",")
@ -590,15 +665,100 @@ class DivaBase():
for index, value in enumerate(pd_song_list): for index, value in enumerate(pd_song_list):
if "-1" not in pd_song_list[index]: if "-1" not in pd_song_list[index]:
profile_pd_db_song = self.data.score.get_best_user_score(data["pd_id"], pd_song_list[index], pd_song_difficulty[index], pd_song_edition[index]) profile_pd_db_song = self.data.score.get_best_user_score(
data["pd_id"],
pd_song_list[index],
pd_song_difficulty[index],
pd_song_edition[index],
)
if profile_pd_db_song is None: if profile_pd_db_song is None:
self.data.score.put_best_score(data["pd_id"], self.version, pd_song_list[index], pd_song_difficulty[index], pd_song_edition[index], pd_song_max_score[index], pd_song_max_atn_pnt[index], pd_song_ranking[index], pd_song_sort_kind, pd_song_cool_cnt[index], pd_song_fine_cnt[index], pd_song_safe_cnt[index], pd_song_sad_cnt[index], pd_song_worst_cnt[index], pd_song_max_combo[index]) self.data.score.put_best_score(
self.data.score.put_playlog(data["pd_id"], self.version, pd_song_list[index], pd_song_difficulty[index], pd_song_edition[index], pd_song_max_score[index], pd_song_max_atn_pnt[index], pd_song_ranking[index], pd_song_sort_kind, pd_song_cool_cnt[index], pd_song_fine_cnt[index], pd_song_safe_cnt[index], pd_song_sad_cnt[index], pd_song_worst_cnt[index], pd_song_max_combo[index]) data["pd_id"],
self.version,
pd_song_list[index],
pd_song_difficulty[index],
pd_song_edition[index],
pd_song_max_score[index],
pd_song_max_atn_pnt[index],
pd_song_ranking[index],
pd_song_sort_kind,
pd_song_cool_cnt[index],
pd_song_fine_cnt[index],
pd_song_safe_cnt[index],
pd_song_sad_cnt[index],
pd_song_worst_cnt[index],
pd_song_max_combo[index],
)
self.data.score.put_playlog(
data["pd_id"],
self.version,
pd_song_list[index],
pd_song_difficulty[index],
pd_song_edition[index],
pd_song_max_score[index],
pd_song_max_atn_pnt[index],
pd_song_ranking[index],
pd_song_sort_kind,
pd_song_cool_cnt[index],
pd_song_fine_cnt[index],
pd_song_safe_cnt[index],
pd_song_sad_cnt[index],
pd_song_worst_cnt[index],
pd_song_max_combo[index],
)
elif int(pd_song_max_score[index]) >= int(profile_pd_db_song["score"]): elif int(pd_song_max_score[index]) >= int(profile_pd_db_song["score"]):
self.data.score.put_best_score(data["pd_id"], self.version, pd_song_list[index], pd_song_difficulty[index], pd_song_edition[index], pd_song_max_score[index], pd_song_max_atn_pnt[index], pd_song_ranking[index], pd_song_sort_kind, pd_song_cool_cnt[index], pd_song_fine_cnt[index], pd_song_safe_cnt[index], pd_song_sad_cnt[index], pd_song_worst_cnt[index], pd_song_max_combo[index]) self.data.score.put_best_score(
self.data.score.put_playlog(data["pd_id"], self.version, pd_song_list[index], pd_song_difficulty[index], pd_song_edition[index], pd_song_max_score[index], pd_song_max_atn_pnt[index], pd_song_ranking[index], pd_song_sort_kind, pd_song_cool_cnt[index], pd_song_fine_cnt[index], pd_song_safe_cnt[index], pd_song_sad_cnt[index], pd_song_worst_cnt[index], pd_song_max_combo[index]) data["pd_id"],
self.version,
pd_song_list[index],
pd_song_difficulty[index],
pd_song_edition[index],
pd_song_max_score[index],
pd_song_max_atn_pnt[index],
pd_song_ranking[index],
pd_song_sort_kind,
pd_song_cool_cnt[index],
pd_song_fine_cnt[index],
pd_song_safe_cnt[index],
pd_song_sad_cnt[index],
pd_song_worst_cnt[index],
pd_song_max_combo[index],
)
self.data.score.put_playlog(
data["pd_id"],
self.version,
pd_song_list[index],
pd_song_difficulty[index],
pd_song_edition[index],
pd_song_max_score[index],
pd_song_max_atn_pnt[index],
pd_song_ranking[index],
pd_song_sort_kind,
pd_song_cool_cnt[index],
pd_song_fine_cnt[index],
pd_song_safe_cnt[index],
pd_song_sad_cnt[index],
pd_song_worst_cnt[index],
pd_song_max_combo[index],
)
elif int(pd_song_max_score[index]) != int(profile_pd_db_song["score"]): elif int(pd_song_max_score[index]) != int(profile_pd_db_song["score"]):
self.data.score.put_playlog(data["pd_id"], self.version, pd_song_list[index], pd_song_difficulty[index], pd_song_edition[index], pd_song_max_score[index], pd_song_max_atn_pnt[index], pd_song_ranking[index], pd_song_sort_kind, pd_song_cool_cnt[index], pd_song_fine_cnt[index], pd_song_safe_cnt[index], pd_song_sad_cnt[index], pd_song_worst_cnt[index], pd_song_max_combo[index]) self.data.score.put_playlog(
data["pd_id"],
self.version,
pd_song_list[index],
pd_song_difficulty[index],
pd_song_edition[index],
pd_song_max_score[index],
pd_song_max_atn_pnt[index],
pd_song_ranking[index],
pd_song_sort_kind,
pd_song_cool_cnt[index],
pd_song_fine_cnt[index],
pd_song_safe_cnt[index],
pd_song_sad_cnt[index],
pd_song_worst_cnt[index],
pd_song_max_combo[index],
)
# Profile saving based on registration list # Profile saving based on registration list
@ -608,7 +768,7 @@ class DivaBase():
total_atn_pnt = 0 total_atn_pnt = 0
for best_score in best_scores: for best_score in best_scores:
total_atn_pnt += best_score["atn_pnt"] total_atn_pnt += best_score["atn_pnt"]
new_level = (total_atn_pnt // 13979) + 1 new_level = (total_atn_pnt // 13979) + 1
new_level_pnt = round((total_atn_pnt % 13979) / 13979 * 100) new_level_pnt = round((total_atn_pnt % 13979) / 13979 * 100)
@ -630,7 +790,7 @@ class DivaBase():
nxt_dffclty=int(data["nxt_dffclty"]), nxt_dffclty=int(data["nxt_dffclty"]),
nxt_edtn=int(data["nxt_edtn"]), nxt_edtn=int(data["nxt_edtn"]),
my_qst_id=data["my_qst_id"], my_qst_id=data["my_qst_id"],
my_qst_sts=data["my_qst_sts"] my_qst_sts=data["my_qst_sts"],
) )
response += f"&lv_num={new_level}" response += f"&lv_num={new_level}"
@ -663,35 +823,51 @@ class DivaBase():
response += "&my_ccd_r_hnd=-1,-1,-1,-1,-1" response += "&my_ccd_r_hnd=-1,-1,-1,-1,-1"
response += "&my_ccd_r_vp=-1,-1,-1,-1,-1" response += "&my_ccd_r_vp=-1,-1,-1,-1,-1"
return ( response ) return response
def handle_end_request(self, data: Dict) -> Dict: def handle_end_request(self, data: Dict) -> Dict:
profile = self.data.profile.get_profile(data["pd_id"], self.version) profile = self.data.profile.get_profile(data["pd_id"], self.version)
self.data.profile.update_profile( self.data.profile.update_profile(
profile["user"], profile["user"], my_qst_id=data["my_qst_id"], my_qst_sts=data["my_qst_sts"]
my_qst_id=data["my_qst_id"],
my_qst_sts=data["my_qst_sts"]
) )
return (f'') return f""
def handle_shop_exit_request(self, data: Dict) -> Dict: def handle_shop_exit_request(self, data: Dict) -> Dict:
self.data.item.put_shop(data["pd_id"], self.version, data["mdl_eqp_cmn_ary"], data["c_itm_eqp_cmn_ary"], data["ms_itm_flg_cmn_ary"]) self.data.item.put_shop(
data["pd_id"],
self.version,
data["mdl_eqp_cmn_ary"],
data["c_itm_eqp_cmn_ary"],
data["ms_itm_flg_cmn_ary"],
)
if int(data["use_pv_mdl_eqp"]) == 1: if int(data["use_pv_mdl_eqp"]) == 1:
self.data.pv_customize.put_pv_customize(data["pd_id"], self.version, data["ply_pv_id"], self.data.pv_customize.put_pv_customize(
data["mdl_eqp_pv_ary"], data["c_itm_eqp_pv_ary"], data["ms_itm_flg_pv_ary"]) data["pd_id"],
self.version,
data["ply_pv_id"],
data["mdl_eqp_pv_ary"],
data["c_itm_eqp_pv_ary"],
data["ms_itm_flg_pv_ary"],
)
else: else:
self.data.pv_customize.put_pv_customize(data["pd_id"], self.version, data["ply_pv_id"], self.data.pv_customize.put_pv_customize(
"-1,-1,-1", "-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1", "1,1,1,1,1,1,1,1,1,1,1,1") data["pd_id"],
self.version,
data["ply_pv_id"],
"-1,-1,-1",
"-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1",
"1,1,1,1,1,1,1,1,1,1,1,1",
)
response = "&shp_rslt=1" response = "&shp_rslt=1"
return ( response ) return response
def handle_card_procedure_request(self, data: Dict) -> str: def handle_card_procedure_request(self, data: Dict) -> str:
profile = self.data.profile.get_profile(data["aime_id"], self.version) profile = self.data.profile.get_profile(data["aime_id"], self.version)
if profile is None: if profile is None:
return "&cd_adm_result=0" return "&cd_adm_result=0"
response = "&cd_adm_result=1" response = "&cd_adm_result=1"
response += "&chg_name_price=100" response += "&chg_name_price=100"
response += "&accept_idx=100" response += "&accept_idx=100"
@ -706,20 +882,18 @@ class DivaBase():
response += f"&passwd_stat={profile['passwd_stat']}" response += f"&passwd_stat={profile['passwd_stat']}"
return response return response
def handle_change_name_request(self, data: Dict) -> str: def handle_change_name_request(self, data: Dict) -> str:
profile = self.data.profile.get_profile(data["pd_id"], self.version) profile = self.data.profile.get_profile(data["pd_id"], self.version)
# make sure user has enough Vocaloid Points # make sure user has enough Vocaloid Points
if profile["vcld_pts"] < int(data["chg_name_price"]): if profile["vcld_pts"] < int(data["chg_name_price"]):
return "&cd_adm_result=0" return "&cd_adm_result=0"
# update the vocaloid points and player name # update the vocaloid points and player name
new_vcld_pts = profile["vcld_pts"] - int(data["chg_name_price"]) new_vcld_pts = profile["vcld_pts"] - int(data["chg_name_price"])
self.data.profile.update_profile( self.data.profile.update_profile(
profile["user"], profile["user"], player_name=data["player_name"], vcld_pts=new_vcld_pts
player_name=data["player_name"],
vcld_pts=new_vcld_pts
) )
response = "&cd_adm_result=1" response = "&cd_adm_result=1"
@ -728,19 +902,17 @@ class DivaBase():
response += f"&player_name={data['player_name']}" response += f"&player_name={data['player_name']}"
return response return response
def handle_change_passwd_request(self, data: Dict) -> str: def handle_change_passwd_request(self, data: Dict) -> str:
profile = self.data.profile.get_profile(data["pd_id"], self.version) profile = self.data.profile.get_profile(data["pd_id"], self.version)
# TODO: return correct error number instead of 0 # TODO: return correct error number instead of 0
if (data["passwd"] != profile["passwd"]): if data["passwd"] != profile["passwd"]:
return "&cd_adm_result=0" return "&cd_adm_result=0"
# set password to true and update the saved password # set password to true and update the saved password
self.data.profile.update_profile( self.data.profile.update_profile(
profile["user"], profile["user"], passwd_stat=1, passwd=data["new_passwd"]
passwd_stat=1,
passwd=data["new_passwd"]
) )
response = "&cd_adm_result=1" response = "&cd_adm_result=1"

View File

@ -1,30 +1,40 @@
from core.config import CoreConfig from core.config import CoreConfig
class DivaServerConfig(): class DivaServerConfig:
def __init__(self, parent_config: "DivaConfig") -> None: def __init__(self, parent_config: "DivaConfig") -> 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, 'diva', 'server', 'enable', default=True) return CoreConfig.get_config_field(
self.__config, "diva", "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, 'diva', 'server', 'loglevel', default="info")) return CoreConfig.str_to_loglevel(
CoreConfig.get_config_field(
self.__config, "diva", "server", "loglevel", default="info"
)
)
class DivaModsConfig(): class DivaModsConfig:
def __init__(self, parent_config: "DivaConfig") -> None: def __init__(self, parent_config: "DivaConfig") -> None:
self.__config = parent_config self.__config = parent_config
@property @property
def unlock_all_modules(self) -> bool: def unlock_all_modules(self) -> bool:
return CoreConfig.get_config_field(self.__config, 'diva', 'mods', 'unlock_all_modules', default=True) return CoreConfig.get_config_field(
self.__config, "diva", "mods", "unlock_all_modules", default=True
)
@property @property
def unlock_all_items(self) -> bool: def unlock_all_items(self) -> bool:
return CoreConfig.get_config_field(self.__config, 'diva', 'mods', 'unlock_all_items', default=True) return CoreConfig.get_config_field(
self.__config, "diva", "mods", "unlock_all_items", default=True
)
class DivaConfig(dict): class DivaConfig(dict):

View File

@ -1,4 +1,4 @@
class DivaConstants(): class DivaConstants:
GAME_CODE = "SBZV" GAME_CODE = "SBZV"
CONFIG_NAME = "diva.yaml" CONFIG_NAME = "diva.yaml"
@ -10,4 +10,4 @@ class DivaConstants():
@classmethod @classmethod
def game_ver_to_string(cls, ver: int): def game_ver_to_string(cls, ver: int):
return cls.VERSION_NAMES[ver] return cls.VERSION_NAMES[ver]

View File

@ -1,6 +1,14 @@
from core.data import Data from core.data import Data
from core.config import CoreConfig from core.config import CoreConfig
from titles.diva.schema import DivaProfileData, DivaScoreData, DivaModuleData, DivaCustomizeItemData, DivaPvCustomizeData, DivaItemData, DivaStaticData from titles.diva.schema import (
DivaProfileData,
DivaScoreData,
DivaModuleData,
DivaCustomizeItemData,
DivaPvCustomizeData,
DivaItemData,
DivaStaticData,
)
class DivaData(Data): class DivaData(Data):

View File

@ -14,85 +14,112 @@ from titles.diva.config import DivaConfig
from titles.diva.const import DivaConstants from titles.diva.const import DivaConstants
from titles.diva.base import DivaBase from titles.diva.base import DivaBase
class DivaServlet():
class DivaServlet:
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 = DivaConfig() self.game_cfg = DivaConfig()
if path.exists(f"{cfg_dir}/{DivaConstants.CONFIG_NAME}"): if path.exists(f"{cfg_dir}/{DivaConstants.CONFIG_NAME}"):
self.game_cfg.update(yaml.safe_load(open(f"{cfg_dir}/{DivaConstants.CONFIG_NAME}"))) self.game_cfg.update(
yaml.safe_load(open(f"{cfg_dir}/{DivaConstants.CONFIG_NAME}"))
)
self.base = DivaBase(core_cfg, self.game_cfg) self.base = DivaBase(core_cfg, self.game_cfg)
self.logger = logging.getLogger("diva") self.logger = logging.getLogger("diva")
log_fmt_str = "[%(asctime)s] Diva | %(levelname)s | %(message)s" log_fmt_str = "[%(asctime)s] Diva | %(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, "diva"), encoding='utf8', fileHandler = TimedRotatingFileHandler(
when="d", backupCount=10) "{0}/{1}.log".format(self.core_cfg.server.log_dir, "diva"),
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
)
@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 = DivaConfig() game_cfg = DivaConfig()
if path.exists(f"{cfg_dir}/{DivaConstants.CONFIG_NAME}"): if path.exists(f"{cfg_dir}/{DivaConstants.CONFIG_NAME}"):
game_cfg.update(yaml.safe_load(open(f"{cfg_dir}/{DivaConstants.CONFIG_NAME}"))) game_cfg.update(
yaml.safe_load(open(f"{cfg_dir}/{DivaConstants.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, req: Request, version: int, url_path: str) -> bytes: def render_POST(self, req: Request, version: int, url_path: str) -> bytes:
req_raw = req.content.getvalue() req_raw = req.content.getvalue()
url_header = req.getAllHeaders() url_header = req.getAllHeaders()
#Ping Dispatch # Ping Dispatch
if "THIS_STRING_SEPARATES"in str(url_header): if "THIS_STRING_SEPARATES" in str(url_header):
binary_request = req_raw.splitlines() binary_request = req_raw.splitlines()
binary_cmd_decoded = binary_request[3].decode("utf-8") binary_cmd_decoded = binary_request[3].decode("utf-8")
binary_array = binary_cmd_decoded.split('&') binary_array = binary_cmd_decoded.split("&")
bin_req_data = {} bin_req_data = {}
for kvp in binary_array: for kvp in binary_array:
split_bin = kvp.split("=") split_bin = kvp.split("=")
bin_req_data[split_bin[0]] = split_bin[1] bin_req_data[split_bin[0]] = split_bin[1]
self.logger.info(f"Binary {bin_req_data['cmd']} Request") self.logger.info(f"Binary {bin_req_data['cmd']} Request")
self.logger.debug(bin_req_data) self.logger.debug(bin_req_data)
handler = getattr(self.base, f"handle_{bin_req_data['cmd']}_request") handler = getattr(self.base, f"handle_{bin_req_data['cmd']}_request")
resp = handler(bin_req_data) resp = handler(bin_req_data)
self.logger.debug(f"Response cmd={bin_req_data['cmd']}&req_id={bin_req_data['req_id']}&stat=ok{resp}") self.logger.debug(
return f"cmd={bin_req_data['cmd']}&req_id={bin_req_data['req_id']}&stat=ok{resp}".encode('utf-8') f"Response cmd={bin_req_data['cmd']}&req_id={bin_req_data['req_id']}&stat=ok{resp}"
)
return f"cmd={bin_req_data['cmd']}&req_id={bin_req_data['req_id']}&stat=ok{resp}".encode(
"utf-8"
)
# Main Dispatch
json_string = json.dumps(
req_raw.decode("utf-8")
) # Take the response and decode as UTF-8 and dump
b64string = json_string.replace(
r"\n", "\n"
) # Remove all \n and separate them as new lines
gz_string = base64.b64decode(b64string) # Decompressing the base64 string
#Main Dispatch
json_string = json.dumps(req_raw.decode("utf-8")) #Take the response and decode as UTF-8 and dump
b64string = json_string.replace(r'\n', '\n') # Remove all \n and separate them as new lines
gz_string = base64.b64decode(b64string) # Decompressing the base64 string
try: try:
url_data = zlib.decompress( gz_string ).decode("utf-8") # Decompressing the gzip url_data = zlib.decompress(gz_string).decode(
"utf-8"
) # Decompressing the gzip
except zlib.error as e: except zlib.error as e:
self.logger.error(f"Failed to defalte! {e} -> {gz_string}") self.logger.error(f"Failed to defalte! {e} -> {gz_string}")
return "stat=0" return "stat=0"
req_kvp = urllib.parse.unquote(url_data) req_kvp = urllib.parse.unquote(url_data)
req_data = {} req_data = {}
# We then need to split each parts with & so we can reuse them to fill out the requests # We then need to split each parts with & so we can reuse them to fill out the requests
splitted_request = str.split(req_kvp, "&") splitted_request = str.split(req_kvp, "&")
for kvp in splitted_request: for kvp in splitted_request:
@ -109,15 +136,25 @@ class DivaServlet():
handler = getattr(self.base, func_to_find) handler = getattr(self.base, func_to_find)
resp = handler(req_data) resp = handler(req_data)
except AttributeError as e: except AttributeError as e:
self.logger.warning(f"Unhandled {req_data['cmd']} request {e}") self.logger.warning(f"Unhandled {req_data['cmd']} request {e}")
return f"cmd={req_data['cmd']}&req_id={req_data['req_id']}&stat=ok".encode('utf-8') return f"cmd={req_data['cmd']}&req_id={req_data['req_id']}&stat=ok".encode(
"utf-8"
)
except Exception as e: except Exception as e:
self.logger.error(f"Error handling method {func_to_find} {e}") self.logger.error(f"Error handling method {func_to_find} {e}")
return f"cmd={req_data['cmd']}&req_id={req_data['req_id']}&stat=ok".encode('utf-8') return f"cmd={req_data['cmd']}&req_id={req_data['req_id']}&stat=ok".encode(
"utf-8"
)
req.responseHeaders.addRawHeader(b"content-type", b"text/plain") req.responseHeaders.addRawHeader(b"content-type", b"text/plain")
self.logger.debug(f"Response cmd={req_data['cmd']}&req_id={req_data['req_id']}&stat=ok{resp}") self.logger.debug(
f"Response cmd={req_data['cmd']}&req_id={req_data['req_id']}&stat=ok{resp}"
)
return f"cmd={req_data['cmd']}&req_id={req_data['req_id']}&stat=ok{resp}".encode('utf-8') return (
f"cmd={req_data['cmd']}&req_id={req_data['req_id']}&stat=ok{resp}".encode(
"utf-8"
)
)

View File

@ -7,13 +7,23 @@ from core.config import CoreConfig
from titles.diva.database import DivaData from titles.diva.database import DivaData
from titles.diva.const import DivaConstants from titles.diva.const import DivaConstants
class DivaReader(BaseReader): class DivaReader(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 = DivaData(config) self.data = DivaData(config)
try: try:
self.logger.info(f"Start importer for {DivaConstants.game_ver_to_string(version)}") self.logger.info(
f"Start importer for {DivaConstants.game_ver_to_string(version)}"
)
except IndexError: except IndexError:
self.logger.error(f"Invalid project diva version {version}") self.logger.error(f"Invalid project diva version {version}")
exit(1) exit(1)
@ -30,7 +40,7 @@ class DivaReader(BaseReader):
if not path.exists(f"{self.bin_dir}/rom"): if not path.exists(f"{self.bin_dir}/rom"):
self.logger.warn(f"Couldn't find rom folder in {self.bin_dir}, skipping") self.logger.warn(f"Couldn't find rom folder in {self.bin_dir}, skipping")
pull_bin_rom = False pull_bin_rom = False
if self.opt_dir is not None: if self.opt_dir is not None:
opt_dirs = self.get_data_directories(self.opt_dir) opt_dirs = self.get_data_directories(self.opt_dir)
else: else:
@ -44,18 +54,25 @@ class DivaReader(BaseReader):
if pull_opt_rom: if pull_opt_rom:
for dir in opt_dirs: for dir in opt_dirs:
self.read_rom(f"{dir}/rom") self.read_rom(f"{dir}/rom")
def read_ram(self, ram_root_dir: str) -> None: def read_ram(self, ram_root_dir: str) -> None:
self.logger.info(f"Read RAM from {ram_root_dir}") self.logger.info(f"Read RAM from {ram_root_dir}")
if path.exists(f"{ram_root_dir}/databank"): if path.exists(f"{ram_root_dir}/databank"):
for root, dirs, files in walk(f"{ram_root_dir}/databank"): for root, dirs, files in walk(f"{ram_root_dir}/databank"):
for file in files: for file in files:
if file.startswith("ShopCatalog_") or file.startswith("CustomizeItemCatalog_") or \ if (
(file.startswith("QuestInfo") and not file.startswith("QuestInfoTm")): file.startswith("ShopCatalog_")
or file.startswith("CustomizeItemCatalog_")
or (
file.startswith("QuestInfo")
and not file.startswith("QuestInfoTm")
)
):
with open(f"{root}/{file}", "r") as f: with open(f"{root}/{file}", "r") as f:
file_data: str = urllib.parse.unquote(urllib.parse.unquote(f.read())) file_data: str = urllib.parse.unquote(
urllib.parse.unquote(f.read())
)
if file_data == "***": if file_data == "***":
self.logger.info(f"{file} is empty, skipping") self.logger.info(f"{file} is empty, skipping")
continue continue
@ -70,23 +87,54 @@ class DivaReader(BaseReader):
if file.startswith("ShopCatalog_"): if file.startswith("ShopCatalog_"):
for x in range(0, len(split), 7): for x in range(0, len(split), 7):
self.logger.info(f"Added shop item {split[x+0]}") self.logger.info(
f"Added shop item {split[x+0]}"
)
self.data.static.put_shop(self.version, split[x+0], split[x+2], split[x+6], split[x+3], self.data.static.put_shop(
split[x+1], split[x+4], split[x+5]) self.version,
split[x + 0],
split[x + 2],
split[x + 6],
split[x + 3],
split[x + 1],
split[x + 4],
split[x + 5],
)
elif file.startswith("CustomizeItemCatalog_") and len(split) >= 7: elif (
file.startswith("CustomizeItemCatalog_")
and len(split) >= 7
):
for x in range(0, len(split), 7): for x in range(0, len(split), 7):
self.logger.info(f"Added item {split[x+0]}") self.logger.info(f"Added item {split[x+0]}")
self.data.static.put_items(self.version, split[x+0], split[x+2], split[x+6], split[x+3], self.data.static.put_items(
split[x+1], split[x+4], split[x+5]) self.version,
split[x + 0],
split[x + 2],
split[x + 6],
split[x + 3],
split[x + 1],
split[x + 4],
split[x + 5],
)
elif file.startswith("QuestInfo") and len(split) >= 9: elif file.startswith("QuestInfo") and len(split) >= 9:
self.logger.info(f"Added quest {split[0]}") self.logger.info(f"Added quest {split[0]}")
self.data.static.put_quests(self.version, split[0], split[6], split[2], split[3], self.data.static.put_quests(
split[7], split[8], split[1], split[4], split[5]) self.version,
split[0],
split[6],
split[2],
split[3],
split[7],
split[8],
split[1],
split[4],
split[5],
)
else: else:
continue continue
@ -102,13 +150,13 @@ class DivaReader(BaseReader):
elif path.exists(f"{rom_root_dir}/pv_db.txt"): elif path.exists(f"{rom_root_dir}/pv_db.txt"):
file_path = f"{rom_root_dir}/pv_db.txt" file_path = f"{rom_root_dir}/pv_db.txt"
else: else:
self.logger.warn(f"Cannot find pv_db.txt or mdata_pv_db.txt in {rom_root_dir}, skipping") self.logger.warn(
f"Cannot find pv_db.txt or mdata_pv_db.txt in {rom_root_dir}, skipping"
)
return return
with open(file_path, "r", encoding="utf-8") as f: with open(file_path, "r", encoding="utf-8") as f:
for line in f.readlines(): for line in f.readlines():
if line.startswith("#") or not line: if line.startswith("#") or not line:
continue continue
@ -127,14 +175,13 @@ class DivaReader(BaseReader):
for x in range(1, len(key_split)): for x in range(1, len(key_split)):
key_args.append(key_split[x]) key_args.append(key_split[x])
try: try:
pv_list[pv_id] = self.add_branch(pv_list[pv_id], key_args, val) pv_list[pv_id] = self.add_branch(pv_list[pv_id], key_args, val)
except KeyError: except KeyError:
pv_list[pv_id] = {} pv_list[pv_id] = {}
pv_list[pv_id] = self.add_branch(pv_list[pv_id], key_args, val) pv_list[pv_id] = self.add_branch(pv_list[pv_id], key_args, val)
for pv_id, pv_data in pv_list.items(): for pv_id, pv_data in pv_list.items():
song_id = int(pv_id.split("_")[1]) song_id = int(pv_id.split("_")[1])
if "songinfo" not in pv_data: if "songinfo" not in pv_data:
@ -148,46 +195,99 @@ class DivaReader(BaseReader):
if "music" not in pv_data["songinfo"]: if "music" not in pv_data["songinfo"]:
pv_data["songinfo"]["music"] = "-" pv_data["songinfo"]["music"] = "-"
if "easy" in pv_data['difficulty'] and '0' in pv_data['difficulty']['easy']: if "easy" in pv_data["difficulty"] and "0" in pv_data["difficulty"]["easy"]:
diff = pv_data['difficulty']['easy']['0']['level'].split('_') diff = pv_data["difficulty"]["easy"]["0"]["level"].split("_")
self.logger.info(f"Added song {song_id} chart 0") self.logger.info(f"Added song {song_id} chart 0")
self.data.static.put_music(self.version, song_id, 0, pv_data["song_name"], pv_data["songinfo"]["arranger"], self.data.static.put_music(
pv_data["songinfo"]["illustrator"], pv_data["songinfo"]["lyrics"], pv_data["songinfo"]["music"], self.version,
float(f"{diff[2]}.{diff[3]}"), pv_data["bpm"], pv_data["date"]) song_id,
0,
if "normal" in pv_data['difficulty'] and '0' in pv_data['difficulty']['normal']: pv_data["song_name"],
diff = pv_data['difficulty']['normal']['0']['level'].split('_') pv_data["songinfo"]["arranger"],
pv_data["songinfo"]["illustrator"],
pv_data["songinfo"]["lyrics"],
pv_data["songinfo"]["music"],
float(f"{diff[2]}.{diff[3]}"),
pv_data["bpm"],
pv_data["date"],
)
if (
"normal" in pv_data["difficulty"]
and "0" in pv_data["difficulty"]["normal"]
):
diff = pv_data["difficulty"]["normal"]["0"]["level"].split("_")
self.logger.info(f"Added song {song_id} chart 1") self.logger.info(f"Added song {song_id} chart 1")
self.data.static.put_music(self.version, song_id, 1, pv_data["song_name"], pv_data["songinfo"]["arranger"], self.data.static.put_music(
pv_data["songinfo"]["illustrator"], pv_data["songinfo"]["lyrics"], pv_data["songinfo"]["music"], self.version,
float(f"{diff[2]}.{diff[3]}"), pv_data["bpm"], pv_data["date"]) song_id,
1,
if "hard" in pv_data['difficulty'] and '0' in pv_data['difficulty']['hard']: pv_data["song_name"],
diff = pv_data['difficulty']['hard']['0']['level'].split('_') pv_data["songinfo"]["arranger"],
pv_data["songinfo"]["illustrator"],
pv_data["songinfo"]["lyrics"],
pv_data["songinfo"]["music"],
float(f"{diff[2]}.{diff[3]}"),
pv_data["bpm"],
pv_data["date"],
)
if "hard" in pv_data["difficulty"] and "0" in pv_data["difficulty"]["hard"]:
diff = pv_data["difficulty"]["hard"]["0"]["level"].split("_")
self.logger.info(f"Added song {song_id} chart 2") self.logger.info(f"Added song {song_id} chart 2")
self.data.static.put_music(self.version, song_id, 2, pv_data["song_name"], pv_data["songinfo"]["arranger"], self.data.static.put_music(
pv_data["songinfo"]["illustrator"], pv_data["songinfo"]["lyrics"], pv_data["songinfo"]["music"], self.version,
float(f"{diff[2]}.{diff[3]}"), pv_data["bpm"], pv_data["date"]) song_id,
2,
if "extreme" in pv_data['difficulty']: pv_data["song_name"],
if "0" in pv_data['difficulty']['extreme']: pv_data["songinfo"]["arranger"],
diff = pv_data['difficulty']['extreme']['0']['level'].split('_') pv_data["songinfo"]["illustrator"],
pv_data["songinfo"]["lyrics"],
pv_data["songinfo"]["music"],
float(f"{diff[2]}.{diff[3]}"),
pv_data["bpm"],
pv_data["date"],
)
if "extreme" in pv_data["difficulty"]:
if "0" in pv_data["difficulty"]["extreme"]:
diff = pv_data["difficulty"]["extreme"]["0"]["level"].split("_")
self.logger.info(f"Added song {song_id} chart 3") self.logger.info(f"Added song {song_id} chart 3")
self.data.static.put_music(self.version, song_id, 3, pv_data["song_name"], pv_data["songinfo"]["arranger"], self.data.static.put_music(
pv_data["songinfo"]["illustrator"], pv_data["songinfo"]["lyrics"], pv_data["songinfo"]["music"], self.version,
float(f"{diff[2]}.{diff[3]}"), pv_data["bpm"], pv_data["date"]) song_id,
3,
pv_data["song_name"],
pv_data["songinfo"]["arranger"],
pv_data["songinfo"]["illustrator"],
pv_data["songinfo"]["lyrics"],
pv_data["songinfo"]["music"],
float(f"{diff[2]}.{diff[3]}"),
pv_data["bpm"],
pv_data["date"],
)
if "1" in pv_data['difficulty']['extreme']: if "1" in pv_data["difficulty"]["extreme"]:
diff = pv_data['difficulty']['extreme']['1']['level'].split('_') diff = pv_data["difficulty"]["extreme"]["1"]["level"].split("_")
self.logger.info(f"Added song {song_id} chart 4") self.logger.info(f"Added song {song_id} chart 4")
self.data.static.put_music(self.version, song_id, 4, pv_data["song_name"], pv_data["songinfo"]["arranger"], self.data.static.put_music(
pv_data["songinfo"]["illustrator"], pv_data["songinfo"]["lyrics"], pv_data["songinfo"]["music"], self.version,
float(f"{diff[2]}.{diff[3]}"), pv_data["bpm"], pv_data["date"]) song_id,
4,
pv_data["song_name"],
pv_data["songinfo"]["arranger"],
pv_data["songinfo"]["illustrator"],
pv_data["songinfo"]["lyrics"],
pv_data["songinfo"]["music"],
float(f"{diff[2]}.{diff[3]}"),
pv_data["bpm"],
pv_data["date"],
)
def add_branch(self, tree: Dict, vector: List, value: str): def add_branch(self, tree: Dict, vector: List, value: str):
""" """
@ -195,9 +295,9 @@ class DivaReader(BaseReader):
Author: iJames on StackOverflow Author: iJames on StackOverflow
""" """
key = vector[0] key = vector[0]
tree[key] = value \ tree[key] = (
if len(vector) == 1 \ value
else self.add_branch(tree[key] if key in tree else {}, if len(vector) == 1
vector[1:], else self.add_branch(tree[key] if key in tree else {}, vector[1:], value)
value) )
return tree return tree

View File

@ -6,6 +6,12 @@ from titles.diva.schema.pv_customize import DivaPvCustomizeData
from titles.diva.schema.item import DivaItemData from titles.diva.schema.item import DivaItemData
from titles.diva.schema.static import DivaStaticData from titles.diva.schema.static import DivaStaticData
__all__ = [DivaProfileData, DivaScoreData, DivaModuleData, __all__ = [
DivaCustomizeItemData, DivaPvCustomizeData, DivaItemData, DivaProfileData,
DivaStaticData] DivaScoreData,
DivaModuleData,
DivaCustomizeItemData,
DivaPvCustomizeData,
DivaItemData,
DivaStaticData,
]

View File

@ -10,25 +10,29 @@ customize = Table(
"diva_profile_customize_item", "diva_profile_customize_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("version", Integer, nullable=False), Column("version", Integer, nullable=False),
Column("item_id", Integer, nullable=False), Column("item_id", Integer, nullable=False),
UniqueConstraint("user", "version", "item_id", name="diva_profile_customize_item_uk"), UniqueConstraint(
mysql_charset='utf8mb4' "user", "version", "item_id", name="diva_profile_customize_item_uk"
),
mysql_charset="utf8mb4",
) )
class DivaCustomizeItemData(BaseData): class DivaCustomizeItemData(BaseData):
def put_customize_item(self, aime_id: int, version: int, item_id: int) -> None: def put_customize_item(self, aime_id: int, version: int, item_id: int) -> None:
sql = insert(customize).values( sql = insert(customize).values(version=version, user=aime_id, item_id=item_id)
version=version,
user=aime_id,
item_id=item_id
)
result = self.execute(sql) result = self.execute(sql)
if result is None: if result is None:
self.logger.error(f"{__name__} Failed to insert diva profile customize item! aime id: {aime_id} item: {item_id}") self.logger.error(
f"{__name__} Failed to insert diva profile customize item! aime id: {aime_id} item: {item_id}"
)
return None return None
return result.lastrowid return result.lastrowid
@ -36,10 +40,9 @@ class DivaCustomizeItemData(BaseData):
""" """
Given a game version and an aime id, return all the customize items, not used directly Given a game version and an aime id, return all the customize items, not used directly
""" """
sql = customize.select(and_( sql = customize.select(
customize.c.version == version, and_(customize.c.version == version, customize.c.user == aime_id)
customize.c.user == aime_id )
))
result = self.execute(sql) result = self.execute(sql)
if result is None: if result is None:

View File

@ -11,37 +11,48 @@ shop = Table(
"diva_profile_shop", "diva_profile_shop",
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("mdl_eqp_ary", String(32)), Column("mdl_eqp_ary", String(32)),
Column("c_itm_eqp_ary", String(59)), Column("c_itm_eqp_ary", String(59)),
Column("ms_itm_flg_ary", String(59)), Column("ms_itm_flg_ary", String(59)),
UniqueConstraint("user", "version", name="diva_profile_shop_uk"), UniqueConstraint("user", "version", name="diva_profile_shop_uk"),
mysql_charset='utf8mb4' mysql_charset="utf8mb4",
) )
class DivaItemData(BaseData): class DivaItemData(BaseData):
def put_shop(self, aime_id: int, version: int, mdl_eqp_ary: str, def put_shop(
c_itm_eqp_ary: str, ms_itm_flg_ary: str) -> None: self,
aime_id: int,
version: int,
mdl_eqp_ary: str,
c_itm_eqp_ary: str,
ms_itm_flg_ary: str,
) -> None:
sql = insert(shop).values( sql = insert(shop).values(
version=version, version=version,
user=aime_id, user=aime_id,
mdl_eqp_ary=mdl_eqp_ary, mdl_eqp_ary=mdl_eqp_ary,
c_itm_eqp_ary=c_itm_eqp_ary, c_itm_eqp_ary=c_itm_eqp_ary,
ms_itm_flg_ary=ms_itm_flg_ary ms_itm_flg_ary=ms_itm_flg_ary,
) )
conflict = sql.on_duplicate_key_update( conflict = sql.on_duplicate_key_update(
mdl_eqp_ary=mdl_eqp_ary, mdl_eqp_ary=mdl_eqp_ary,
c_itm_eqp_ary=c_itm_eqp_ary, c_itm_eqp_ary=c_itm_eqp_ary,
ms_itm_flg_ary=ms_itm_flg_ary ms_itm_flg_ary=ms_itm_flg_ary,
) )
result = self.execute(conflict) result = self.execute(conflict)
if result is None: if result is None:
self.logger.error(f"{__name__} Failed to insert diva profile! aime id: {aime_id} array: {mdl_eqp_ary}") self.logger.error(
f"{__name__} Failed to insert diva profile! aime id: {aime_id} array: {mdl_eqp_ary}"
)
return None return None
return result.lastrowid return result.lastrowid
@ -49,10 +60,7 @@ class DivaItemData(BaseData):
""" """
Given a game version and either a profile or aime id, return the profile Given a game version and either a profile or aime id, return the profile
""" """
sql = shop.select(and_( sql = shop.select(and_(shop.c.version == version, shop.c.user == aime_id))
shop.c.version == version,
shop.c.user == aime_id
))
result = self.execute(sql) result = self.execute(sql)
if result is None: if result is None:

View File

@ -10,25 +10,27 @@ module = Table(
"diva_profile_module", "diva_profile_module",
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("module_id", Integer, nullable=False), Column("module_id", Integer, nullable=False),
UniqueConstraint("user", "version", "module_id", name="diva_profile_module_uk"), UniqueConstraint("user", "version", "module_id", name="diva_profile_module_uk"),
mysql_charset='utf8mb4' mysql_charset="utf8mb4",
) )
class DivaModuleData(BaseData): class DivaModuleData(BaseData):
def put_module(self, aime_id: int, version: int, module_id: int) -> None: def put_module(self, aime_id: int, version: int, module_id: int) -> None:
sql = insert(module).values( sql = insert(module).values(version=version, user=aime_id, module_id=module_id)
version=version,
user=aime_id,
module_id=module_id
)
result = self.execute(sql) result = self.execute(sql)
if result is None: if result is None:
self.logger.error(f"{__name__} Failed to insert diva profile module! aime id: {aime_id} module: {module_id}") self.logger.error(
f"{__name__} Failed to insert diva profile module! aime id: {aime_id} module: {module_id}"
)
return None return None
return result.lastrowid return result.lastrowid
@ -36,10 +38,7 @@ class DivaModuleData(BaseData):
""" """
Given a game version and an aime id, return all the modules, not used directly Given a game version and an aime id, return all the modules, not used directly
""" """
sql = module.select(and_( sql = module.select(and_(module.c.version == version, module.c.user == aime_id))
module.c.version == version,
module.c.user == aime_id
))
result = self.execute(sql) result = self.execute(sql)
if result is None: if result is None:

View File

@ -11,8 +11,11 @@ profile = Table(
"diva_profile", "diva_profile",
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", Column(
onupdate="cascade"), nullable=False), "user",
ForeignKey("aime_user.id", ondelete="cascade", onupdate="cascade"),
nullable=False,
),
Column("version", Integer, nullable=False), Column("version", Integer, nullable=False),
Column("player_name", String(10), nullable=False), Column("player_name", String(10), nullable=False),
Column("lv_str", String(24), nullable=False, server_default="Dab on 'em"), Column("lv_str", String(24), nullable=False, server_default="Dab on 'em"),
@ -29,10 +32,8 @@ profile = Table(
Column("use_pv_skn_eqp", Boolean, nullable=False, server_default="0"), Column("use_pv_skn_eqp", Boolean, nullable=False, server_default="0"),
Column("use_pv_btn_se_eqp", Boolean, nullable=False, server_default="1"), Column("use_pv_btn_se_eqp", Boolean, nullable=False, server_default="1"),
Column("use_pv_sld_se_eqp", Boolean, nullable=False, server_default="0"), Column("use_pv_sld_se_eqp", Boolean, nullable=False, server_default="0"),
Column("use_pv_chn_sld_se_eqp", Boolean, Column("use_pv_chn_sld_se_eqp", Boolean, nullable=False, server_default="0"),
nullable=False, server_default="0"), Column("use_pv_sldr_tch_se_eqp", Boolean, nullable=False, server_default="0"),
Column("use_pv_sldr_tch_se_eqp", Boolean,
nullable=False, server_default="0"),
Column("nxt_pv_id", Integer, nullable=False, server_default="708"), Column("nxt_pv_id", Integer, nullable=False, server_default="708"),
Column("nxt_dffclty", Integer, nullable=False, server_default="2"), Column("nxt_dffclty", Integer, nullable=False, server_default="2"),
Column("nxt_edtn", Integer, nullable=False, server_default="0"), Column("nxt_edtn", Integer, nullable=False, server_default="0"),
@ -44,35 +45,39 @@ profile = Table(
Column("lv_plt_id", Integer, nullable=False, server_default="1"), Column("lv_plt_id", Integer, nullable=False, server_default="1"),
Column("passwd_stat", Integer, nullable=False, server_default="0"), Column("passwd_stat", Integer, nullable=False, server_default="0"),
Column("passwd", String(12), nullable=False, server_default="**********"), Column("passwd", String(12), nullable=False, server_default="**********"),
Column("my_qst_id", String( Column(
128), server_default="-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1"), "my_qst_id",
Column("my_qst_sts", String( String(128),
128), server_default="-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1"), server_default="-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1",
),
Column(
"my_qst_sts",
String(128),
server_default="-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1",
),
UniqueConstraint("user", "version", name="diva_profile_uk"), UniqueConstraint("user", "version", name="diva_profile_uk"),
mysql_charset='utf8mb4' mysql_charset="utf8mb4",
) )
class DivaProfileData(BaseData): class DivaProfileData(BaseData):
def create_profile(self, version: int, aime_id: int, def create_profile(
player_name: str) -> Optional[int]: self, version: int, aime_id: int, player_name: str
) -> Optional[int]:
""" """
Given a game version, aime id, and player_name, create a profile and return it's ID Given a game version, aime id, and player_name, create a profile and return it's ID
""" """
sql = insert(profile).values( sql = insert(profile).values(
version=version, version=version, user=aime_id, player_name=player_name
user=aime_id,
player_name=player_name
) )
conflict = sql.on_duplicate_key_update( conflict = sql.on_duplicate_key_update(player_name=sql.inserted.player_name)
player_name=sql.inserted.player_name
)
result = self.execute(conflict) result = self.execute(conflict)
if result is None: if result is None:
self.logger.error( self.logger.error(
f"{__name__} Failed to insert diva profile! aime id: {aime_id} username: {player_name}") f"{__name__} Failed to insert diva profile! aime id: {aime_id} username: {player_name}"
)
return None return None
return result.lastrowid return result.lastrowid
@ -86,17 +91,17 @@ class DivaProfileData(BaseData):
result = self.execute(sql) result = self.execute(sql)
if result is None: if result is None:
self.logger.error( self.logger.error(
f"update_profile: failed to update profile! profile: {aime_id}") f"update_profile: failed to update profile! profile: {aime_id}"
)
return None return None
def get_profile(self, aime_id: int, version: int) -> Optional[List[Dict]]: def get_profile(self, aime_id: int, version: int) -> Optional[List[Dict]]:
""" """
Given a game version and either a profile or aime id, return the profile Given a game version and either a profile or aime id, return the profile
""" """
sql = profile.select(and_( sql = profile.select(
profile.c.version == version, and_(profile.c.version == version, profile.c.user == aime_id)
profile.c.user == aime_id )
))
result = self.execute(sql) result = self.execute(sql)
if result is None: if result is None:

View File

@ -10,27 +10,44 @@ pv_customize = Table(
"diva_profile_pv_customize", "diva_profile_pv_customize",
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("pv_id", Integer, nullable=False), Column("pv_id", Integer, nullable=False),
Column("mdl_eqp_ary", String(14), server_default="-999,-999,-999"), Column("mdl_eqp_ary", String(14), server_default="-999,-999,-999"),
Column("c_itm_eqp_ary", String(59), server_default="-999,-999,-999,-999,-999,-999,-999,-999,-999,-999,-999,-999"), Column(
Column("ms_itm_flg_ary", String(59), server_default="-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1"), "c_itm_eqp_ary",
String(59),
server_default="-999,-999,-999,-999,-999,-999,-999,-999,-999,-999,-999,-999",
),
Column(
"ms_itm_flg_ary",
String(59),
server_default="-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1",
),
Column("skin", Integer, server_default="-1"), Column("skin", Integer, server_default="-1"),
Column("btn_se", Integer, server_default="-1"), Column("btn_se", Integer, server_default="-1"),
Column("sld_se", Integer, server_default="-1"), Column("sld_se", Integer, server_default="-1"),
Column("chsld_se", Integer, server_default="-1"), Column("chsld_se", Integer, server_default="-1"),
Column("sldtch_se", Integer, server_default="-1"), Column("sldtch_se", Integer, server_default="-1"),
UniqueConstraint("user", "version", "pv_id", name="diva_profile_pv_customize_uk"), UniqueConstraint("user", "version", "pv_id", name="diva_profile_pv_customize_uk"),
mysql_charset='utf8mb4' mysql_charset="utf8mb4",
) )
class DivaPvCustomizeData(BaseData): class DivaPvCustomizeData(BaseData):
def put_pv_customize(self, aime_id: int, version: int, pv_id: int, def put_pv_customize(
mdl_eqp_ary: str, c_itm_eqp_ary: str, self,
ms_itm_flg_ary: str) -> Optional[int]: aime_id: int,
version: int,
pv_id: int,
mdl_eqp_ary: str,
c_itm_eqp_ary: str,
ms_itm_flg_ary: str,
) -> Optional[int]:
sql = insert(pv_customize).values( sql = insert(pv_customize).values(
version=version, version=version,
user=aime_id, user=aime_id,
@ -49,19 +66,19 @@ class DivaPvCustomizeData(BaseData):
result = self.execute(conflict) result = self.execute(conflict)
if result is None: if result is None:
self.logger.error(f"{__name__} Failed to insert diva pv customize! aime id: {aime_id}") self.logger.error(
f"{__name__} Failed to insert diva pv customize! aime id: {aime_id}"
)
return None return None
return result.lastrowid return result.lastrowid
def get_pv_customize(self, aime_id: int, def get_pv_customize(self, aime_id: int, pv_id: int) -> Optional[List[Dict]]:
pv_id: int) -> Optional[List[Dict]]:
""" """
Given either a profile or aime id, return a Pv Customize row Given either a profile or aime id, return a Pv Customize row
""" """
sql = pv_customize.select(and_( sql = pv_customize.select(
pv_customize.c.user == aime_id, and_(pv_customize.c.user == aime_id, pv_customize.c.pv_id == pv_id)
pv_customize.c.pv_id == pv_id )
))
result = self.execute(sql) result = self.execute(sql)
if result is None: if result is None:

View File

@ -28,7 +28,7 @@ score = Table(
Column("worst", Integer), Column("worst", Integer),
Column("max_combo", Integer), Column("max_combo", Integer),
UniqueConstraint("user", "pv_id", "difficulty", "edition", name="diva_score_uk"), UniqueConstraint("user", "pv_id", "difficulty", "edition", name="diva_score_uk"),
mysql_charset='utf8mb4' mysql_charset="utf8mb4",
) )
playlog = Table( playlog = Table(
@ -51,16 +51,29 @@ playlog = Table(
Column("worst", Integer), Column("worst", Integer),
Column("max_combo", Integer), Column("max_combo", Integer),
Column("date_scored", TIMESTAMP, server_default=func.now()), Column("date_scored", TIMESTAMP, server_default=func.now()),
mysql_charset='utf8mb4' mysql_charset="utf8mb4",
) )
class DivaScoreData(BaseData): class DivaScoreData(BaseData):
def put_best_score(self, user_id: int, game_version: int, song_id: int, def put_best_score(
difficulty: int, edition: int, song_score: int, self,
atn_pnt: int, clr_kind: int, sort_kind: int, user_id: int,
cool: int, fine: int, safe: int, sad: int, game_version: int,
worst: int, max_combo: int) -> Optional[int]: song_id: int,
difficulty: int,
edition: int,
song_score: int,
atn_pnt: int,
clr_kind: int,
sort_kind: int,
cool: int,
fine: int,
safe: int,
sad: int,
worst: int,
max_combo: int,
) -> Optional[int]:
""" """
Update the user's best score for a chart Update the user's best score for a chart
""" """
@ -98,16 +111,30 @@ class DivaScoreData(BaseData):
result = self.execute(conflict) result = self.execute(conflict)
if result is None: if result is None:
self.logger.error( self.logger.error(
f"{__name__} failed to insert best score! profile: {user_id}, song: {song_id}") f"{__name__} failed to insert best score! profile: {user_id}, song: {song_id}"
)
return None return None
return result.lastrowid return result.lastrowid
def put_playlog(self, user_id: int, game_version: int, song_id: int, def put_playlog(
difficulty: int, edition: int, song_score: int, self,
atn_pnt: int, clr_kind: int, sort_kind: int, user_id: int,
cool: int, fine: int, safe: int, sad: int, game_version: int,
worst: int, max_combo: int) -> Optional[int]: song_id: int,
difficulty: int,
edition: int,
song_score: int,
atn_pnt: int,
clr_kind: int,
sort_kind: int,
cool: int,
fine: int,
safe: int,
sad: int,
worst: int,
max_combo: int,
) -> Optional[int]:
""" """
Add an entry to the user's play log Add an entry to the user's play log
""" """
@ -126,24 +153,28 @@ class DivaScoreData(BaseData):
safe=safe, safe=safe,
sad=sad, sad=sad,
worst=worst, worst=worst,
max_combo=max_combo max_combo=max_combo,
) )
result = self.execute(sql) result = self.execute(sql)
if result is None: if result is None:
self.logger.error( self.logger.error(
f"{__name__} failed to insert playlog! profile: {user_id}, song: {song_id}, chart: {difficulty}") f"{__name__} failed to insert playlog! profile: {user_id}, song: {song_id}, chart: {difficulty}"
)
return None return None
return result.lastrowid return result.lastrowid
def get_best_user_score(self, user_id: int, pv_id: int, difficulty: int, def get_best_user_score(
edition: int) -> Optional[Dict]: self, user_id: int, pv_id: int, difficulty: int, edition: int
) -> Optional[Dict]:
sql = score.select( sql = score.select(
and_(score.c.user == user_id, and_(
score.c.pv_id == pv_id, score.c.user == user_id,
score.c.difficulty == difficulty, score.c.pv_id == pv_id,
score.c.edition == edition) score.c.difficulty == difficulty,
score.c.edition == edition,
)
) )
result = self.execute(sql) result = self.execute(sql)
@ -151,36 +182,48 @@ class DivaScoreData(BaseData):
return None return None
return result.fetchone() return result.fetchone()
def get_top3_scores(self, pv_id: int, difficulty: int, def get_top3_scores(
edition: int) -> Optional[List[Dict]]: self, pv_id: int, difficulty: int, edition: int
sql = score.select( ) -> Optional[List[Dict]]:
and_(score.c.pv_id == pv_id, sql = (
score.c.difficulty == difficulty, score.select(
score.c.edition == edition) and_(
).order_by(score.c.score.desc()).limit(3) score.c.pv_id == pv_id,
score.c.difficulty == difficulty,
score.c.edition == edition,
)
)
.order_by(score.c.score.desc())
.limit(3)
)
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()
def get_global_ranking(self, user_id: int, pv_id: int, difficulty: int, def get_global_ranking(
edition: int) -> Optional[List]: self, user_id: int, pv_id: int, difficulty: int, edition: int
) -> Optional[List]:
# get the subquery max score of a user with pv_id, difficulty and # get the subquery max score of a user with pv_id, difficulty and
# edition # edition
sql_sub = select([score.c.score]).filter( sql_sub = (
score.c.user == user_id, select([score.c.score])
score.c.pv_id == pv_id, .filter(
score.c.difficulty == difficulty, score.c.user == user_id,
score.c.edition == edition score.c.pv_id == pv_id,
).scalar_subquery() score.c.difficulty == difficulty,
score.c.edition == edition,
)
.scalar_subquery()
)
# Perform the main query, also rename the resulting column to ranking # Perform the main query, also rename the resulting column to ranking
sql = select(func.count(score.c.id).label("ranking")).filter( sql = select(func.count(score.c.id).label("ranking")).filter(
score.c.score >= sql_sub, score.c.score >= sql_sub,
score.c.pv_id == pv_id, score.c.pv_id == pv_id,
score.c.difficulty == difficulty, score.c.difficulty == difficulty,
score.c.edition == edition score.c.edition == edition,
) )
result = self.execute(sql) result = self.execute(sql)

View File

@ -25,7 +25,7 @@ music = Table(
Column("bpm", Integer), Column("bpm", Integer),
Column("date", String(255)), Column("date", String(255)),
UniqueConstraint("version", "songId", "chartId", name="diva_static_music_uk"), UniqueConstraint("version", "songId", "chartId", name="diva_static_music_uk"),
mysql_charset='utf8mb4' mysql_charset="utf8mb4",
) )
quests = Table( quests = Table(
@ -43,9 +43,8 @@ quests = Table(
Column("quest_order", Integer), Column("quest_order", Integer),
Column("start_datetime", String(255)), Column("start_datetime", String(255)),
Column("end_datetime", String(255)), Column("end_datetime", String(255)),
UniqueConstraint("version", "questId", name="diva_static_quests_uk"), UniqueConstraint("version", "questId", name="diva_static_quests_uk"),
mysql_charset='utf8mb4' mysql_charset="utf8mb4",
) )
shop = Table( shop = Table(
@ -62,7 +61,7 @@ shop = Table(
Column("end_date", String(255)), Column("end_date", String(255)),
Column("enabled", Boolean, server_default="1"), Column("enabled", Boolean, server_default="1"),
UniqueConstraint("version", "shopId", name="diva_static_shop_uk"), UniqueConstraint("version", "shopId", name="diva_static_shop_uk"),
mysql_charset='utf8mb4' mysql_charset="utf8mb4",
) )
items = Table( items = Table(
@ -79,64 +78,91 @@ items = Table(
Column("end_date", String(255)), Column("end_date", String(255)),
Column("enabled", Boolean, server_default="1"), Column("enabled", Boolean, server_default="1"),
UniqueConstraint("version", "itemId", name="diva_static_items_uk"), UniqueConstraint("version", "itemId", name="diva_static_items_uk"),
mysql_charset='utf8mb4' mysql_charset="utf8mb4",
) )
class DivaStaticData(BaseData): class DivaStaticData(BaseData):
def put_quests(self, version: int, questId: int, name: str, kind: int, unknown_0: int, unknown_1: int, unknown_2: int, quest_order: int, start_datetime: str, end_datetime: str) -> Optional[int]: def put_quests(
self,
version: int,
questId: int,
name: str,
kind: int,
unknown_0: int,
unknown_1: int,
unknown_2: int,
quest_order: int,
start_datetime: str,
end_datetime: str,
) -> Optional[int]:
sql = insert(quests).values( sql = insert(quests).values(
version = version, version=version,
questId = questId, questId=questId,
name = name, name=name,
kind = kind, kind=kind,
unknown_0 = unknown_0, unknown_0=unknown_0,
unknown_1 = unknown_1, unknown_1=unknown_1,
unknown_2 = unknown_2, unknown_2=unknown_2,
quest_order = quest_order, quest_order=quest_order,
start_datetime = start_datetime, start_datetime=start_datetime,
end_datetime = end_datetime end_datetime=end_datetime,
) )
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 get_enabled_quests(self, version: int) -> Optional[List[Row]]: def get_enabled_quests(self, version: int) -> Optional[List[Row]]:
sql = select(quests).where(and_(quests.c.version == version, quests.c.quest_enable == True)) sql = select(quests).where(
and_(quests.c.version == version, quests.c.quest_enable == 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 put_shop(self, version: int, shopId: int, name: str, type: int, points: int, unknown_0: int, start_date: str, end_date: str) -> Optional[int]: def put_shop(
self,
version: int,
shopId: int,
name: str,
type: int,
points: int,
unknown_0: int,
start_date: str,
end_date: str,
) -> Optional[int]:
sql = insert(shop).values( sql = insert(shop).values(
version = version, version=version,
shopId = shopId, shopId=shopId,
name = name, name=name,
type = type, type=type,
points = points, points=points,
unknown_0 = unknown_0, unknown_0=unknown_0,
start_date = start_date, start_date=start_date,
end_date = end_date end_date=end_date,
) )
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 get_enabled_shop(self, version: int, shopId: int) -> Optional[Row]: def get_enabled_shop(self, version: int, shopId: int) -> Optional[Row]:
sql = select(shop).where(and_( sql = select(shop).where(
shop.c.version == version, and_(
shop.c.shopId == shopId, shop.c.version == version,
shop.c.enabled == True)) shop.c.shopId == shopId,
shop.c.enabled == True,
)
)
result = self.execute(sql) result = self.execute(sql)
if result is None: if result is None:
@ -144,40 +170,52 @@ class DivaStaticData(BaseData):
return result.fetchone() return result.fetchone()
def get_enabled_shops(self, version: int) -> Optional[List[Row]]: def get_enabled_shops(self, version: int) -> Optional[List[Row]]:
sql = select(shop).where(and_( sql = select(shop).where(
shop.c.version == version, and_(shop.c.version == version, shop.c.enabled == True)
shop.c.enabled == True)) )
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()
def put_items(self, version: int, itemId: int, name: str, type: int, points: int, unknown_0: int, start_date: str, end_date: str) -> Optional[int]: def put_items(
self,
version: int,
itemId: int,
name: str,
type: int,
points: int,
unknown_0: int,
start_date: str,
end_date: str,
) -> Optional[int]:
sql = insert(items).values( sql = insert(items).values(
version = version, version=version,
itemId = itemId, itemId=itemId,
name = name, name=name,
type = type, type=type,
points = points, points=points,
unknown_0 = unknown_0, unknown_0=unknown_0,
start_date = start_date, start_date=start_date,
end_date = end_date end_date=end_date,
) )
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 get_enabled_item(self, version: int, itemId: int) -> Optional[Row]: def get_enabled_item(self, version: int, itemId: int) -> Optional[Row]:
sql = select(items).where(and_( sql = select(items).where(
items.c.version == version, and_(
items.c.itemId == itemId, items.c.version == version,
items.c.enabled == True)) items.c.itemId == itemId,
items.c.enabled == True,
)
)
result = self.execute(sql) result = self.execute(sql)
if result is None: if result is None:
@ -185,66 +223,89 @@ class DivaStaticData(BaseData):
return result.fetchone() return result.fetchone()
def get_enabled_items(self, version: int) -> Optional[List[Row]]: def get_enabled_items(self, version: int) -> Optional[List[Row]]:
sql = select(items).where(and_( sql = select(items).where(
items.c.version == version, and_(items.c.version == version, items.c.enabled == True)
items.c.enabled == True)) )
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()
def put_music(self, version: int, song: int, chart: int, title: str, arranger: str, illustrator: str, def put_music(
lyrics: str, music_comp: str, level: float, bpm: int, date: str) -> Optional[int]: self,
version: int,
song: int,
chart: int,
title: str,
arranger: str,
illustrator: str,
lyrics: str,
music_comp: str,
level: float,
bpm: int,
date: str,
) -> Optional[int]:
sql = insert(music).values( sql = insert(music).values(
version = version, version=version,
songId = song, songId=song,
chartId = chart, chartId=chart,
title = title, title=title,
vocaloid_arranger = arranger, vocaloid_arranger=arranger,
pv_illustrator = illustrator, pv_illustrator=illustrator,
lyrics = lyrics, lyrics=lyrics,
bg_music = music_comp, bg_music=music_comp,
level = level, level=level,
bpm = bpm, bpm=bpm,
date = date date=date,
) )
conflict = sql.on_duplicate_key_update( conflict = sql.on_duplicate_key_update(
title = title, title=title,
vocaloid_arranger = arranger, vocaloid_arranger=arranger,
pv_illustrator = illustrator, pv_illustrator=illustrator,
lyrics = lyrics, lyrics=lyrics,
bg_music = music_comp, bg_music=music_comp,
level = level, level=level,
bpm = bpm, bpm=bpm,
date = date date=date,
) )
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_music(self, version: int, song_id: Optional[int] = None) -> Optional[List[Row]]: def get_music(
self, version: int, song_id: Optional[int] = None
) -> Optional[List[Row]]:
if song_id is None: if song_id is None:
sql = select(music).where(music.c.version == version) sql = select(music).where(music.c.version == version)
else: else:
sql = select(music).where(and_( sql = select(music).where(
music.c.version == version, and_(
music.c.songId == song_id, music.c.version == version,
)) music.c.songId == song_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 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()

View File

@ -7,4 +7,4 @@ index = Mai2Servlet
database = Mai2Data database = Mai2Data
reader = Mai2Reader reader = Mai2Reader
game_codes = [Mai2Constants.GAME_CODE] game_codes = [Mai2Constants.GAME_CODE]
current_schema_version = 2 current_schema_version = 2

View File

@ -7,7 +7,8 @@ from titles.mai2.const import Mai2Constants
from titles.mai2.config import Mai2Config from titles.mai2.config import Mai2Config
from titles.mai2.database import Mai2Data from titles.mai2.database import Mai2Data
class Mai2Base():
class Mai2Base:
def __init__(self, cfg: CoreConfig, game_cfg: Mai2Config) -> None: def __init__(self, cfg: CoreConfig, game_cfg: Mai2Config) -> None:
self.core_config = cfg self.core_config = cfg
self.game_config = game_cfg self.game_config = game_cfg
@ -17,23 +18,27 @@ class Mai2Base():
self.logger = logging.getLogger("mai2") self.logger = logging.getLogger("mai2")
def handle_get_game_setting_api_request(self, data: Dict): def handle_get_game_setting_api_request(self, data: Dict):
reboot_start = date.strftime(datetime.now() + timedelta(hours=3), Mai2Constants.DATE_TIME_FORMAT) reboot_start = date.strftime(
reboot_end = date.strftime(datetime.now() + timedelta(hours=4), Mai2Constants.DATE_TIME_FORMAT) datetime.now() + timedelta(hours=3), Mai2Constants.DATE_TIME_FORMAT
)
reboot_end = date.strftime(
datetime.now() + timedelta(hours=4), Mai2Constants.DATE_TIME_FORMAT
)
return { return {
"gameSetting": { "gameSetting": {
"isMaintenance": "false", "isMaintenance": "false",
"requestInterval": 10, "requestInterval": 10,
"rebootStartTime": reboot_start, "rebootStartTime": reboot_start,
"rebootEndTime": reboot_end, "rebootEndTime": reboot_end,
"movieUploadLimit": 10000, "movieUploadLimit": 10000,
"movieStatus": 0, "movieStatus": 0,
"movieServerUri": "", "movieServerUri": "",
"deliverServerUri": "", "deliverServerUri": "",
"oldServerUri": "", "oldServerUri": "",
"usbDlServerUri": "", "usbDlServerUri": "",
"rebootInterval": 0 "rebootInterval": 0,
}, },
"isAouAccession": "true", "isAouAccession": "true",
} }
def handle_get_game_ranking_api_request(self, data: Dict) -> Dict: def handle_get_game_ranking_api_request(self, data: Dict) -> Dict:
@ -46,34 +51,44 @@ class Mai2Base():
def handle_get_game_event_api_request(self, data: Dict) -> Dict: def handle_get_game_event_api_request(self, data: Dict) -> Dict:
events = self.data.static.get_enabled_events(self.version) events = self.data.static.get_enabled_events(self.version)
events_lst = [] events_lst = []
if events is None: return {"type": data["type"], "length": 0, "gameEventList": []} if events is None:
return {"type": data["type"], "length": 0, "gameEventList": []}
for event in events: for event in events:
events_lst.append({ events_lst.append(
"type": event["type"], {
"id": event["eventId"], "type": event["type"],
"startDate": "2017-12-05 07:00:00.0", "id": event["eventId"],
"endDate": "2099-12-31 00:00:00.0" "startDate": "2017-12-05 07:00:00.0",
}) "endDate": "2099-12-31 00:00:00.0",
}
)
return {"type": data["type"], "length": len(events_lst), "gameEventList": events_lst} return {
"type": data["type"],
"length": len(events_lst),
"gameEventList": events_lst,
}
def handle_get_game_ng_music_id_api_request(self, data: Dict) -> Dict: def handle_get_game_ng_music_id_api_request(self, data: Dict) -> Dict:
return {"length": 0, "musicIdList": []} return {"length": 0, "musicIdList": []}
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_tickets(self.version, 1) game_charge_list = self.data.static.get_enabled_tickets(self.version, 1)
if game_charge_list is None: return {"length": 0, "gameChargeList": []} if game_charge_list is None:
return {"length": 0, "gameChargeList": []}
charge_list = [] charge_list = []
for x in range(len(game_charge_list)): for x in range(len(game_charge_list)):
charge_list.append({ charge_list.append(
"orderId": x, {
"chargeId": game_charge_list[x]["ticketId"], "orderId": x,
"price": game_charge_list[x]["price"], "chargeId": game_charge_list[x]["ticketId"],
"startDate": "2017-12-05 07:00:00.0", "price": game_charge_list[x]["price"],
"endDate": "2099-12-31 00:00:00.0" "startDate": "2017-12-05 07:00:00.0",
}) "endDate": "2099-12-31 00:00:00.0",
}
)
return {"length": len(charge_list), "gameChargeList": charge_list} return {"length": len(charge_list), "gameChargeList": charge_list}
@ -92,7 +107,8 @@ class Mai2Base():
def handle_get_user_preview_api_request(self, data: Dict) -> Dict: def handle_get_user_preview_api_request(self, data: Dict) -> Dict:
p = self.data.profile.get_profile_detail(data["userId"], self.version) p = self.data.profile.get_profile_detail(data["userId"], self.version)
o = self.data.profile.get_profile_option(data["userId"], self.version) o = self.data.profile.get_profile_option(data["userId"], self.version)
if p is None or o is None: return {} # Register if p is None or o is None:
return {} # Register
profile = p._asdict() profile = p._asdict()
option = o._asdict() option = o._asdict()
@ -106,20 +122,24 @@ class Mai2Base():
"lastLoginDate": profile["lastLoginDate"], "lastLoginDate": profile["lastLoginDate"],
"lastPlayDate": profile["lastPlayDate"], "lastPlayDate": profile["lastPlayDate"],
"playerRating": profile["playerRating"], "playerRating": profile["playerRating"],
"nameplateId": 0, # Unused "nameplateId": 0, # Unused
"iconId": profile["iconId"], "iconId": profile["iconId"],
"trophyId": 0, # Unused "trophyId": 0, # Unused
"partnerId": profile["partnerId"], "partnerId": profile["partnerId"],
"frameId": profile["frameId"], "frameId": profile["frameId"],
"dispRate": option["dispRate"], # 0: all/begin, 1: disprate, 2: dispDan, 3: hide, 4: end "dispRate": option[
"dispRate"
], # 0: all/begin, 1: disprate, 2: dispDan, 3: hide, 4: end
"totalAwake": profile["totalAwake"], "totalAwake": profile["totalAwake"],
"isNetMember": profile["isNetMember"], "isNetMember": profile["isNetMember"],
"dailyBonusDate": profile["dailyBonusDate"], "dailyBonusDate": profile["dailyBonusDate"],
"headPhoneVolume": option["headPhoneVolume"], "headPhoneVolume": option["headPhoneVolume"],
"isInherit": False, # Not sure what this is or does?? "isInherit": False, # Not sure what this is or does??
"banState": profile["banState"] if profile["banState"] is not None else 0 # New with uni+ "banState": profile["banState"]
if profile["banState"] is not None
else 0, # New with uni+
} }
def handle_user_login_api_request(self, data: Dict) -> Dict: def handle_user_login_api_request(self, data: Dict) -> Dict:
profile = self.data.profile.get_profile_detail(data["userId"], self.version) profile = self.data.profile.get_profile_detail(data["userId"], self.version)
@ -137,10 +157,10 @@ class Mai2Base():
"returnCode": 1, "returnCode": 1,
"lastLoginDate": lastLoginDate, "lastLoginDate": lastLoginDate,
"loginCount": loginCt, "loginCount": loginCt,
"consecutiveLoginCount": 0, # We don't really have a way to track this... "consecutiveLoginCount": 0, # We don't really have a way to track this...
"loginId": loginCt # Used with the playlog! "loginId": loginCt, # Used with the playlog!
} }
def handle_upload_user_playlog_api_request(self, data: Dict) -> Dict: def handle_upload_user_playlog_api_request(self, data: Dict) -> Dict:
user_id = data["userId"] user_id = data["userId"]
playlog = data["userPlaylog"] playlog = data["userPlaylog"]
@ -154,50 +174,83 @@ class Mai2Base():
if "userData" in upsert and len(upsert["userData"]) > 0: if "userData" in upsert and len(upsert["userData"]) > 0:
upsert["userData"][0]["isNetMember"] = 1 upsert["userData"][0]["isNetMember"] = 1
upsert["userData"][0].pop("accessCode") upsert["userData"][0].pop("accessCode")
self.data.profile.put_profile_detail(user_id, self.version, upsert["userData"][0]) self.data.profile.put_profile_detail(
user_id, self.version, upsert["userData"][0]
)
if "userExtend" in upsert and len(upsert["userExtend"]) > 0: if "userExtend" in upsert and len(upsert["userExtend"]) > 0:
self.data.profile.put_profile_extend(user_id, self.version, upsert["userExtend"][0]) self.data.profile.put_profile_extend(
user_id, self.version, upsert["userExtend"][0]
)
if "userGhost" in upsert: if "userGhost" in upsert:
for ghost in upsert["userGhost"]: for ghost in upsert["userGhost"]:
self.data.profile.put_profile_extend(user_id, self.version, ghost) self.data.profile.put_profile_extend(user_id, self.version, ghost)
if "userOption" in upsert and len(upsert["userOption"]) > 0: if "userOption" in upsert and len(upsert["userOption"]) > 0:
self.data.profile.put_profile_option(user_id, self.version, upsert["userOption"][0]) self.data.profile.put_profile_option(
user_id, self.version, upsert["userOption"][0]
)
if "userRatingList" in upsert and len(upsert["userRatingList"]) > 0: if "userRatingList" in upsert and len(upsert["userRatingList"]) > 0:
self.data.profile.put_profile_rating(user_id, self.version, upsert["userRatingList"][0]) self.data.profile.put_profile_rating(
user_id, self.version, upsert["userRatingList"][0]
)
if "userActivityList" in upsert and len(upsert["userActivityList"]) > 0: if "userActivityList" in upsert and len(upsert["userActivityList"]) > 0:
for k,v in upsert["userActivityList"][0].items(): for k, v in upsert["userActivityList"][0].items():
for act in v: for act in v:
self.data.profile.put_profile_activity(user_id, act) self.data.profile.put_profile_activity(user_id, act)
if upsert["isNewCharacterList"] and int(upsert["isNewCharacterList"]) > 0: if upsert["isNewCharacterList"] and int(upsert["isNewCharacterList"]) > 0:
for char in upsert["userCharacterList"]: for char in upsert["userCharacterList"]:
self.data.item.put_character(user_id, char["characterId"], char["level"], char["awakening"], char["useCount"]) self.data.item.put_character(
user_id,
char["characterId"],
char["level"],
char["awakening"],
char["useCount"],
)
if upsert["isNewItemList"] and int(upsert["isNewItemList"]) > 0: if upsert["isNewItemList"] and int(upsert["isNewItemList"]) > 0:
for item in upsert["userItemList"]: for item in upsert["userItemList"]:
self.data.item.put_item(user_id, int(item["itemKind"]), item["itemId"], item["stock"], item["isValid"]) self.data.item.put_item(
user_id,
int(item["itemKind"]),
item["itemId"],
item["stock"],
item["isValid"],
)
if upsert["isNewLoginBonusList"] and int(upsert["isNewLoginBonusList"]) > 0: if upsert["isNewLoginBonusList"] and int(upsert["isNewLoginBonusList"]) > 0:
for login_bonus in upsert["userLoginBonusList"]: for login_bonus in upsert["userLoginBonusList"]:
self.data.item.put_login_bonus(user_id, login_bonus["bonusId"], login_bonus["point"], login_bonus["isCurrent"], login_bonus["isComplete"]) self.data.item.put_login_bonus(
user_id,
login_bonus["bonusId"],
login_bonus["point"],
login_bonus["isCurrent"],
login_bonus["isComplete"],
)
if upsert["isNewMapList"] and int(upsert["isNewMapList"]) > 0: if upsert["isNewMapList"] and int(upsert["isNewMapList"]) > 0:
for map in upsert["userMapList"]: for map in upsert["userMapList"]:
self.data.item.put_map(user_id, map["mapId"], map["distance"], map["isLock"], map["isClear"], map["isComplete"]) self.data.item.put_map(
user_id,
map["mapId"],
map["distance"],
map["isLock"],
map["isClear"],
map["isComplete"],
)
if upsert["isNewMusicDetailList"] and int(upsert["isNewMusicDetailList"]) > 0: if upsert["isNewMusicDetailList"] and int(upsert["isNewMusicDetailList"]) > 0:
for music in upsert["userMusicDetailList"]: for music in upsert["userMusicDetailList"]:
self.data.score.put_best_score(user_id, music) self.data.score.put_best_score(user_id, music)
if upsert["isNewCourseList"] and int(upsert["isNewCourseList"]) > 0: if upsert["isNewCourseList"] and int(upsert["isNewCourseList"]) > 0:
for course in upsert["userCourseList"]: for course in upsert["userCourseList"]:
self.data.score.put_course(user_id, course) self.data.score.put_course(user_id, course)
if upsert["isNewFavoriteList"] and int(upsert["isNewFavoriteList"]) > 0: if upsert["isNewFavoriteList"] and int(upsert["isNewFavoriteList"]) > 0:
for fav in upsert["userFavoriteList"]: for fav in upsert["userFavoriteList"]:
self.data.item.put_favorite(user_id, fav["kind"], fav["itemIdList"]) self.data.item.put_favorite(user_id, fav["kind"], fav["itemIdList"])
@ -211,45 +264,39 @@ class Mai2Base():
def handle_get_user_data_api_request(self, data: Dict) -> Dict: def handle_get_user_data_api_request(self, data: Dict) -> Dict:
profile = self.data.profile.get_profile_detail(data["userId"], self.version) profile = self.data.profile.get_profile_detail(data["userId"], self.version)
if profile is None: return if profile is None:
return
profile_dict = profile._asdict() profile_dict = profile._asdict()
profile_dict.pop("id") profile_dict.pop("id")
profile_dict.pop("user") profile_dict.pop("user")
profile_dict.pop("version") profile_dict.pop("version")
return { return {"userId": data["userId"], "userData": profile_dict}
"userId": data["userId"],
"userData": profile_dict
}
def handle_get_user_extend_api_request(self, data: Dict) -> Dict: def handle_get_user_extend_api_request(self, data: Dict) -> Dict:
extend = self.data.profile.get_profile_extend(data["userId"], self.version) extend = self.data.profile.get_profile_extend(data["userId"], self.version)
if extend is None: return if extend is None:
return
extend_dict = extend._asdict() extend_dict = extend._asdict()
extend_dict.pop("id") extend_dict.pop("id")
extend_dict.pop("user") extend_dict.pop("user")
extend_dict.pop("version") extend_dict.pop("version")
return { return {"userId": data["userId"], "userExtend": extend_dict}
"userId": data["userId"],
"userExtend": extend_dict
}
def handle_get_user_option_api_request(self, data: Dict) -> Dict: def handle_get_user_option_api_request(self, data: Dict) -> Dict:
options = self.data.profile.get_profile_option(data["userId"], self.version) options = self.data.profile.get_profile_option(data["userId"], self.version)
if options is None: return if options is None:
return
options_dict = options._asdict() options_dict = options._asdict()
options_dict.pop("id") options_dict.pop("id")
options_dict.pop("user") options_dict.pop("user")
options_dict.pop("version") options_dict.pop("version")
return { return {"userId": data["userId"], "userOption": options_dict}
"userId": data["userId"],
"userOption": options_dict
}
def handle_get_user_card_api_request(self, data: Dict) -> Dict: def handle_get_user_card_api_request(self, data: Dict) -> Dict:
return {"userId": data["userId"], "nextIndex": 0, "userCardList": []} return {"userId": data["userId"], "nextIndex": 0, "userCardList": []}
@ -266,73 +313,83 @@ class Mai2Base():
for x in range(next_idx, data["maxCount"]): for x in range(next_idx, data["maxCount"]):
try: try:
user_item_list.append({"item_kind": user_items[x]["item_kind"], "item_id": user_items[x]["item_id"], user_item_list.append(
"stock": user_items[x]["stock"], "isValid": user_items[x]["is_valid"]}) {
except: break "item_kind": user_items[x]["item_kind"],
"item_id": user_items[x]["item_id"],
"stock": user_items[x]["stock"],
"isValid": user_items[x]["is_valid"],
}
)
except:
break
if len(user_item_list) == data["maxCount"]: if len(user_item_list) == data["maxCount"]:
next_idx = data["nextIndex"] + data["maxCount"] + 1 next_idx = data["nextIndex"] + data["maxCount"] + 1
break break
return {"userId": data["userId"], "nextIndex": next_idx, "itemKind": kind, "userItemList": user_item_list} return {
"userId": data["userId"],
"nextIndex": next_idx,
"itemKind": kind,
"userItemList": user_item_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"])
chara_list = [] chara_list = []
for chara in characters: for chara in characters:
chara_list.append({ chara_list.append(
"characterId": chara["character_id"], {
"level": chara["level"], "characterId": chara["character_id"],
"awakening": chara["awakening"], "level": chara["level"],
"useCount": chara["use_count"], "awakening": chara["awakening"],
}) "useCount": chara["use_count"],
}
)
return {"userId": data["userId"], "userCharacterList": chara_list} return {"userId": data["userId"], "userCharacterList": chara_list}
def handle_get_user_favorite_api_request(self, data: Dict) -> Dict: def handle_get_user_favorite_api_request(self, data: Dict) -> Dict:
favorites = self.data.item.get_favorites(data["userId"], data["itemKind"]) favorites = self.data.item.get_favorites(data["userId"], data["itemKind"])
if favorites is None: return if favorites is None:
return
userFavs = [] userFavs = []
for fav in favorites: for fav in favorites:
userFavs.append({ userFavs.append(
"userId": data["userId"], {
"itemKind": fav["itemKind"], "userId": data["userId"],
"itemIdList": fav["itemIdList"] "itemKind": fav["itemKind"],
}) "itemIdList": fav["itemIdList"],
}
)
return { return {"userId": data["userId"], "userFavoriteData": userFavs}
"userId": data["userId"],
"userFavoriteData": userFavs
}
def handle_get_user_ghost_api_request(self, data: Dict) -> Dict: def handle_get_user_ghost_api_request(self, data: Dict) -> Dict:
ghost = self.data.profile.get_profile_ghost(data["userId"], self.version) ghost = self.data.profile.get_profile_ghost(data["userId"], self.version)
if ghost is None: return if ghost is None:
return
ghost_dict = ghost._asdict() ghost_dict = ghost._asdict()
ghost_dict.pop("user") ghost_dict.pop("user")
ghost_dict.pop("id") ghost_dict.pop("id")
ghost_dict.pop("version_int") ghost_dict.pop("version_int")
return { return {"userId": data["userId"], "userGhost": ghost_dict}
"userId": data["userId"],
"userGhost": ghost_dict
}
def handle_get_user_rating_api_request(self, data: Dict) -> Dict: def handle_get_user_rating_api_request(self, data: Dict) -> Dict:
rating = self.data.profile.get_profile_rating(data["userId"], self.version) rating = self.data.profile.get_profile_rating(data["userId"], self.version)
if rating is None: return if rating is None:
return
rating_dict = rating._asdict() rating_dict = rating._asdict()
rating_dict.pop("user") rating_dict.pop("user")
rating_dict.pop("id") rating_dict.pop("id")
rating_dict.pop("version") rating_dict.pop("version")
return { return {"userId": data["userId"], "userRating": rating_dict}
"userId": data["userId"],
"userRating": rating_dict
}
def handle_get_user_activity_api_request(self, data: Dict) -> Dict: def handle_get_user_activity_api_request(self, data: Dict) -> Dict:
""" """
@ -340,31 +397,27 @@ class Mai2Base():
""" """
playlist = self.data.profile.get_profile_activity(data["userId"], 1) playlist = self.data.profile.get_profile_activity(data["userId"], 1)
musiclist = self.data.profile.get_profile_activity(data["userId"], 2) musiclist = self.data.profile.get_profile_activity(data["userId"], 2)
if playlist is None or musiclist is None: return if playlist is None or musiclist is None:
return
plst = [] plst = []
mlst = [] mlst = []
for play in playlist: for play in playlist:
tmp = play._asdict() tmp = play._asdict()
tmp["id"] = tmp["activityId"] tmp["id"] = tmp["activityId"]
tmp.pop("activityId") tmp.pop("activityId")
tmp.pop("user") tmp.pop("user")
plst.append(tmp) plst.append(tmp)
for music in musiclist: for music in musiclist:
tmp = music._asdict() tmp = music._asdict()
tmp["id"] = tmp["activityId"] tmp["id"] = tmp["activityId"]
tmp.pop("activityId") tmp.pop("activityId")
tmp.pop("user") tmp.pop("user")
mlst.append(tmp) mlst.append(tmp)
return { return {"userActivity": {"playList": plst, "musicList": mlst}}
"userActivity": {
"playList": plst,
"musicList": mlst
}
}
def handle_get_user_course_api_request(self, data: Dict) -> Dict: def handle_get_user_course_api_request(self, data: Dict) -> Dict:
user_courses = self.data.score.get_courses(data["userId"]) user_courses = self.data.score.get_courses(data["userId"])
@ -389,21 +442,30 @@ class Mai2Base():
for x in range(data["nextIndex"], data["maxCount"] + data["nextIndex"]): for x in range(data["nextIndex"], data["maxCount"] + data["nextIndex"]):
try: try:
friend_season_ranking_list.append({ friend_season_ranking_list.append(
"mapId": friend_season_ranking_list[x]["map_id"], {
"distance": friend_season_ranking_list[x]["distance"], "mapId": friend_season_ranking_list[x]["map_id"],
"isLock": friend_season_ranking_list[x]["is_lock"], "distance": friend_season_ranking_list[x]["distance"],
"isClear": friend_season_ranking_list[x]["is_clear"], "isLock": friend_season_ranking_list[x]["is_lock"],
"isComplete": friend_season_ranking_list[x]["is_complete"], "isClear": friend_season_ranking_list[x]["is_clear"],
}) "isComplete": friend_season_ranking_list[x]["is_complete"],
}
)
except: except:
break break
# We're capped and still have some left to go # We're capped and still have some left to go
if len(friend_season_ranking_list) == data["maxCount"] and len(friend_season_ranking) > data["maxCount"] + data["nextIndex"]: if (
len(friend_season_ranking_list) == data["maxCount"]
and len(friend_season_ranking) > data["maxCount"] + data["nextIndex"]
):
next_index = data["maxCount"] + data["nextIndex"] next_index = data["maxCount"] + data["nextIndex"]
return {"userId": data["userId"], "nextIndex": next_index, "userFriendSeasonRankingList": friend_season_ranking_list} return {
"userId": data["userId"],
"nextIndex": next_index,
"userFriendSeasonRankingList": friend_season_ranking_list,
}
def handle_get_user_map_api_request(self, data: Dict) -> Dict: def handle_get_user_map_api_request(self, data: Dict) -> Dict:
maps = self.data.item.get_maps(data["userId"]) maps = self.data.item.get_maps(data["userId"])
@ -412,21 +474,30 @@ class Mai2Base():
for x in range(data["nextIndex"], data["maxCount"] + data["nextIndex"]): for x in range(data["nextIndex"], data["maxCount"] + data["nextIndex"]):
try: try:
map_list.append({ map_list.append(
"mapId": maps[x]["map_id"], {
"distance": maps[x]["distance"], "mapId": maps[x]["map_id"],
"isLock": maps[x]["is_lock"], "distance": maps[x]["distance"],
"isClear": maps[x]["is_clear"], "isLock": maps[x]["is_lock"],
"isComplete": maps[x]["is_complete"], "isClear": maps[x]["is_clear"],
}) "isComplete": maps[x]["is_complete"],
}
)
except: except:
break break
# We're capped and still have some left to go # We're capped and still have some left to go
if len(map_list) == data["maxCount"] and len(maps) > data["maxCount"] + data["nextIndex"]: if (
len(map_list) == data["maxCount"]
and len(maps) > data["maxCount"] + data["nextIndex"]
):
next_index = data["maxCount"] + data["nextIndex"] next_index = data["maxCount"] + data["nextIndex"]
return {"userId": data["userId"], "nextIndex": next_index, "userMapList": map_list} return {
"userId": data["userId"],
"nextIndex": next_index,
"userMapList": map_list,
}
def handle_get_user_login_bonus_api_request(self, data: Dict) -> Dict: def handle_get_user_login_bonus_api_request(self, data: Dict) -> Dict:
login_bonuses = self.data.item.get_login_bonuses(data["userId"]) login_bonuses = self.data.item.get_login_bonuses(data["userId"])
@ -435,20 +506,29 @@ class Mai2Base():
for x in range(data["nextIndex"], data["maxCount"] + data["nextIndex"]): for x in range(data["nextIndex"], data["maxCount"] + data["nextIndex"]):
try: try:
login_bonus_list.append({ login_bonus_list.append(
"bonusId": login_bonuses[x]["bonus_id"], {
"point": login_bonuses[x]["point"], "bonusId": login_bonuses[x]["bonus_id"],
"isCurrent": login_bonuses[x]["is_current"], "point": login_bonuses[x]["point"],
"isComplete": login_bonuses[x]["is_complete"], "isCurrent": login_bonuses[x]["is_current"],
}) "isComplete": login_bonuses[x]["is_complete"],
}
)
except: except:
break break
# We're capped and still have some left to go # We're capped and still have some left to go
if len(login_bonus_list) == data["maxCount"] and len(login_bonuses) > data["maxCount"] + data["nextIndex"]: if (
len(login_bonus_list) == data["maxCount"]
and len(login_bonuses) > data["maxCount"] + data["nextIndex"]
):
next_index = data["maxCount"] + data["nextIndex"] next_index = data["maxCount"] + data["nextIndex"]
return {"userId": data["userId"], "nextIndex": next_index, "userLoginBonusList": login_bonus_list} return {
"userId": data["userId"],
"nextIndex": next_index,
"userLoginBonusList": login_bonus_list,
}
def handle_get_user_region_api_request(self, data: Dict) -> Dict: def handle_get_user_region_api_request(self, data: Dict) -> Dict:
return {"userId": data["userId"], "length": 0, "userRegionList": []} return {"userId": data["userId"], "length": 0, "userRegionList": []}
@ -460,18 +540,24 @@ class Mai2Base():
if songs is not None: if songs is not None:
for song in songs: for song in songs:
music_detail_list.append({ music_detail_list.append(
"musicId": song["song_id"], {
"level": song["chart_id"], "musicId": song["song_id"],
"playCount": song["play_count"], "level": song["chart_id"],
"achievement": song["achievement"], "playCount": song["play_count"],
"comboStatus": song["combo_status"], "achievement": song["achievement"],
"syncStatus": song["sync_status"], "comboStatus": song["combo_status"],
"deluxscoreMax": song["dx_score"], "syncStatus": song["sync_status"],
"scoreRank": song["score_rank"], "deluxscoreMax": song["dx_score"],
}) "scoreRank": song["score_rank"],
}
)
if len(music_detail_list) == data["maxCount"]: if len(music_detail_list) == data["maxCount"]:
next_index = data["maxCount"] + data["nextIndex"] next_index = data["maxCount"] + data["nextIndex"]
break break
return {"userId": data["userId"], "nextIndex": next_index, "userMusicList": [{"userMusicDetailList": music_detail_list}]} return {
"userId": data["userId"],
"nextIndex": next_index,
"userMusicList": [{"userMusicDetailList": music_detail_list}],
}

View File

@ -1,17 +1,25 @@
from core.config import CoreConfig from core.config import CoreConfig
class Mai2ServerConfig():
class Mai2ServerConfig:
def __init__(self, parent: "Mai2Config") -> None: def __init__(self, parent: "Mai2Config") -> None:
self.__config = parent self.__config = parent
@property @property
def enable(self) -> bool: def enable(self) -> bool:
return CoreConfig.get_config_field(self.__config, 'mai2', 'server', 'enable', default=True) return CoreConfig.get_config_field(
self.__config, "mai2", "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, 'mai2', 'server', 'loglevel', default="info")) return CoreConfig.str_to_loglevel(
CoreConfig.get_config_field(
self.__config, "mai2", "server", "loglevel", default="info"
)
)
class Mai2Config(dict): class Mai2Config(dict):
def __init__(self) -> None: def __init__(self) -> None:
self.server = Mai2ServerConfig(self) self.server = Mai2ServerConfig(self)

View File

@ -1,4 +1,4 @@
class Mai2Constants(): class Mai2Constants:
GRADE = { GRADE = {
"D": 0, "D": 0,
"C": 1, "C": 1,
@ -13,22 +13,10 @@ class Mai2Constants():
"SS": 10, "SS": 10,
"SS+": 11, "SS+": 11,
"SSS": 12, "SSS": 12,
"SSS+": 13 "SSS+": 13,
}
FC = {
"None": 0,
"FC": 1,
"FC+": 2,
"AP": 3,
"AP+": 4
}
SYNC = {
"None": 0,
"FS": 1,
"FS+": 2,
"FDX": 3,
"FDX+": 4
} }
FC = {"None": 0, "FC": 1, "FC+": 2, "AP": 3, "AP+": 4}
SYNC = {"None": 0, "FS": 1, "FS+": 2, "FDX": 3, "FDX+": 4}
DATE_TIME_FORMAT = "%Y-%m-%d %H:%M:%S" DATE_TIME_FORMAT = "%Y-%m-%d %H:%M:%S"
@ -43,9 +31,15 @@ class Mai2Constants():
VER_MAIMAI_DX_UNIVERSE = 4 VER_MAIMAI_DX_UNIVERSE = 4
VER_MAIMAI_DX_UNIVERSE_PLUS = 5 VER_MAIMAI_DX_UNIVERSE_PLUS = 5
VERSION_STRING = ("maimai Delux", "maimai Delux PLUS", "maimai Delux Splash", "maimai Delux Splash PLUS", "maimai Delux Universe", VERSION_STRING = (
"maimai Delux Universe PLUS") "maimai Delux",
"maimai Delux PLUS",
"maimai Delux Splash",
"maimai Delux Splash PLUS",
"maimai Delux Universe",
"maimai Delux Universe PLUS",
)
@classmethod @classmethod
def game_ver_to_string(cls, ver: int): def game_ver_to_string(cls, ver: int):
return cls.VERSION_STRING[ver] return cls.VERSION_STRING[ver]

View File

@ -1,6 +1,12 @@
from core.data import Data from core.data import Data
from core.config import CoreConfig from core.config import CoreConfig
from titles.mai2.schema import Mai2ItemData, Mai2ProfileData, Mai2StaticData, Mai2ScoreData from titles.mai2.schema import (
Mai2ItemData,
Mai2ProfileData,
Mai2StaticData,
Mai2ScoreData,
)
class Mai2Data(Data): class Mai2Data(Data):
def __init__(self, cfg: CoreConfig) -> None: def __init__(self, cfg: CoreConfig) -> None:
@ -9,4 +15,4 @@ class Mai2Data(Data):
self.profile = Mai2ProfileData(self.config, self.session) self.profile = Mai2ProfileData(self.config, self.session)
self.item = Mai2ItemData(self.config, self.session) self.item = Mai2ItemData(self.config, self.session)
self.static = Mai2StaticData(self.config, self.session) self.static = Mai2StaticData(self.config, self.session)
self.score = Mai2ScoreData(self.config, self.session) self.score = Mai2ScoreData(self.config, self.session)

View File

@ -20,12 +20,14 @@ from titles.mai2.universe import Mai2Universe
from titles.mai2.universeplus import Mai2UniversePlus from titles.mai2.universeplus import Mai2UniversePlus
class Mai2Servlet(): class Mai2Servlet:
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 = Mai2Config() self.game_cfg = Mai2Config()
if path.exists(f"{cfg_dir}/{Mai2Constants.CONFIG_NAME}"): if path.exists(f"{cfg_dir}/{Mai2Constants.CONFIG_NAME}"):
self.game_cfg.update(yaml.safe_load(open(f"{cfg_dir}/{Mai2Constants.CONFIG_NAME}"))) self.game_cfg.update(
yaml.safe_load(open(f"{cfg_dir}/{Mai2Constants.CONFIG_NAME}"))
)
self.versions = [ self.versions = [
Mai2Base(core_cfg, self.game_cfg), Mai2Base(core_cfg, self.game_cfg),
@ -39,34 +41,52 @@ class Mai2Servlet():
self.logger = logging.getLogger("mai2") self.logger = logging.getLogger("mai2")
log_fmt_str = "[%(asctime)s] Mai2 | %(levelname)s | %(message)s" log_fmt_str = "[%(asctime)s] Mai2 | %(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, "mai2"), encoding='utf8', fileHandler = TimedRotatingFileHandler(
when="d", backupCount=10) "{0}/{1}.log".format(self.core_cfg.server.log_dir, "mai2"),
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
)
@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 = Mai2Config() game_cfg = Mai2Config()
if path.exists(f"{cfg_dir}/{Mai2Constants.CONFIG_NAME}"): if path.exists(f"{cfg_dir}/{Mai2Constants.CONFIG_NAME}"):
game_cfg.update(yaml.safe_load(open(f"{cfg_dir}/{Mai2Constants.CONFIG_NAME}"))) game_cfg.update(
yaml.safe_load(open(f"{cfg_dir}/{Mai2Constants.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/", f"{core_cfg.title.hostname}:{core_cfg.title.port}/") return (
True,
return (True, f"http://{core_cfg.title.hostname}/{game_code}/$v/", f"{core_cfg.title.hostname}/") f"http://{core_cfg.title.hostname}:{core_cfg.title.port}/{game_code}/$v/",
f"{core_cfg.title.hostname}:{core_cfg.title.port}/",
)
return (
True,
f"http://{core_cfg.title.hostname}/{game_code}/$v/",
f"{core_cfg.title.hostname}/",
)
def render_POST(self, request: Request, version: int, url_path: str) -> bytes: def render_POST(self, request: Request, version: int, url_path: str) -> bytes:
if url_path.lower() == "/ping": if url_path.lower() == "/ping":
@ -78,34 +98,36 @@ class Mai2Servlet():
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 = Mai2Constants.VER_MAIMAI_DX internal_ver = Mai2Constants.VER_MAIMAI_DX
elif version >= 105 and version < 110: # Plus elif version >= 105 and version < 110: # Plus
internal_ver = Mai2Constants.VER_MAIMAI_DX_PLUS internal_ver = Mai2Constants.VER_MAIMAI_DX_PLUS
elif version >= 110 and version < 115: # Splash elif version >= 110 and version < 115: # Splash
internal_ver = Mai2Constants.VER_MAIMAI_DX_SPLASH internal_ver = Mai2Constants.VER_MAIMAI_DX_SPLASH
elif version >= 115 and version < 120: # Splash Plus elif version >= 115 and version < 120: # Splash Plus
internal_ver = Mai2Constants.VER_MAIMAI_DX_SPLASH_PLUS internal_ver = Mai2Constants.VER_MAIMAI_DX_SPLASH_PLUS
elif version >= 120 and version < 125: # Universe elif version >= 120 and version < 125: # Universe
internal_ver = Mai2Constants.VER_MAIMAI_DX_UNIVERSE internal_ver = Mai2Constants.VER_MAIMAI_DX_UNIVERSE
elif version >= 125: # Universe Plus elif version >= 125: # Universe Plus
internal_ver = Mai2Constants.VER_MAIMAI_DX_UNIVERSE_PLUS internal_ver = Mai2Constants.VER_MAIMAI_DX_UNIVERSE_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
self.logger.error("Encryption not supported at this time") self.logger.error("Encryption not supported at this time")
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 zlib.compress(b'{"stat": "0"}') return zlib.compress(b'{"stat": "0"}')
req_data = json.loads(unzip) req_data = json.loads(unzip)
self.logger.info(f"v{version} {endpoint} request - {req_data}") self.logger.info(f"v{version} {endpoint} request - {req_data}")
func_to_find = "handle_" + inflection.underscore(endpoint) + "_request" func_to_find = "handle_" + inflection.underscore(endpoint) + "_request"
@ -121,10 +143,10 @@ class Mai2Servlet():
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.info(f"Response {resp}") self.logger.info(f"Response {resp}")
return zlib.compress(json.dumps(resp, ensure_ascii=False).encode("utf-8")) return zlib.compress(json.dumps(resp, ensure_ascii=False).encode("utf-8"))

View File

@ -8,7 +8,8 @@ from titles.mai2.base import Mai2Base
from titles.mai2.config import Mai2Config from titles.mai2.config import Mai2Config
from titles.mai2.const import Mai2Constants from titles.mai2.const import Mai2Constants
class Mai2Plus(Mai2Base): class Mai2Plus(Mai2Base):
def __init__(self, cfg: CoreConfig, game_cfg: Mai2Config) -> None: def __init__(self, cfg: CoreConfig, game_cfg: Mai2Config) -> None:
super().__init__(cfg, game_cfg) super().__init__(cfg, game_cfg)
self.version = Mai2Constants.VER_MAIMAI_DX_PLUS self.version = Mai2Constants.VER_MAIMAI_DX_PLUS

View File

@ -11,25 +11,35 @@ from read import BaseReader
from titles.mai2.const import Mai2Constants from titles.mai2.const import Mai2Constants
from titles.mai2.database import Mai2Data from titles.mai2.database import Mai2Data
class Mai2Reader(BaseReader): class Mai2Reader(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 = Mai2Data(config) self.data = Mai2Data(config)
try: try:
self.logger.info(f"Start importer for {Mai2Constants.game_ver_to_string(version)}") self.logger.info(
f"Start importer for {Mai2Constants.game_ver_to_string(version)}"
)
except IndexError: except IndexError:
self.logger.error(f"Invalid maidx version {version}") self.logger.error(f"Invalid maidx 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.get_events(f"{dir}/event") self.get_events(f"{dir}/event")
@ -43,47 +53,64 @@ class Mai2Reader(BaseReader):
for dir in dirs: for dir in dirs:
if os.path.exists(f"{root}/{dir}/Event.xml"): if os.path.exists(f"{root}/{dir}/Event.xml"):
with open(f"{root}/{dir}/Event.xml", encoding="utf-8") as f: with open(f"{root}/{dir}/Event.xml", 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
id = int(troot.find('name').find('id').text) id = int(troot.find("name").find("id").text)
event_type = int(troot.find('infoType').text) event_type = int(troot.find("infoType").text)
self.data.static.put_game_event(self.version, event_type, id, name) self.data.static.put_game_event(
self.version, event_type, id, name
)
self.logger.info(f"Added event {id}...") self.logger.info(f"Added event {id}...")
def read_music(self, base_dir: str) -> None: def read_music(self, base_dir: str) -> None:
self.logger.info(f"Reading music from {base_dir}...") self.logger.info(f"Reading music from {base_dir}...")
for root, dirs, files in os.walk(base_dir): for root, dirs, files in os.walk(base_dir):
for dir in dirs: for dir in dirs:
if os.path.exists(f"{root}/{dir}/Music.xml"): if os.path.exists(f"{root}/{dir}/Music.xml"):
with open(f"{root}/{dir}/Music.xml", encoding="utf-8") as f: with open(f"{root}/{dir}/Music.xml", encoding="utf-8") as f:
troot = ET.fromstring(f.read()) troot = ET.fromstring(f.read())
song_id = int(troot.find('name').find('id').text) song_id = int(troot.find("name").find("id").text)
title = troot.find('name').find('str').text title = troot.find("name").find("str").text
artist = troot.find('artistName').find('str').text artist = troot.find("artistName").find("str").text
genre = troot.find('genreName').find('str').text genre = troot.find("genreName").find("str").text
bpm = int(troot.find('bpm').text) bpm = int(troot.find("bpm").text)
added_ver = troot.find('AddVersion').find('str').text added_ver = troot.find("AddVersion").find("str").text
note_data = troot.find('notesData').findall('Notes') note_data = troot.find("notesData").findall("Notes")
for dif in note_data: for dif in note_data:
path = dif.find('file').find('path').text path = dif.find("file").find("path").text
if path is not None: if path is not None:
if os.path.exists(f"{root}/{dir}/{path}"): if os.path.exists(f"{root}/{dir}/{path}"):
chart_id = int(path.split(".")[0].split('_')[1]) chart_id = int(path.split(".")[0].split("_")[1])
diff_num = float(f"{dif.find('level').text}.{dif.find('levelDecimal').text}") diff_num = float(
note_designer = dif.find('notesDesigner').find('str').text f"{dif.find('level').text}.{dif.find('levelDecimal').text}"
)
note_designer = (
dif.find("notesDesigner").find("str").text
)
self.data.static.put_game_music(
self.version,
song_id,
chart_id,
title,
artist,
genre,
bpm,
added_ver,
diff_num,
note_designer,
)
self.logger.info(
f"Added music id {song_id} chart {chart_id}"
)
self.data.static.put_game_music(self.version, song_id, chart_id, title, artist,
genre, bpm, added_ver, diff_num, note_designer)
self.logger.info(f"Added music id {song_id} chart {chart_id}")
def read_tickets(self, base_dir: str) -> None: def read_tickets(self, base_dir: str) -> None:
self.logger.info(f"Reading tickets from {base_dir}...") self.logger.info(f"Reading tickets from {base_dir}...")
@ -91,13 +118,14 @@ class Mai2Reader(BaseReader):
for dir in dirs: for dir in dirs:
if os.path.exists(f"{root}/{dir}/Ticket.xml"): if os.path.exists(f"{root}/{dir}/Ticket.xml"):
with open(f"{root}/{dir}/Ticket.xml", encoding="utf-8") as f: with open(f"{root}/{dir}/Ticket.xml", 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
id = int(troot.find('name').find('id').text) id = int(troot.find("name").find("id").text)
ticket_type = int(troot.find('ticketKind').find('id').text) ticket_type = int(troot.find("ticketKind").find("id").text)
price = int(troot.find('creditNum').text) price = int(troot.find("creditNum").text)
self.data.static.put_game_ticket(self.version, id, ticket_type, price, name) self.data.static.put_game_ticket(
self.version, id, ticket_type, price, name
)
self.logger.info(f"Added ticket {id}...") self.logger.info(f"Added ticket {id}...")

View File

@ -3,4 +3,4 @@ from titles.mai2.schema.item import Mai2ItemData
from titles.mai2.schema.static import Mai2StaticData from titles.mai2.schema.static import Mai2StaticData
from titles.mai2.schema.score import Mai2ScoreData from titles.mai2.schema.score import Mai2ScoreData
__all__ = [Mai2ProfileData, Mai2ItemData, Mai2StaticData, Mai2ScoreData] __all__ = [Mai2ProfileData, Mai2ItemData, Mai2StaticData, Mai2ScoreData]

View File

@ -12,20 +12,28 @@ character = Table(
"mai2_item_character", "mai2_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("character_id", Integer, nullable=False), Column("character_id", Integer, nullable=False),
Column("level", Integer, nullable=False, server_default="1"), Column("level", Integer, nullable=False, server_default="1"),
Column("awakening", Integer, nullable=False, server_default="0"), Column("awakening", Integer, nullable=False, server_default="0"),
Column("use_count", Integer, nullable=False, server_default="0"), Column("use_count", Integer, nullable=False, server_default="0"),
UniqueConstraint("user", "character_id", name="mai2_item_character_uk"), UniqueConstraint("user", "character_id", name="mai2_item_character_uk"),
mysql_charset='utf8mb4' mysql_charset="utf8mb4",
) )
card = Table( card = Table(
"mai2_item_card", "mai2_item_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("card_kind", Integer, nullable=False), Column("card_kind", Integer, nullable=False),
Column("card_id", Integer, nullable=False), Column("card_id", Integer, nullable=False),
Column("chara_id", Integer, nullable=False), Column("chara_id", Integer, nullable=False),
@ -33,54 +41,70 @@ card = Table(
Column("start_date", String(255), nullable=False), Column("start_date", String(255), nullable=False),
Column("end_date", String(255), nullable=False), Column("end_date", String(255), nullable=False),
UniqueConstraint("user", "card_kind", "card_id", name="mai2_item_card_uk"), UniqueConstraint("user", "card_kind", "card_id", name="mai2_item_card_uk"),
mysql_charset='utf8mb4' mysql_charset="utf8mb4",
) )
item = Table( item = Table(
"mai2_item_item", "mai2_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("item_kind", Integer, nullable=False), Column("item_kind", Integer, nullable=False),
Column("item_id", Integer, nullable=False), Column("item_id", Integer, nullable=False),
Column("stock", Integer, nullable=False, server_default="1"), Column("stock", Integer, nullable=False, server_default="1"),
Column("is_valid", Boolean, nullable=False, server_default="1"), Column("is_valid", Boolean, nullable=False, server_default="1"),
UniqueConstraint("user", "item_kind", "item_id", name="mai2_item_item_uk"), UniqueConstraint("user", "item_kind", "item_id", name="mai2_item_item_uk"),
mysql_charset='utf8mb4' mysql_charset="utf8mb4",
) )
map = Table( map = Table(
"mai2_item_map", "mai2_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("map_id", Integer, nullable=False), Column("map_id", Integer, nullable=False),
Column("distance", Integer, nullable=False), Column("distance", Integer, nullable=False),
Column("is_lock", Boolean, nullable=False, server_default="0"), Column("is_lock", Boolean, nullable=False, server_default="0"),
Column("is_clear", Boolean, nullable=False, server_default="0"), Column("is_clear", Boolean, nullable=False, server_default="0"),
Column("is_complete", Boolean, nullable=False, server_default="0"), Column("is_complete", Boolean, nullable=False, server_default="0"),
UniqueConstraint("user", "map_id", name="mai2_item_map_uk"), UniqueConstraint("user", "map_id", name="mai2_item_map_uk"),
mysql_charset='utf8mb4' mysql_charset="utf8mb4",
) )
login_bonus = Table( login_bonus = Table(
"mai2_item_login_bonus", "mai2_item_login_bonus",
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("bonus_id", Integer, nullable=False), Column("bonus_id", Integer, nullable=False),
Column("point", Integer, nullable=False), Column("point", Integer, nullable=False),
Column("is_current", Boolean, nullable=False, server_default="0"), Column("is_current", Boolean, nullable=False, server_default="0"),
Column("is_complete", Boolean, nullable=False, server_default="0"), Column("is_complete", Boolean, nullable=False, server_default="0"),
UniqueConstraint("user", "bonus_id", name="mai2_item_login_bonus_uk"), UniqueConstraint("user", "bonus_id", name="mai2_item_login_bonus_uk"),
mysql_charset='utf8mb4' mysql_charset="utf8mb4",
) )
friend_season_ranking = Table( friend_season_ranking = Table(
"mai2_item_friend_season_ranking", "mai2_item_friend_season_ranking",
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("season_id", Integer, nullable=False), Column("season_id", Integer, nullable=False),
Column("point", Integer, nullable=False), Column("point", Integer, nullable=False),
Column("rank", Integer, nullable=False), Column("rank", Integer, nullable=False),
@ -88,35 +112,46 @@ friend_season_ranking = Table(
Column("user_name", String(8), nullable=False), Column("user_name", String(8), nullable=False),
Column("record_date", String(255), nullable=False), Column("record_date", String(255), nullable=False),
UniqueConstraint("user", "season_id", "user_name", name="mai2_item_login_bonus_uk"), UniqueConstraint("user", "season_id", "user_name", name="mai2_item_login_bonus_uk"),
mysql_charset='utf8mb4' mysql_charset="utf8mb4",
) )
favorite = Table( favorite = Table(
"mai2_item_favorite", "mai2_item_favorite",
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("itemKind", Integer, nullable=False), Column("itemKind", Integer, nullable=False),
Column("itemIdList", JSON), Column("itemIdList", JSON),
UniqueConstraint("user", "itemKind", name="mai2_item_favorite_uk"), UniqueConstraint("user", "itemKind", name="mai2_item_favorite_uk"),
mysql_charset='utf8mb4' mysql_charset="utf8mb4",
) )
charge = Table( charge = Table(
"mai2_item_charge", "mai2_item_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("charge_id", Integer, nullable=False), Column("charge_id", Integer, nullable=False),
Column("stock", Integer, nullable=False), Column("stock", Integer, nullable=False),
Column("purchase_date", String(255), nullable=False), Column("purchase_date", String(255), nullable=False),
Column("valid_date", String(255), nullable=False), Column("valid_date", String(255), nullable=False),
UniqueConstraint("user", "charge_id", name="mai2_item_charge_uk"), UniqueConstraint("user", "charge_id", name="mai2_item_charge_uk"),
mysql_charset='utf8mb4' mysql_charset="utf8mb4",
) )
class Mai2ItemData(BaseData): class Mai2ItemData(BaseData):
def put_item(self, user_id: int, item_kind: int, item_id: int, stock: int, is_valid: bool) -> None: def put_item(
self, user_id: int, item_kind: int, item_id: int, stock: int, is_valid: bool
) -> None:
sql = insert(item).values( sql = insert(item).values(
user=user_id, user=user_id,
item_kind=item_kind, item_kind=item_kind,
@ -132,28 +167,47 @@ class Mai2ItemData(BaseData):
result = self.execute(conflict) result = self.execute(conflict)
if result is None: if result is None:
self.logger.warn(f"put_item: failed to insert item! user_id: {user_id}, item_kind: {item_kind}, item_id: {item_id}") self.logger.warn(
f"put_item: failed to insert item! user_id: {user_id}, item_kind: {item_kind}, item_id: {item_id}"
)
return None return None
return result.lastrowid return result.lastrowid
def get_items(self, user_id: int, item_kind: int = None) -> Optional[List[Row]]: def get_items(self, user_id: int, item_kind: int = None) -> Optional[List[Row]]:
if item_kind is None: if item_kind is None:
sql = item.select(item.c.user == user_id) sql = item.select(item.c.user == user_id)
else: else:
sql = item.select(and_(item.c.user == user_id, item.c.item_kind == item_kind)) sql = item.select(
and_(item.c.user == user_id, item.c.item_kind == item_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 get_item(self, user_id: int, item_kind: int, item_id: int) -> Optional[Row]: def get_item(self, user_id: int, item_kind: int, item_id: int) -> Optional[Row]:
sql = item.select(and_(item.c.user == user_id, item.c.item_kind == item_kind, item.c.item_id == item_id)) sql = item.select(
and_(
item.c.user == user_id,
item.c.item_kind == item_kind,
item.c.item_id == item_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_login_bonus(self, user_id: int, bonus_id: int, point: int, is_current: bool, is_complete: bool) -> None: def put_login_bonus(
self,
user_id: int,
bonus_id: int,
point: int,
is_current: bool,
is_complete: bool,
) -> None:
sql = insert(login_bonus).values( sql = insert(login_bonus).values(
user=user_id, user=user_id,
bonus_id=bonus_id, bonus_id=bonus_id,
@ -170,25 +224,39 @@ class Mai2ItemData(BaseData):
result = self.execute(conflict) result = self.execute(conflict)
if result is None: if result is None:
self.logger.warn(f"put_login_bonus: failed to insert item! user_id: {user_id}, bonus_id: {bonus_id}, point: {point}") self.logger.warn(
f"put_login_bonus: failed to insert item! user_id: {user_id}, bonus_id: {bonus_id}, point: {point}"
)
return None return None
return result.lastrowid return result.lastrowid
def get_login_bonuses(self, user_id: int) -> Optional[List[Row]]: def get_login_bonuses(self, user_id: int) -> Optional[List[Row]]:
sql = login_bonus.select(login_bonus.c.user == user_id) sql = login_bonus.select(login_bonus.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 get_login_bonus(self, user_id: int, bonus_id: int) -> Optional[Row]: def get_login_bonus(self, user_id: int, bonus_id: int) -> Optional[Row]:
sql = login_bonus.select(and_(login_bonus.c.user == user_id, login_bonus.c.bonus_id == bonus_id)) sql = login_bonus.select(
and_(login_bonus.c.user == user_id, login_bonus.c.bonus_id == bonus_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_map(self, user_id: int, map_id: int, distance: int, is_lock: bool, is_clear: bool, is_complete: bool) -> None: def put_map(
self,
user_id: int,
map_id: int,
distance: int,
is_lock: bool,
is_clear: bool,
is_complete: bool,
) -> None:
sql = insert(map).values( sql = insert(map).values(
user=user_id, user=user_id,
map_id=map_id, map_id=map_id,
@ -207,25 +275,36 @@ class Mai2ItemData(BaseData):
result = self.execute(conflict) result = self.execute(conflict)
if result is None: if result is None:
self.logger.warn(f"put_map: failed to insert item! user_id: {user_id}, map_id: {map_id}, distance: {distance}") self.logger.warn(
f"put_map: failed to insert item! user_id: {user_id}, map_id: {map_id}, distance: {distance}"
)
return 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 = map.select(map.c.user == user_id) sql = map.select(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 get_map(self, user_id: int, map_id: int) -> Optional[Row]: def get_map(self, user_id: int, map_id: int) -> Optional[Row]:
sql = map.select(and_(map.c.user == user_id, map.c.map_id == map_id)) sql = map.select(and_(map.c.user == user_id, map.c.map_id == map_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_character(self, user_id: int, character_id: int, level: int, awakening: int, use_count: int) -> None: def put_character(
self,
user_id: int,
character_id: int,
level: int,
awakening: int,
use_count: int,
) -> None:
sql = insert(character).values( sql = insert(character).values(
user=user_id, user=user_id,
character_id=character_id, character_id=character_id,
@ -242,57 +321,64 @@ class Mai2ItemData(BaseData):
result = self.execute(conflict) result = self.execute(conflict)
if result is None: if result is None:
self.logger.warn(f"put_character: failed to insert item! user_id: {user_id}, character_id: {character_id}, level: {level}") self.logger.warn(
f"put_character: failed to insert item! user_id: {user_id}, character_id: {character_id}, level: {level}"
)
return None return None
return result.lastrowid return result.lastrowid
def get_characters(self, user_id: int) -> Optional[List[Row]]: def get_characters(self, user_id: int) -> Optional[List[Row]]:
sql = character.select(character.c.user == user_id) sql = character.select(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 get_character(self, user_id: int, character_id: int) -> Optional[Row]: def get_character(self, user_id: int, character_id: int) -> Optional[Row]:
sql = character.select(and_(character.c.user == user_id, character.c.character_id == character_id)) sql = character.select(
and_(character.c.user == user_id, character.c.character_id == 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_friend_season_ranking(self, user_id: int) -> Optional[Row]: def get_friend_season_ranking(self, user_id: int) -> Optional[Row]:
sql = friend_season_ranking.select(friend_season_ranking.c.user == user_id) sql = friend_season_ranking.select(friend_season_ranking.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.fetchone() return result.fetchone()
def put_favorite(self, user_id: int, kind: int, item_id_list: List[int]) -> Optional[int]: def put_favorite(
self, user_id: int, kind: int, item_id_list: List[int]
) -> Optional[int]:
sql = insert(favorite).values( sql = insert(favorite).values(
user=user_id, user=user_id, kind=kind, item_id_list=item_id_list
kind=kind,
item_id_list=item_id_list
) )
conflict = sql.on_duplicate_key_update( conflict = sql.on_duplicate_key_update(item_id_list=item_id_list)
item_id_list=item_id_list
)
result = self.execute(conflict) result = self.execute(conflict)
if result is None: if result is None:
self.logger.warn(f"put_favorite: failed to insert item! user_id: {user_id}, kind: {kind}") self.logger.warn(
f"put_favorite: failed to insert item! user_id: {user_id}, kind: {kind}"
)
return None return None
return result.lastrowid return result.lastrowid
def get_favorites(self, user_id: int, kind: int = None) -> Optional[Row]: def get_favorites(self, user_id: int, kind: int = None) -> Optional[Row]:
if kind is None: if kind is None:
sql = favorite.select(favorite.c.user == user_id) sql = favorite.select(favorite.c.user == user_id)
else: else:
sql = favorite.select(and_( sql = favorite.select(
favorite.c.user == user_id, and_(favorite.c.user == user_id, favorite.c.itemKind == kind)
favorite.c.itemKind == kind )
))
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()

View File

@ -14,7 +14,11 @@ detail = Table(
"mai2_profile_detail", "mai2_profile_detail",
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("userName", String(25)), Column("userName", String(25)),
Column("isNetMember", Integer), Column("isNetMember", Integer),
@ -41,9 +45,9 @@ detail = Table(
Column("lastRomVersion", String(25)), Column("lastRomVersion", String(25)),
Column("lastDataVersion", String(25)), Column("lastDataVersion", String(25)),
Column("lastLoginDate", String(25)), Column("lastLoginDate", String(25)),
Column("lastPairLoginDate", String(25)), # new with uni+ Column("lastPairLoginDate", String(25)), # new with uni+
Column("lastPlayDate", String(25)), Column("lastPlayDate", String(25)),
Column("lastTrialPlayDate", String(25)), # new with uni+ Column("lastTrialPlayDate", String(25)), # new with uni+
Column("lastPlayCredit", Integer), Column("lastPlayCredit", Integer),
Column("lastPlayMode", Integer), Column("lastPlayMode", Integer),
Column("lastPlaceId", Integer), Column("lastPlaceId", Integer),
@ -90,16 +94,20 @@ detail = Table(
Column("playerOldRating", BigInteger), Column("playerOldRating", BigInteger),
Column("playerNewRating", BigInteger), Column("playerNewRating", BigInteger),
Column("dateTime", BigInteger), Column("dateTime", BigInteger),
Column("banState", Integer), # new with uni+ Column("banState", Integer), # new with uni+
UniqueConstraint("user", "version", name="mai2_profile_detail_uk"), UniqueConstraint("user", "version", name="mai2_profile_detail_uk"),
mysql_charset='utf8mb4' mysql_charset="utf8mb4",
) )
ghost = Table( ghost = Table(
"mai2_profile_ghost", "mai2_profile_ghost",
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_int", Integer, nullable=False), Column("version_int", Integer, nullable=False),
Column("name", String(25)), Column("name", String(25)),
Column("iconId", Integer), Column("iconId", Integer),
@ -120,15 +128,21 @@ ghost = Table(
Column("resultBitList", JSON), Column("resultBitList", JSON),
Column("resultNum", Integer), Column("resultNum", Integer),
Column("achievement", Integer), Column("achievement", Integer),
UniqueConstraint("user", "version", "musicId", "difficulty", name="mai2_profile_ghost_uk"), UniqueConstraint(
mysql_charset='utf8mb4' "user", "version", "musicId", "difficulty", name="mai2_profile_ghost_uk"
),
mysql_charset="utf8mb4",
) )
extend = Table( extend = Table(
"mai2_profile_extend", "mai2_profile_extend",
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("selectMusicId", Integer), Column("selectMusicId", Integer),
Column("selectDifficultyId", Integer), Column("selectDifficultyId", Integer),
@ -145,14 +159,18 @@ extend = Table(
Column("selectedCardList", JSON), Column("selectedCardList", JSON),
Column("encountMapNpcList", JSON), Column("encountMapNpcList", JSON),
UniqueConstraint("user", "version", name="mai2_profile_extend_uk"), UniqueConstraint("user", "version", name="mai2_profile_extend_uk"),
mysql_charset='utf8mb4' mysql_charset="utf8mb4",
) )
option = Table( option = Table(
"mai2_profile_option", "mai2_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("version", Integer, nullable=False), Column("version", Integer, nullable=False),
Column("selectMusicId", Integer), Column("selectMusicId", Integer),
Column("optionKind", Integer), Column("optionKind", Integer),
@ -200,14 +218,18 @@ option = Table(
Column("sortTab", Integer), Column("sortTab", Integer),
Column("sortMusic", Integer), Column("sortMusic", Integer),
UniqueConstraint("user", "version", name="mai2_profile_option_uk"), UniqueConstraint("user", "version", name="mai2_profile_option_uk"),
mysql_charset='utf8mb4' mysql_charset="utf8mb4",
) )
rating = Table( rating = Table(
"mai2_profile_rating", "mai2_profile_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("version", Integer, nullable=False), Column("version", Integer, nullable=False),
Column("rating", Integer), Column("rating", Integer),
Column("ratingList", JSON), Column("ratingList", JSON),
@ -216,26 +238,34 @@ rating = Table(
Column("nextNewRatingList", JSON), Column("nextNewRatingList", JSON),
Column("udemae", JSON), Column("udemae", JSON),
UniqueConstraint("user", "version", name="mai2_profile_rating_uk"), UniqueConstraint("user", "version", name="mai2_profile_rating_uk"),
mysql_charset='utf8mb4' mysql_charset="utf8mb4",
) )
region = Table( region = Table(
"mai2_profile_region", "mai2_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, server_default="1"), Column("playCount", Integer, server_default="1"),
Column("created", String(25)), Column("created", String(25)),
UniqueConstraint("user", "regionId", name="mai2_profile_region_uk"), UniqueConstraint("user", "regionId", name="mai2_profile_region_uk"),
mysql_charset='utf8mb4' mysql_charset="utf8mb4",
) )
activity = Table( activity = Table(
"mai2_profile_activity", "mai2_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, nullable=False), Column("kind", Integer, nullable=False),
Column("activityId", Integer, nullable=False), Column("activityId", Integer, nullable=False),
Column("param1", Integer, nullable=False), Column("param1", Integer, nullable=False),
@ -244,11 +274,14 @@ activity = Table(
Column("param4", Integer, nullable=False), Column("param4", Integer, nullable=False),
Column("sortNumber", Integer, nullable=False), Column("sortNumber", Integer, nullable=False),
UniqueConstraint("user", "kind", "activityId", name="mai2_profile_activity_uk"), UniqueConstraint("user", "kind", "activityId", name="mai2_profile_activity_uk"),
mysql_charset='utf8mb4' mysql_charset="utf8mb4",
) )
class Mai2ProfileData(BaseData): class Mai2ProfileData(BaseData):
def put_profile_detail(self, user_id: int, version: int, detail_data: Dict) -> Optional[Row]: def put_profile_detail(
self, user_id: int, version: int, detail_data: Dict
) -> Optional[Row]:
detail_data["user"] = user_id detail_data["user"] = user_id
detail_data["version"] = version detail_data["version"] = version
sql = insert(detail).values(**detail_data) sql = insert(detail).values(**detail_data)
@ -257,18 +290,25 @@ class Mai2ProfileData(BaseData):
result = self.execute(conflict) result = self.execute(conflict)
if result is None: if result is None:
self.logger.warn(f"put_profile: Failed to create profile! user_id {user_id}") self.logger.warn(
f"put_profile: Failed to create profile! user_id {user_id}"
)
return None return None
return result.lastrowid return result.lastrowid
def get_profile_detail(self, user_id: int, version: int) -> Optional[Row]: def get_profile_detail(self, user_id: int, version: int) -> Optional[Row]:
sql = select(detail).where(and_(detail.c.user == user_id, detail.c.version == version)) sql = select(detail).where(
and_(detail.c.user == user_id, detail.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_ghost(self, user_id: int, version: int, ghost_data: Dict) -> Optional[int]: def put_profile_ghost(
self, user_id: int, version: int, ghost_data: Dict
) -> Optional[int]:
ghost_data["user"] = user_id ghost_data["user"] = user_id
ghost_data["version_int"] = version ghost_data["version_int"] = version
@ -282,13 +322,18 @@ class Mai2ProfileData(BaseData):
return result.lastrowid return result.lastrowid
def get_profile_ghost(self, user_id: int, version: int) -> Optional[Row]: def get_profile_ghost(self, user_id: int, version: int) -> Optional[Row]:
sql = select(ghost).where(and_(ghost.c.user == user_id, ghost.c.version_int == version)) sql = select(ghost).where(
and_(ghost.c.user == user_id, ghost.c.version_int == 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_extend(self, user_id: int, version: int, extend_data: Dict) -> Optional[int]: def put_profile_extend(
self, user_id: int, version: int, extend_data: Dict
) -> Optional[int]:
extend_data["user"] = user_id extend_data["user"] = user_id
extend_data["version"] = version extend_data["version"] = version
@ -302,13 +347,18 @@ class Mai2ProfileData(BaseData):
return result.lastrowid return result.lastrowid
def get_profile_extend(self, user_id: int, version: int) -> Optional[Row]: def get_profile_extend(self, user_id: int, version: int) -> Optional[Row]:
sql = select(extend).where(and_(extend.c.user == user_id, extend.c.version == version)) sql = select(extend).where(
and_(extend.c.user == user_id, extend.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, user_id: int, version: int, option_data: Dict) -> Optional[int]: def put_profile_option(
self, user_id: int, version: int, option_data: Dict
) -> Optional[int]:
option_data["user"] = user_id option_data["user"] = user_id
option_data["version"] = version option_data["version"] = version
@ -322,13 +372,18 @@ class Mai2ProfileData(BaseData):
return result.lastrowid return result.lastrowid
def get_profile_option(self, user_id: int, version: int) -> Optional[Row]: def get_profile_option(self, user_id: int, version: int) -> Optional[Row]:
sql = select(option).where(and_(option.c.user == user_id, option.c.version == version)) sql = select(option).where(
and_(option.c.user == user_id, option.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_rating(self, user_id: int, version: int, rating_data: Dict) -> Optional[int]: def put_profile_rating(
self, user_id: int, version: int, rating_data: Dict
) -> Optional[int]:
rating_data["user"] = user_id rating_data["user"] = user_id
rating_data["version"] = version rating_data["version"] = version
@ -342,23 +397,24 @@ class Mai2ProfileData(BaseData):
return result.lastrowid return result.lastrowid
def get_profile_rating(self, user_id: int, version: int) -> Optional[Row]: def get_profile_rating(self, user_id: int, version: int) -> Optional[Row]:
sql = select(rating).where(and_(rating.c.user == user_id, rating.c.version == version)) sql = select(rating).where(
and_(rating.c.user == user_id, rating.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_region(self, user_id: int, region_id: int) -> Optional[int]: def put_profile_region(self, user_id: int, region_id: int) -> Optional[int]:
sql = insert(region).values( sql = insert(region).values(
user = user_id, user=user_id,
regionId = region_id, regionId=region_id,
created = datetime.strftime(datetime.now(), Mai2Constants.DATE_TIME_FORMAT) created=datetime.strftime(datetime.now(), Mai2Constants.DATE_TIME_FORMAT),
)
conflict = sql.on_duplicate_key_update(
playCount = region.c.playCount + 1
) )
conflict = sql.on_duplicate_key_update(playCount=region.c.playCount + 1)
result = self.execute(conflict) result = self.execute(conflict)
if result is None: if result is None:
self.logger.warn(f"put_region: failed to update! {user_id}") self.logger.warn(f"put_region: failed to update! {user_id}")
@ -369,34 +425,38 @@ class Mai2ProfileData(BaseData):
sql = select(region).where(region.c.user == user_id) sql = select(region).where(region.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_profile_activity(self, user_id: int, activity_data: Dict) -> Optional[int]: def put_profile_activity(self, user_id: int, activity_data: Dict) -> Optional[int]:
if "id" in activity_data: if "id" in activity_data:
activity_data["activityId"] = activity_data["id"] activity_data["activityId"] = activity_data["id"]
activity_data.pop("id") activity_data.pop("id")
activity_data["user"] = user_id activity_data["user"] = user_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! user_id: {user_id}") self.logger.warn(
f"put_profile_activity: failed to update! user_id: {user_id}"
)
return None return None
return result.lastrowid return result.lastrowid
def get_profile_activity(self, user_id: int, kind: int = None) -> Optional[Row]: def get_profile_activity(self, user_id: int, kind: int = None) -> Optional[Row]:
sql = activity.select( sql = activity.select(
and_( and_(
activity.c.user == user_id, activity.c.user == user_id,
(activity.c.kind == kind) if kind is not None else True, (activity.c.kind == kind) if kind is not None else True,
) )
) )
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()

View File

@ -12,7 +12,11 @@ best_score = Table(
"mai2_score_best", "mai2_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),
@ -22,14 +26,18 @@ best_score = Table(
Column("deluxscoreMax", Integer), Column("deluxscoreMax", Integer),
Column("scoreRank", Integer), Column("scoreRank", Integer),
UniqueConstraint("user", "musicId", "level", name="mai2_score_best_uk"), UniqueConstraint("user", "musicId", "level", name="mai2_score_best_uk"),
mysql_charset='utf8mb4' mysql_charset="utf8mb4",
) )
playlog = Table( playlog = Table(
"mai2_playlog", "mai2_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("userId", BigInteger), Column("userId", BigInteger),
Column("orderId", Integer), Column("orderId", Integer),
Column("playlogId", BigInteger), Column("playlogId", BigInteger),
@ -136,14 +144,18 @@ playlog = Table(
Column("extNum1", Integer), Column("extNum1", Integer),
Column("extNum2", Integer), Column("extNum2", Integer),
Column("trialPlayAchievement", Integer), Column("trialPlayAchievement", Integer),
mysql_charset='utf8mb4' mysql_charset="utf8mb4",
) )
course = Table( course = Table(
"mai2_score_course", "mai2_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("isLastClear", Boolean), Column("isLastClear", Boolean),
Column("totalRestlife", Integer), Column("totalRestlife", Integer),
@ -157,9 +169,10 @@ course = Table(
Column("bestDeluxscore", Integer), Column("bestDeluxscore", Integer),
Column("bestDeluxscoreDate", String(25)), Column("bestDeluxscoreDate", String(25)),
UniqueConstraint("user", "courseId", name="mai2_score_best_uk"), UniqueConstraint("user", "courseId", name="mai2_score_best_uk"),
mysql_charset='utf8mb4' mysql_charset="utf8mb4",
) )
class Mai2ScoreData(BaseData): class Mai2ScoreData(BaseData):
def put_best_score(self, user_id: int, score_data: Dict) -> Optional[int]: def put_best_score(self, user_id: int, score_data: Dict) -> Optional[int]:
score_data["user"] = user_id score_data["user"] = user_id
@ -169,33 +182,39 @@ class Mai2ScoreData(BaseData):
result = self.execute(conflict) result = self.execute(conflict)
if result is None: if result is None:
self.logger.error(f"put_best_score: Failed to insert best score! user_id {user_id}") self.logger.error(
f"put_best_score: Failed to insert best score! user_id {user_id}"
)
return None return None
return result.lastrowid return result.lastrowid
def get_best_scores(self, user_id: int, song_id: int = None) -> Optional[List[Row]]: def get_best_scores(self, user_id: int, song_id: int = None) -> Optional[List[Row]]:
sql = best_score.select( sql = best_score.select(
and_( and_(
best_score.c.user == user_id, best_score.c.user == user_id,
(best_score.c.song_id == song_id) if song_id is not None else True (best_score.c.song_id == song_id) if song_id is not None else 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_best_score(self, user_id: int, song_id: int, chart_id: int) -> Optional[Row]: def get_best_score(
self, user_id: int, song_id: int, chart_id: int
) -> Optional[Row]:
sql = best_score.select( sql = best_score.select(
and_( and_(
best_score.c.user == user_id, best_score.c.user == user_id,
best_score.c.song_id == song_id, best_score.c.song_id == song_id,
best_score.c.chart_id == chart_id best_score.c.chart_id == 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_playlog(self, user_id: int, playlog_data: Dict) -> Optional[int]: def put_playlog(self, user_id: int, playlog_data: Dict) -> Optional[int]:
@ -209,7 +228,7 @@ class Mai2ScoreData(BaseData):
self.logger.error(f"put_playlog: Failed to insert! user_id {user_id}") self.logger.error(f"put_playlog: Failed to insert! user_id {user_id}")
return None return None
return result.lastrowid return result.lastrowid
def put_course(self, user_id: int, course_data: Dict) -> Optional[int]: def put_course(self, user_id: int, course_data: Dict) -> Optional[int]:
course_data["user"] = user_id course_data["user"] = user_id
sql = insert(course).values(**course_data) sql = insert(course).values(**course_data)
@ -221,10 +240,11 @@ class Mai2ScoreData(BaseData):
self.logger.error(f"put_course: Failed to insert! user_id {user_id}") self.logger.error(f"put_course: Failed to insert! user_id {user_id}")
return None return None
return result.lastrowid return result.lastrowid
def get_courses(self, user_id: int) -> Optional[List[Row]]: def get_courses(self, user_id: int) -> Optional[List[Row]]:
sql = course.select(best_score.c.user == user_id) sql = course.select(best_score.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.fetchone() return result.fetchone()

View File

@ -12,20 +12,20 @@ event = Table(
"mai2_static_event", "mai2_static_event",
metadata, metadata,
Column("id", Integer, primary_key=True, nullable=False), Column("id", Integer, primary_key=True, nullable=False),
Column("version", Integer,nullable=False), Column("version", Integer, nullable=False),
Column("eventId", Integer), Column("eventId", Integer),
Column("type", Integer), Column("type", Integer),
Column("name", String(255)), Column("name", String(255)),
Column("enabled", Boolean, server_default="1"), Column("enabled", Boolean, server_default="1"),
UniqueConstraint("version", "eventId", "type", name="mai2_static_event_uk"), UniqueConstraint("version", "eventId", "type", name="mai2_static_event_uk"),
mysql_charset='utf8mb4' mysql_charset="utf8mb4",
) )
music = Table( music = Table(
"mai2_static_music", "mai2_static_music",
metadata, metadata,
Column("id", Integer, primary_key=True, nullable=False), Column("id", Integer, primary_key=True, nullable=False),
Column("version", Integer,nullable=False), Column("version", Integer, nullable=False),
Column("songId", Integer), Column("songId", Integer),
Column("chartId", Integer), Column("chartId", Integer),
Column("title", String(255)), Column("title", String(255)),
@ -36,39 +36,42 @@ music = Table(
Column("difficulty", Float), Column("difficulty", Float),
Column("noteDesigner", String(255)), Column("noteDesigner", String(255)),
UniqueConstraint("songId", "chartId", "version", name="mai2_static_music_uk"), UniqueConstraint("songId", "chartId", "version", name="mai2_static_music_uk"),
mysql_charset='utf8mb4' mysql_charset="utf8mb4",
) )
ticket = Table( ticket = Table(
"mai2_static_ticket", "mai2_static_ticket",
metadata, metadata,
Column("id", Integer, primary_key=True, nullable=False), Column("id", Integer, primary_key=True, nullable=False),
Column("version", Integer,nullable=False), Column("version", Integer, nullable=False),
Column("ticketId", Integer), Column("ticketId", Integer),
Column("kind", Integer), Column("kind", Integer),
Column("name", String(255)), Column("name", String(255)),
Column("price", Integer, server_default="1"), Column("price", Integer, server_default="1"),
Column("enabled", Boolean, server_default="1"), Column("enabled", Boolean, server_default="1"),
UniqueConstraint("version","ticketId", name="mai2_static_ticket_uk"), UniqueConstraint("version", "ticketId", name="mai2_static_ticket_uk"),
mysql_charset='utf8mb4' mysql_charset="utf8mb4",
) )
class Mai2StaticData(BaseData): class Mai2StaticData(BaseData):
def put_game_event(self, version: int, type: int, event_id: int, name: str) -> Optional[int]: def put_game_event(
self, version: int, type: int, event_id: int, name: str
) -> Optional[int]:
sql = insert(event).values( sql = insert(event).values(
version = version, version=version,
type = type, type=type,
eventId = event_id, eventId=event_id,
name = name, name=name,
) )
conflict = sql.on_duplicate_key_update( conflict = sql.on_duplicate_key_update(eventId=event_id)
eventId = event_id
)
result = self.execute(conflict) result = self.execute(conflict)
if result is None: if result is None:
self.logger.warning(f"put_game_event: Failed to insert event! event_id {event_id} type {type} name {name}") self.logger.warning(
f"put_game_event: Failed to insert event! event_id {event_id} type {type} name {name}"
)
return result.lastrowid return result.lastrowid
def get_game_events(self, version: int) -> Optional[List[Row]]: def get_game_events(self, version: int) -> Optional[List[Row]]:
@ -78,50 +81,65 @@ class Mai2StaticData(BaseData):
if result is None: if result is None:
return None return None
return result.fetchall() return result.fetchall()
def get_enabled_events(self, version: int) -> Optional[List[Row]]: def get_enabled_events(self, version: int) -> Optional[List[Row]]:
sql = select(event).where(and_( sql = select(event).where(
event.c.version == version, and_(event.c.version == version, event.c.enabled == True)
event.c.enabled == True
))
result = self.execute(sql)
if result is None: return None
return result.fetchall()
def toggle_game_events(self, version: int, event_id: int, toggle: bool) -> Optional[List]:
sql = event.update(and_(event.c.version == version, event.c.event_id == event_id)).values(
enabled = int(toggle)
) )
result = self.execute(sql) result = self.execute(sql)
if result is None: if result is None:
self.logger.warning(f"toggle_game_events: Failed to update event! event_id {event_id} toggle {toggle}") return None
return result.fetchall()
def toggle_game_events(
self, version: int, event_id: int, toggle: bool
) -> Optional[List]:
sql = event.update(
and_(event.c.version == version, event.c.event_id == event_id)
).values(enabled=int(toggle))
result = self.execute(sql)
if result is None:
self.logger.warning(
f"toggle_game_events: Failed to update event! event_id {event_id} toggle {toggle}"
)
return result.last_updated_params() return result.last_updated_params()
def put_game_music(self, version: int, song_id: int, chart_id: int, title: str, artist: str, def put_game_music(
genre: str, bpm: str, added_version: str, difficulty: float, note_designer: str) -> None: self,
version: int,
song_id: int,
chart_id: int,
title: str,
artist: str,
genre: str,
bpm: str,
added_version: str,
difficulty: float,
note_designer: str,
) -> None:
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,
genre = genre, genre=genre,
bpm = bpm, bpm=bpm,
addedVersion = added_version, addedVersion=added_version,
difficulty = difficulty, difficulty=difficulty,
noteDesigner = note_designer, noteDesigner=note_designer,
) )
conflict = sql.on_duplicate_key_update( conflict = sql.on_duplicate_key_update(
title = title, title=title,
artist = artist, artist=artist,
genre = genre, genre=genre,
bpm = bpm, bpm=bpm,
addedVersion = added_version, addedVersion=added_version,
difficulty = difficulty, difficulty=difficulty,
noteDesigner = note_designer, noteDesigner=note_designer,
) )
result = self.execute(conflict) result = self.execute(conflict)
@ -129,50 +147,64 @@ class Mai2StaticData(BaseData):
self.logger.warn(f"Failed to insert song {song_id} chart {chart_id}") self.logger.warn(f"Failed to insert song {song_id} chart {chart_id}")
return None return None
return result.lastrowid return result.lastrowid
def put_game_ticket(self, version: int, ticket_id: int, ticket_type: int, ticket_price: int, name: str) -> Optional[int]: def put_game_ticket(
self,
version: int,
ticket_id: int,
ticket_type: int,
ticket_price: int,
name: str,
) -> Optional[int]:
sql = insert(ticket).values( sql = insert(ticket).values(
version = version, version=version,
ticketId = ticket_id, ticketId=ticket_id,
kind = ticket_type, kind=ticket_type,
price = ticket_price, price=ticket_price,
name = name name=name,
) )
conflict = sql.on_duplicate_key_update( conflict = sql.on_duplicate_key_update(price=ticket_price)
price = ticket_price
)
result = self.execute(conflict) result = self.execute(conflict)
if result is None: if result is None:
self.logger.warn(f"Failed to insert charge {ticket_id} type {ticket_type}") self.logger.warn(f"Failed to insert charge {ticket_id} type {ticket_type}")
return None return None
return result.lastrowid return result.lastrowid
def get_enabled_tickets(self, version: int, kind: int = None) -> Optional[List[Row]]: def get_enabled_tickets(
self, version: int, kind: int = None
) -> Optional[List[Row]]:
if kind is not None: if kind is not None:
sql = select(ticket).where(and_( sql = select(ticket).where(
ticket.c.version == version, and_(
ticket.c.enabled == True, ticket.c.version == version,
ticket.c.kind == kind ticket.c.enabled == True,
)) ticket.c.kind == kind,
)
)
else: else:
sql = select(ticket).where(and_( sql = select(ticket).where(
ticket.c.version == version, and_(ticket.c.version == version, ticket.c.enabled == True)
ticket.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_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()

View File

@ -8,7 +8,8 @@ from titles.mai2.base import Mai2Base
from titles.mai2.config import Mai2Config from titles.mai2.config import Mai2Config
from titles.mai2.const import Mai2Constants from titles.mai2.const import Mai2Constants
class Mai2Splash(Mai2Base): class Mai2Splash(Mai2Base):
def __init__(self, cfg: CoreConfig, game_cfg: Mai2Config) -> None: def __init__(self, cfg: CoreConfig, game_cfg: Mai2Config) -> None:
super().__init__(cfg, game_cfg) super().__init__(cfg, game_cfg)
self.version = Mai2Constants.VER_MAIMAI_DX_SPLASH self.version = Mai2Constants.VER_MAIMAI_DX_SPLASH

View File

@ -8,7 +8,8 @@ from titles.mai2.base import Mai2Base
from titles.mai2.config import Mai2Config from titles.mai2.config import Mai2Config
from titles.mai2.const import Mai2Constants from titles.mai2.const import Mai2Constants
class Mai2SplashPlus(Mai2Base): class Mai2SplashPlus(Mai2Base):
def __init__(self, cfg: CoreConfig, game_cfg: Mai2Config) -> None: def __init__(self, cfg: CoreConfig, game_cfg: Mai2Config) -> None:
super().__init__(cfg, game_cfg) super().__init__(cfg, game_cfg)
self.version = Mai2Constants.VER_MAIMAI_DX_SPLASH_PLUS self.version = Mai2Constants.VER_MAIMAI_DX_SPLASH_PLUS

View File

@ -8,7 +8,8 @@ from titles.mai2.base import Mai2Base
from titles.mai2.const import Mai2Constants from titles.mai2.const import Mai2Constants
from titles.mai2.config import Mai2Config from titles.mai2.config import Mai2Config
class Mai2Universe(Mai2Base): class Mai2Universe(Mai2Base):
def __init__(self, cfg: CoreConfig, game_cfg: Mai2Config) -> None: def __init__(self, cfg: CoreConfig, game_cfg: Mai2Config) -> None:
super().__init__(cfg, game_cfg) super().__init__(cfg, game_cfg)
self.version = Mai2Constants.VER_MAIMAI_DX_UNIVERSE self.version = Mai2Constants.VER_MAIMAI_DX_UNIVERSE

View File

@ -8,7 +8,8 @@ from titles.mai2.base import Mai2Base
from titles.mai2.const import Mai2Constants from titles.mai2.const import Mai2Constants
from titles.mai2.config import Mai2Config from titles.mai2.config import Mai2Config
class Mai2UniversePlus(Mai2Base): class Mai2UniversePlus(Mai2Base):
def __init__(self, cfg: CoreConfig, game_cfg: Mai2Config) -> None: def __init__(self, cfg: CoreConfig, game_cfg: Mai2Config) -> None:
super().__init__(cfg, game_cfg) super().__init__(cfg, game_cfg)
self.version = Mai2Constants.VER_MAIMAI_DX_UNIVERSE_PLUS self.version = Mai2Constants.VER_MAIMAI_DX_UNIVERSE_PLUS

View File

@ -7,4 +7,4 @@ index = OngekiServlet
database = OngekiData database = OngekiData
reader = OngekiReader reader = OngekiReader
game_codes = [OngekiConstants.GAME_CODE] game_codes = [OngekiConstants.GAME_CODE]
current_schema_version = 2 current_schema_version = 2

View File

@ -11,6 +11,7 @@ from titles.ongeki.config import OngekiConfig
from titles.ongeki.database import OngekiData from titles.ongeki.database import OngekiData
from titles.ongeki.config import OngekiConfig from titles.ongeki.config import OngekiConfig
class OngekiBattleGrade(Enum): class OngekiBattleGrade(Enum):
FAILED = 0 FAILED = 0
DRAW = 1 DRAW = 1
@ -21,6 +22,7 @@ class OngekiBattleGrade(Enum):
UNBELIEVABLE_GOLD = 6 UNBELIEVABLE_GOLD = 6
UNBELIEVABLE_RAINBOW = 7 UNBELIEVABLE_RAINBOW = 7
class OngekiBattlePointGrade(Enum): class OngekiBattlePointGrade(Enum):
FRESHMAN = 0 FRESHMAN = 0
KYU10 = 1 KYU10 = 1
@ -45,20 +47,22 @@ class OngekiBattlePointGrade(Enum):
DAN10 = 20 DAN10 = 20
SODEN = 21 SODEN = 21
class OngekiTechnicalGrade(Enum): class OngekiTechnicalGrade(Enum):
D = 0 D = 0
C = 1 C = 1
B = 2 B = 2
BB = 3 BB = 3
BBB = 4 BBB = 4
A = 5 A = 5
AA = 6 AA = 6
AAA = 7 AAA = 7
S = 8 S = 8
SS = 9 SS = 9
SSS = 10 SSS = 10
SSSp = 11 SSSp = 11
class OngekiDifficulty(Enum): class OngekiDifficulty(Enum):
BASIC = 0 BASIC = 0
ADVANCED = 1 ADVANCED = 1
@ -66,6 +70,7 @@ class OngekiDifficulty(Enum):
MASTER = 3 MASTER = 3
LUNATIC = 10 LUNATIC = 10
class OngekiGPLogKind(Enum): class OngekiGPLogKind(Enum):
NONE = 0 NONE = 0
BUY1_START = 1 BUY1_START = 1
@ -82,22 +87,28 @@ class OngekiGPLogKind(Enum):
PAY_MAS_UNLOCK = 13 PAY_MAS_UNLOCK = 13
PAY_MONEY = 14 PAY_MONEY = 14
class OngekiBase():
class OngekiBase:
def __init__(self, core_cfg: CoreConfig, game_cfg: OngekiConfig) -> None: def __init__(self, core_cfg: CoreConfig, game_cfg: OngekiConfig) -> None:
self.core_cfg = core_cfg self.core_cfg = core_cfg
self.game_cfg = game_cfg self.game_cfg = game_cfg
self.data = OngekiData(core_cfg) self.data = OngekiData(core_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("ongeki") self.logger = logging.getLogger("ongeki")
self.game = OngekiConstants.GAME_CODE self.game = OngekiConstants.GAME_CODE
self.version = OngekiConstants.VER_ONGEKI self.version = OngekiConstants.VER_ONGEKI
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": {
"dataVersion": "1.00.00", "dataVersion": "1.00.00",
@ -128,20 +139,53 @@ class OngekiBase():
def handle_get_game_ranking_api_request(self, data: Dict) -> Dict: def handle_get_game_ranking_api_request(self, data: Dict) -> Dict:
return {"length": 0, "gameRankingList": []} return {"length": 0, "gameRankingList": []}
def handle_get_game_point_api_request(self, data: Dict) -> Dict: def handle_get_game_point_api_request(self, data: Dict) -> Dict:
""" """
Sets the GP ammount for A and B sets for 1 - 3 crdits Sets the GP ammount for A and B sets for 1 - 3 crdits
""" """
return {"length":6,"gamePointList":[ return {
{"type":0,"cost":100,"startDate":"2000-01-01 05:00:00.0","endDate":"2099-01-01 05:00:00.0"}, "length": 6,
{"type":1,"cost":200,"startDate":"2000-01-01 05:00:00.0","endDate":"2099-01-01 05:00:00.0"}, "gamePointList": [
{"type":2,"cost":300,"startDate":"2000-01-01 05:00:00.0","endDate":"2099-01-01 05:00:00.0"}, {
{"type":3,"cost":120,"startDate":"2000-01-01 05:00:00.0","endDate":"2099-01-01 05:00:00.0"}, "type": 0,
{"type":4,"cost":240,"startDate":"2000-01-01 05:00:00.0","endDate":"2099-01-01 05:00:00.0"}, "cost": 100,
{"type":5,"cost":360,"startDate":"2000-01-01 05:00:00.0","endDate":"2099-01-01 05:00:00.0"} "startDate": "2000-01-01 05:00:00.0",
]} "endDate": "2099-01-01 05:00:00.0",
},
{
"type": 1,
"cost": 200,
"startDate": "2000-01-01 05:00:00.0",
"endDate": "2099-01-01 05:00:00.0",
},
{
"type": 2,
"cost": 300,
"startDate": "2000-01-01 05:00:00.0",
"endDate": "2099-01-01 05:00:00.0",
},
{
"type": 3,
"cost": 120,
"startDate": "2000-01-01 05:00:00.0",
"endDate": "2099-01-01 05:00:00.0",
},
{
"type": 4,
"cost": 240,
"startDate": "2000-01-01 05:00:00.0",
"endDate": "2099-01-01 05:00:00.0",
},
{
"type": 5,
"cost": 360,
"startDate": "2000-01-01 05:00:00.0",
"endDate": "2099-01-01 05:00:00.0",
},
],
}
def handle_game_login_api_request(self, data: Dict) -> Dict: def handle_game_login_api_request(self, data: Dict) -> Dict:
return {"returnCode": 1, "apiName": "gameLogin"} return {"returnCode": 1, "apiName": "gameLogin"}
@ -184,11 +228,19 @@ class OngekiBase():
def handle_upsert_user_gplog_api_request(self, data: Dict) -> Dict: def handle_upsert_user_gplog_api_request(self, data: Dict) -> Dict:
user = data["userId"] user = data["userId"]
if user >= 200000000000000: # Account for guest play if user >= 200000000000000: # Account for guest play
user = None user = None
self.data.log.put_gp_log(user, data["usedCredit"], data["placeName"], data["userGplog"]["trxnDate"], self.data.log.put_gp_log(
data["userGplog"]["placeId"], data["userGplog"]["kind"], data["userGplog"]["pattern"], data["userGplog"]["currentGP"]) user,
data["usedCredit"],
data["placeName"],
data["userGplog"]["trxnDate"],
data["userGplog"]["placeId"],
data["userGplog"]["kind"],
data["userGplog"]["pattern"],
data["userGplog"]["currentGP"],
)
return {"returnCode": 1, "apiName": "UpsertUserGplogApi"} return {"returnCode": 1, "apiName": "UpsertUserGplogApi"}
@ -197,39 +249,53 @@ class OngekiBase():
def handle_get_game_event_api_request(self, data: Dict) -> Dict: def handle_get_game_event_api_request(self, data: Dict) -> Dict:
evts = self.data.static.get_enabled_events(self.version) evts = self.data.static.get_enabled_events(self.version)
evt_list = [] evt_list = []
for event in evts: for event in evts:
evt_list.append({ evt_list.append(
"type": event["type"], {
"id": event["eventId"], "type": event["type"],
"startDate": "2017-12-05 07:00:00.0", "id": event["eventId"],
"endDate": "2099-12-31 00:00:00.0" "startDate": "2017-12-05 07:00:00.0",
}) "endDate": "2099-12-31 00:00:00.0",
}
return {"type": data["type"], "length": len(evt_list), "gameEventList": evt_list} )
return {
"type": data["type"],
"length": len(evt_list),
"gameEventList": evt_list,
}
def handle_get_game_id_list_api_request(self, data: Dict) -> Dict: def handle_get_game_id_list_api_request(self, data: Dict) -> Dict:
game_idlist: list[str, Any] = [] #1 to 230 & 8000 to 8050 game_idlist: list[str, Any] = [] # 1 to 230 & 8000 to 8050
if data["type"] == 1: if data["type"] == 1:
for i in range(1,231): for i in range(1, 231):
game_idlist.append({"type": 1, "id": i}) game_idlist.append({"type": 1, "id": i})
return {"type": data["type"], "length": len(game_idlist), "gameIdlistList": game_idlist} return {
"type": data["type"],
"length": len(game_idlist),
"gameIdlistList": game_idlist,
}
elif data["type"] == 2: elif data["type"] == 2:
for i in range(8000,8051): for i in range(8000, 8051):
game_idlist.append({"type": 2, "id": i}) game_idlist.append({"type": 2, "id": i})
return {"type": data["type"], "length": len(game_idlist), "gameIdlistList": game_idlist} return {
"type": data["type"],
"length": len(game_idlist),
"gameIdlistList": game_idlist,
}
def handle_get_user_region_api_request(self, data: Dict) -> Dict: def handle_get_user_region_api_request(self, data: Dict) -> Dict:
return {"userId": data["userId"], "length": 0, "userRegionList": []} return {"userId": data["userId"], "length": 0, "userRegionList": []}
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: if profile is None:
return { return {
"userId": data["userId"], "userId": data["userId"],
"isLogin": False, "isLogin": False,
"lastLoginDate": "0000-00-00 00:00:00", "lastLoginDate": "0000-00-00 00:00:00",
"userName": "", "userName": "",
@ -240,12 +306,12 @@ class OngekiBase():
"lastGameId": "", "lastGameId": "",
"lastRomVersion": "", "lastRomVersion": "",
"lastDataVersion": "", "lastDataVersion": "",
"lastPlayDate": "", "lastPlayDate": "",
"nameplateId": 0, "nameplateId": 0,
"trophyId": 0, "trophyId": 0,
"cardId": 0, "cardId": 0,
"dispPlayerLv": 0, "dispPlayerLv": 0,
"dispRating": 0, "dispRating": 0,
"dispBP": 0, "dispBP": 0,
"headphone": 0, "headphone": 0,
"banStatus": 0, "banStatus": 0,
@ -253,7 +319,7 @@ class OngekiBase():
} }
return { return {
"userId": data["userId"], "userId": data["userId"],
"isLogin": False, "isLogin": False,
"lastLoginDate": profile["lastPlayDate"], "lastLoginDate": profile["lastPlayDate"],
"userName": profile["userName"], "userName": profile["userName"],
@ -264,12 +330,12 @@ class OngekiBase():
"lastGameId": profile["lastGameId"], "lastGameId": profile["lastGameId"],
"lastRomVersion": profile["lastRomVersion"], "lastRomVersion": profile["lastRomVersion"],
"lastDataVersion": profile["lastDataVersion"], "lastDataVersion": profile["lastDataVersion"],
"lastPlayDate": profile["lastPlayDate"], "lastPlayDate": profile["lastPlayDate"],
"nameplateId": profile["nameplateId"], "nameplateId": profile["nameplateId"],
"trophyId": profile["trophyId"], "trophyId": profile["trophyId"],
"cardId": profile["cardId"], "cardId": profile["cardId"],
"dispPlayerLv": profile["dispPlayerLv"], "dispPlayerLv": profile["dispPlayerLv"],
"dispRating": profile["dispRating"], "dispRating": profile["dispRating"],
"dispBP": profile["dispBP"], "dispBP": profile["dispBP"],
"headphone": profile["headphone"], "headphone": profile["headphone"],
"banStatus": profile["banStatus"], "banStatus": profile["banStatus"],
@ -297,7 +363,8 @@ class OngekiBase():
def handle_get_user_tech_event_api_request(self, data: Dict) -> Dict: def handle_get_user_tech_event_api_request(self, data: Dict) -> Dict:
user_tech_event_list = self.data.item.get_tech_event(data["userId"]) user_tech_event_list = self.data.item.get_tech_event(data["userId"])
if user_tech_event_list is None: return {} if user_tech_event_list is None:
return {}
tech_evt = [] tech_evt = []
for evt in user_tech_event_list: for evt in user_tech_event_list:
@ -313,11 +380,11 @@ class OngekiBase():
} }
def handle_get_user_tech_event_ranking_api_request(self, data: Dict) -> Dict: def handle_get_user_tech_event_ranking_api_request(self, data: Dict) -> Dict:
#user_event_ranking_list = self.data.item.get_tech_event_ranking(data["userId"]) # user_event_ranking_list = self.data.item.get_tech_event_ranking(data["userId"])
#if user_event_ranking_list is None: return {} # if user_event_ranking_list is None: return {}
evt_ranking = [] evt_ranking = []
#for evt in user_event_ranking_list: # for evt in user_event_ranking_list:
# tmp = evt._asdict() # tmp = evt._asdict()
# tmp.pop("id") # tmp.pop("id")
# tmp.pop("user") # tmp.pop("user")
@ -331,7 +398,8 @@ class OngekiBase():
def handle_get_user_kop_api_request(self, data: Dict) -> Dict: def handle_get_user_kop_api_request(self, data: Dict) -> Dict:
kop_list = self.data.profile.get_kop(data["userId"]) kop_list = self.data.profile.get_kop(data["userId"])
if kop_list is None: return {} if kop_list is None:
return {}
for kop in kop_list: for kop in kop_list:
kop.pop("user") kop.pop("user")
@ -349,10 +417,10 @@ class OngekiBase():
next_idx = data["nextIndex"] next_idx = data["nextIndex"]
start_idx = next_idx start_idx = next_idx
end_idx = max_ct + start_idx end_idx = max_ct + start_idx
if len(song_list[start_idx:]) > max_ct: if len(song_list[start_idx:]) > max_ct:
next_idx += max_ct next_idx += max_ct
else: else:
next_idx = -1 next_idx = -1
@ -360,15 +428,20 @@ class OngekiBase():
"userId": data["userId"], "userId": data["userId"],
"length": len(song_list[start_idx:end_idx]), "length": len(song_list[start_idx:end_idx]),
"nextIndex": next_idx, "nextIndex": next_idx,
"userMusicList": song_list[start_idx:end_idx] "userMusicList": song_list[start_idx:end_idx],
} }
def handle_get_user_item_api_request(self, data: Dict) -> Dict: def handle_get_user_item_api_request(self, data: Dict) -> Dict:
kind = data["nextIndex"] / 10000000000 kind = data["nextIndex"] / 10000000000
p = self.data.item.get_items(data["userId"], kind) p = self.data.item.get_items(data["userId"], kind)
if p is None: if p is None:
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(data["nextIndex"] % 10000000000, len(p)): for i in range(data["nextIndex"] % 10000000000, len(p)):
@ -381,14 +454,23 @@ class OngekiBase():
xout = kind * 10000000000 + (data["nextIndex"] % 10000000000) + len(items) xout = kind * 10000000000 + (data["nextIndex"] % 10000000000) + len(items)
if len(items) < data["maxCount"] or data["maxCount"] == 0: nextIndex = 0 if len(items) < data["maxCount"] or data["maxCount"] == 0:
else: nextIndex = xout nextIndex = 0
else:
nextIndex = xout
return {"userId": data["userId"], "nextIndex": int(nextIndex), "itemKind": int(kind), "length": len(items), "userItemList": items} return {
"userId": data["userId"],
"nextIndex": int(nextIndex),
"itemKind": int(kind),
"length": len(items),
"userItemList": items,
}
def handle_get_user_option_api_request(self, data: Dict) -> Dict: def handle_get_user_option_api_request(self, data: Dict) -> Dict:
o = self.data.profile.get_profile_options(data["userId"]) o = self.data.profile.get_profile_options(data["userId"])
if o is None: return {} if o is None:
return {}
# get the dict representation of the row so we can modify values # get the dict representation of the row so we can modify values
user_opts = o._asdict() user_opts = o._asdict()
@ -401,14 +483,17 @@ class OngekiBase():
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 {}
cards = self.data.card.get_user_cards(data["userId"]) cards = self.data.card.get_user_cards(data["userId"])
if cards is None or len(cards) == 0: if cards is None or len(cards) == 0:
# This should never happen # This should never happen
self.logger.error(f"handle_get_user_data_api_request: Internal error - No cards found for user id {data['userId']}") self.logger.error(
f"handle_get_user_data_api_request: Internal error - No cards found for user id {data['userId']}"
)
return {} return {}
# get the dict representation of the row so we can modify values # get the dict representation of the row so we can modify values
user_data = p._asdict() user_data = p._asdict()
@ -422,14 +507,14 @@ class OngekiBase():
# add access code that we don't store # add access code that we don't store
user_data["accessCode"] = cards[0]["access_code"] user_data["accessCode"] = cards[0]["access_code"]
return {"userId": data["userId"], "userData":user_data} return {"userId": data["userId"], "userData": user_data}
def handle_get_user_event_ranking_api_request(self, data: Dict) -> Dict: def handle_get_user_event_ranking_api_request(self, data: Dict) -> Dict:
#user_event_ranking_list = self.data.item.get_event_ranking(data["userId"]) # user_event_ranking_list = self.data.item.get_event_ranking(data["userId"])
#if user_event_ranking_list is None: return {} # if user_event_ranking_list is None: return {}
evt_ranking = [] evt_ranking = []
#for evt in user_event_ranking_list: # for evt in user_event_ranking_list:
# tmp = evt._asdict() # tmp = evt._asdict()
# tmp.pop("id") # tmp.pop("id")
# tmp.pop("user") # tmp.pop("user")
@ -443,7 +528,8 @@ class OngekiBase():
def handle_get_user_login_bonus_api_request(self, data: Dict) -> Dict: def handle_get_user_login_bonus_api_request(self, data: Dict) -> Dict:
user_login_bonus_list = self.data.item.get_login_bonuses(data["userId"]) user_login_bonus_list = self.data.item.get_login_bonuses(data["userId"])
if user_login_bonus_list is None: return {} if user_login_bonus_list is None:
return {}
login_bonuses = [] login_bonuses = []
for scenerio in user_login_bonus_list: for scenerio in user_login_bonus_list:
@ -451,16 +537,19 @@ class OngekiBase():
tmp.pop("id") tmp.pop("id")
tmp.pop("user") tmp.pop("user")
login_bonuses.append(tmp) login_bonuses.append(tmp)
return { return {
"userId": data["userId"], "userId": data["userId"],
"length": len(login_bonuses), "length": len(login_bonuses),
"userLoginBonusList": login_bonuses "userLoginBonusList": login_bonuses,
} }
def handle_get_user_bp_base_request(self, data: Dict) -> Dict: def handle_get_user_bp_base_request(self, data: Dict) -> Dict:
p = self.data.profile.get_profile(self.game, self.version, user_id = data["userId"]) p = self.data.profile.get_profile(
if p is None: return {} self.game, self.version, user_id=data["userId"]
)
if p is None:
return {}
profile = json.loads(p["data"]) profile = json.loads(p["data"])
return { return {
"userId": data["userId"], "userId": data["userId"],
@ -470,7 +559,8 @@ class OngekiBase():
def handle_get_user_recent_rating_api_request(self, data: Dict) -> Dict: def handle_get_user_recent_rating_api_request(self, data: Dict) -> Dict:
recent_rating = self.data.profile.get_profile_recent_rating(data["userId"]) recent_rating = self.data.profile.get_profile_recent_rating(data["userId"])
if recent_rating is None: return {} if recent_rating is None:
return {}
userRecentRatingList = recent_rating["recentRating"] userRecentRatingList = recent_rating["recentRating"]
@ -482,31 +572,35 @@ class OngekiBase():
def handle_get_user_activity_api_request(self, data: Dict) -> Dict: def handle_get_user_activity_api_request(self, data: Dict) -> Dict:
activity = self.data.profile.get_profile_activity(data["userId"], data["kind"]) activity = self.data.profile.get_profile_activity(data["userId"], data["kind"])
if activity is None: return {} if activity is None:
return {}
user_activity = [] user_activity = []
for act in activity: for act in activity:
user_activity.append({ user_activity.append(
"kind": act["kind"], {
"id": act["activityId"], "kind": act["kind"],
"sortNumber": act["sortNumber"], "id": act["activityId"],
"param1": act["param1"], "sortNumber": act["sortNumber"],
"param2": act["param2"], "param1": act["param1"],
"param3": act["param3"], "param2": act["param2"],
"param4": act["param4"], "param3": act["param3"],
}) "param4": act["param4"],
}
)
return { return {
"userId": data["userId"], "userId": data["userId"],
"length": len(user_activity), "length": len(user_activity),
"kind": data["kind"], "kind": data["kind"],
"userActivityList": user_activity "userActivityList": user_activity,
} }
def handle_get_user_story_api_request(self, data: Dict) -> Dict: def handle_get_user_story_api_request(self, data: Dict) -> Dict:
user_stories = self.data.item.get_stories(data["userId"]) user_stories = self.data.item.get_stories(data["userId"])
if user_stories is None: return {} if user_stories is None:
return {}
story_list = [] story_list = []
for story in user_stories: for story in user_stories:
@ -516,14 +610,15 @@ class OngekiBase():
story_list.append(tmp) story_list.append(tmp)
return { return {
"userId": data["userId"], "userId": data["userId"],
"length": len(story_list), "length": len(story_list),
"userStoryList": story_list "userStoryList": story_list,
} }
def handle_get_user_chapter_api_request(self, data: Dict) -> Dict: def handle_get_user_chapter_api_request(self, data: Dict) -> Dict:
user_chapters = self.data.item.get_chapters(data["userId"]) user_chapters = self.data.item.get_chapters(data["userId"])
if user_chapters is None: return {} if user_chapters is None:
return {}
chapter_list = [] chapter_list = []
for chapter in user_chapters: for chapter in user_chapters:
@ -531,11 +626,11 @@ class OngekiBase():
tmp.pop("id") tmp.pop("id")
tmp.pop("user") tmp.pop("user")
chapter_list.append(tmp) chapter_list.append(tmp)
return { return {
"userId": data["userId"], "userId": data["userId"],
"length": len(chapter_list), "length": len(chapter_list),
"userChapterList": chapter_list "userChapterList": chapter_list,
} }
def handle_get_user_training_room_by_key_api_request(self, data: Dict) -> Dict: def handle_get_user_training_room_by_key_api_request(self, data: Dict) -> Dict:
@ -547,7 +642,8 @@ class OngekiBase():
def handle_get_user_character_api_request(self, data: Dict) -> Dict: def handle_get_user_character_api_request(self, data: Dict) -> Dict:
user_characters = self.data.item.get_characters(data["userId"]) user_characters = self.data.item.get_characters(data["userId"])
if user_characters is None: return {} if user_characters is None:
return {}
character_list = [] character_list = []
for character in user_characters: for character in user_characters:
@ -555,16 +651,17 @@ class OngekiBase():
tmp.pop("id") tmp.pop("id")
tmp.pop("user") tmp.pop("user")
character_list.append(tmp) character_list.append(tmp)
return { return {
"userId": data["userId"], "userId": data["userId"],
"length": len(character_list), "length": len(character_list),
"userCharacterList": character_list "userCharacterList": character_list,
} }
def handle_get_user_card_api_request(self, data: Dict) -> Dict: def handle_get_user_card_api_request(self, data: Dict) -> Dict:
user_cards = self.data.item.get_cards(data["userId"]) user_cards = self.data.item.get_cards(data["userId"])
if user_cards is None: return {} if user_cards is None:
return {}
card_list = [] card_list = []
for card in user_cards: for card in user_cards:
@ -572,17 +669,18 @@ class OngekiBase():
tmp.pop("id") tmp.pop("id")
tmp.pop("user") tmp.pop("user")
card_list.append(tmp) card_list.append(tmp)
return { return {
"userId": data["userId"], "userId": data["userId"],
"length": len(card_list), "length": len(card_list),
"userCardList": card_list "userCardList": card_list,
} }
def handle_get_user_deck_by_key_api_request(self, data: Dict) -> Dict: def handle_get_user_deck_by_key_api_request(self, data: Dict) -> Dict:
# Auth key doesn't matter, it just wants all the decks # Auth key doesn't matter, it just wants all the decks
decks = self.data.item.get_decks(data["userId"]) decks = self.data.item.get_decks(data["userId"])
if decks is None: return {} if decks is None:
return {}
deck_list = [] deck_list = []
for deck in decks: for deck in decks:
@ -599,7 +697,8 @@ class OngekiBase():
def handle_get_user_trade_item_api_request(self, data: Dict) -> Dict: def handle_get_user_trade_item_api_request(self, data: Dict) -> Dict:
user_trade_items = self.data.item.get_trade_items(data["userId"]) user_trade_items = self.data.item.get_trade_items(data["userId"])
if user_trade_items is None: return {} if user_trade_items is None:
return {}
trade_item_list = [] trade_item_list = []
for trade_item in user_trade_items: for trade_item in user_trade_items:
@ -616,7 +715,8 @@ class OngekiBase():
def handle_get_user_scenario_api_request(self, data: Dict) -> Dict: def handle_get_user_scenario_api_request(self, data: Dict) -> Dict:
user_scenerio = self.data.item.get_scenerios(data["userId"]) user_scenerio = self.data.item.get_scenerios(data["userId"])
if user_scenerio is None: return {} if user_scenerio is None:
return {}
scenerio_list = [] scenerio_list = []
for scenerio in user_scenerio: for scenerio in user_scenerio:
@ -624,7 +724,7 @@ class OngekiBase():
tmp.pop("id") tmp.pop("id")
tmp.pop("user") tmp.pop("user")
scenerio_list.append(tmp) scenerio_list.append(tmp)
return { return {
"userId": data["userId"], "userId": data["userId"],
"length": len(scenerio_list), "length": len(scenerio_list),
@ -633,7 +733,8 @@ class OngekiBase():
def handle_get_user_ratinglog_api_request(self, data: Dict) -> Dict: def handle_get_user_ratinglog_api_request(self, data: Dict) -> Dict:
rating_log = self.data.profile.get_profile_rating_log(data["userId"]) rating_log = self.data.profile.get_profile_rating_log(data["userId"])
if rating_log is None: return {} if rating_log is None:
return {}
userRatinglogList = [] userRatinglogList = []
for rating in rating_log: for rating in rating_log:
@ -650,7 +751,8 @@ class OngekiBase():
def handle_get_user_mission_point_api_request(self, data: Dict) -> Dict: def handle_get_user_mission_point_api_request(self, data: Dict) -> Dict:
user_mission_point_list = self.data.item.get_mission_points(data["userId"]) user_mission_point_list = self.data.item.get_mission_points(data["userId"])
if user_mission_point_list is None: return {} if user_mission_point_list is None:
return {}
mission_point_list = [] mission_point_list = []
for evt_music in user_mission_point_list: for evt_music in user_mission_point_list:
@ -667,7 +769,8 @@ class OngekiBase():
def handle_get_user_event_point_api_request(self, data: Dict) -> Dict: def handle_get_user_event_point_api_request(self, data: Dict) -> Dict:
user_event_point_list = self.data.item.get_event_points(data["userId"]) user_event_point_list = self.data.item.get_event_points(data["userId"])
if user_event_point_list is None: return {} if user_event_point_list is None:
return {}
event_point_list = [] event_point_list = []
for evt_music in user_event_point_list: for evt_music in user_event_point_list:
@ -684,7 +787,8 @@ class OngekiBase():
def handle_get_user_music_item_api_request(self, data: Dict) -> Dict: def handle_get_user_music_item_api_request(self, data: Dict) -> Dict:
user_music_item_list = self.data.item.get_music_items(data["userId"]) user_music_item_list = self.data.item.get_music_items(data["userId"])
if user_music_item_list is None: return {} if user_music_item_list is None:
return {}
music_item_list = [] music_item_list = []
for evt_music in user_music_item_list: for evt_music in user_music_item_list:
@ -701,7 +805,8 @@ class OngekiBase():
def handle_get_user_event_music_api_request(self, data: Dict) -> Dict: def handle_get_user_event_music_api_request(self, data: Dict) -> Dict:
user_evt_music_list = self.data.item.get_event_music(data["userId"]) user_evt_music_list = self.data.item.get_event_music(data["userId"])
if user_evt_music_list is None: return {} if user_evt_music_list is None:
return {}
evt_music_list = [] evt_music_list = []
for evt_music in user_evt_music_list: for evt_music in user_evt_music_list:
@ -718,7 +823,8 @@ class OngekiBase():
def handle_get_user_boss_api_request(self, data: Dict) -> Dict: def handle_get_user_boss_api_request(self, data: Dict) -> Dict:
p = self.data.item.get_bosses(data["userId"]) p = self.data.item.get_bosses(data["userId"])
if p is None: return {} if p is None:
return {}
boss_list = [] boss_list = []
for boss in p: for boss in p:
@ -740,7 +846,9 @@ class OngekiBase():
# The isNew fields are new as of Red and up. We just won't use them for now. # The isNew fields are new as of Red and up. We just won't use them for now.
if "userData" in upsert and len(upsert["userData"]) > 0: if "userData" in upsert and len(upsert["userData"]) > 0:
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 "userOption" in upsert and len(upsert["userOption"]) > 0: if "userOption" in upsert and len(upsert["userOption"]) > 0:
self.data.profile.put_profile_options(user_id, upsert["userOption"][0]) self.data.profile.put_profile_options(user_id, upsert["userOption"][0])
@ -751,27 +859,37 @@ class OngekiBase():
if "userActivityList" in upsert: if "userActivityList" in upsert:
for act in upsert["userActivityList"]: for act in upsert["userActivityList"]:
self.data.profile.put_profile_activity(user_id, act["kind"], act["id"], act["sortNumber"], act["param1"], self.data.profile.put_profile_activity(
act["param2"], act["param3"], act["param4"]) user_id,
act["kind"],
act["id"],
act["sortNumber"],
act["param1"],
act["param2"],
act["param3"],
act["param4"],
)
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 "userBpBaseList" in upsert: if "userBpBaseList" in upsert:
self.data.profile.put_profile_bp_list(user_id, upsert["userBpBaseList"]) self.data.profile.put_profile_bp_list(user_id, upsert["userBpBaseList"])
if "userMusicDetailList" in upsert: if "userMusicDetailList" in upsert:
for x in upsert["userMusicDetailList"]: for x in upsert["userMusicDetailList"]:
self.data.score.put_best_score(user_id, x) self.data.score.put_best_score(user_id, x)
if "userCharacterList" in upsert: if "userCharacterList" in upsert:
for x in upsert["userCharacterList"]: for x in upsert["userCharacterList"]:
self.data.item.put_character(user_id, x) self.data.item.put_character(user_id, x)
if "userCardList" in upsert: if "userCardList" in upsert:
for x in upsert["userCardList"]: for x in upsert["userCardList"]:
self.data.item.put_card(user_id, x) self.data.item.put_card(user_id, x)
if "userDeckList" in upsert: if "userDeckList" in upsert:
for x in upsert["userDeckList"]: for x in upsert["userDeckList"]:
self.data.item.put_deck(user_id, x) self.data.item.put_deck(user_id, x)
@ -779,43 +897,45 @@ class OngekiBase():
if "userTrainingRoomList" in upsert: if "userTrainingRoomList" in upsert:
for x in upsert["userTrainingRoomList"]: for x in upsert["userTrainingRoomList"]:
self.data.profile.put_training_room(user_id, x) self.data.profile.put_training_room(user_id, x)
if "userStoryList" in upsert: if "userStoryList" in upsert:
for x in upsert["userStoryList"]: for x in upsert["userStoryList"]:
self.data.item.put_story(user_id, x) self.data.item.put_story(user_id, x)
if "userChapterList" in upsert: if "userChapterList" in upsert:
for x in upsert["userChapterList"]: for x in upsert["userChapterList"]:
self.data.item.put_chapter(user_id, x) self.data.item.put_chapter(user_id, x)
if "userMemoryChapterList" in upsert: if "userMemoryChapterList" in upsert:
for x in upsert["userMemoryChapterList"]: for x in upsert["userMemoryChapterList"]:
self.data.item.put_memorychapter(user_id, x) self.data.item.put_memorychapter(user_id, x)
if "userItemList" in upsert: if "userItemList" in upsert:
for x in upsert["userItemList"]: for x in upsert["userItemList"]:
self.data.item.put_item(user_id, x) self.data.item.put_item(user_id, x)
if "userMusicItemList" in upsert: if "userMusicItemList" in upsert:
for x in upsert["userMusicItemList"]: for x in upsert["userMusicItemList"]:
self.data.item.put_music_item(user_id, x) self.data.item.put_music_item(user_id, x)
if "userLoginBonusList" in upsert: if "userLoginBonusList" in upsert:
for x in upsert["userLoginBonusList"]: for x in upsert["userLoginBonusList"]:
self.data.item.put_login_bonus(user_id, x) self.data.item.put_login_bonus(user_id, x)
if "userEventPointList" in upsert: if "userEventPointList" in upsert:
for x in upsert["userEventPointList"]: for x in upsert["userEventPointList"]:
self.data.item.put_event_point(user_id, x) self.data.item.put_event_point(user_id, x)
if "userMissionPointList" in upsert: if "userMissionPointList" in upsert:
for x in upsert["userMissionPointList"]: for x in upsert["userMissionPointList"]:
self.data.item.put_mission_point(user_id, x) self.data.item.put_mission_point(user_id, x)
if "userRatinglogList" in upsert: if "userRatinglogList" in upsert:
for x in upsert["userRatinglogList"]: for x in upsert["userRatinglogList"]:
self.data.profile.put_profile_rating_log(user_id, x["dataVersion"], x["highestRating"]) self.data.profile.put_profile_rating_log(
user_id, x["dataVersion"], x["highestRating"]
)
if "userBossList" in upsert: if "userBossList" in upsert:
for x in upsert["userBossList"]: for x in upsert["userBossList"]:
self.data.item.put_boss(user_id, x) self.data.item.put_boss(user_id, x)
@ -844,7 +964,7 @@ class OngekiBase():
for x in upsert["userKopList"]: for x in upsert["userKopList"]:
self.data.profile.put_kop(user_id, x) self.data.profile.put_kop(user_id, x)
return {'returnCode': 1, 'apiName': 'upsertUserAll'} return {"returnCode": 1, "apiName": "upsertUserAll"}
def handle_get_user_rival_api_request(self, data: Dict) -> Dict: def handle_get_user_rival_api_request(self, data: Dict) -> Dict:
""" """
@ -857,29 +977,28 @@ class OngekiBase():
"length": 0, "length": 0,
"userRivalList": [], "userRivalList": [],
} }
return { return {
"userId": data["userId"], "userId": data["userId"],
"length": len(rival_list), "length": len(rival_list),
"userRivalList": rival_list._asdict(), "userRivalList": rival_list._asdict(),
} }
def handle_get_user_rival_data_api_reqiest(self, data:Dict) -> Dict: def handle_get_user_rival_data_api_reqiest(self, data: Dict) -> Dict:
""" """
Added in Bright Added in Bright
""" """
rivals = [] rivals = []
for rival in data["userRivalList"]: for rival in data["userRivalList"]:
name = self.data.profile.get_profile_name(rival["rivalUserId"], self.version) name = self.data.profile.get_profile_name(
rival["rivalUserId"], self.version
)
if name is None: if name is None:
continue continue
rivals.append({ rivals.append({"rivalUserId": rival["rival"], "rivalUserName": name})
"rivalUserId": rival["rival"],
"rivalUserName": name
})
return { return {
"userId": data["userId"], "userId": data["userId"],
"length": len(rivals), "length": len(rivals),
@ -893,11 +1012,9 @@ class OngekiBase():
rival_id = data["rivalUserId"] rival_id = data["rivalUserId"]
next_idx = data["nextIndex"] next_idx = data["nextIndex"]
max_ct = data["maxCount"] max_ct = data["maxCount"]
music = self.handle_get_user_music_api_request({ music = self.handle_get_user_music_api_request(
"userId": rival_id, {"userId": rival_id, "nextIndex": next_idx, "maxCount": max_ct}
"nextIndex": next_idx, )
"maxCount": max_ct
})
for song in music["userMusicList"]: for song in music["userMusicList"]:
song["userRivalMusicDetailList"] = song["userMusicDetailList"] song["userRivalMusicDetailList"] = song["userMusicDetailList"]
@ -921,18 +1038,15 @@ class OngekiBase():
tmp = md._asdict() tmp = md._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"])
break break
if not found: if not found:
song_list.append({ song_list.append({"length": 1, "userMusicDetailList": [tmp]})
"length": 1,
"userMusicDetailList": [tmp]
})
return song_list return song_list

View File

@ -31,7 +31,8 @@ class OngekiBright(OngekiBase):
if cards is None or len(cards) == 0: if cards is None or len(cards) == 0:
# This should never happen # This should never happen
self.logger.error( self.logger.error(
f"handle_get_user_data_api_request: Internal error - No cards found for user id {data['userId']}") f"handle_get_user_data_api_request: Internal error - No cards found for user id {data['userId']}"
)
return {} return {}
# get the dict representation of the row so we can modify values # get the dict representation of the row so we can modify values
@ -86,7 +87,7 @@ class OngekiBright(OngekiBase):
"userId": data["userId"], "userId": data["userId"],
"length": len(card_list[start_idx:end_idx]), "length": len(card_list[start_idx:end_idx]),
"nextIndex": next_idx, "nextIndex": next_idx,
"userCardList": card_list[start_idx:end_idx] "userCardList": card_list[start_idx:end_idx],
} }
def handle_cm_get_user_character_api_request(self, data: Dict) -> Dict: def handle_cm_get_user_character_api_request(self, data: Dict) -> Dict:
@ -115,31 +116,26 @@ class OngekiBright(OngekiBase):
"userId": data["userId"], "userId": data["userId"],
"length": len(character_list[start_idx:end_idx]), "length": len(character_list[start_idx:end_idx]),
"nextIndex": next_idx, "nextIndex": next_idx,
"userCharacterList": character_list[start_idx:end_idx] "userCharacterList": character_list[start_idx:end_idx],
} }
def handle_get_user_gacha_api_request(self, data: Dict) -> Dict: def handle_get_user_gacha_api_request(self, data: Dict) -> Dict:
user_gachas = self.data.item.get_user_gachas(data["userId"]) user_gachas = self.data.item.get_user_gachas(data["userId"])
if user_gachas is None: if user_gachas is None:
return { return {"userId": data["userId"], "length": 0, "userGachaList": []}
"userId": data["userId"],
"length": 0,
"userGachaList": []
}
user_gacha_list = [] user_gacha_list = []
for gacha in user_gachas: for gacha in user_gachas:
tmp = gacha._asdict() tmp = gacha._asdict()
tmp.pop("id") tmp.pop("id")
tmp.pop("user") tmp.pop("user")
tmp["dailyGachaDate"] = datetime.strftime( tmp["dailyGachaDate"] = datetime.strftime(tmp["dailyGachaDate"], "%Y-%m-%d")
tmp["dailyGachaDate"], "%Y-%m-%d")
user_gacha_list.append(tmp) user_gacha_list.append(tmp)
return { return {
"userId": data["userId"], "userId": data["userId"],
"length": len(user_gacha_list), "length": len(user_gacha_list),
"userGachaList": user_gacha_list "userGachaList": user_gacha_list,
} }
def handle_cm_get_user_item_api_request(self, data: Dict) -> Dict: def handle_cm_get_user_item_api_request(self, data: Dict) -> Dict:
@ -147,21 +143,16 @@ class OngekiBright(OngekiBase):
def handle_cm_get_user_gacha_supply_api_request(self, data: Dict) -> Dict: def handle_cm_get_user_gacha_supply_api_request(self, data: Dict) -> Dict:
# not used for now? not sure what it even does # not used for now? not sure what it even does
user_gacha_supplies = self.data.item.get_user_gacha_supplies( user_gacha_supplies = self.data.item.get_user_gacha_supplies(data["userId"])
data["userId"])
if user_gacha_supplies is None: if user_gacha_supplies is None:
return { return {"supplyId": 1, "length": 0, "supplyCardList": []}
"supplyId": 1,
"length": 0,
"supplyCardList": []
}
supply_list = [gacha["cardId"] for gacha in user_gacha_supplies] supply_list = [gacha["cardId"] for gacha in user_gacha_supplies]
return { return {
"supplyId": 1, "supplyId": 1,
"length": len(supply_list), "length": len(supply_list),
"supplyCardList": supply_list "supplyCardList": supply_list,
} }
def handle_get_game_gacha_api_request(self, data: Dict) -> Dict: def handle_get_game_gacha_api_request(self, data: Dict) -> Dict:
@ -182,29 +173,33 @@ class OngekiBright(OngekiBase):
tmp = gacha._asdict() tmp = gacha._asdict()
tmp.pop("id") tmp.pop("id")
tmp.pop("version") tmp.pop("version")
tmp["startDate"] = datetime.strftime( tmp["startDate"] = datetime.strftime(tmp["startDate"], "%Y-%m-%d %H:%M:%S")
tmp["startDate"], "%Y-%m-%d %H:%M:%S") tmp["endDate"] = datetime.strftime(tmp["endDate"], "%Y-%m-%d %H:%M:%S")
tmp["endDate"] = datetime.strftime(
tmp["endDate"], "%Y-%m-%d %H:%M:%S")
tmp["noticeStartDate"] = datetime.strftime( tmp["noticeStartDate"] = datetime.strftime(
tmp["noticeStartDate"], "%Y-%m-%d %H:%M:%S") tmp["noticeStartDate"], "%Y-%m-%d %H:%M:%S"
)
tmp["noticeEndDate"] = datetime.strftime( tmp["noticeEndDate"] = datetime.strftime(
tmp["noticeEndDate"], "%Y-%m-%d %H:%M:%S") tmp["noticeEndDate"], "%Y-%m-%d %H:%M:%S"
)
tmp["convertEndDate"] = datetime.strftime( tmp["convertEndDate"] = datetime.strftime(
tmp["convertEndDate"], "%Y-%m-%d %H:%M:%S") tmp["convertEndDate"], "%Y-%m-%d %H:%M:%S"
)
# make sure to only show gachas for the current version # make sure to only show gachas for the current version
# so only up to bright, 1140 is the first bright memory gacha # so only up to bright, 1140 is the first bright memory gacha
if self.version == OngekiConstants.VER_ONGEKI_BRIGHT_MEMORY: if self.version == OngekiConstants.VER_ONGEKI_BRIGHT_MEMORY:
game_gacha_list.append(tmp) game_gacha_list.append(tmp)
elif self.version == OngekiConstants.VER_ONGEKI_BRIGHT and tmp["gachaId"] < 1140: elif (
self.version == OngekiConstants.VER_ONGEKI_BRIGHT
and tmp["gachaId"] < 1140
):
game_gacha_list.append(tmp) game_gacha_list.append(tmp)
return { return {
"length": len(game_gacha_list), "length": len(game_gacha_list),
"gameGachaList": game_gacha_list, "gameGachaList": game_gacha_list,
# no clue # no clue
"registIdList": [] "registIdList": [],
} }
def handle_roll_gacha_api_request(self, data: Dict) -> Dict: def handle_roll_gacha_api_request(self, data: Dict) -> Dict:
@ -251,7 +246,7 @@ class OngekiBright(OngekiBase):
assert len(rarity) == 100 assert len(rarity) == 100
# uniform distribution to get the rarity of the card # uniform distribution to get the rarity of the card
rolls = [rarity[randint(0, len(rarity)-1)] for _ in range(num_rolls)] rolls = [rarity[randint(0, len(rarity) - 1)] for _ in range(num_rolls)]
# if SSR book used, make sure you always get one SSR # if SSR book used, make sure you always get one SSR
if book_used == 1: if book_used == 1:
@ -273,15 +268,9 @@ class OngekiBright(OngekiBase):
gacha_cards = self.data.static.get_gacha_cards(gacha_id) gacha_cards = self.data.static.get_gacha_cards(gacha_id)
for card in gacha_cards: for card in gacha_cards:
if card["rarity"] == 3: if card["rarity"] == 3:
cards_sr.append({ cards_sr.append({"cardId": card["cardId"], "rarity": 2})
"cardId": card["cardId"],
"rarity": 2
})
elif card["rarity"] == 4: elif card["rarity"] == 4:
cards_ssr.append({ cards_ssr.append({"cardId": card["cardId"], "rarity": 3})
"cardId": card["cardId"],
"rarity": 3
})
else: else:
cards_sr = self.data.static.get_cards_by_rarity(self.version, 2) cards_sr = self.data.static.get_cards_by_rarity(self.version, 2)
cards_ssr = self.data.static.get_cards_by_rarity(self.version, 3) cards_ssr = self.data.static.get_cards_by_rarity(self.version, 3)
@ -294,46 +283,39 @@ class OngekiBright(OngekiBase):
for card in gacha_cards: for card in gacha_cards:
# make sure to add the cards to the corresponding rarity # make sure to add the cards to the corresponding rarity
if card["rarity"] == 2: if card["rarity"] == 2:
cards_r += [{ cards_r += [{"cardId": card["cardId"], "rarity": 1}] * chances
"cardId": card["cardId"],
"rarity": 1
}] * chances
if card["rarity"] == 3: if card["rarity"] == 3:
cards_sr += [{ cards_sr += [{"cardId": card["cardId"], "rarity": 2}] * chances
"cardId": card["cardId"],
"rarity": 2
}] * chances
elif card["rarity"] == 4: elif card["rarity"] == 4:
cards_ssr += [{ cards_ssr += [{"cardId": card["cardId"], "rarity": 3}] * chances
"cardId": card["cardId"],
"rarity": 3
}] * chances
# get the card id for each roll # get the card id for each roll
rolled_cards = [] rolled_cards = []
for i in range(len(rolls)): for i in range(len(rolls)):
if rolls[i] == 1: if rolls[i] == 1:
rolled_cards.append(cards_r[randint(0, len(cards_r)-1)]) rolled_cards.append(cards_r[randint(0, len(cards_r) - 1)])
elif rolls[i] == 2: elif rolls[i] == 2:
rolled_cards.append(cards_sr[randint(0, len(cards_sr)-1)]) rolled_cards.append(cards_sr[randint(0, len(cards_sr) - 1)])
elif rolls[i] == 3: elif rolls[i] == 3:
rolled_cards.append(cards_ssr[randint(0, len(cards_ssr)-1)]) rolled_cards.append(cards_ssr[randint(0, len(cards_ssr) - 1)])
game_gacha_card_list = [] game_gacha_card_list = []
for card in rolled_cards: for card in rolled_cards:
game_gacha_card_list.append({ game_gacha_card_list.append(
"gachaId": data["gachaId"], {
"cardId": card["cardId"], "gachaId": data["gachaId"],
# +1 because Card Maker is weird "cardId": card["cardId"],
"rarity": card["rarity"] + 1, # +1 because Card Maker is weird
"weight": 1, "rarity": card["rarity"] + 1,
"isPickup": False, "weight": 1,
"isSelect": False "isPickup": False,
}) "isSelect": False,
}
)
return { return {
"length": len(game_gacha_card_list), "length": len(game_gacha_card_list),
"gameGachaCardList": game_gacha_card_list "gameGachaCardList": game_gacha_card_list,
} }
def handle_cm_upsert_user_gacha_api_request(self, data: Dict): def handle_cm_upsert_user_gacha_api_request(self, data: Dict):
@ -342,12 +324,12 @@ class OngekiBright(OngekiBase):
gacha_id = data["gachaId"] gacha_id = data["gachaId"]
gacha_count = data["gachaCnt"] gacha_count = data["gachaCnt"]
play_date = datetime.strptime(data["playDate"][:10], '%Y-%m-%d') play_date = datetime.strptime(data["playDate"][:10], "%Y-%m-%d")
select_point = data["selectPoint"] select_point = data["selectPoint"]
total_gacha_count, ceiling_gacha_count = 0, 0 total_gacha_count, ceiling_gacha_count = 0, 0
daily_gacha_cnt, five_gacha_cnt, eleven_gacha_cnt = 0, 0, 0 daily_gacha_cnt, five_gacha_cnt, eleven_gacha_cnt = 0, 0, 0
daily_gacha_date = datetime.strptime('2000-01-01', '%Y-%m-%d') daily_gacha_date = datetime.strptime("2000-01-01", "%Y-%m-%d")
# check if the user previously rolled the exact same gacha # check if the user previously rolled the exact same gacha
user_gacha = self.data.item.get_user_gacha(user_id, gacha_id) user_gacha = self.data.item.get_user_gacha(user_id, gacha_id)
@ -374,9 +356,11 @@ class OngekiBright(OngekiBase):
selectPoint=select_point, selectPoint=select_point,
useSelectPoint=0, useSelectPoint=0,
dailyGachaCnt=daily_gacha_cnt + gacha_count, dailyGachaCnt=daily_gacha_cnt + gacha_count,
fiveGachaCnt=five_gacha_cnt+1 if gacha_count == 5 else five_gacha_cnt, fiveGachaCnt=five_gacha_cnt + 1 if gacha_count == 5 else five_gacha_cnt,
elevenGachaCnt=eleven_gacha_cnt+1 if gacha_count == 11 else eleven_gacha_cnt, elevenGachaCnt=eleven_gacha_cnt + 1
dailyGachaDate=daily_gacha_date if gacha_count == 11
else eleven_gacha_cnt,
dailyGachaDate=daily_gacha_date,
) )
if "userData" in upsert and len(upsert["userData"]) > 0: if "userData" in upsert and len(upsert["userData"]) > 0:
@ -385,11 +369,13 @@ class OngekiBright(OngekiBase):
if p is not None: if p is not None:
# save the bright memory profile # save the bright memory profile
self.data.profile.put_profile_data( self.data.profile.put_profile_data(
user_id, self.version, upsert["userData"][0]) user_id, self.version, upsert["userData"][0]
)
else: else:
# save the bright profile # save the bright profile
self.data.profile.put_profile_data( self.data.profile.put_profile_data(
user_id, self.version, upsert["userData"][0]) user_id, self.version, upsert["userData"][0]
)
if "userCharacterList" in upsert: if "userCharacterList" in upsert:
for x in upsert["userCharacterList"]: for x in upsert["userCharacterList"]:
@ -407,7 +393,7 @@ class OngekiBright(OngekiBase):
# if "gameGachaCardList" in upsert: # if "gameGachaCardList" in upsert:
# for x in upsert["gameGachaCardList"]: # for x in upsert["gameGachaCardList"]:
return {'returnCode': 1, 'apiName': 'CMUpsertUserGachaApi'} return {"returnCode": 1, "apiName": "CMUpsertUserGachaApi"}
def handle_cm_upsert_user_select_gacha_api_request(self, data: Dict) -> Dict: def handle_cm_upsert_user_select_gacha_api_request(self, data: Dict) -> Dict:
upsert = data["cmUpsertUserSelectGacha"] upsert = data["cmUpsertUserSelectGacha"]
@ -419,11 +405,13 @@ class OngekiBright(OngekiBase):
if p is not None: if p is not None:
# save the bright memory profile # save the bright memory profile
self.data.profile.put_profile_data( self.data.profile.put_profile_data(
user_id, self.version, upsert["userData"][0]) user_id, self.version, upsert["userData"][0]
)
else: else:
# save the bright profile # save the bright profile
self.data.profile.put_profile_data( self.data.profile.put_profile_data(
user_id, self.version, upsert["userData"][0]) user_id, self.version, upsert["userData"][0]
)
if "userCharacterList" in upsert: if "userCharacterList" in upsert:
for x in upsert["userCharacterList"]: for x in upsert["userCharacterList"]:
@ -439,10 +427,10 @@ class OngekiBright(OngekiBase):
user_id, user_id,
x["gachaId"], x["gachaId"],
selectPoint=0, selectPoint=0,
useSelectPoint=x["useSelectPoint"] useSelectPoint=x["useSelectPoint"],
) )
return {'returnCode': 1, 'apiName': 'cmUpsertUserSelectGacha'} return {"returnCode": 1, "apiName": "cmUpsertUserSelectGacha"}
def handle_get_game_gacha_card_by_id_api_request(self, data: Dict) -> Dict: def handle_get_game_gacha_card_by_id_api_request(self, data: Dict) -> Dict:
game_gacha_cards = self.data.static.get_gacha_cards(data["gachaId"]) game_gacha_cards = self.data.static.get_gacha_cards(data["gachaId"])
@ -459,7 +447,7 @@ class OngekiBright(OngekiBase):
"rarity": 4, "rarity": 4,
"weight": 1, "weight": 1,
"isPickup": False, "isPickup": False,
"isSelect": True "isSelect": True,
}, },
{ {
"gachaId": data["gachaId"], "gachaId": data["gachaId"],
@ -467,7 +455,7 @@ class OngekiBright(OngekiBase):
"rarity": 3, "rarity": 3,
"weight": 2, "weight": 2,
"isPickup": False, "isPickup": False,
"isSelect": True "isSelect": True,
}, },
{ {
"gachaId": data["gachaId"], "gachaId": data["gachaId"],
@ -475,7 +463,7 @@ class OngekiBright(OngekiBase):
"rarity": 3, "rarity": 3,
"weight": 2, "weight": 2,
"isPickup": False, "isPickup": False,
"isSelect": True "isSelect": True,
}, },
{ {
"gachaId": data["gachaId"], "gachaId": data["gachaId"],
@ -483,7 +471,7 @@ class OngekiBright(OngekiBase):
"rarity": 2, "rarity": 2,
"weight": 3, "weight": 3,
"isPickup": False, "isPickup": False,
"isSelect": True "isSelect": True,
}, },
{ {
"gachaId": data["gachaId"], "gachaId": data["gachaId"],
@ -491,7 +479,7 @@ class OngekiBright(OngekiBase):
"rarity": 2, "rarity": 2,
"weight": 3, "weight": 3,
"isPickup": False, "isPickup": False,
"isSelect": True "isSelect": True,
}, },
{ {
"gachaId": data["gachaId"], "gachaId": data["gachaId"],
@ -499,12 +487,12 @@ class OngekiBright(OngekiBase):
"rarity": 2, "rarity": 2,
"weight": 3, "weight": 3,
"isPickup": False, "isPickup": False,
"isSelect": True "isSelect": True,
} },
], ],
"emissionList": [], "emissionList": [],
"afterCalcList": [], "afterCalcList": [],
"ssrBookCalcList": [] "ssrBookCalcList": [],
} }
game_gacha_card_list = [] game_gacha_card_list = []
@ -521,7 +509,7 @@ class OngekiBright(OngekiBase):
# again no clue # again no clue
"emissionList": [], "emissionList": [],
"afterCalcList": [], "afterCalcList": [],
"ssrBookCalcList": [] "ssrBookCalcList": [],
} }
def handle_get_game_theater_api_request(self, data: Dict) -> Dict: def handle_get_game_theater_api_request(self, data: Dict) -> Dict:
@ -548,18 +536,14 @@ class OngekiBright(OngekiBase):
} }
""" """
return { return {"length": 0, "gameTheaterList": [], "registIdList": []}
"length": 0,
"gameTheaterList": [],
"registIdList": []
}
def handle_cm_upsert_user_print_playlog_api_request(self, data: Dict) -> Dict: def handle_cm_upsert_user_print_playlog_api_request(self, data: Dict) -> Dict:
return { return {
"returnCode": 1, "returnCode": 1,
"orderId": 0, "orderId": 0,
"serialId": "11111111111111111111", "serialId": "11111111111111111111",
"apiName": "CMUpsertUserPrintPlaylogApi" "apiName": "CMUpsertUserPrintPlaylogApi",
} }
def handle_cm_upsert_user_printlog_api_request(self, data: Dict) -> Dict: def handle_cm_upsert_user_printlog_api_request(self, data: Dict) -> Dict:
@ -567,34 +551,32 @@ class OngekiBright(OngekiBase):
"returnCode": 1, "returnCode": 1,
"orderId": 0, "orderId": 0,
"serialId": "11111111111111111111", "serialId": "11111111111111111111",
"apiName": "CMUpsertUserPrintlogApi" "apiName": "CMUpsertUserPrintlogApi",
} }
def handle_cm_upsert_user_print_api_request(self, data: Dict) -> Dict: def handle_cm_upsert_user_print_api_request(self, data: Dict) -> Dict:
user_print_detail = data["userPrintDetail"] user_print_detail = data["userPrintDetail"]
# generate random serial id # generate random serial id
serial_id = ''.join([str(randint(0, 9)) for _ in range(20)]) serial_id = "".join([str(randint(0, 9)) for _ in range(20)])
# not needed because are either zero or unset # not needed because are either zero or unset
user_print_detail.pop("orderId") user_print_detail.pop("orderId")
user_print_detail.pop("printNumber") user_print_detail.pop("printNumber")
user_print_detail.pop("serialId") user_print_detail.pop("serialId")
user_print_detail["printDate"] = datetime.strptime( user_print_detail["printDate"] = datetime.strptime(
user_print_detail["printDate"], '%Y-%m-%d' user_print_detail["printDate"], "%Y-%m-%d"
) )
# add the entry to the user print table with the random serialId # add the entry to the user print table with the random serialId
self.data.item.put_user_print_detail( self.data.item.put_user_print_detail(
data["userId"], data["userId"], serial_id, user_print_detail
serial_id,
user_print_detail
) )
return { return {
"returnCode": 1, "returnCode": 1,
"serialId": serial_id, "serialId": serial_id,
"apiName": "CMUpsertUserPrintApi" "apiName": "CMUpsertUserPrintApi",
} }
def handle_cm_upsert_user_all_api_request(self, data: Dict) -> Dict: def handle_cm_upsert_user_all_api_request(self, data: Dict) -> Dict:
@ -607,17 +589,26 @@ class OngekiBright(OngekiBase):
if p is not None: if p is not None:
# save the bright memory profile # save the bright memory profile
self.data.profile.put_profile_data( self.data.profile.put_profile_data(
user_id, self.version, upsert["userData"][0]) user_id, self.version, upsert["userData"][0]
)
else: else:
# save the bright profile # save the bright profile
self.data.profile.put_profile_data( self.data.profile.put_profile_data(
user_id, self.version, upsert["userData"][0]) user_id, self.version, upsert["userData"][0]
)
if "userActivityList" in upsert: if "userActivityList" in upsert:
for act in upsert["userActivityList"]: for act in upsert["userActivityList"]:
self.data.profile.put_profile_activity( self.data.profile.put_profile_activity(
user_id, act["kind"], act["id"], act["sortNumber"], user_id,
act["param1"], act["param2"], act["param3"], act["param4"]) act["kind"],
act["id"],
act["sortNumber"],
act["param1"],
act["param2"],
act["param3"],
act["param4"],
)
if "userItemList" in upsert: if "userItemList" in upsert:
for x in upsert["userItemList"]: for x in upsert["userItemList"]:
@ -627,4 +618,4 @@ class OngekiBright(OngekiBase):
for x in upsert["userCardList"]: for x in upsert["userCardList"]:
self.data.item.put_card(user_id, x) self.data.item.put_card(user_id, x)
return {'returnCode': 1, 'apiName': 'cmUpsertUserAll'} return {"returnCode": 1, "apiName": "cmUpsertUserAll"}

View File

@ -30,14 +30,96 @@ class OngekiBrightMemory(OngekiBright):
def handle_get_user_memory_chapter_api_request(self, data: Dict) -> Dict: def handle_get_user_memory_chapter_api_request(self, data: Dict) -> Dict:
memories = self.data.item.get_memorychapters(data["userId"]) memories = self.data.item.get_memorychapters(data["userId"])
if not memories: if not memories:
return {"userId": data["userId"], "length":6, "userMemoryChapterList":[ return {
{"gaugeId":0, "isClear": False, "gaugeNum": 0, "chapterId": 70001, "jewelCount": 0, "isBossWatched": False, "isStoryWatched": False, "isDialogWatched": False, "isEndingWatched": False, "lastPlayMusicId": 0, "lastPlayMusicLevel": 0, "lastPlayMusicCategory": 0}, "userId": data["userId"],
{"gaugeId":0, "isClear": False, "gaugeNum": 0, "chapterId": 70002, "jewelCount": 0, "isBossWatched": False, "isStoryWatched": False, "isDialogWatched": False, "isEndingWatched": False, "lastPlayMusicId": 0, "lastPlayMusicLevel": 0, "lastPlayMusicCategory": 0}, "length": 6,
{"gaugeId":0, "isClear": False, "gaugeNum": 0, "chapterId": 70003, "jewelCount": 0, "isBossWatched": False, "isStoryWatched": False, "isDialogWatched": False, "isEndingWatched": False, "lastPlayMusicId": 0, "lastPlayMusicLevel": 0, "lastPlayMusicCategory": 0}, "userMemoryChapterList": [
{"gaugeId":0, "isClear": False, "gaugeNum": 0, "chapterId": 70004, "jewelCount": 0, "isBossWatched": False, "isStoryWatched": False, "isDialogWatched": False, "isEndingWatched": False, "lastPlayMusicId": 0, "lastPlayMusicLevel": 0, "lastPlayMusicCategory": 0}, {
{"gaugeId":0, "isClear": False, "gaugeNum": 0, "chapterId": 70005, "jewelCount": 0, "isBossWatched": False, "isStoryWatched": False, "isDialogWatched": False, "isEndingWatched": False, "lastPlayMusicId": 0, "lastPlayMusicLevel": 0, "lastPlayMusicCategory": 0}, "gaugeId": 0,
{"gaugeId":0, "isClear": False, "gaugeNum": 0, "chapterId": 70099, "jewelCount": 0, "isBossWatched": False, "isStoryWatched": False, "isDialogWatched": False, "isEndingWatched": False, "lastPlayMusicId": 0, "lastPlayMusicLevel": 0, "lastPlayMusicCategory": 0} "isClear": False,
]} "gaugeNum": 0,
"chapterId": 70001,
"jewelCount": 0,
"isBossWatched": False,
"isStoryWatched": False,
"isDialogWatched": False,
"isEndingWatched": False,
"lastPlayMusicId": 0,
"lastPlayMusicLevel": 0,
"lastPlayMusicCategory": 0,
},
{
"gaugeId": 0,
"isClear": False,
"gaugeNum": 0,
"chapterId": 70002,
"jewelCount": 0,
"isBossWatched": False,
"isStoryWatched": False,
"isDialogWatched": False,
"isEndingWatched": False,
"lastPlayMusicId": 0,
"lastPlayMusicLevel": 0,
"lastPlayMusicCategory": 0,
},
{
"gaugeId": 0,
"isClear": False,
"gaugeNum": 0,
"chapterId": 70003,
"jewelCount": 0,
"isBossWatched": False,
"isStoryWatched": False,
"isDialogWatched": False,
"isEndingWatched": False,
"lastPlayMusicId": 0,
"lastPlayMusicLevel": 0,
"lastPlayMusicCategory": 0,
},
{
"gaugeId": 0,
"isClear": False,
"gaugeNum": 0,
"chapterId": 70004,
"jewelCount": 0,
"isBossWatched": False,
"isStoryWatched": False,
"isDialogWatched": False,
"isEndingWatched": False,
"lastPlayMusicId": 0,
"lastPlayMusicLevel": 0,
"lastPlayMusicCategory": 0,
},
{
"gaugeId": 0,
"isClear": False,
"gaugeNum": 0,
"chapterId": 70005,
"jewelCount": 0,
"isBossWatched": False,
"isStoryWatched": False,
"isDialogWatched": False,
"isEndingWatched": False,
"lastPlayMusicId": 0,
"lastPlayMusicLevel": 0,
"lastPlayMusicCategory": 0,
},
{
"gaugeId": 0,
"isClear": False,
"gaugeNum": 0,
"chapterId": 70099,
"jewelCount": 0,
"isBossWatched": False,
"isStoryWatched": False,
"isDialogWatched": False,
"isEndingWatched": False,
"lastPlayMusicId": 0,
"lastPlayMusicLevel": 0,
"lastPlayMusicCategory": 0,
},
],
}
memory_chp = [] memory_chp = []
for chp in memories: for chp in memories:
@ -49,14 +131,11 @@ class OngekiBrightMemory(OngekiBright):
return { return {
"userId": data["userId"], "userId": data["userId"],
"length": len(memory_chp), "length": len(memory_chp),
"userMemoryChapterList": memory_chp "userMemoryChapterList": memory_chp,
} }
def handle_get_game_music_release_state_api_request(self, data: Dict) -> Dict: def handle_get_game_music_release_state_api_request(self, data: Dict) -> Dict:
return { return {"techScore": 0, "cardNum": 0}
"techScore": 0,
"cardNum": 0
}
def handle_cm_get_user_data_api_request(self, data: Dict) -> Dict: def handle_cm_get_user_data_api_request(self, data: Dict) -> Dict:
# check for a bright memory profile # check for a bright memory profile

View File

@ -3,26 +3,34 @@ from typing import List
from core.config import CoreConfig from core.config import CoreConfig
class OngekiServerConfig(): class OngekiServerConfig:
def __init__(self, parent_config: "OngekiConfig") -> None: def __init__(self, parent_config: "OngekiConfig") -> 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, 'ongeki', 'server', 'enable', default=True) return CoreConfig.get_config_field(
self.__config, "ongeki", "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, 'ongeki', 'server', 'loglevel', default="info")) return CoreConfig.str_to_loglevel(
CoreConfig.get_config_field(
self.__config, "ongeki", "server", "loglevel", default="info"
)
)
class OngekiGachaConfig(): class OngekiGachaConfig:
def __init__(self, parent_config: "OngekiConfig") -> None: def __init__(self, parent_config: "OngekiConfig") -> None:
self.__config = parent_config self.__config = parent_config
@property @property
def enabled_gachas(self) -> List[int]: def enabled_gachas(self) -> List[int]:
return CoreConfig.get_config_field(self.__config, 'ongeki', 'gachas', 'enabled_gachas', default=[]) return CoreConfig.get_config_field(
self.__config, "ongeki", "gachas", "enabled_gachas", default=[]
)
class OngekiConfig(dict): class OngekiConfig(dict):

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